mirror of
https://github.com/harivansh-afk/sandbox-agent.git
synced 2026-04-16 11:03:48 +00:00
chore: sync workspace changes
This commit is contained in:
parent
d24f983e2c
commit
bf58891edf
139 changed files with 5454 additions and 8986 deletions
|
|
@ -0,0 +1,71 @@
|
|||
import { Download, RefreshCw } from "lucide-react";
|
||||
import type { AgentInfo, AgentModeInfo } from "sandbox-agent";
|
||||
import CapabilityBadges from "../agents/CapabilityBadges";
|
||||
import { emptyCapabilities } from "../../types/agents";
|
||||
|
||||
const AgentsTab = ({
|
||||
agents,
|
||||
defaultAgents,
|
||||
modesByAgent,
|
||||
onRefresh,
|
||||
onInstall
|
||||
}: {
|
||||
agents: AgentInfo[];
|
||||
defaultAgents: string[];
|
||||
modesByAgent: Record<string, AgentModeInfo[]>;
|
||||
onRefresh: () => void;
|
||||
onInstall: (agentId: string, reinstall: boolean) => void;
|
||||
}) => {
|
||||
return (
|
||||
<>
|
||||
<div className="inline-row" style={{ marginBottom: 16 }}>
|
||||
<button className="button secondary small" onClick={onRefresh}>
|
||||
<RefreshCw className="button-icon" /> Refresh
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{agents.length === 0 && <div className="card-meta">No agents reported. Click refresh to check.</div>}
|
||||
|
||||
{(agents.length
|
||||
? agents
|
||||
: defaultAgents.map((id) => ({
|
||||
id,
|
||||
installed: false,
|
||||
version: undefined,
|
||||
path: undefined,
|
||||
capabilities: emptyCapabilities
|
||||
}))).map((agent) => (
|
||||
<div key={agent.id} className="card">
|
||||
<div className="card-header">
|
||||
<span className="card-title">{agent.id}</span>
|
||||
<span className={`pill ${agent.installed ? "success" : "danger"}`}>
|
||||
{agent.installed ? "Installed" : "Missing"}
|
||||
</span>
|
||||
</div>
|
||||
<div className="card-meta">
|
||||
{agent.version ? `v${agent.version}` : "Version unknown"}
|
||||
{agent.path && <span className="mono muted" style={{ marginLeft: 8 }}>{agent.path}</span>}
|
||||
</div>
|
||||
<div style={{ marginTop: 8 }}>
|
||||
<CapabilityBadges capabilities={agent.capabilities ?? emptyCapabilities} />
|
||||
</div>
|
||||
{modesByAgent[agent.id] && modesByAgent[agent.id].length > 0 && (
|
||||
<div className="card-meta" style={{ marginTop: 8 }}>
|
||||
Modes: {modesByAgent[agent.id].map((mode) => mode.id).join(", ")}
|
||||
</div>
|
||||
)}
|
||||
<div className="card-actions">
|
||||
<button className="button secondary small" onClick={() => onInstall(agent.id, false)}>
|
||||
<Download className="button-icon" /> Install
|
||||
</button>
|
||||
<button className="button ghost small" onClick={() => onInstall(agent.id, true)}>
|
||||
Reinstall
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default AgentsTab;
|
||||
|
|
@ -0,0 +1,105 @@
|
|||
import { HelpCircle, Shield } from "lucide-react";
|
||||
import type { PermissionEventData, QuestionEventData } from "sandbox-agent";
|
||||
import { formatJson } from "../../utils/format";
|
||||
|
||||
const ApprovalsTab = ({
|
||||
questionRequests,
|
||||
permissionRequests,
|
||||
questionSelections,
|
||||
onSelectQuestionOption,
|
||||
onAnswerQuestion,
|
||||
onRejectQuestion,
|
||||
onReplyPermission
|
||||
}: {
|
||||
questionRequests: QuestionEventData[];
|
||||
permissionRequests: PermissionEventData[];
|
||||
questionSelections: Record<string, string[][]>;
|
||||
onSelectQuestionOption: (requestId: string, optionLabel: string) => void;
|
||||
onAnswerQuestion: (request: QuestionEventData) => void;
|
||||
onRejectQuestion: (requestId: string) => void;
|
||||
onReplyPermission: (requestId: string, reply: "once" | "always" | "reject") => void;
|
||||
}) => {
|
||||
return (
|
||||
<>
|
||||
{questionRequests.length === 0 && permissionRequests.length === 0 ? (
|
||||
<div className="card-meta">No pending approvals.</div>
|
||||
) : (
|
||||
<>
|
||||
{questionRequests.map((request) => {
|
||||
const selections = questionSelections[request.question_id] ?? [];
|
||||
const selected = selections[0] ?? [];
|
||||
const answered = selected.length > 0;
|
||||
return (
|
||||
<div key={request.question_id} className="card">
|
||||
<div className="card-header">
|
||||
<span className="card-title">
|
||||
<HelpCircle className="button-icon" style={{ marginRight: 6 }} />
|
||||
Question
|
||||
</span>
|
||||
<span className="pill accent">Pending</span>
|
||||
</div>
|
||||
<div style={{ marginTop: 12 }}>
|
||||
<div style={{ fontSize: 12, marginBottom: 8 }}>{request.prompt}</div>
|
||||
<div className="option-list">
|
||||
{request.options.map((option) => {
|
||||
const isSelected = selected.includes(option);
|
||||
return (
|
||||
<label key={option} className="option-item">
|
||||
<input
|
||||
type="radio"
|
||||
checked={isSelected}
|
||||
onChange={() => onSelectQuestionOption(request.question_id, option)}
|
||||
/>
|
||||
<span>{option}</span>
|
||||
</label>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
<div className="card-actions">
|
||||
<button className="button success small" disabled={!answered} onClick={() => onAnswerQuestion(request)}>
|
||||
Reply
|
||||
</button>
|
||||
<button className="button danger small" onClick={() => onRejectQuestion(request.question_id)}>
|
||||
Reject
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
|
||||
{permissionRequests.map((request) => (
|
||||
<div key={request.permission_id} className="card">
|
||||
<div className="card-header">
|
||||
<span className="card-title">
|
||||
<Shield className="button-icon" style={{ marginRight: 6 }} />
|
||||
Permission
|
||||
</span>
|
||||
<span className="pill accent">Pending</span>
|
||||
</div>
|
||||
<div className="card-meta" style={{ marginTop: 8 }}>
|
||||
{request.action}
|
||||
</div>
|
||||
{request.metadata !== null && request.metadata !== undefined && (
|
||||
<pre className="code-block">{formatJson(request.metadata)}</pre>
|
||||
)}
|
||||
<div className="card-actions">
|
||||
<button className="button success small" onClick={() => onReplyPermission(request.permission_id, "once")}>
|
||||
Allow Once
|
||||
</button>
|
||||
<button className="button secondary small" onClick={() => onReplyPermission(request.permission_id, "always")}>
|
||||
Always
|
||||
</button>
|
||||
<button className="button danger small" onClick={() => onReplyPermission(request.permission_id, "reject")}>
|
||||
Reject
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default ApprovalsTab;
|
||||
|
|
@ -0,0 +1,89 @@
|
|||
import { Cloud, PlayCircle, Terminal } from "lucide-react";
|
||||
import type { AgentInfo, AgentModeInfo, UniversalEvent } from "sandbox-agent";
|
||||
import AgentsTab from "./AgentsTab";
|
||||
import EventsTab from "./EventsTab";
|
||||
import RequestLogTab from "./RequestLogTab";
|
||||
import type { RequestLog } from "../../types/requestLog";
|
||||
|
||||
export type DebugTab = "log" | "events" | "agents";
|
||||
|
||||
const DebugPanel = ({
|
||||
debugTab,
|
||||
onDebugTabChange,
|
||||
events,
|
||||
offset,
|
||||
onFetchEvents,
|
||||
onResetEvents,
|
||||
requestLog,
|
||||
copiedLogId,
|
||||
onClearRequestLog,
|
||||
onCopyRequestLog,
|
||||
agents,
|
||||
defaultAgents,
|
||||
modesByAgent,
|
||||
onRefreshAgents,
|
||||
onInstallAgent
|
||||
}: {
|
||||
debugTab: DebugTab;
|
||||
onDebugTabChange: (tab: DebugTab) => void;
|
||||
events: UniversalEvent[];
|
||||
offset: number;
|
||||
onFetchEvents: () => void;
|
||||
onResetEvents: () => void;
|
||||
requestLog: RequestLog[];
|
||||
copiedLogId: number | null;
|
||||
onClearRequestLog: () => void;
|
||||
onCopyRequestLog: (entry: RequestLog) => void;
|
||||
agents: AgentInfo[];
|
||||
defaultAgents: string[];
|
||||
modesByAgent: Record<string, AgentModeInfo[]>;
|
||||
onRefreshAgents: () => void;
|
||||
onInstallAgent: (agentId: string, reinstall: boolean) => void;
|
||||
}) => {
|
||||
return (
|
||||
<div className="debug-panel">
|
||||
<div className="debug-tabs">
|
||||
<button className={`debug-tab ${debugTab === "events" ? "active" : ""}`} onClick={() => onDebugTabChange("events")}>
|
||||
<PlayCircle className="button-icon" style={{ marginRight: 4, width: 12, height: 12 }} />
|
||||
Events
|
||||
{events.length > 0 && <span className="debug-tab-badge">{events.length}</span>}
|
||||
</button>
|
||||
<button className={`debug-tab ${debugTab === "log" ? "active" : ""}`} onClick={() => onDebugTabChange("log")}>
|
||||
<Terminal className="button-icon" style={{ marginRight: 4, width: 12, height: 12 }} />
|
||||
Request Log
|
||||
</button>
|
||||
<button className={`debug-tab ${debugTab === "agents" ? "active" : ""}`} onClick={() => onDebugTabChange("agents")}>
|
||||
<Cloud className="button-icon" style={{ marginRight: 4, width: 12, height: 12 }} />
|
||||
Agents
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div className="debug-content">
|
||||
{debugTab === "log" && (
|
||||
<RequestLogTab
|
||||
requestLog={requestLog}
|
||||
copiedLogId={copiedLogId}
|
||||
onClear={onClearRequestLog}
|
||||
onCopy={onCopyRequestLog}
|
||||
/>
|
||||
)}
|
||||
|
||||
{debugTab === "events" && (
|
||||
<EventsTab events={events} offset={offset} onFetch={onFetchEvents} onClear={onResetEvents} />
|
||||
)}
|
||||
|
||||
{debugTab === "agents" && (
|
||||
<AgentsTab
|
||||
agents={agents}
|
||||
defaultAgents={defaultAgents}
|
||||
modesByAgent={modesByAgent}
|
||||
onRefresh={onRefreshAgents}
|
||||
onInstall={onInstallAgent}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default DebugPanel;
|
||||
|
|
@ -0,0 +1,91 @@
|
|||
import { ChevronDown, ChevronRight } from "lucide-react";
|
||||
import { useEffect, useState } from "react";
|
||||
import type { UniversalEvent } from "sandbox-agent";
|
||||
import { formatJson, formatTime } from "../../utils/format";
|
||||
import { getEventCategory, getEventClass, getEventIcon, getEventKey, getEventType } from "./eventUtils";
|
||||
|
||||
const EventsTab = ({
|
||||
events,
|
||||
offset,
|
||||
onFetch,
|
||||
onClear
|
||||
}: {
|
||||
events: UniversalEvent[];
|
||||
offset: number;
|
||||
onFetch: () => void;
|
||||
onClear: () => void;
|
||||
}) => {
|
||||
const [collapsedEvents, setCollapsedEvents] = useState<Record<string, boolean>>({});
|
||||
|
||||
useEffect(() => {
|
||||
if (events.length === 0) {
|
||||
setCollapsedEvents({});
|
||||
}
|
||||
}, [events.length]);
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="inline-row" style={{ marginBottom: 12, justifyContent: "space-between" }}>
|
||||
<span className="card-meta">Offset: {offset}</span>
|
||||
<div className="inline-row">
|
||||
<button className="button ghost small" onClick={onFetch}>
|
||||
Fetch
|
||||
</button>
|
||||
<button className="button ghost small" onClick={onClear}>
|
||||
Clear
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{events.length === 0 ? (
|
||||
<div className="card-meta">No events yet. Start streaming to receive events.</div>
|
||||
) : (
|
||||
<div className="event-list">
|
||||
{[...events].reverse().map((event) => {
|
||||
const type = getEventType(event);
|
||||
const category = getEventCategory(type);
|
||||
const eventClass = `${category} ${getEventClass(type)}`;
|
||||
const eventKey = getEventKey(event);
|
||||
const isCollapsed = collapsedEvents[eventKey] ?? true;
|
||||
const toggleCollapsed = () =>
|
||||
setCollapsedEvents((prev) => ({
|
||||
...prev,
|
||||
[eventKey]: !(prev[eventKey] ?? true)
|
||||
}));
|
||||
const Icon = getEventIcon(type);
|
||||
return (
|
||||
<div key={eventKey} className={`event-item ${isCollapsed ? "collapsed" : "expanded"}`}>
|
||||
<button
|
||||
className="event-summary"
|
||||
type="button"
|
||||
onClick={toggleCollapsed}
|
||||
title={isCollapsed ? "Expand payload" : "Collapse payload"}
|
||||
>
|
||||
<span className={`event-icon ${eventClass}`}>
|
||||
<Icon size={14} />
|
||||
</span>
|
||||
<div className="event-summary-main">
|
||||
<div className="event-title-row">
|
||||
<span className={`event-type ${eventClass}`}>{type}</span>
|
||||
<span className="event-time">{formatTime(event.time)}</span>
|
||||
</div>
|
||||
<div className="event-id">
|
||||
Event #{event.event_id || event.sequence} - seq {event.sequence} - {event.source}
|
||||
{event.synthetic ? " (synthetic)" : ""}
|
||||
</div>
|
||||
</div>
|
||||
<span className="event-chevron">
|
||||
{isCollapsed ? <ChevronRight size={16} /> : <ChevronDown size={16} />}
|
||||
</span>
|
||||
</button>
|
||||
{!isCollapsed && <pre className="code-block event-payload">{formatJson(event.data)}</pre>}
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default EventsTab;
|
||||
|
|
@ -0,0 +1,52 @@
|
|||
import { Clipboard } from "lucide-react";
|
||||
|
||||
import type { RequestLog } from "../../types/requestLog";
|
||||
|
||||
const RequestLogTab = ({
|
||||
requestLog,
|
||||
copiedLogId,
|
||||
onClear,
|
||||
onCopy
|
||||
}: {
|
||||
requestLog: RequestLog[];
|
||||
copiedLogId: number | null;
|
||||
onClear: () => void;
|
||||
onCopy: (entry: RequestLog) => void;
|
||||
}) => {
|
||||
return (
|
||||
<>
|
||||
<div className="inline-row" style={{ marginBottom: 12, justifyContent: "space-between" }}>
|
||||
<span className="card-meta">{requestLog.length} requests</span>
|
||||
<button className="button ghost small" onClick={onClear}>
|
||||
Clear
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{requestLog.length === 0 ? (
|
||||
<div className="card-meta">No requests logged yet.</div>
|
||||
) : (
|
||||
requestLog.map((entry) => (
|
||||
<div key={entry.id} className="log-item">
|
||||
<span className="log-method">{entry.method}</span>
|
||||
<span className="log-url text-truncate">{entry.url}</span>
|
||||
<span className={`log-status ${entry.status && entry.status < 400 ? "ok" : "error"}`}>
|
||||
{entry.status || "ERR"}
|
||||
</span>
|
||||
<div className="log-meta">
|
||||
<span>
|
||||
{entry.time}
|
||||
{entry.error && ` - ${entry.error}`}
|
||||
</span>
|
||||
<button className="copy-button" onClick={() => onCopy(entry)}>
|
||||
<Clipboard />
|
||||
{copiedLogId === entry.id ? "Copied" : "curl"}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
))
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default RequestLogTab;
|
||||
|
|
@ -0,0 +1,63 @@
|
|||
import {
|
||||
Activity,
|
||||
AlertTriangle,
|
||||
Brain,
|
||||
CheckCircle,
|
||||
FileDiff,
|
||||
HelpCircle,
|
||||
MessageSquare,
|
||||
PauseCircle,
|
||||
PlayCircle,
|
||||
Shield,
|
||||
Terminal,
|
||||
Wrench,
|
||||
Zap
|
||||
} from "lucide-react";
|
||||
import type { UniversalEvent } from "sandbox-agent";
|
||||
|
||||
export const getEventType = (event: UniversalEvent) => event.type;
|
||||
|
||||
export const getEventKey = (event: UniversalEvent) =>
|
||||
event.event_id ? `id:${event.event_id}` : `seq:${event.sequence}`;
|
||||
|
||||
export const getEventCategory = (type: string) => type.split(".")[0] ?? type;
|
||||
|
||||
export const getEventClass = (type: string) => type.replace(/\./g, "-");
|
||||
|
||||
export const getEventIcon = (type: string) => {
|
||||
switch (type) {
|
||||
case "session.started":
|
||||
return PlayCircle;
|
||||
case "session.ended":
|
||||
return PauseCircle;
|
||||
case "item.started":
|
||||
return MessageSquare;
|
||||
case "item.delta":
|
||||
return Activity;
|
||||
case "item.completed":
|
||||
return CheckCircle;
|
||||
case "question.requested":
|
||||
return HelpCircle;
|
||||
case "question.resolved":
|
||||
return CheckCircle;
|
||||
case "permission.requested":
|
||||
return Shield;
|
||||
case "permission.resolved":
|
||||
return CheckCircle;
|
||||
case "error":
|
||||
return AlertTriangle;
|
||||
case "agent.unparsed":
|
||||
return Brain;
|
||||
default:
|
||||
if (type.startsWith("item.")) return MessageSquare;
|
||||
if (type.startsWith("session.")) return PlayCircle;
|
||||
if (type.startsWith("error")) return AlertTriangle;
|
||||
if (type.startsWith("agent.")) return Brain;
|
||||
if (type.startsWith("question.")) return HelpCircle;
|
||||
if (type.startsWith("permission.")) return Shield;
|
||||
if (type.startsWith("file.")) return FileDiff;
|
||||
if (type.startsWith("command.")) return Terminal;
|
||||
if (type.startsWith("tool.")) return Wrench;
|
||||
return Zap;
|
||||
}
|
||||
};
|
||||
Loading…
Add table
Add a link
Reference in a new issue