mirror of
https://github.com/harivansh-afk/sandbox-agent.git
synced 2026-04-16 18:03:56 +00:00
acp spec (#155)
This commit is contained in:
parent
70287ec471
commit
e72eb9f611
264 changed files with 18559 additions and 51021 deletions
|
|
@ -1,7 +1,7 @@
|
|||
import { ArrowLeft, ArrowRight, ChevronDown, ChevronRight, Pencil, Plus, X } from "lucide-react";
|
||||
import { useEffect, useRef, useState } from "react";
|
||||
import type { AgentInfo, AgentModelInfo, AgentModeInfo, SkillSource } from "sandbox-agent";
|
||||
import type { McpServerEntry } from "../App";
|
||||
import type { AgentInfo, AgentModelInfo, AgentModeInfo, SkillSource } from "../types/legacyApi";
|
||||
|
||||
export type SessionConfig = {
|
||||
model: string;
|
||||
|
|
@ -14,8 +14,7 @@ const agentLabels: Record<string, string> = {
|
|||
claude: "Claude Code",
|
||||
codex: "Codex",
|
||||
opencode: "OpenCode",
|
||||
amp: "Amp",
|
||||
mock: "Mock"
|
||||
amp: "Amp"
|
||||
};
|
||||
|
||||
const validateServerJson = (json: string): string | null => {
|
||||
|
|
|
|||
|
|
@ -45,7 +45,7 @@ const badges = [
|
|||
type BadgeItem = (typeof badges)[number];
|
||||
|
||||
const getEnabled = (featureCoverage: FeatureCoverageView, key: BadgeItem["key"]) =>
|
||||
Boolean((featureCoverage as Record<string, boolean | undefined>)[key]);
|
||||
Boolean((featureCoverage as unknown as Record<string, boolean | undefined>)[key]);
|
||||
|
||||
const FeatureCoverageBadges = ({ featureCoverage }: { featureCoverage: FeatureCoverageView }) => {
|
||||
return (
|
||||
|
|
|
|||
|
|
@ -37,7 +37,9 @@ const ChatMessages = ({
|
|||
const isInProgress = item.status === "in_progress";
|
||||
const isFailed = item.status === "failed";
|
||||
const messageClass = getMessageClass(item);
|
||||
const statusLabel = item.status !== "completed" ? item.status.replace("_", " ") : "";
|
||||
const statusValue = item.status ?? "";
|
||||
const statusLabel =
|
||||
statusValue && statusValue !== "completed" ? statusValue.replace("_", " ") : "";
|
||||
const kindLabel = item.kind.replace("_", " ");
|
||||
|
||||
return (
|
||||
|
|
|
|||
|
|
@ -1,7 +1,14 @@
|
|||
import { MessageSquare, Plus, Square, Terminal } from "lucide-react";
|
||||
import { useEffect, useRef, useState } from "react";
|
||||
import type { AgentInfo, AgentModelInfo, AgentModeInfo, PermissionEventData, QuestionEventData, SkillSource } from "sandbox-agent";
|
||||
import type { McpServerEntry } from "../../App";
|
||||
import type {
|
||||
AgentInfo,
|
||||
AgentModelInfo,
|
||||
AgentModeInfo,
|
||||
PermissionEventData,
|
||||
QuestionEventData,
|
||||
SkillSource
|
||||
} from "../../types/legacyApi";
|
||||
import ApprovalsTab from "../debug/ApprovalsTab";
|
||||
import SessionCreateMenu, { type SessionConfig } from "../SessionCreateMenu";
|
||||
import ChatInput from "./ChatInput";
|
||||
|
|
@ -175,11 +182,6 @@ const ChatPanel = ({
|
|||
<Terminal className="empty-state-icon" />
|
||||
<div className="empty-state-title">Ready to Chat</div>
|
||||
<p className="empty-state-text">Send a message to start a conversation with the agent.</p>
|
||||
{agentLabel === "Mock" && (
|
||||
<div className="mock-agent-hint">
|
||||
The mock agent simulates agent responses for testing the inspector UI without requiring API credentials. Send <code>help</code> for available commands.
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
) : (
|
||||
<ChatMessages
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import type { UniversalItem } from "sandbox-agent";
|
||||
import type { UniversalItem } from "../../types/legacyApi";
|
||||
|
||||
export const getMessageClass = (item: UniversalItem) => {
|
||||
if (item.kind === "tool_call" || item.kind === "tool_result") return "tool";
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import type { ContentPart } from "sandbox-agent";
|
||||
import type { ContentPart } from "../../types/legacyApi";
|
||||
import { formatJson } from "../../utils/format";
|
||||
|
||||
const renderContentPart = (part: ContentPart, index: number) => {
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import type { UniversalItem } from "sandbox-agent";
|
||||
import type { UniversalItem } from "../../types/legacyApi";
|
||||
|
||||
export type TimelineEntry = {
|
||||
id: string;
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
import { Download, Loader2, RefreshCw } from "lucide-react";
|
||||
import { useState } from "react";
|
||||
import type { AgentInfo, AgentModeInfo } from "sandbox-agent";
|
||||
import type { AgentInfo, AgentModeInfo } from "../../types/legacyApi";
|
||||
import FeatureCoverageBadges from "../agents/FeatureCoverageBadges";
|
||||
import { emptyFeatureCoverage } from "../../types/agents";
|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import { HelpCircle, Shield } from "lucide-react";
|
||||
import type { PermissionEventData, QuestionEventData } from "sandbox-agent";
|
||||
import type { PermissionEventData, QuestionEventData } from "../../types/legacyApi";
|
||||
import { formatJson } from "../../utils/format";
|
||||
|
||||
const ApprovalsTab = ({
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import { Cloud, PlayCircle, Terminal } from "lucide-react";
|
||||
import type { AgentInfo, AgentModeInfo, UniversalEvent } from "sandbox-agent";
|
||||
import type { AgentInfo, AgentModeInfo, UniversalEvent } from "../../types/legacyApi";
|
||||
import AgentsTab from "./AgentsTab";
|
||||
import EventsTab from "./EventsTab";
|
||||
import RequestLogTab from "./RequestLogTab";
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
import { ChevronDown, ChevronRight } from "lucide-react";
|
||||
import { useEffect, useState } from "react";
|
||||
import type { UniversalEvent } from "sandbox-agent";
|
||||
import type { UniversalEvent } from "../../types/legacyApi";
|
||||
import { formatJson, formatTime } from "../../utils/format";
|
||||
import { getEventCategory, getEventClass, getEventIcon, getEventKey, getEventType } from "./eventUtils";
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,8 @@
|
|||
import { Clipboard } from "lucide-react";
|
||||
import { ChevronDown, ChevronRight, Clipboard } from "lucide-react";
|
||||
import { useState } from "react";
|
||||
|
||||
import type { RequestLog } from "../../types/requestLog";
|
||||
import { formatJson } from "../../utils/format";
|
||||
|
||||
const RequestLogTab = ({
|
||||
requestLog,
|
||||
|
|
@ -13,6 +15,12 @@ const RequestLogTab = ({
|
|||
onClear: () => void;
|
||||
onCopy: (entry: RequestLog) => void;
|
||||
}) => {
|
||||
const [expanded, setExpanded] = useState<Record<number, boolean>>({});
|
||||
|
||||
const toggleExpanded = (id: number) => {
|
||||
setExpanded((prev) => ({ ...prev, [id]: !prev[id] }));
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="inline-row" style={{ marginBottom: 12, justifyContent: "space-between" }}>
|
||||
|
|
@ -25,28 +33,94 @@ const RequestLogTab = ({
|
|||
{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>
|
||||
))
|
||||
<div className="event-list">
|
||||
{requestLog.map((entry) => {
|
||||
const isExpanded = expanded[entry.id] ?? false;
|
||||
const hasDetails = entry.headers || entry.body || entry.responseBody;
|
||||
return (
|
||||
<div key={entry.id} className={`event-item ${isExpanded ? "expanded" : "collapsed"}`}>
|
||||
<button
|
||||
className="event-summary"
|
||||
type="button"
|
||||
onClick={() => hasDetails && toggleExpanded(entry.id)}
|
||||
title={hasDetails ? (isExpanded ? "Collapse" : "Expand") : undefined}
|
||||
style={{ cursor: hasDetails ? "pointer" : "default" }}
|
||||
>
|
||||
<div className="event-summary-main" style={{ flex: 1 }}>
|
||||
<div className="event-title-row">
|
||||
<span className="log-method">{entry.method}</span>
|
||||
<span className="log-url text-truncate" style={{ flex: 1 }}>{entry.url}</span>
|
||||
<span className={`log-status ${entry.status && entry.status < 400 ? "ok" : "error"}`}>
|
||||
{entry.status || "ERR"}
|
||||
</span>
|
||||
</div>
|
||||
<div className="event-id">
|
||||
{entry.time}
|
||||
{entry.error && ` - ${entry.error}`}
|
||||
</div>
|
||||
</div>
|
||||
<span
|
||||
className="copy-button"
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
onCopy(entry);
|
||||
}}
|
||||
role="button"
|
||||
tabIndex={0}
|
||||
onKeyDown={(e) => {
|
||||
if (e.key === "Enter" || e.key === " ") {
|
||||
e.stopPropagation();
|
||||
onCopy(entry);
|
||||
}
|
||||
}}
|
||||
>
|
||||
<Clipboard size={14} />
|
||||
{copiedLogId === entry.id ? "Copied" : "curl"}
|
||||
</span>
|
||||
{hasDetails && (
|
||||
<span className="event-chevron">
|
||||
{isExpanded ? <ChevronDown size={16} /> : <ChevronRight size={16} />}
|
||||
</span>
|
||||
)}
|
||||
</button>
|
||||
{isExpanded && (
|
||||
<div className="event-payload" style={{ padding: "8px 12px" }}>
|
||||
{entry.headers && Object.keys(entry.headers).length > 0 && (
|
||||
<div style={{ marginBottom: 8 }}>
|
||||
<div className="part-title">Request Headers</div>
|
||||
<pre className="code-block">{Object.entries(entry.headers).map(([k, v]) => `${k}: ${v}`).join("\n")}</pre>
|
||||
</div>
|
||||
)}
|
||||
{entry.body && (
|
||||
<div style={{ marginBottom: 8 }}>
|
||||
<div className="part-title">Request Body</div>
|
||||
<pre className="code-block">{formatJsonSafe(entry.body)}</pre>
|
||||
</div>
|
||||
)}
|
||||
{entry.responseBody && (
|
||||
<div>
|
||||
<div className="part-title">Response Body</div>
|
||||
<pre className="code-block">{formatJsonSafe(entry.responseBody)}</pre>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
const formatJsonSafe = (text: string): string => {
|
||||
try {
|
||||
const parsed = JSON.parse(text);
|
||||
return formatJson(parsed);
|
||||
} catch {
|
||||
return text;
|
||||
}
|
||||
};
|
||||
|
||||
export default RequestLogTab;
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ import {
|
|||
CheckCircle,
|
||||
FileDiff,
|
||||
HelpCircle,
|
||||
Info,
|
||||
MessageSquare,
|
||||
PauseCircle,
|
||||
PlayCircle,
|
||||
|
|
@ -13,7 +14,7 @@ import {
|
|||
Wrench,
|
||||
Zap
|
||||
} from "lucide-react";
|
||||
import type { UniversalEvent } from "sandbox-agent";
|
||||
import type { UniversalEvent } from "../../types/legacyApi";
|
||||
|
||||
export const getEventType = (event: UniversalEvent) => event.type;
|
||||
|
||||
|
|
@ -26,10 +27,45 @@ export const getEventClass = (type: string) => type.replace(/\./g, "-");
|
|||
|
||||
export const getEventIcon = (type: string) => {
|
||||
switch (type) {
|
||||
// ACP session update events
|
||||
case "acp.agent_message_chunk":
|
||||
return MessageSquare;
|
||||
case "acp.user_message_chunk":
|
||||
return MessageSquare;
|
||||
case "acp.agent_thought_chunk":
|
||||
return Brain;
|
||||
case "acp.tool_call":
|
||||
return Wrench;
|
||||
case "acp.tool_call_update":
|
||||
return Activity;
|
||||
case "acp.plan":
|
||||
return FileDiff;
|
||||
case "acp.session_info_update":
|
||||
return Info;
|
||||
case "acp.usage_update":
|
||||
return Info;
|
||||
case "acp.current_mode_update":
|
||||
return Info;
|
||||
case "acp.config_option_update":
|
||||
return Info;
|
||||
case "acp.available_commands_update":
|
||||
return Terminal;
|
||||
|
||||
// Inspector lifecycle events
|
||||
case "inspector.turn_started":
|
||||
return PlayCircle;
|
||||
case "inspector.turn_ended":
|
||||
return PauseCircle;
|
||||
case "inspector.user_message":
|
||||
return MessageSquare;
|
||||
|
||||
// Session lifecycle (inspector-emitted)
|
||||
case "session.started":
|
||||
return PlayCircle;
|
||||
case "session.ended":
|
||||
return PauseCircle;
|
||||
|
||||
// Legacy synthetic events
|
||||
case "turn.started":
|
||||
return PlayCircle;
|
||||
case "turn.ended":
|
||||
|
|
@ -40,6 +76,8 @@ export const getEventIcon = (type: string) => {
|
|||
return Activity;
|
||||
case "item.completed":
|
||||
return CheckCircle;
|
||||
|
||||
// Approval events
|
||||
case "question.requested":
|
||||
return HelpCircle;
|
||||
case "question.resolved":
|
||||
|
|
@ -48,11 +86,16 @@ export const getEventIcon = (type: string) => {
|
|||
return Shield;
|
||||
case "permission.resolved":
|
||||
return CheckCircle;
|
||||
|
||||
// Error events
|
||||
case "error":
|
||||
return AlertTriangle;
|
||||
case "agent.unparsed":
|
||||
return Brain;
|
||||
|
||||
default:
|
||||
if (type.startsWith("acp.")) return Zap;
|
||||
if (type.startsWith("inspector.")) return Info;
|
||||
if (type.startsWith("item.")) return MessageSquare;
|
||||
if (type.startsWith("session.")) return PlayCircle;
|
||||
if (type.startsWith("error")) return AlertTriangle;
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue