mirror of
https://github.com/fabro-sh/fabro.git
synced 2026-10-03 02:24:33 +00:00
Merge pull request #863 from fabro-sh/stage-view-from-agent
Read the stage view from the agent's fold
This commit is contained in:
commit
f146312cda
31 changed files with 683 additions and 2843 deletions
|
|
@ -3,17 +3,25 @@ import TestRenderer, { act } from "react-test-renderer";
|
|||
import { MemoryRouter } from "react-router";
|
||||
|
||||
import {
|
||||
AgentSessionActivity,
|
||||
CompactionReason,
|
||||
ContextWindowCategory,
|
||||
ContextWindowCountMethod,
|
||||
ContextWindowStaleness,
|
||||
FailoverContinuation,
|
||||
FailoverStop,
|
||||
SkillActivationSource,
|
||||
TodoListKind,
|
||||
TodoStatus,
|
||||
ToolCategory,
|
||||
} from "@qltysh/fabro-api-client";
|
||||
import type {
|
||||
AgentErrorData,
|
||||
AgentSessionProjection,
|
||||
McpToolSummary,
|
||||
StageContextWindow,
|
||||
StageProjection,
|
||||
TokenUsage,
|
||||
} from "@qltysh/fabro-api-client";
|
||||
|
||||
import { StageInsightsSidebar } from "./stage-insights-sidebar";
|
||||
|
|
@ -33,6 +41,58 @@ function makeStage(overrides: Partial<StageProjection> = {}): StageProjection {
|
|||
};
|
||||
}
|
||||
|
||||
const NO_TOKENS: TokenUsage = { input: 0, output: 0, reasoning: 0, cache_read: 0, cache_write: 0 };
|
||||
|
||||
/** The coding agent's fold of a stage that has seen nothing yet. */
|
||||
function makeAgent(overrides: Partial<AgentSessionProjection> = {}): AgentSessionProjection {
|
||||
return {
|
||||
root_session_id: "ses_root",
|
||||
route: { provider: "anthropic", model: "claude-opus-4-7" },
|
||||
activity: AgentSessionActivity.RUNNING,
|
||||
usage: NO_TOKENS,
|
||||
cost_usd_micros: null,
|
||||
messages: 0,
|
||||
descendants: {},
|
||||
context_window: null,
|
||||
tools: {},
|
||||
mcp_servers: {},
|
||||
skills: { available: [], activated: [] },
|
||||
subagent_counts: { spawned: 0, turns_started: 0, completed: 0, failed: 0, closed: 0 },
|
||||
todos: {},
|
||||
subagents: [],
|
||||
compactions: [],
|
||||
failovers: [],
|
||||
files_touched: [],
|
||||
last_file_touched: null,
|
||||
prompts: 1,
|
||||
prompt: {
|
||||
completed: false,
|
||||
usage: NO_TOKENS,
|
||||
cost_usd_micros: null,
|
||||
messages: 0,
|
||||
context_window: null,
|
||||
tool_calls: 0,
|
||||
descendants: {},
|
||||
subagents: { spawned: 0, turns_started: 0, completed: 0, failed: 0, closed: 0 },
|
||||
compactions: [],
|
||||
files_touched: [],
|
||||
last_file_touched: null,
|
||||
},
|
||||
...overrides,
|
||||
};
|
||||
}
|
||||
|
||||
function mcpTools(count: number): McpToolSummary[] {
|
||||
return Array.from({ length: count }, (_, i) => ({
|
||||
name: `mcp__server__tool_${i}`,
|
||||
original_name: `tool_${i}`,
|
||||
}));
|
||||
}
|
||||
|
||||
function agentError(message: string): AgentErrorData {
|
||||
return { kind: "llm", message };
|
||||
}
|
||||
|
||||
function makeContextWindow(overrides: Partial<StageContextWindow> = {}): StageContextWindow {
|
||||
return {
|
||||
stage_id: "implement@1",
|
||||
|
|
@ -60,13 +120,13 @@ function makeContextWindow(overrides: Partial<StageContextWindow> = {}): StageCo
|
|||
// bun:test runs in a node-like env without a DOM, so shim `window.localStorage`
|
||||
// once — the sidebar feature-detects `typeof window` to decide whether to
|
||||
// persist collapse state. Seeding the shim lets us open default-collapsed
|
||||
// sections (Skills, MCPs) in the assertions below. Other test files (e.g.
|
||||
// sections in the assertions below. Other test files (e.g.
|
||||
// services-panel.test.tsx) install their own window and rely on
|
||||
// `delete globalThis.window` cleanup, so this descriptor stays configurable.
|
||||
let restoreWindow: (() => void) | null = null;
|
||||
beforeAll(() => {
|
||||
const store = new Map<string, string>();
|
||||
for (const key of ["todos", "context", "tools", "skills", "mcps"]) {
|
||||
for (const key of ["todos", "context", "files", "subagents", "tools", "skills", "mcps"]) {
|
||||
store.set(`fabro:stage-insights-section:${key}`, "1");
|
||||
}
|
||||
const stub = {
|
||||
|
|
@ -108,23 +168,34 @@ function render(stage: StageProjection | undefined, contextWindow: StageContextW
|
|||
}
|
||||
|
||||
describe("StageInsightsSidebar", () => {
|
||||
test("renders todo done/total ratio", () => {
|
||||
test("renders the root agent's todo list and counts the subagent lists apart", () => {
|
||||
const stage = makeStage({
|
||||
todos: {
|
||||
kind: TodoListKind.ANTHROPIC_TASKS,
|
||||
list_id: "anthropic_tasks:root",
|
||||
items: [
|
||||
{ id: "1", status: TodoStatus.COMPLETED, order: 0, subject: "Plan refactor" },
|
||||
{ id: "2", status: TodoStatus.COMPLETED, order: 1, subject: "Add tests" },
|
||||
{ id: "3", status: TodoStatus.IN_PROGRESS, order: 2, subject: "Land migration" },
|
||||
{ id: "4", status: TodoStatus.PENDING, order: 3, subject: "Review with Kieran" },
|
||||
],
|
||||
},
|
||||
agent: makeAgent({
|
||||
todos: {
|
||||
"anthropic_tasks:ses_root": {
|
||||
kind: TodoListKind.ANTHROPIC_TASKS,
|
||||
list_id: "anthropic_tasks:ses_root",
|
||||
items: [
|
||||
{ id: "1", status: TodoStatus.COMPLETED, order: 0, subject: "Plan refactor" },
|
||||
{ id: "2", status: TodoStatus.COMPLETED, order: 1, subject: "Add tests" },
|
||||
{ id: "3", status: TodoStatus.IN_PROGRESS, order: 2, subject: "Land migration" },
|
||||
{ id: "4", status: TodoStatus.PENDING, order: 3, subject: "Review with Kieran" },
|
||||
],
|
||||
},
|
||||
"openai_plan:ses_child": {
|
||||
kind: TodoListKind.OPENAI_PLAN,
|
||||
list_id: "openai_plan:ses_child",
|
||||
items: [{ id: "c1", status: TodoStatus.PENDING, order: 0, subject: "Child plan step" }],
|
||||
},
|
||||
},
|
||||
}),
|
||||
});
|
||||
const dom = render(stage, null);
|
||||
expect(dom).toContain("2/4");
|
||||
expect(dom).toContain("Plan refactor");
|
||||
expect(dom).toContain("Land migration");
|
||||
expect(dom).not.toContain("Child plan step");
|
||||
expect(dom).toContain("+1 subagent list");
|
||||
});
|
||||
|
||||
test("renders context window percent and breakdown labels", () => {
|
||||
|
|
@ -147,6 +218,24 @@ describe("StageInsightsSidebar", () => {
|
|||
expect(dom).not.toContain("31%");
|
||||
});
|
||||
|
||||
test("renders the compactions row under the context window", () => {
|
||||
const stage = makeStage({
|
||||
agent: makeAgent({
|
||||
compactions: [
|
||||
{
|
||||
reason: CompactionReason.THRESHOLD,
|
||||
original_turn_count: 20,
|
||||
preserved_turn_count: 6,
|
||||
summary_token_estimate: 500,
|
||||
tracked_file_count: 3,
|
||||
},
|
||||
],
|
||||
}),
|
||||
});
|
||||
const dom = render(stage, makeContextWindow());
|
||||
expect(dom).toContain("1 compaction · last kept 6 of 20 turns");
|
||||
});
|
||||
|
||||
test("renders projected agent tool names and invoked state", () => {
|
||||
const dom = render(
|
||||
makeStage({
|
||||
|
|
@ -179,29 +268,16 @@ describe("StageInsightsSidebar", () => {
|
|||
expect(dom).toContain("Used");
|
||||
});
|
||||
|
||||
test("renders mcp server used/total count, marks invoked servers as 'used'", () => {
|
||||
test("derives mcp server status from the fold and marks invoked servers as 'used'", () => {
|
||||
const dom = render(
|
||||
makeStage({
|
||||
mcp_servers: [
|
||||
{
|
||||
server_name: "context7",
|
||||
tool_count: 12,
|
||||
status: { kind: "ready", tools: [] },
|
||||
invoked: true,
|
||||
agent: makeAgent({
|
||||
mcp_servers: {
|
||||
context7: { tools: mcpTools(12), error: null, invoked: true },
|
||||
filesystem: { tools: mcpTools(3), error: null, invoked: false },
|
||||
atlassian: { tools: [], error: "auth failed", invoked: false },
|
||||
},
|
||||
{
|
||||
server_name: "filesystem",
|
||||
tool_count: 3,
|
||||
status: { kind: "ready", tools: [] },
|
||||
invoked: false,
|
||||
},
|
||||
{
|
||||
server_name: "atlassian",
|
||||
tool_count: 0,
|
||||
status: { kind: "failed", error: "auth failed" },
|
||||
invoked: false,
|
||||
},
|
||||
],
|
||||
}),
|
||||
}),
|
||||
null,
|
||||
);
|
||||
|
|
@ -213,19 +289,17 @@ describe("StageInsightsSidebar", () => {
|
|||
expect(dom).toContain("3 tools");
|
||||
expect(dom).toContain("atlassian");
|
||||
expect(dom).toContain("Failed");
|
||||
expect(dom).toContain("auth failed");
|
||||
});
|
||||
|
||||
test("renders a disconnected mcp server as disconnected, still counted as used", () => {
|
||||
const dom = render(
|
||||
makeStage({
|
||||
mcp_servers: [
|
||||
{
|
||||
server_name: "github",
|
||||
tool_count: 4,
|
||||
status: { kind: "disconnected", error: "transport closed" },
|
||||
invoked: true,
|
||||
agent: makeAgent({
|
||||
mcp_servers: {
|
||||
github: { tools: mcpTools(4), error: null, invoked: true, disconnected: "transport closed" },
|
||||
},
|
||||
],
|
||||
}),
|
||||
}),
|
||||
null,
|
||||
);
|
||||
|
|
@ -238,18 +312,20 @@ describe("StageInsightsSidebar", () => {
|
|||
test("shows skill activated/available ratio with source label", () => {
|
||||
const dom = render(
|
||||
makeStage({
|
||||
skills: {
|
||||
activated: [
|
||||
{ name: "frontend-design", source: SkillActivationSource.SLASH },
|
||||
{ name: "debug", source: SkillActivationSource.TOOL },
|
||||
],
|
||||
available: [
|
||||
{ name: "frontend-design", description: "" },
|
||||
{ name: "debug", description: "" },
|
||||
{ name: "tdd", description: "" },
|
||||
{ name: "ce-review", description: "" },
|
||||
],
|
||||
},
|
||||
agent: makeAgent({
|
||||
skills: {
|
||||
activated: [
|
||||
{ name: "frontend-design", source: SkillActivationSource.SLASH },
|
||||
{ name: "debug", source: SkillActivationSource.TOOL },
|
||||
],
|
||||
available: [
|
||||
{ name: "frontend-design", description: "" },
|
||||
{ name: "debug", description: "" },
|
||||
{ name: "tdd", description: "" },
|
||||
{ name: "ce-review", description: "" },
|
||||
],
|
||||
},
|
||||
}),
|
||||
}),
|
||||
null,
|
||||
);
|
||||
|
|
@ -259,9 +335,83 @@ describe("StageInsightsSidebar", () => {
|
|||
expect(dom).toContain("+2 more available");
|
||||
});
|
||||
|
||||
test("lists the files the session tree wrote and marks the last one", () => {
|
||||
const dom = render(
|
||||
makeStage({
|
||||
agent: makeAgent({
|
||||
files_touched: ["/workspace/src/lib.rs", "/workspace/src/main.rs"],
|
||||
last_file_touched: "/workspace/src/main.rs",
|
||||
}),
|
||||
}),
|
||||
null,
|
||||
);
|
||||
expect(dom).toContain("src/lib.rs");
|
||||
expect(dom).toContain("src/main.rs");
|
||||
expect(dom).toContain("last");
|
||||
// Full paths stay in the tooltip.
|
||||
expect(dom).toContain("/workspace/src/lib.rs");
|
||||
});
|
||||
|
||||
test("renders subagents with their status", () => {
|
||||
const dom = render(
|
||||
makeStage({
|
||||
agent: makeAgent({
|
||||
subagents: [
|
||||
{ agent_id: "sub-1", depth: 1, task: "Review the module", status: { status: "completed", success: true, turns_used: 3 } },
|
||||
{ agent_id: "sub-2", depth: 1, task: "Check the tests", status: { status: "failed", error: agentError("boom") } },
|
||||
{ agent_id: "sub-3", depth: 1, task: "Still looking", status: { status: "running" } },
|
||||
],
|
||||
}),
|
||||
}),
|
||||
null,
|
||||
);
|
||||
expect(dom).toContain("2/3");
|
||||
expect(dom).toContain("Review the module");
|
||||
expect(dom).toContain("3 turns");
|
||||
expect(dom).toContain("Check the tests");
|
||||
expect(dom).toContain("Failed");
|
||||
expect(dom).toContain("boom");
|
||||
expect(dom).toContain("Still looking");
|
||||
expect(dom).toContain("running");
|
||||
});
|
||||
|
||||
test("shows a failover badge with the route the session moved to and why it stopped", () => {
|
||||
const dom = render(
|
||||
makeStage({
|
||||
agent: makeAgent({
|
||||
route: { provider: "openai", model: "gpt-5.4" },
|
||||
failovers: [
|
||||
{
|
||||
from: "anthropic/claude-opus-4-7",
|
||||
to: "openai/gpt-5.4",
|
||||
attempt: 1,
|
||||
error: agentError("rate limited"),
|
||||
usage: NO_TOKENS,
|
||||
inference_ms: 120,
|
||||
tool_ms: 30,
|
||||
continuation: FailoverContinuation.CONTINUE_TURN,
|
||||
},
|
||||
],
|
||||
failover_stopped: {
|
||||
route: "openai/gpt-5.4",
|
||||
attempt: 1,
|
||||
reason: FailoverStop.EXHAUSTED,
|
||||
error: agentError("key revoked"),
|
||||
},
|
||||
}),
|
||||
}),
|
||||
null,
|
||||
);
|
||||
expect(dom).toContain("Moved to openai/gpt-5.4 after 1 attempt");
|
||||
expect(dom).toContain("Stopped: routes exhausted");
|
||||
expect(dom).toContain("rate limited");
|
||||
expect(dom).toContain("key revoked");
|
||||
});
|
||||
|
||||
test("renders empty-friendly content when stage projection is missing", () => {
|
||||
const dom = render(undefined, null);
|
||||
// sidebar still renders even with no data
|
||||
expect(dom).toContain("Agent");
|
||||
expect(dom).not.toContain("Moved to");
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -11,24 +11,33 @@ import {
|
|||
XCircleIcon,
|
||||
} from "@heroicons/react/24/solid";
|
||||
import {
|
||||
ArrowsRightLeftIcon,
|
||||
CheckBadgeIcon,
|
||||
CommandLineIcon,
|
||||
DocumentTextIcon,
|
||||
ListBulletIcon,
|
||||
PuzzlePieceIcon,
|
||||
ServerStackIcon,
|
||||
Squares2X2Icon,
|
||||
UserGroupIcon,
|
||||
WrenchScrewdriverIcon,
|
||||
} from "@heroicons/react/24/outline";
|
||||
import {
|
||||
ContextWindowCategory,
|
||||
ContextWindowStaleness,
|
||||
FailoverStop,
|
||||
SkillActivationSource,
|
||||
TodoStatus,
|
||||
} from "@qltysh/fabro-api-client";
|
||||
import type {
|
||||
ActivatedSkill,
|
||||
AgentSessionActivatedSkill,
|
||||
AgentSessionCompaction,
|
||||
AgentSessionFailoverStop,
|
||||
AgentSessionMcpServer,
|
||||
AgentSessionProjection,
|
||||
AgentSessionRouteFailover,
|
||||
AgentSessionSubagent,
|
||||
ContextWindowBreakdownItem,
|
||||
McpServerProjection,
|
||||
SkillSummary,
|
||||
StageContextWindow,
|
||||
StageProjection,
|
||||
|
|
@ -41,14 +50,16 @@ import { formatTokenCount } from "../lib/format";
|
|||
const COLLAPSED_STORAGE_KEY = "fabro:stage-insights-sidebar-collapsed";
|
||||
const SECTION_STORAGE_PREFIX = "fabro:stage-insights-section:";
|
||||
|
||||
type SectionKey = "todos" | "context" | "tools" | "skills" | "mcps";
|
||||
type SectionKey = "todos" | "context" | "files" | "subagents" | "tools" | "skills" | "mcps";
|
||||
|
||||
const SECTIONS_DEFAULT_OPEN: Record<SectionKey, boolean> = {
|
||||
todos: true,
|
||||
context: false,
|
||||
tools: false,
|
||||
skills: false,
|
||||
mcps: false,
|
||||
todos: true,
|
||||
context: false,
|
||||
files: false,
|
||||
subagents: false,
|
||||
tools: false,
|
||||
skills: false,
|
||||
mcps: false,
|
||||
};
|
||||
|
||||
export interface StageInsightsSidebarProps {
|
||||
|
|
@ -58,6 +69,11 @@ export interface StageInsightsSidebarProps {
|
|||
contextWindow: StageContextWindow | null | undefined;
|
||||
}
|
||||
|
||||
/**
|
||||
* The agent stage's sidebar. Everything about the agent's session comes from
|
||||
* `stage.agent`, the coding agent's own fold of the stage's events; the
|
||||
* stage itself contributes the tool catalog it was handed.
|
||||
*/
|
||||
export function StageInsightsSidebar({ stage, contextWindow }: StageInsightsSidebarProps) {
|
||||
const [collapsed, setCollapsed] = useState(loadStoredCollapsed);
|
||||
const toggleCollapsed = useCallback(() => {
|
||||
|
|
@ -68,14 +84,24 @@ export function StageInsightsSidebar({ stage, contextWindow }: StageInsightsSide
|
|||
});
|
||||
}, []);
|
||||
|
||||
const todos = stage?.todos ?? null;
|
||||
const skills = stage?.skills ?? { activated: [], available: [] };
|
||||
const agent = stage?.agent ?? null;
|
||||
const todoLists = agent ? Object.values(agent.todos) : [];
|
||||
const todos = rootTodoList(agent);
|
||||
const otherTodoLists = todos ? todoLists.length - 1 : todoLists.length;
|
||||
const skills = agent?.skills ?? { activated: [], available: [] };
|
||||
const agentTools = stage?.agent_tools ?? [];
|
||||
const mcpServers = stage?.mcp_servers ?? [];
|
||||
const mcpServers = agent ? mcpServerRows(agent.mcp_servers) : [];
|
||||
const files = agent?.files_touched ?? [];
|
||||
const lastFile = agent?.last_file_touched ?? null;
|
||||
const subagents = agent?.subagents ?? [];
|
||||
const failovers = agent?.failovers ?? [];
|
||||
const failoverStopped = agent?.failover_stopped ?? null;
|
||||
const compactions = agent?.compactions ?? [];
|
||||
|
||||
const todoStats = countTodoStats(todos);
|
||||
const activatedSkillNames = new Set(skills.activated.map((s) => s.name));
|
||||
const invokedToolCount = agentTools.filter((tool) => tool.invoked).length;
|
||||
const finishedSubagents = subagents.filter((s) => s.status.status !== "running").length;
|
||||
|
||||
return (
|
||||
<aside
|
||||
|
|
@ -104,6 +130,8 @@ export function StageInsightsSidebar({ stage, contextWindow }: StageInsightsSide
|
|||
</button>
|
||||
</div>
|
||||
|
||||
<FailoverBadge collapsed={collapsed} failovers={failovers} stopped={failoverStopped} />
|
||||
|
||||
<div className="mt-3 flex flex-col gap-4">
|
||||
{todoStats.total > 0 && (
|
||||
<CollapsibleSection
|
||||
|
|
@ -114,11 +142,39 @@ export function StageInsightsSidebar({ stage, contextWindow }: StageInsightsSide
|
|||
count={`${todoStats.done}/${todoStats.total}`}
|
||||
empty={false}
|
||||
>
|
||||
<TodoSection todos={todos} />
|
||||
<TodoSection todos={todos} otherLists={otherTodoLists} />
|
||||
</CollapsibleSection>
|
||||
)}
|
||||
|
||||
<ContextWindowSection collapsed={collapsed} snapshot={contextWindow ?? null} />
|
||||
<ContextWindowSection
|
||||
collapsed={collapsed}
|
||||
snapshot={contextWindow ?? null}
|
||||
compactions={compactions}
|
||||
/>
|
||||
|
||||
<CollapsibleSection
|
||||
sectionKey="files"
|
||||
title="Files"
|
||||
icon={DocumentTextIcon}
|
||||
collapsed={collapsed}
|
||||
count={files.length}
|
||||
empty={files.length === 0}
|
||||
hideCountWhenCollapsed
|
||||
>
|
||||
<FilesSection files={files} lastFile={lastFile} />
|
||||
</CollapsibleSection>
|
||||
|
||||
<CollapsibleSection
|
||||
sectionKey="subagents"
|
||||
title="Subagents"
|
||||
icon={UserGroupIcon}
|
||||
collapsed={collapsed}
|
||||
count={`${finishedSubagents}/${subagents.length}`}
|
||||
empty={subagents.length === 0}
|
||||
hideCountWhenCollapsed
|
||||
>
|
||||
<SubagentsSection subagents={subagents} />
|
||||
</CollapsibleSection>
|
||||
|
||||
<CollapsibleSection
|
||||
sectionKey="skills"
|
||||
|
|
@ -228,6 +284,52 @@ function CollapsibleSection({
|
|||
);
|
||||
}
|
||||
|
||||
// ---------- Failover ----------
|
||||
|
||||
interface FailoverBadgeProps {
|
||||
collapsed: boolean;
|
||||
failovers: AgentSessionRouteFailover[];
|
||||
stopped: AgentSessionFailoverStop | null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Where the session's model moved: the last fallback route the root took,
|
||||
* and whether the prompt then stopped although routes were named. The
|
||||
* failures that caused each are the hover text.
|
||||
*/
|
||||
function FailoverBadge({ collapsed, failovers, stopped }: FailoverBadgeProps) {
|
||||
const last = failovers.length > 0 ? failovers[failovers.length - 1] : null;
|
||||
if (!last && !stopped) return null;
|
||||
const details = [
|
||||
...failovers.map((move) => `${move.from} failed: ${move.error.message}`),
|
||||
stopped ? `${stopped.route}: ${stopped.error.message}` : null,
|
||||
].filter((line): line is string => line != null);
|
||||
if (collapsed) {
|
||||
return (
|
||||
<div className="mt-2 flex justify-center" title={details.join("\n")}>
|
||||
<ArrowsRightLeftIcon className="size-4 shrink-0 text-amber" aria-label="Model failover" />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<div className="mt-2 space-y-0.5 px-2 text-[11px] text-amber" title={details.join("\n")}>
|
||||
{last && (
|
||||
<p className="flex items-center gap-1">
|
||||
<ArrowsRightLeftIcon className="size-3.5 shrink-0" aria-hidden="true" />
|
||||
<span className="min-w-0 truncate">
|
||||
{`Moved to ${last.to} after ${last.attempt} ${last.attempt === 1 ? "attempt" : "attempts"}`}
|
||||
</span>
|
||||
</p>
|
||||
)}
|
||||
{stopped && (
|
||||
<p className="pl-4.5">
|
||||
{`Stopped: ${stopped.reason === FailoverStop.EXHAUSTED ? "routes exhausted" : "failure not eligible for failover"}`}
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
// ---------- Todos ----------
|
||||
|
||||
interface TodoStats {
|
||||
|
|
@ -235,6 +337,18 @@ interface TodoStats {
|
|||
total: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* The root agent's own list: the one keyed by the root session's id. Subagent
|
||||
* plans are separate lists in the same map.
|
||||
*/
|
||||
function rootTodoList(agent: AgentSessionProjection | null): TodoListProjection | null {
|
||||
if (!agent) return null;
|
||||
const lists = Object.values(agent.todos);
|
||||
const rootId = agent.root_session_id;
|
||||
const root = rootId ? lists.find((list) => list.list_id.endsWith(rootId)) : undefined;
|
||||
return root ?? (lists.length === 1 ? lists[0] : null);
|
||||
}
|
||||
|
||||
function countTodoStats(list: TodoListProjection | null): TodoStats {
|
||||
const items = list?.items ?? [];
|
||||
let done = 0;
|
||||
|
|
@ -244,16 +358,23 @@ function countTodoStats(list: TodoListProjection | null): TodoStats {
|
|||
return { done, total: items.length };
|
||||
}
|
||||
|
||||
function TodoSection({ todos }: { todos: TodoListProjection | null }) {
|
||||
function TodoSection({ todos, otherLists }: { todos: TodoListProjection | null; otherLists: number }) {
|
||||
if (!todos || (todos.items?.length ?? 0) === 0) return <p className="text-xs text-fg-muted">No todos.</p>;
|
||||
const items = Array.from(todos.items ?? []);
|
||||
items.sort((a, b) => a.order - b.order);
|
||||
return (
|
||||
<ul className="space-y-1">
|
||||
{items.map((item) => (
|
||||
<TodoRow key={item.id} todo={item} />
|
||||
))}
|
||||
</ul>
|
||||
<div className="space-y-2">
|
||||
<ul className="space-y-1">
|
||||
{items.map((item) => (
|
||||
<TodoRow key={item.id} todo={item} />
|
||||
))}
|
||||
</ul>
|
||||
{otherLists > 0 && (
|
||||
<p className="text-[11px] text-fg-muted">
|
||||
{`+${otherLists} subagent ${otherLists === 1 ? "list" : "lists"}`}
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
|
|
@ -297,9 +418,10 @@ function EmptyCircleIcon({ className }: { className?: string }) {
|
|||
interface ContextWindowSectionProps {
|
||||
collapsed: boolean;
|
||||
snapshot: StageContextWindow | null;
|
||||
compactions: AgentSessionCompaction[];
|
||||
}
|
||||
|
||||
function ContextWindowSection({ collapsed, snapshot }: ContextWindowSectionProps) {
|
||||
function ContextWindowSection({ collapsed, snapshot, compactions }: ContextWindowSectionProps) {
|
||||
const [open, setOpen] = useState(() => loadStoredSectionOpen("context"));
|
||||
const toggle = useCallback(() => {
|
||||
setOpen((prev) => {
|
||||
|
|
@ -343,12 +465,25 @@ function ContextWindowSection({ collapsed, snapshot }: ContextWindowSectionProps
|
|||
<ContextBar snapshot={snapshot} />
|
||||
</div>
|
||||
<ContextBreakdown snapshot={snapshot} />
|
||||
<CompactionsRow compactions={compactions} />
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
/** How often the conversation was compacted, and what the last one kept. */
|
||||
function CompactionsRow({ compactions }: { compactions: AgentSessionCompaction[] }) {
|
||||
if (compactions.length === 0) return null;
|
||||
const last = compactions[compactions.length - 1];
|
||||
const noun = compactions.length === 1 ? "compaction" : "compactions";
|
||||
return (
|
||||
<p className="mt-2 px-2 text-[11px] text-fg-muted" title={`Last compaction: ${last.reason}`}>
|
||||
{`${compactions.length} ${noun} · last kept ${last.preserved_turn_count} of ${last.original_turn_count} turns`}
|
||||
</p>
|
||||
);
|
||||
}
|
||||
|
||||
function ContextBar({ snapshot }: { snapshot: StageContextWindow | null }) {
|
||||
if (!snapshot || snapshot.usage_percent == null) {
|
||||
return (
|
||||
|
|
@ -484,10 +619,102 @@ function categoryLabel(category: ContextWindowCategory): string {
|
|||
}
|
||||
}
|
||||
|
||||
// ---------- Files ----------
|
||||
|
||||
/** Files the stage's agent and its subagents wrote or edited, sorted. */
|
||||
function FilesSection({ files, lastFile }: { files: string[]; lastFile: string | null }) {
|
||||
if (files.length === 0) return <p className="text-xs text-fg-muted">No files written.</p>;
|
||||
return (
|
||||
<ul className="space-y-1">
|
||||
{files.map((path) => {
|
||||
const isLast = path === lastFile;
|
||||
return (
|
||||
<li key={path} title={path} className="flex items-center gap-1.5">
|
||||
<DocumentTextIcon className="size-3.5 shrink-0 text-fg-muted" aria-hidden="true" />
|
||||
<span className={`min-w-0 flex-1 truncate font-mono text-[11px] ${isLast ? "text-fg-2" : "text-fg-3"}`}>
|
||||
{fileLabel(path)}
|
||||
</span>
|
||||
{isLast && (
|
||||
<span className="text-[10px] uppercase tracking-wider text-fg-muted">last</span>
|
||||
)}
|
||||
</li>
|
||||
);
|
||||
})}
|
||||
</ul>
|
||||
);
|
||||
}
|
||||
|
||||
/** The path from its last two segments, so a deep tree still reads. */
|
||||
function fileLabel(path: string): string {
|
||||
const segments = path.split("/").filter((segment) => segment.length > 0);
|
||||
return segments.length <= 2 ? path : segments.slice(-2).join("/");
|
||||
}
|
||||
|
||||
// ---------- Subagents ----------
|
||||
|
||||
function SubagentsSection({ subagents }: { subagents: AgentSessionSubagent[] }) {
|
||||
if (subagents.length === 0) return <p className="text-xs text-fg-muted">No subagents.</p>;
|
||||
return (
|
||||
<ul className="space-y-1">
|
||||
{subagents.map((subagent) => (
|
||||
<SubagentRow key={subagent.agent_id} subagent={subagent} />
|
||||
))}
|
||||
</ul>
|
||||
);
|
||||
}
|
||||
|
||||
function SubagentRow({ subagent }: { subagent: AgentSessionSubagent }) {
|
||||
const { status } = subagent;
|
||||
const title = status.status === "failed" ? `${subagent.task}\n${status.error.message}` : subagent.task;
|
||||
return (
|
||||
<li className="flex items-center gap-1.5" title={title}>
|
||||
<SubagentStatusIcon subagent={subagent} />
|
||||
<span className="min-w-0 flex-1 truncate text-xs text-fg-2">{subagent.task}</span>
|
||||
<SubagentStatusBadge subagent={subagent} />
|
||||
</li>
|
||||
);
|
||||
}
|
||||
|
||||
function SubagentStatusIcon({ subagent }: { subagent: AgentSessionSubagent }) {
|
||||
const { status } = subagent;
|
||||
switch (status.status) {
|
||||
case "running":
|
||||
return <ArrowPathIcon className="size-3.5 shrink-0 animate-spin text-teal-500" aria-label="Running" />;
|
||||
case "completed":
|
||||
return status.success ? (
|
||||
<CheckCircleIcon className="size-3.5 shrink-0 text-mint" aria-label="Completed" />
|
||||
) : (
|
||||
<ExclamationTriangleIcon className="size-3.5 shrink-0 text-amber" aria-label="Completed without success" />
|
||||
);
|
||||
case "failed":
|
||||
return <XCircleIcon className="size-3.5 shrink-0 text-coral" aria-label="Failed" />;
|
||||
case "closed":
|
||||
return <EmptyCircleIcon className="size-3.5 shrink-0 text-fg-muted" aria-label="Closed" />;
|
||||
}
|
||||
}
|
||||
|
||||
function SubagentStatusBadge({ subagent }: { subagent: AgentSessionSubagent }) {
|
||||
const { status } = subagent;
|
||||
switch (status.status) {
|
||||
case "running":
|
||||
return <span className="text-[10px] uppercase tracking-wider text-teal-500">running</span>;
|
||||
case "completed":
|
||||
return (
|
||||
<span className="font-mono text-[10px] tabular-nums text-fg-muted">
|
||||
{`${status.turns_used} ${status.turns_used === 1 ? "turn" : "turns"}`}
|
||||
</span>
|
||||
);
|
||||
case "failed":
|
||||
return <span className="text-[10px] uppercase tracking-wider text-coral">Failed</span>;
|
||||
case "closed":
|
||||
return <span className="text-[10px] uppercase tracking-wider text-fg-muted">closed</span>;
|
||||
}
|
||||
}
|
||||
|
||||
// ---------- Skills ----------
|
||||
|
||||
interface SkillsSectionProps {
|
||||
activated: ActivatedSkill[];
|
||||
activated: AgentSessionActivatedSkill[];
|
||||
available: SkillSummary[];
|
||||
activatedNames: Set<string>;
|
||||
}
|
||||
|
|
@ -517,7 +744,7 @@ function SkillsSection({ activated, available, activatedNames }: SkillsSectionPr
|
|||
);
|
||||
}
|
||||
|
||||
function SkillSourceIcon({ source }: { source: ActivatedSkill["source"] }) {
|
||||
function SkillSourceIcon({ source }: { source: AgentSessionActivatedSkill["source"] }) {
|
||||
const Icon = source === SkillActivationSource.SLASH ? CommandLineIcon : PuzzlePieceIcon;
|
||||
return <Icon className="size-3.5 shrink-0 text-fg-muted" />;
|
||||
}
|
||||
|
|
@ -549,20 +776,49 @@ function AgentToolsSection({ tools }: { tools: ToolSummary[] }) {
|
|||
|
||||
// ---------- MCPs ----------
|
||||
|
||||
function McpSection({ servers }: { servers: McpServerProjection[] }) {
|
||||
type McpStatus = "ready" | "failed" | "disconnected";
|
||||
|
||||
interface McpServerRow {
|
||||
name: string;
|
||||
status: McpStatus;
|
||||
/** Why it failed, or what closed its connection. */
|
||||
error: string | null;
|
||||
toolCount: number;
|
||||
invoked: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* The agent's MCP servers as rows. A closed connection outranks a failed
|
||||
* start, which outranks ready; the tool count is the tools it advertised.
|
||||
*/
|
||||
function mcpServerRows(servers: Record<string, AgentSessionMcpServer>): McpServerRow[] {
|
||||
return Object.entries(servers).map(([name, server]) => {
|
||||
const status: McpStatus =
|
||||
server.disconnected != null ? "disconnected" : server.error != null ? "failed" : "ready";
|
||||
return {
|
||||
name,
|
||||
status,
|
||||
error: server.disconnected ?? server.error ?? null,
|
||||
toolCount: server.tools.length,
|
||||
invoked: server.invoked,
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
function McpSection({ servers }: { servers: McpServerRow[] }) {
|
||||
if (servers.length === 0) return <p className="text-xs text-fg-muted">No MCP servers.</p>;
|
||||
return (
|
||||
<ul className="space-y-1">
|
||||
{servers.map((server) => {
|
||||
// Dim unused servers so the eye lands on the invoked ones first;
|
||||
// failed and disconnected servers keep their tone regardless.
|
||||
const nameClass = server.status.kind === "ready" && !server.invoked
|
||||
const nameClass = server.status === "ready" && !server.invoked
|
||||
? "min-w-0 flex-1 truncate text-xs text-fg-muted"
|
||||
: "min-w-0 flex-1 truncate text-xs text-fg-2";
|
||||
return (
|
||||
<li key={server.server_name} className="flex items-center gap-1.5">
|
||||
<li key={server.name} className="flex items-center gap-1.5" title={server.error ?? undefined}>
|
||||
<McpStatusIcon status={server.status} />
|
||||
<span className={nameClass}>{server.server_name}</span>
|
||||
<span className={nameClass}>{server.name}</span>
|
||||
<McpStatusBadge server={server} />
|
||||
</li>
|
||||
);
|
||||
|
|
@ -571,8 +827,8 @@ function McpSection({ servers }: { servers: McpServerProjection[] }) {
|
|||
);
|
||||
}
|
||||
|
||||
function McpStatusIcon({ status }: { status: McpServerProjection["status"] }) {
|
||||
switch (status.kind) {
|
||||
function McpStatusIcon({ status }: { status: McpStatus }) {
|
||||
switch (status) {
|
||||
case "ready":
|
||||
return <CheckCircleIcon className="size-3.5 shrink-0 text-mint" aria-label="Ready" />;
|
||||
case "disconnected":
|
||||
|
|
@ -587,14 +843,14 @@ function McpStatusIcon({ status }: { status: McpServerProjection["status"] }) {
|
|||
}
|
||||
}
|
||||
|
||||
function McpStatusBadge({ server }: { server: McpServerProjection }) {
|
||||
switch (server.status.kind) {
|
||||
function McpStatusBadge({ server }: { server: McpServerRow }) {
|
||||
switch (server.status) {
|
||||
case "ready":
|
||||
return (
|
||||
<span className="font-mono text-[10px] tabular-nums text-fg-muted">
|
||||
{server.invoked
|
||||
? "used"
|
||||
: `${server.tool_count} ${server.tool_count === 1 ? "tool" : "tools"}`}
|
||||
: `${server.toolCount} ${server.toolCount === 1 ? "tool" : "tools"}`}
|
||||
</span>
|
||||
);
|
||||
case "disconnected":
|
||||
|
|
|
|||
|
|
@ -514,3 +514,27 @@ async function waitFor(condition: () => boolean, timeoutMs = 200) {
|
|||
}
|
||||
throw new Error("condition did not become true before timeout");
|
||||
}
|
||||
|
||||
describe("agent session events", () => {
|
||||
test("refresh the run state the stage sidebar reads its agent facts from", () => {
|
||||
for (const event of [
|
||||
"agent.route.failover",
|
||||
"agent.route.failover.stopped",
|
||||
"agent.mcp.server.ready",
|
||||
"agent.mcp.server.failed",
|
||||
"agent.mcp.server.disconnected",
|
||||
"agent.skills.discovered",
|
||||
"agent.skill.activated",
|
||||
"agent.sub.spawned",
|
||||
"agent.sub.completed",
|
||||
"agent.sub.failed",
|
||||
"agent.compaction.completed",
|
||||
]) {
|
||||
expect(queryKeysForRunEvent("run-1", event, "code@1")).toEqual([
|
||||
queryKeys.runs.state("run-1"),
|
||||
queryKeys.runs.events("run-1", 1000),
|
||||
queryKeys.runs.stageEvents("run-1", "code@1"),
|
||||
]);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -152,6 +152,26 @@ const TODO_EVENTS = new Set([
|
|||
"todo.updated",
|
||||
"todo.deleted",
|
||||
]);
|
||||
// The coding agent's own events whose facts the stage sidebar reads from
|
||||
// `StageProjection.agent`: route moves, MCP server outcomes, skills,
|
||||
// subagent lifecycle, and compactions. Each refreshes the run state the
|
||||
// sidebar renders from, like a todo mutation does. Files the tree wrote
|
||||
// arrive with `agent.tool.completed`, which already refreshes it.
|
||||
const AGENT_SESSION_EVENTS = new Set([
|
||||
"agent.route.failover",
|
||||
"agent.route.failover.stopped",
|
||||
"agent.mcp.server.ready",
|
||||
"agent.mcp.server.failed",
|
||||
"agent.mcp.server.disconnected",
|
||||
"agent.skills.discovered",
|
||||
"agent.skill.activated",
|
||||
"agent.sub.spawned",
|
||||
"agent.sub.turn.started",
|
||||
"agent.sub.completed",
|
||||
"agent.sub.failed",
|
||||
"agent.sub.closed",
|
||||
"agent.compaction.completed",
|
||||
]);
|
||||
|
||||
function liveTimingKeys(runId: string): Key[] {
|
||||
return [
|
||||
|
|
@ -288,7 +308,7 @@ export function queryKeysForRunEvent(
|
|||
: [];
|
||||
}
|
||||
|
||||
if (TODO_EVENTS.has(event)) {
|
||||
if (TODO_EVENTS.has(event) || AGENT_SESSION_EVENTS.has(event)) {
|
||||
const keys: Key[] = [
|
||||
queryKeys.runs.state(runId),
|
||||
queryKeys.runs.events(runId, 1000),
|
||||
|
|
|
|||
|
|
@ -1660,9 +1660,9 @@ Pebble's `RouteFailover`, `McpServerReady`, `McpServerFailed`, and
|
|||
`McpServerDisconnected` events, stored verbatim with pebble's envelope in
|
||||
`properties` like every other pebble event. Fabro also mirrors each onto
|
||||
its own `agent.failover`, `agent.mcp.ready`, `agent.mcp.failed`, and
|
||||
`agent.mcp.disconnected`, which the store folds into `StageProjection`'s
|
||||
`mcp_servers`; the pebble events feed `StageProjection.agent`. The mirrors
|
||||
go once every reader is on `agent`.
|
||||
`agent.mcp.disconnected`. The stage view reads MCP state from
|
||||
`StageProjection.agent`, which the pebble events feed; the mirrors change
|
||||
nothing on the stage any more and go next.
|
||||
|
||||
### `agent.route.failover.stopped`
|
||||
|
||||
|
|
|
|||
|
|
@ -11029,8 +11029,11 @@ components:
|
|||
example: "2026-05-23T12:34:56Z"
|
||||
event_seq:
|
||||
type: ["integer", "null"]
|
||||
format: uint32
|
||||
minimum: 1
|
||||
format: uint64
|
||||
minimum: 0
|
||||
description: >-
|
||||
The coding agent's own event sequence for the snapshot, when it
|
||||
carried one; not the run event sequence.
|
||||
example: 42
|
||||
breakdown:
|
||||
type: array
|
||||
|
|
@ -11170,23 +11173,6 @@ components:
|
|||
oneOf:
|
||||
- $ref: "#/components/schemas/BillingModelRef"
|
||||
- type: "null"
|
||||
todos:
|
||||
oneOf:
|
||||
- $ref: "#/components/schemas/TodoListProjection"
|
||||
- type: "null"
|
||||
description: |
|
||||
Todo / task list owned by this stage's root agent session. OpenAI
|
||||
child sessions have separate per-session plans that do not appear
|
||||
here. Anthropic task lists are root-scoped and shared with child
|
||||
sessions, so child mutations of that shared list do appear here.
|
||||
subagents:
|
||||
type: array
|
||||
description: Subagents spawned by this stage, in replay/insertion order.
|
||||
items:
|
||||
$ref: "#/components/schemas/SubAgentProjection"
|
||||
skills:
|
||||
$ref: "#/components/schemas/SkillsProjection"
|
||||
description: Agent skills discovered and activated during this stage.
|
||||
permission_level:
|
||||
oneOf:
|
||||
- $ref: "#/components/schemas/PermissionLevel"
|
||||
|
|
@ -11199,16 +11185,6 @@ components:
|
|||
Tool parameter schemas are intentionally omitted from this projection.
|
||||
items:
|
||||
$ref: "#/components/schemas/ToolSummary"
|
||||
mcp_servers:
|
||||
type: array
|
||||
description: MCP servers observed by this stage.
|
||||
items:
|
||||
$ref: "#/components/schemas/McpServerProjection"
|
||||
context_window:
|
||||
oneOf:
|
||||
- $ref: "#/components/schemas/ContextWindowSnapshot"
|
||||
- type: "null"
|
||||
description: Latest content-free context-window snapshot for this agent stage.
|
||||
inference:
|
||||
oneOf:
|
||||
- $ref: "#/components/schemas/StageInferenceProjection"
|
||||
|
|
@ -11340,102 +11316,6 @@ components:
|
|||
- text
|
||||
- tool_call
|
||||
|
||||
SubAgentProjection:
|
||||
description: Current projected state for one subagent spawned by an agent stage.
|
||||
type: object
|
||||
required:
|
||||
- agent_id
|
||||
- depth
|
||||
- task
|
||||
- status
|
||||
properties:
|
||||
agent_id:
|
||||
type: string
|
||||
depth:
|
||||
type: integer
|
||||
minimum: 0
|
||||
task:
|
||||
type: string
|
||||
status:
|
||||
$ref: "#/components/schemas/SubAgentStatus"
|
||||
|
||||
SubAgentStatus:
|
||||
description: Projected lifecycle status for a subagent.
|
||||
oneOf:
|
||||
- $ref: "#/components/schemas/SubAgentStatusRunning"
|
||||
- $ref: "#/components/schemas/SubAgentStatusCompleted"
|
||||
- $ref: "#/components/schemas/SubAgentStatusFailed"
|
||||
- $ref: "#/components/schemas/SubAgentStatusClosed"
|
||||
discriminator:
|
||||
propertyName: kind
|
||||
mapping:
|
||||
running: "#/components/schemas/SubAgentStatusRunning"
|
||||
completed: "#/components/schemas/SubAgentStatusCompleted"
|
||||
failed: "#/components/schemas/SubAgentStatusFailed"
|
||||
closed: "#/components/schemas/SubAgentStatusClosed"
|
||||
|
||||
SubAgentStatusRunning:
|
||||
type: object
|
||||
required:
|
||||
- kind
|
||||
properties:
|
||||
kind:
|
||||
type: string
|
||||
enum: [running]
|
||||
|
||||
SubAgentStatusCompleted:
|
||||
type: object
|
||||
required:
|
||||
- kind
|
||||
- success
|
||||
- turns_used
|
||||
properties:
|
||||
kind:
|
||||
type: string
|
||||
enum: [completed]
|
||||
success:
|
||||
type: boolean
|
||||
turns_used:
|
||||
type: integer
|
||||
minimum: 0
|
||||
|
||||
SubAgentStatusFailed:
|
||||
type: object
|
||||
required:
|
||||
- kind
|
||||
- error
|
||||
properties:
|
||||
kind:
|
||||
type: string
|
||||
enum: [failed]
|
||||
error:
|
||||
description: Provider/tool error payload captured by the subagent event.
|
||||
|
||||
SubAgentStatusClosed:
|
||||
type: object
|
||||
required:
|
||||
- kind
|
||||
properties:
|
||||
kind:
|
||||
type: string
|
||||
enum: [closed]
|
||||
|
||||
SkillsProjection:
|
||||
description: Agent skills discovered and activated during a stage.
|
||||
type: object
|
||||
required:
|
||||
- available
|
||||
- activated
|
||||
properties:
|
||||
available:
|
||||
type: array
|
||||
items:
|
||||
$ref: "#/components/schemas/SkillSummary"
|
||||
activated:
|
||||
type: array
|
||||
items:
|
||||
$ref: "#/components/schemas/ActivatedSkill"
|
||||
|
||||
SkillSummary:
|
||||
description: Summary of an available agent skill.
|
||||
type: object
|
||||
|
|
@ -11448,18 +11328,6 @@ components:
|
|||
description:
|
||||
type: string
|
||||
|
||||
ActivatedSkill:
|
||||
description: One observed agent skill activation.
|
||||
type: object
|
||||
required:
|
||||
- name
|
||||
- source
|
||||
properties:
|
||||
name:
|
||||
type: string
|
||||
source:
|
||||
$ref: "#/components/schemas/SkillActivationSource"
|
||||
|
||||
SkillActivationSource:
|
||||
description: Source that activated an agent skill.
|
||||
type: string
|
||||
|
|
@ -11556,91 +11424,6 @@ components:
|
|||
type: string
|
||||
enum: [read, write, shell, subagent, other]
|
||||
|
||||
McpServerProjection:
|
||||
description: Projected state for one MCP server observed by an agent stage.
|
||||
type: object
|
||||
required:
|
||||
- server_name
|
||||
- tool_count
|
||||
- status
|
||||
- invoked
|
||||
properties:
|
||||
server_name:
|
||||
type: string
|
||||
tool_count:
|
||||
type: integer
|
||||
minimum: 0
|
||||
status:
|
||||
$ref: "#/components/schemas/McpServerStatus"
|
||||
invoked:
|
||||
type: boolean
|
||||
description: True once the agent has invoked at least one tool from this server during the stage.
|
||||
|
||||
McpServerStatus:
|
||||
description: Projected MCP server readiness status.
|
||||
oneOf:
|
||||
- $ref: "#/components/schemas/McpServerStatusReady"
|
||||
- $ref: "#/components/schemas/McpServerStatusFailed"
|
||||
- $ref: "#/components/schemas/McpServerStatusDisconnected"
|
||||
discriminator:
|
||||
propertyName: kind
|
||||
mapping:
|
||||
ready: "#/components/schemas/McpServerStatusReady"
|
||||
failed: "#/components/schemas/McpServerStatusFailed"
|
||||
disconnected: "#/components/schemas/McpServerStatusDisconnected"
|
||||
|
||||
McpServerStatusReady:
|
||||
type: object
|
||||
required:
|
||||
- kind
|
||||
- tools
|
||||
properties:
|
||||
kind:
|
||||
type: string
|
||||
enum: [ready]
|
||||
tools:
|
||||
type: array
|
||||
items:
|
||||
$ref: "#/components/schemas/AgentMcpToolSummary"
|
||||
|
||||
McpServerStatusFailed:
|
||||
type: object
|
||||
required:
|
||||
- kind
|
||||
- error
|
||||
properties:
|
||||
kind:
|
||||
type: string
|
||||
enum: [failed]
|
||||
error:
|
||||
type: string
|
||||
|
||||
McpServerStatusDisconnected:
|
||||
description: The server was ready and then its connection closed during the stage; its tools fail until the session ends.
|
||||
type: object
|
||||
required:
|
||||
- kind
|
||||
- error
|
||||
properties:
|
||||
kind:
|
||||
type: string
|
||||
enum: [disconnected]
|
||||
error:
|
||||
type: string
|
||||
description: What closed the connection, as the client observed it.
|
||||
|
||||
AgentMcpToolSummary:
|
||||
description: Summary of one tool exposed by an MCP server.
|
||||
type: object
|
||||
required:
|
||||
- name
|
||||
- original_name
|
||||
properties:
|
||||
name:
|
||||
type: string
|
||||
original_name:
|
||||
type: string
|
||||
|
||||
AgentSessionProjection:
|
||||
description: >-
|
||||
The coding agent's fold of one stage's event stream: token counts and
|
||||
|
|
|
|||
|
|
@ -1772,7 +1772,11 @@ async fn get_run_stage_context_window(
|
|||
.into_response();
|
||||
}
|
||||
|
||||
let Some(snapshot) = stage.context_window.as_ref() else {
|
||||
let Some(snapshot) = stage
|
||||
.agent
|
||||
.as_ref()
|
||||
.and_then(|agent| agent.context_window.as_ref())
|
||||
else {
|
||||
return Json(StageContextWindow::unavailable(
|
||||
stage_id,
|
||||
StageContextWindowUnavailableReason::NotObserved,
|
||||
|
|
@ -1789,7 +1793,11 @@ async fn get_run_stage_context_window(
|
|||
}
|
||||
|
||||
fn is_agent_context_window_stage(stage: &StageProjection) -> bool {
|
||||
if stage.context_window.is_some() {
|
||||
if stage
|
||||
.agent
|
||||
.as_ref()
|
||||
.is_some_and(|agent| agent.context_window.is_some())
|
||||
{
|
||||
return true;
|
||||
}
|
||||
if stage.handler == Some(StageHandler::Agent) {
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
|
|
@ -404,10 +404,6 @@ fn main() {
|
|||
&[],
|
||||
),
|
||||
("TodoListProjection", "fabro_types::TodoListProjection", &[]),
|
||||
("SubAgentProjection", "fabro_types::SubAgentProjection", &[]),
|
||||
("SubAgentStatus", "fabro_types::SubAgentStatus", &[]),
|
||||
("SkillsProjection", "fabro_types::SkillsProjection", &[]),
|
||||
("ActivatedSkill", "fabro_types::ActivatedSkill", &[]),
|
||||
("SkillSummary", "fabro_types::SkillSummary", &[]),
|
||||
(
|
||||
"SkillActivationSource",
|
||||
|
|
@ -423,17 +419,6 @@ fn main() {
|
|||
"fabro_types::AgentToolsAvailableProps",
|
||||
&[],
|
||||
),
|
||||
(
|
||||
"McpServerProjection",
|
||||
"fabro_types::McpServerProjection",
|
||||
&[],
|
||||
),
|
||||
("McpServerStatus", "fabro_types::McpServerStatus", &[]),
|
||||
(
|
||||
"AgentMcpToolSummary",
|
||||
"fabro_types::AgentMcpToolSummary",
|
||||
&[],
|
||||
),
|
||||
// Pebble's own fold of a stage's agent events, embedded in
|
||||
// `StageProjection.agent`. Every nested type is pebble's; the schema
|
||||
// names carry an `AgentSession` prefix where fabro already has a
|
||||
|
|
|
|||
|
|
@ -38,17 +38,16 @@ pub mod types {
|
|||
BlockedReason, FailureReason, PendingReason, RunControlAction, RunStatus, SuccessReason,
|
||||
};
|
||||
pub use fabro_types::{
|
||||
ActivatedSkill, AgentControlState, AgentEventProps, AgentMcpToolSummary,
|
||||
AgentToolsAvailableProps, AskFabro, AuthMethod, AutomationRef, BilledModelUsage,
|
||||
BilledTokenCounts, BlobHash, CommandTermination, Conclusion, ContextWindowBreakdownItem,
|
||||
ContextWindowCategory, ContextWindowCountMethod, ContextWindowSnapshot,
|
||||
ContextWindowStaleness, ContextWindowWarning, CreateVariableRequest, DiffStats,
|
||||
DiffSummary, DirtyStatus, EventEnvelope, ExecOutputTail, FailureCategory, FailureDetail,
|
||||
FailureSignature, GitContext, GitRunTarget, GitRunTarget as AutomationGitWorkflowSource,
|
||||
IdpIdentity, IntegrationConnectionKind, IntegrationConnectionState,
|
||||
IntegrationConnectionStatus, IntegrationProvider, IntegrationStatus, InterviewOption,
|
||||
InterviewQuestionRecord, LlmOutputKind, McpServerDraft as CreateMcpServerRequest,
|
||||
McpServerProjection, McpServerReplace as ReplaceMcpServerRequest, McpServerStatus,
|
||||
AgentControlState, AgentEventProps, AgentToolsAvailableProps, AskFabro, AuthMethod,
|
||||
AutomationRef, BilledModelUsage, BilledTokenCounts, BlobHash, CommandTermination,
|
||||
Conclusion, ContextWindowBreakdownItem, ContextWindowCategory, ContextWindowCountMethod,
|
||||
ContextWindowSnapshot, ContextWindowStaleness, ContextWindowWarning, CreateVariableRequest,
|
||||
DiffStats, DiffSummary, DirtyStatus, EventEnvelope, ExecOutputTail, FailureCategory,
|
||||
FailureDetail, FailureSignature, GitContext, GitRunTarget,
|
||||
GitRunTarget as AutomationGitWorkflowSource, IdpIdentity, IntegrationConnectionKind,
|
||||
IntegrationConnectionState, IntegrationConnectionStatus, IntegrationProvider,
|
||||
IntegrationStatus, InterviewOption, InterviewQuestionRecord, LlmOutputKind,
|
||||
McpServerDraft as CreateMcpServerRequest, McpServerReplace as ReplaceMcpServerRequest,
|
||||
McpServerView as McpServer, McpTransportView, Model, ModelControls, ModelCosts,
|
||||
ModelFeatures, ModelLimits, ModelRef as BillingModelRef, ModelTestMode, PairId,
|
||||
PairMessageId, PairMessageRecord, PairMessageRequest, PairRecord, PairStartRequest,
|
||||
|
|
@ -65,13 +64,13 @@ pub mod types {
|
|||
SandboxDetails, SandboxInfo, SandboxListMeta, SandboxListResponse, SandboxProviderKind,
|
||||
SandboxProviderLookupError, SandboxService, SandboxServiceListResponse, SecretMetadata,
|
||||
SecretType, ServerSettings, SessionDetail, SessionId, SessionStatus, SessionSummary,
|
||||
SessionTurn, SkillActivationSource, SkillSummary, SkillsProjection, StageCompletion,
|
||||
StageContextWindow, StageContextWindowUnavailableReason, StageHandler, StageId,
|
||||
StageInferenceProjection, StageModelUsage, StageOutcome, StageProjection, StageState,
|
||||
StageToolBatchProjection, SubAgentProjection, SubAgentStatus, SystemActorKind,
|
||||
SystemIntegrationStatus, SystemIntegrationsResponse, TodoListProjection, ToolCategory,
|
||||
ToolSource, ToolSummary, TurnId, UpdateVariableRequest, UserPrincipal, Variable,
|
||||
VariableListResponse, WorkflowPath, WorkflowSettings, WorkflowVersion, WorkflowVersionId,
|
||||
SessionTurn, SkillActivationSource, SkillSummary, StageCompletion, StageContextWindow,
|
||||
StageContextWindowUnavailableReason, StageHandler, StageId, StageInferenceProjection,
|
||||
StageModelUsage, StageOutcome, StageProjection, StageState, StageToolBatchProjection,
|
||||
SystemActorKind, SystemIntegrationStatus, SystemIntegrationsResponse, TodoListProjection,
|
||||
ToolCategory, ToolSource, ToolSummary, TurnId, UpdateVariableRequest, UserPrincipal,
|
||||
Variable, VariableListResponse, WorkflowPath, WorkflowSettings, WorkflowVersion,
|
||||
WorkflowVersionId,
|
||||
};
|
||||
pub use lithos_llm::catalog::{ModelHandle, ProviderId};
|
||||
pub use lithos_llm::types::{
|
||||
|
|
|
|||
|
|
@ -1,8 +1,7 @@
|
|||
use std::any::{TypeId, type_name};
|
||||
|
||||
use fabro_api::types::{
|
||||
ActivatedSkill as ApiActivatedSkill, AgentControlState as ApiAgentControlState,
|
||||
AgentMcpToolSummary as ApiAgentMcpToolSummary,
|
||||
AgentControlState as ApiAgentControlState,
|
||||
AgentToolsAvailableProps as ApiAgentToolsAvailableProps,
|
||||
BilledModelUsage as ApiBilledModelUsage,
|
||||
ContextWindowBreakdownItem as ApiContextWindowBreakdownItem,
|
||||
|
|
@ -11,26 +10,23 @@ use fabro_api::types::{
|
|||
ContextWindowSnapshot as ApiContextWindowSnapshot,
|
||||
ContextWindowStaleness as ApiContextWindowStaleness,
|
||||
ContextWindowWarning as ApiContextWindowWarning, LlmOutputKind as ApiLlmOutputKind,
|
||||
McpServerProjection as ApiMcpServerProjection, McpServerStatus as ApiMcpServerStatus,
|
||||
ParallelBranchResult as ApiParallelBranchResult, PermissionLevel as ApiPermissionLevel,
|
||||
SkillActivationSource as ApiSkillActivationSource, SkillSummary as ApiSkillSummary,
|
||||
SkillsProjection as ApiSkillsProjection, StageContextWindow as ApiStageContextWindow,
|
||||
StageContextWindow as ApiStageContextWindow,
|
||||
StageContextWindowUnavailableReason as ApiStageContextWindowUnavailableReason,
|
||||
StageInferenceProjection as ApiStageInferenceProjection, StageProjection as ApiStageProjection,
|
||||
StageToolBatchProjection as ApiStageToolBatchProjection,
|
||||
SubAgentProjection as ApiSubAgentProjection, SubAgentStatus as ApiSubAgentStatus,
|
||||
TodoListProjection as ApiTodoListProjection, ToolCategory as ApiToolCategory,
|
||||
ToolSource as ApiToolSource, ToolSummary as ApiToolSummary,
|
||||
};
|
||||
use fabro_types::{
|
||||
ActivatedSkill, AgentControlState, AgentMcpToolSummary, AgentToolsAvailableProps,
|
||||
BilledModelUsage, ContextWindowBreakdownItem, ContextWindowCategory, ContextWindowCountMethod,
|
||||
ContextWindowSnapshot, ContextWindowStaleness, ContextWindowWarning, LlmOutputKind,
|
||||
McpServerProjection, McpServerStatus, ModelRef, ParallelBranchId, ParallelBranchResult,
|
||||
PermissionLevel, SkillActivationSource, SkillSummary, SkillsProjection, StageContextWindow,
|
||||
AgentControlState, AgentToolsAvailableProps, BilledModelUsage, ContextWindowBreakdownItem,
|
||||
ContextWindowCategory, ContextWindowCountMethod, ContextWindowSnapshot, ContextWindowStaleness,
|
||||
ContextWindowWarning, LlmOutputKind, ModelRef, ParallelBranchId, ParallelBranchResult,
|
||||
PermissionLevel, SkillActivationSource, SkillSummary, StageContextWindow,
|
||||
StageContextWindowUnavailableReason, StageId, StageInferenceProjection, StageProjection,
|
||||
StageToolBatchProjection, SubAgentProjection, SubAgentStatus, TodoListKind, TodoListProjection,
|
||||
ToolCategory, ToolSource, ToolSummary,
|
||||
StageToolBatchProjection, TodoListKind, TodoListProjection, ToolCategory, ToolSource,
|
||||
ToolSummary,
|
||||
};
|
||||
use lithos_llm::catalog::{ModelId, ProviderId};
|
||||
use lithos_llm::types::TokenCounts;
|
||||
|
|
@ -116,19 +112,12 @@ fn stage_projection_reuses_nested_agent_state_types() {
|
|||
assert_same_type::<ApiAgentControlState, AgentControlState>();
|
||||
assert_same_type::<ApiParallelBranchResult, ParallelBranchResult>();
|
||||
assert_same_type::<ApiTodoListProjection, TodoListProjection>();
|
||||
assert_same_type::<ApiSubAgentProjection, SubAgentProjection>();
|
||||
assert_same_type::<ApiSubAgentStatus, SubAgentStatus>();
|
||||
assert_same_type::<ApiSkillsProjection, SkillsProjection>();
|
||||
assert_same_type::<ApiActivatedSkill, ActivatedSkill>();
|
||||
assert_same_type::<ApiSkillSummary, SkillSummary>();
|
||||
assert_same_type::<ApiSkillActivationSource, SkillActivationSource>();
|
||||
assert_same_type::<ApiToolSummary, ToolSummary>();
|
||||
assert_same_type::<ApiToolSource, ToolSource>();
|
||||
assert_same_type::<ApiToolCategory, ToolCategory>();
|
||||
assert_same_type::<ApiAgentToolsAvailableProps, AgentToolsAvailableProps>();
|
||||
assert_same_type::<ApiMcpServerProjection, McpServerProjection>();
|
||||
assert_same_type::<ApiMcpServerStatus, McpServerStatus>();
|
||||
assert_same_type::<ApiAgentMcpToolSummary, AgentMcpToolSummary>();
|
||||
assert_same_type::<ApiPermissionLevel, PermissionLevel>();
|
||||
assert_same_type::<ApiStageContextWindow, StageContextWindow>();
|
||||
assert_same_type::<ApiContextWindowSnapshot, ContextWindowSnapshot>();
|
||||
|
|
@ -300,45 +289,6 @@ fn stage_projection_round_trips_representative_json() {
|
|||
"cache_read_tokens": 0,
|
||||
"cache_write_tokens": 0
|
||||
},
|
||||
"todos": {
|
||||
"kind": "openai_plan",
|
||||
"list_id": "openai_plan:ses_root",
|
||||
"items": [
|
||||
{
|
||||
"id": "todo-1",
|
||||
"status": "in_progress",
|
||||
"order": 0,
|
||||
"subject": "Write tests",
|
||||
"active_form": "Writing tests"
|
||||
}
|
||||
]
|
||||
},
|
||||
"subagents": [
|
||||
{
|
||||
"agent_id": "sub-1",
|
||||
"depth": 1,
|
||||
"task": "Investigate failing test",
|
||||
"status": {
|
||||
"kind": "completed",
|
||||
"success": true,
|
||||
"turns_used": 3
|
||||
}
|
||||
}
|
||||
],
|
||||
"skills": {
|
||||
"available": [
|
||||
{
|
||||
"name": "rust",
|
||||
"description": "Rust workflow help"
|
||||
}
|
||||
],
|
||||
"activated": [
|
||||
{
|
||||
"name": "rust",
|
||||
"source": "slash"
|
||||
}
|
||||
]
|
||||
},
|
||||
"permission_level": "read-only",
|
||||
"agent_tools": [
|
||||
{
|
||||
|
|
@ -360,41 +310,6 @@ fn stage_projection_round_trips_representative_json() {
|
|||
"invoked": false
|
||||
}
|
||||
],
|
||||
"mcp_servers": [
|
||||
{
|
||||
"server_name": "filesystem",
|
||||
"tool_count": 1,
|
||||
"status": {
|
||||
"kind": "ready",
|
||||
"tools": [
|
||||
{
|
||||
"name": "read_file",
|
||||
"original_name": "read_file"
|
||||
}
|
||||
]
|
||||
},
|
||||
"invoked": true
|
||||
}
|
||||
],
|
||||
"context_window": {
|
||||
"provider": "openai",
|
||||
"model": "gpt-5.4",
|
||||
"context_window_tokens": 400000,
|
||||
"input_tokens": 123456,
|
||||
"usage_percent": 30.864,
|
||||
"count_method": "provider_api_scaled_breakdown",
|
||||
"staleness": "live",
|
||||
"generated_at": "2026-05-23T12:34:56.000Z",
|
||||
"event_seq": 42,
|
||||
"breakdown": [
|
||||
{
|
||||
"category": "system_prompt",
|
||||
"tokens": 30000,
|
||||
"usage_percent": 7.5
|
||||
}
|
||||
],
|
||||
"warnings": []
|
||||
},
|
||||
"inference": {
|
||||
"session_id": "ses_root",
|
||||
"started_at": "2026-04-29T12:34:00Z",
|
||||
|
|
@ -461,7 +376,7 @@ fn permission_level_matches_openapi_json_shape() {
|
|||
}
|
||||
|
||||
#[test]
|
||||
fn nested_agent_state_types_match_openapi_json_shape() {
|
||||
fn todo_list_and_skill_types_match_openapi_json_shape() {
|
||||
for (kind, list_id, wire_kind) in [
|
||||
(
|
||||
TodoListKind::OpenAiPlan,
|
||||
|
|
@ -484,32 +399,6 @@ fn nested_agent_state_types_match_openapi_json_shape() {
|
|||
assert_eq!(api_todo_list, todo_list);
|
||||
}
|
||||
|
||||
let subagent = SubAgentProjection {
|
||||
agent_id: "sub-1".to_string(),
|
||||
depth: 1,
|
||||
task: "Investigate failing test".to_string(),
|
||||
status: SubAgentStatus::Completed {
|
||||
success: true,
|
||||
turns_used: 3,
|
||||
},
|
||||
};
|
||||
let subagent_json = serde_json::to_value(&subagent).unwrap();
|
||||
assert_eq!(
|
||||
subagent_json,
|
||||
json!({
|
||||
"agent_id": "sub-1",
|
||||
"depth": 1,
|
||||
"task": "Investigate failing test",
|
||||
"status": {
|
||||
"kind": "completed",
|
||||
"success": true,
|
||||
"turns_used": 3
|
||||
}
|
||||
})
|
||||
);
|
||||
let api_subagent: ApiSubAgentProjection = serde_json::from_value(subagent_json).unwrap();
|
||||
assert_eq!(api_subagent, subagent);
|
||||
|
||||
let skill = SkillSummary {
|
||||
name: "rust".to_string(),
|
||||
description: "Rust workflow help".to_string(),
|
||||
|
|
@ -529,103 +418,6 @@ fn nested_agent_state_types_match_openapi_json_shape() {
|
|||
assert_eq!(source_json, json!("slash"));
|
||||
let api_source: ApiSkillActivationSource = serde_json::from_value(source_json).unwrap();
|
||||
assert_eq!(api_source, SkillActivationSource::Slash);
|
||||
|
||||
let activated = ActivatedSkill {
|
||||
name: "rust".to_string(),
|
||||
source: SkillActivationSource::Slash,
|
||||
};
|
||||
let skills = SkillsProjection {
|
||||
available: vec![skill],
|
||||
activated: vec![activated],
|
||||
};
|
||||
let skills_json = serde_json::to_value(&skills).unwrap();
|
||||
assert_eq!(
|
||||
skills_json,
|
||||
json!({
|
||||
"available": [
|
||||
{
|
||||
"name": "rust",
|
||||
"description": "Rust workflow help"
|
||||
}
|
||||
],
|
||||
"activated": [
|
||||
{
|
||||
"name": "rust",
|
||||
"source": "slash"
|
||||
}
|
||||
]
|
||||
})
|
||||
);
|
||||
let api_skills: ApiSkillsProjection = serde_json::from_value(skills_json).unwrap();
|
||||
assert_eq!(api_skills, skills);
|
||||
|
||||
let tool = AgentMcpToolSummary {
|
||||
name: "read_file".to_string(),
|
||||
original_name: "read_file".to_string(),
|
||||
};
|
||||
let tool_json = serde_json::to_value(&tool).unwrap();
|
||||
assert_eq!(
|
||||
tool_json,
|
||||
json!({
|
||||
"name": "read_file",
|
||||
"original_name": "read_file"
|
||||
})
|
||||
);
|
||||
let api_tool: ApiAgentMcpToolSummary = serde_json::from_value(tool_json).unwrap();
|
||||
assert_eq!(api_tool, tool);
|
||||
|
||||
let mcp_server = McpServerProjection {
|
||||
server_name: "filesystem".to_string(),
|
||||
tool_count: 1,
|
||||
status: McpServerStatus::Ready { tools: vec![tool] },
|
||||
invoked: true,
|
||||
};
|
||||
let mcp_json = serde_json::to_value(&mcp_server).unwrap();
|
||||
assert_eq!(
|
||||
mcp_json,
|
||||
json!({
|
||||
"server_name": "filesystem",
|
||||
"tool_count": 1,
|
||||
"status": {
|
||||
"kind": "ready",
|
||||
"tools": [
|
||||
{
|
||||
"name": "read_file",
|
||||
"original_name": "read_file"
|
||||
}
|
||||
]
|
||||
},
|
||||
"invoked": true,
|
||||
})
|
||||
);
|
||||
let api_mcp: ApiMcpServerProjection = serde_json::from_value(mcp_json).unwrap();
|
||||
assert_eq!(api_mcp, mcp_server);
|
||||
assert_eq!(mcp_server.tool_count, 1);
|
||||
|
||||
let disconnected = McpServerProjection {
|
||||
server_name: "filesystem".to_string(),
|
||||
tool_count: 1,
|
||||
status: McpServerStatus::Disconnected {
|
||||
error: "transport closed".to_string(),
|
||||
},
|
||||
invoked: true,
|
||||
};
|
||||
let disconnected_json = serde_json::to_value(&disconnected).unwrap();
|
||||
assert_eq!(
|
||||
disconnected_json,
|
||||
json!({
|
||||
"server_name": "filesystem",
|
||||
"tool_count": 1,
|
||||
"status": {
|
||||
"kind": "disconnected",
|
||||
"error": "transport closed"
|
||||
},
|
||||
"invoked": true,
|
||||
})
|
||||
);
|
||||
let api_disconnected: ApiMcpServerProjection =
|
||||
serde_json::from_value(disconnected_json).unwrap();
|
||||
assert_eq!(api_disconnected, disconnected);
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
|
|
|||
|
|
@ -143,10 +143,9 @@ pub use run_intent::{
|
|||
TargetValidationError, ValidatedGitRunTarget, ValidatedRunTarget,
|
||||
};
|
||||
pub use run_projection::{
|
||||
ActivatedSkill, AgentControlState, CheckpointRecord, McpServerProjection, McpServerStatus,
|
||||
PendingInterviewRecord, RunProjection, SkillsProjection, StageContextWindow,
|
||||
AgentControlState, CheckpointRecord, PendingInterviewRecord, RunProjection, StageContextWindow,
|
||||
StageContextWindowUnavailableReason, StageInferenceProjection, StageModelUsage,
|
||||
StageProjection, StageToolBatchProjection, SubAgentProjection, SubAgentStatus, first_event_seq,
|
||||
StageProjection, StageToolBatchProjection, first_event_seq,
|
||||
};
|
||||
pub use run_sandbox::{
|
||||
RunSandbox, RunSandboxFailure, RunSandboxInstance, RunSandboxKind, RunSandboxPlan,
|
||||
|
|
|
|||
|
|
@ -6,19 +6,18 @@ use chrono::{DateTime, Utc};
|
|||
use lithos_llm::types::{ReasoningEffort, Speed};
|
||||
use pebble_coding_agent::events::{
|
||||
ContextWindowBreakdownItem, ContextWindowCountMethod, ContextWindowSnapshot,
|
||||
ContextWindowStaleness, ContextWindowWarning, LlmOutputKind, PermissionLevel,
|
||||
SkillActivationSource, SkillSummary, TodoListProjection, ToolSummary,
|
||||
ContextWindowStaleness, ContextWindowWarning, LlmOutputKind, PermissionLevel, ToolSummary,
|
||||
};
|
||||
use pebble_coding_agent::projection::SessionProjection;
|
||||
use strum::{Display, EnumString, IntoStaticStr};
|
||||
|
||||
use crate::run_event::{AgentSessionActivatedProps, StagePromptProps};
|
||||
use crate::{
|
||||
AgentBackend, AgentMcpToolSummary, BilledModelUsage, BilledTokenCounts, Checkpoint, Conclusion,
|
||||
GitIdentity, InterviewQuestionRecord, InvalidTransition, ModelRef, ParallelBranchId,
|
||||
PullRequestCreation, PullRequestLink, RunApproval, RunControlAction, RunDiff, RunId,
|
||||
RunSandbox, RunSpec, RunStatus, RunTiming, StageCompletion, StageHandler, StageId, StageState,
|
||||
StageTiming, StartRecord, timing,
|
||||
AgentBackend, BilledModelUsage, BilledTokenCounts, Checkpoint, Conclusion, GitIdentity,
|
||||
InterviewQuestionRecord, InvalidTransition, ModelRef, ParallelBranchId, PullRequestCreation,
|
||||
PullRequestLink, RunApproval, RunControlAction, RunDiff, RunId, RunSandbox, RunSpec, RunStatus,
|
||||
RunTiming, StageCompletion, StageHandler, StageId, StageState, StageTiming, StartRecord,
|
||||
timing,
|
||||
};
|
||||
|
||||
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
|
||||
|
|
@ -304,25 +303,10 @@ pub struct StageProjection {
|
|||
/// `usage` to `model`.
|
||||
#[serde(default, skip_serializing_if = "Vec::is_empty")]
|
||||
pub billing_by_model: Vec<BilledModelUsage>,
|
||||
/// Todo/task list owned by the stage's root agent session.
|
||||
///
|
||||
/// OpenAI child sessions own separate per-session plans and do not appear
|
||||
/// here. Anthropic task lists are root-scoped and shared with child
|
||||
/// sessions, so child mutations of that shared list do appear here.
|
||||
#[serde(default, rename = "todos", skip_serializing_if = "Option::is_none")]
|
||||
pub root_agent_todos: Option<TodoListProjection>,
|
||||
#[serde(default, skip_serializing_if = "Vec::is_empty")]
|
||||
pub subagents: Vec<SubAgentProjection>,
|
||||
#[serde(default, skip_serializing_if = "SkillsProjection::is_empty")]
|
||||
pub skills: SkillsProjection,
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
pub permission_level: Option<PermissionLevel>,
|
||||
#[serde(default, skip_serializing_if = "Vec::is_empty")]
|
||||
pub agent_tools: Vec<ToolSummary>,
|
||||
#[serde(default, skip_serializing_if = "Vec::is_empty")]
|
||||
pub mcp_servers: Vec<McpServerProjection>,
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
pub context_window: Option<ContextWindowSnapshot>,
|
||||
/// Open inference bracket for this stage, if the event log contains one.
|
||||
///
|
||||
/// `Some` means exactly *"an `agent.llm.started` was recorded and no
|
||||
|
|
@ -341,10 +325,12 @@ pub struct StageProjection {
|
|||
#[serde(default)]
|
||||
pub agent_control: AgentControlState,
|
||||
/// Pebble's fold of this stage's agent events: the one agent projection,
|
||||
/// fed every `agent.*` and `todo.*` event stored on the stage. Present
|
||||
/// for pebble-backed agent stages once their first agent event is
|
||||
/// stored; `None` for prompt, command, ACP, human, parallel, and
|
||||
/// conditional stages.
|
||||
/// fed every `agent.*` and `todo.*` event stored on the stage, and what
|
||||
/// the stage view reads for todos, subagents, skills, MCP servers, files,
|
||||
/// failovers, compactions, and the context window. Present for
|
||||
/// pebble-backed agent stages once their first agent event is stored;
|
||||
/// `None` for prompt, command, ACP, human, parallel, and conditional
|
||||
/// stages.
|
||||
///
|
||||
/// Its lifetime fields are the stage's totals across every prompt the
|
||||
/// stage ran, because each stage gets its own fold over its own events.
|
||||
|
|
@ -427,67 +413,6 @@ pub enum AgentControlState {
|
|||
WaitingForSteer,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)]
|
||||
pub struct SubAgentProjection {
|
||||
pub agent_id: String,
|
||||
pub depth: usize,
|
||||
pub task: String,
|
||||
pub status: SubAgentStatus,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)]
|
||||
#[serde(tag = "kind", rename_all = "snake_case")]
|
||||
pub enum SubAgentStatus {
|
||||
Running,
|
||||
Completed { success: bool, turns_used: usize },
|
||||
Failed { error: serde_json::Value },
|
||||
Closed,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Default, PartialEq, serde::Serialize, serde::Deserialize)]
|
||||
pub struct SkillsProjection {
|
||||
pub available: Vec<SkillSummary>,
|
||||
pub activated: Vec<ActivatedSkill>,
|
||||
}
|
||||
|
||||
impl SkillsProjection {
|
||||
#[must_use]
|
||||
pub fn is_empty(&self) -> bool {
|
||||
self.available.is_empty() && self.activated.is_empty()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)]
|
||||
pub struct ActivatedSkill {
|
||||
pub name: String,
|
||||
pub source: SkillActivationSource,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)]
|
||||
pub struct McpServerProjection {
|
||||
pub server_name: String,
|
||||
pub tool_count: usize,
|
||||
pub status: McpServerStatus,
|
||||
/// True once any tool from this server has been invoked during the stage.
|
||||
pub invoked: bool,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)]
|
||||
#[serde(tag = "kind", rename_all = "snake_case")]
|
||||
pub enum McpServerStatus {
|
||||
Ready {
|
||||
tools: Vec<AgentMcpToolSummary>,
|
||||
},
|
||||
Failed {
|
||||
error: String,
|
||||
},
|
||||
/// The server was ready and then its connection closed during the
|
||||
/// stage; its tools fail until the session ends.
|
||||
Disconnected {
|
||||
error: String,
|
||||
},
|
||||
}
|
||||
|
||||
/// Convert a 1-based event sequence number into the `NonZeroU32` form used for
|
||||
/// `StageProjection::first_event_seq`. Run event seqs always start at 1.
|
||||
#[must_use]
|
||||
|
|
@ -510,13 +435,8 @@ impl StageProjection {
|
|||
tool_batch: None,
|
||||
usage: BilledTokenCounts::default(),
|
||||
model: None,
|
||||
root_agent_todos: None,
|
||||
subagents: Vec::new(),
|
||||
skills: SkillsProjection::default(),
|
||||
permission_level: None,
|
||||
agent_tools: Vec::new(),
|
||||
mcp_servers: Vec::new(),
|
||||
context_window: None,
|
||||
inference: None,
|
||||
acp_started_at: None,
|
||||
agent_control: AgentControlState::default(),
|
||||
|
|
|
|||
|
|
@ -27,12 +27,10 @@ base.ts
|
|||
common.ts
|
||||
configuration.ts
|
||||
index.ts
|
||||
models/activated-skill.ts
|
||||
models/agent-control-state.ts
|
||||
models/agent-error-data.ts
|
||||
models/agent-error-kind.ts
|
||||
models/agent-event-props.ts
|
||||
models/agent-mcp-tool-summary.ts
|
||||
models/agent-session-activated-props.ts
|
||||
models/agent-session-activated-skill.ts
|
||||
models/agent-session-activity.ts
|
||||
|
|
@ -244,12 +242,7 @@ models/manifest-workflow.ts
|
|||
models/mcp-http-protocol.ts
|
||||
models/mcp-server-list-meta.ts
|
||||
models/mcp-server-list-response.ts
|
||||
models/mcp-server-projection.ts
|
||||
models/mcp-server-settings.ts
|
||||
models/mcp-server-status-disconnected.ts
|
||||
models/mcp-server-status-failed.ts
|
||||
models/mcp-server-status-ready.ts
|
||||
models/mcp-server-status.ts
|
||||
models/mcp-server.ts
|
||||
models/mcp-tool-summary.ts
|
||||
models/mcp-transport-http.ts
|
||||
|
|
@ -508,7 +501,6 @@ models/session-summary.ts
|
|||
models/session-turn.ts
|
||||
models/skill-activation-source.ts
|
||||
models/skill-summary.ts
|
||||
models/skills-projection.ts
|
||||
models/slack-integration-settings.ts
|
||||
models/ssh-access-request.ts
|
||||
models/ssh-access-response.ts
|
||||
|
|
@ -527,12 +519,6 @@ models/stage-tool-batch-projection.ts
|
|||
models/start-record.ts
|
||||
models/start-run-request.ts
|
||||
models/steer-run-request.ts
|
||||
models/sub-agent-projection.ts
|
||||
models/sub-agent-status-closed.ts
|
||||
models/sub-agent-status-completed.ts
|
||||
models/sub-agent-status-failed.ts
|
||||
models/sub-agent-status-running.ts
|
||||
models/sub-agent-status.ts
|
||||
models/submit-answer-multi-selected-request.ts
|
||||
models/submit-answer-no-request.ts
|
||||
models/submit-answer-request.ts
|
||||
|
|
|
|||
|
|
@ -1,26 +0,0 @@
|
|||
/* tslint:disable */
|
||||
/* eslint-disable */
|
||||
/**
|
||||
* Fabro Run API
|
||||
* HTTP API for managing Fabro workflow run executions.
|
||||
*
|
||||
* The version of the OpenAPI document: 0.2.0
|
||||
*
|
||||
*
|
||||
* NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).
|
||||
* https://openapi-generator.tech
|
||||
* Do not edit the class manually.
|
||||
*/
|
||||
|
||||
|
||||
// May contain unused imports in some cases
|
||||
// @ts-ignore
|
||||
import type { SkillActivationSource } from './skill-activation-source';
|
||||
|
||||
/**
|
||||
* One observed agent skill activation.
|
||||
*/
|
||||
export interface ActivatedSkill {
|
||||
'name': string;
|
||||
'source': SkillActivationSource;
|
||||
}
|
||||
|
|
@ -1,23 +0,0 @@
|
|||
/* tslint:disable */
|
||||
/* eslint-disable */
|
||||
/**
|
||||
* Fabro Run API
|
||||
* HTTP API for managing Fabro workflow run executions.
|
||||
*
|
||||
* The version of the OpenAPI document: 0.2.0
|
||||
*
|
||||
*
|
||||
* NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).
|
||||
* https://openapi-generator.tech
|
||||
* Do not edit the class manually.
|
||||
*/
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Summary of one tool exposed by an MCP server.
|
||||
*/
|
||||
export interface AgentMcpToolSummary {
|
||||
'name': string;
|
||||
'original_name': string;
|
||||
}
|
||||
14
lib/packages/fabro-api-client/src/models/index.ts
generated
14
lib/packages/fabro-api-client/src/models/index.ts
generated
|
|
@ -1,9 +1,7 @@
|
|||
export * from './activated-skill';
|
||||
export * from './agent-control-state';
|
||||
export * from './agent-error-data';
|
||||
export * from './agent-error-kind';
|
||||
export * from './agent-event-props';
|
||||
export * from './agent-mcp-tool-summary';
|
||||
export * from './agent-session-activated-props';
|
||||
export * from './agent-session-activated-skill';
|
||||
export * from './agent-session-activity';
|
||||
|
|
@ -215,12 +213,7 @@ export * from './mcp-http-protocol';
|
|||
export * from './mcp-server';
|
||||
export * from './mcp-server-list-meta';
|
||||
export * from './mcp-server-list-response';
|
||||
export * from './mcp-server-projection';
|
||||
export * from './mcp-server-settings';
|
||||
export * from './mcp-server-status';
|
||||
export * from './mcp-server-status-disconnected';
|
||||
export * from './mcp-server-status-failed';
|
||||
export * from './mcp-server-status-ready';
|
||||
export * from './mcp-tool-summary';
|
||||
export * from './mcp-transport';
|
||||
export * from './mcp-transport-http';
|
||||
|
|
@ -478,7 +471,6 @@ export * from './session-summary';
|
|||
export * from './session-turn';
|
||||
export * from './skill-activation-source';
|
||||
export * from './skill-summary';
|
||||
export * from './skills-projection';
|
||||
export * from './slack-integration-settings';
|
||||
export * from './ssh-access-request';
|
||||
export * from './ssh-access-response';
|
||||
|
|
@ -497,12 +489,6 @@ export * from './stage-tool-batch-projection';
|
|||
export * from './start-record';
|
||||
export * from './start-run-request';
|
||||
export * from './steer-run-request';
|
||||
export * from './sub-agent-projection';
|
||||
export * from './sub-agent-status';
|
||||
export * from './sub-agent-status-closed';
|
||||
export * from './sub-agent-status-completed';
|
||||
export * from './sub-agent-status-failed';
|
||||
export * from './sub-agent-status-running';
|
||||
export * from './submit-answer-multi-selected-request';
|
||||
export * from './submit-answer-no-request';
|
||||
export * from './submit-answer-request';
|
||||
|
|
|
|||
|
|
@ -1,31 +0,0 @@
|
|||
/* tslint:disable */
|
||||
/* eslint-disable */
|
||||
/**
|
||||
* Fabro Run API
|
||||
* HTTP API for managing Fabro workflow run executions.
|
||||
*
|
||||
* The version of the OpenAPI document: 0.2.0
|
||||
*
|
||||
*
|
||||
* NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).
|
||||
* https://openapi-generator.tech
|
||||
* Do not edit the class manually.
|
||||
*/
|
||||
|
||||
|
||||
// May contain unused imports in some cases
|
||||
// @ts-ignore
|
||||
import type { McpServerStatus } from './mcp-server-status';
|
||||
|
||||
/**
|
||||
* Projected state for one MCP server observed by an agent stage.
|
||||
*/
|
||||
export interface McpServerProjection {
|
||||
'server_name': string;
|
||||
'tool_count': number;
|
||||
'status': McpServerStatus;
|
||||
/**
|
||||
* True once the agent has invoked at least one tool from this server during the stage.
|
||||
*/
|
||||
'invoked': boolean;
|
||||
}
|
||||
|
|
@ -1,32 +0,0 @@
|
|||
/* tslint:disable */
|
||||
/* eslint-disable */
|
||||
/**
|
||||
* Fabro Run API
|
||||
* HTTP API for managing Fabro workflow run executions.
|
||||
*
|
||||
* The version of the OpenAPI document: 0.2.0
|
||||
*
|
||||
*
|
||||
* NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).
|
||||
* https://openapi-generator.tech
|
||||
* Do not edit the class manually.
|
||||
*/
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* The server was ready and then its connection closed during the stage; its tools fail until the session ends.
|
||||
*/
|
||||
export interface McpServerStatusDisconnected {
|
||||
'kind': McpServerStatusDisconnectedKindEnum;
|
||||
/**
|
||||
* What closed the connection, as the client observed it.
|
||||
*/
|
||||
'error': string;
|
||||
}
|
||||
|
||||
export const McpServerStatusDisconnectedKindEnum = {
|
||||
DISCONNECTED: 'disconnected'
|
||||
} as const;
|
||||
|
||||
export type McpServerStatusDisconnectedKindEnum = typeof McpServerStatusDisconnectedKindEnum[keyof typeof McpServerStatusDisconnectedKindEnum];
|
||||
|
|
@ -1,26 +0,0 @@
|
|||
/* tslint:disable */
|
||||
/* eslint-disable */
|
||||
/**
|
||||
* Fabro Run API
|
||||
* HTTP API for managing Fabro workflow run executions.
|
||||
*
|
||||
* The version of the OpenAPI document: 0.2.0
|
||||
*
|
||||
*
|
||||
* NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).
|
||||
* https://openapi-generator.tech
|
||||
* Do not edit the class manually.
|
||||
*/
|
||||
|
||||
|
||||
|
||||
export interface McpServerStatusFailed {
|
||||
'kind': McpServerStatusFailedKindEnum;
|
||||
'error': string;
|
||||
}
|
||||
|
||||
export const McpServerStatusFailedKindEnum = {
|
||||
FAILED: 'failed'
|
||||
} as const;
|
||||
|
||||
export type McpServerStatusFailedKindEnum = typeof McpServerStatusFailedKindEnum[keyof typeof McpServerStatusFailedKindEnum];
|
||||
|
|
@ -1,29 +0,0 @@
|
|||
/* tslint:disable */
|
||||
/* eslint-disable */
|
||||
/**
|
||||
* Fabro Run API
|
||||
* HTTP API for managing Fabro workflow run executions.
|
||||
*
|
||||
* The version of the OpenAPI document: 0.2.0
|
||||
*
|
||||
*
|
||||
* NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).
|
||||
* https://openapi-generator.tech
|
||||
* Do not edit the class manually.
|
||||
*/
|
||||
|
||||
|
||||
// May contain unused imports in some cases
|
||||
// @ts-ignore
|
||||
import type { AgentMcpToolSummary } from './agent-mcp-tool-summary';
|
||||
|
||||
export interface McpServerStatusReady {
|
||||
'kind': McpServerStatusReadyKindEnum;
|
||||
'tools': Array<AgentMcpToolSummary>;
|
||||
}
|
||||
|
||||
export const McpServerStatusReadyKindEnum = {
|
||||
READY: 'ready'
|
||||
} as const;
|
||||
|
||||
export type McpServerStatusReadyKindEnum = typeof McpServerStatusReadyKindEnum[keyof typeof McpServerStatusReadyKindEnum];
|
||||
|
|
@ -1,33 +0,0 @@
|
|||
/* tslint:disable */
|
||||
/* eslint-disable */
|
||||
/**
|
||||
* Fabro Run API
|
||||
* HTTP API for managing Fabro workflow run executions.
|
||||
*
|
||||
* The version of the OpenAPI document: 0.2.0
|
||||
*
|
||||
*
|
||||
* NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).
|
||||
* https://openapi-generator.tech
|
||||
* Do not edit the class manually.
|
||||
*/
|
||||
|
||||
|
||||
// May contain unused imports in some cases
|
||||
// @ts-ignore
|
||||
import type { AgentMcpToolSummary } from './agent-mcp-tool-summary';
|
||||
// May contain unused imports in some cases
|
||||
// @ts-ignore
|
||||
import type { McpServerStatusDisconnected } from './mcp-server-status-disconnected';
|
||||
// May contain unused imports in some cases
|
||||
// @ts-ignore
|
||||
import type { McpServerStatusFailed } from './mcp-server-status-failed';
|
||||
// May contain unused imports in some cases
|
||||
// @ts-ignore
|
||||
import type { McpServerStatusReady } from './mcp-server-status-ready';
|
||||
|
||||
/**
|
||||
* @type McpServerStatus
|
||||
* Projected MCP server readiness status.
|
||||
*/
|
||||
export type McpServerStatus = { kind: 'disconnected' } & McpServerStatusDisconnected | { kind: 'failed' } & McpServerStatusFailed | { kind: 'ready' } & McpServerStatusReady;
|
||||
|
|
@ -1,29 +0,0 @@
|
|||
/* tslint:disable */
|
||||
/* eslint-disable */
|
||||
/**
|
||||
* Fabro Run API
|
||||
* HTTP API for managing Fabro workflow run executions.
|
||||
*
|
||||
* The version of the OpenAPI document: 0.2.0
|
||||
*
|
||||
*
|
||||
* NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).
|
||||
* https://openapi-generator.tech
|
||||
* Do not edit the class manually.
|
||||
*/
|
||||
|
||||
|
||||
// May contain unused imports in some cases
|
||||
// @ts-ignore
|
||||
import type { ActivatedSkill } from './activated-skill';
|
||||
// May contain unused imports in some cases
|
||||
// @ts-ignore
|
||||
import type { SkillSummary } from './skill-summary';
|
||||
|
||||
/**
|
||||
* Agent skills discovered and activated during a stage.
|
||||
*/
|
||||
export interface SkillsProjection {
|
||||
'available': Array<SkillSummary>;
|
||||
'activated': Array<ActivatedSkill>;
|
||||
}
|
||||
|
|
@ -50,6 +50,9 @@ export interface StageContextWindow {
|
|||
'count_method': ContextWindowCountMethod | null;
|
||||
'staleness': ContextWindowStaleness;
|
||||
'generated_at': string | null;
|
||||
/**
|
||||
* The coding agent\'s own event sequence for the snapshot, when it carried one; not the run event sequence.
|
||||
*/
|
||||
'event_seq': number | null;
|
||||
'breakdown': Array<ContextWindowBreakdownItem>;
|
||||
'warnings': Array<ContextWindowWarning>;
|
||||
|
|
|
|||
|
|
@ -33,21 +33,12 @@ import type { BillingModelRef } from './billing-model-ref';
|
|||
import type { CommandTermination } from './command-termination';
|
||||
// May contain unused imports in some cases
|
||||
// @ts-ignore
|
||||
import type { ContextWindowSnapshot } from './context-window-snapshot';
|
||||
// May contain unused imports in some cases
|
||||
// @ts-ignore
|
||||
import type { McpServerProjection } from './mcp-server-projection';
|
||||
// May contain unused imports in some cases
|
||||
// @ts-ignore
|
||||
import type { ParallelBranchResult } from './parallel-branch-result';
|
||||
// May contain unused imports in some cases
|
||||
// @ts-ignore
|
||||
import type { PermissionLevel } from './permission-level';
|
||||
// May contain unused imports in some cases
|
||||
// @ts-ignore
|
||||
import type { SkillsProjection } from './skills-projection';
|
||||
// May contain unused imports in some cases
|
||||
// @ts-ignore
|
||||
import type { StageCompletion } from './stage-completion';
|
||||
// May contain unused imports in some cases
|
||||
// @ts-ignore
|
||||
|
|
@ -66,12 +57,6 @@ import type { StageTiming } from './stage-timing';
|
|||
import type { StageToolBatchProjection } from './stage-tool-batch-projection';
|
||||
// May contain unused imports in some cases
|
||||
// @ts-ignore
|
||||
import type { SubAgentProjection } from './sub-agent-projection';
|
||||
// May contain unused imports in some cases
|
||||
// @ts-ignore
|
||||
import type { TodoListProjection } from './todo-list-projection';
|
||||
// May contain unused imports in some cases
|
||||
// @ts-ignore
|
||||
import type { ToolSummary } from './tool-summary';
|
||||
|
||||
/**
|
||||
|
|
@ -120,25 +105,11 @@ export interface StageProjection {
|
|||
'tool_batch'?: StageToolBatchProjection | null;
|
||||
'usage': BilledTokenCounts;
|
||||
'model'?: BillingModelRef | null;
|
||||
'todos'?: TodoListProjection | null;
|
||||
/**
|
||||
* Subagents spawned by this stage, in replay/insertion order.
|
||||
*/
|
||||
'subagents'?: Array<SubAgentProjection>;
|
||||
/**
|
||||
* Agent skills discovered and activated during this stage.
|
||||
*/
|
||||
'skills'?: SkillsProjection;
|
||||
'permission_level'?: PermissionLevel | null;
|
||||
/**
|
||||
* Effective model-callable tools exposed to this agent stage session. Tool parameter schemas are intentionally omitted from this projection.
|
||||
*/
|
||||
'agent_tools'?: Array<ToolSummary>;
|
||||
/**
|
||||
* MCP servers observed by this stage.
|
||||
*/
|
||||
'mcp_servers'?: Array<McpServerProjection>;
|
||||
'context_window'?: ContextWindowSnapshot | null;
|
||||
'inference'?: StageInferenceProjection | null;
|
||||
/**
|
||||
* Start of an external ACP agent process, if one is running. ACP agents do not expose Fabro\'s internal LLM brackets, so the process lifetime supplies their live inference estimate.
|
||||
|
|
|
|||
|
|
@ -1,28 +0,0 @@
|
|||
/* tslint:disable */
|
||||
/* eslint-disable */
|
||||
/**
|
||||
* Fabro Run API
|
||||
* HTTP API for managing Fabro workflow run executions.
|
||||
*
|
||||
* The version of the OpenAPI document: 0.2.0
|
||||
*
|
||||
*
|
||||
* NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).
|
||||
* https://openapi-generator.tech
|
||||
* Do not edit the class manually.
|
||||
*/
|
||||
|
||||
|
||||
// May contain unused imports in some cases
|
||||
// @ts-ignore
|
||||
import type { SubAgentStatus } from './sub-agent-status';
|
||||
|
||||
/**
|
||||
* Current projected state for one subagent spawned by an agent stage.
|
||||
*/
|
||||
export interface SubAgentProjection {
|
||||
'agent_id': string;
|
||||
'depth': number;
|
||||
'task': string;
|
||||
'status': SubAgentStatus;
|
||||
}
|
||||
|
|
@ -1,25 +0,0 @@
|
|||
/* tslint:disable */
|
||||
/* eslint-disable */
|
||||
/**
|
||||
* Fabro Run API
|
||||
* HTTP API for managing Fabro workflow run executions.
|
||||
*
|
||||
* The version of the OpenAPI document: 0.2.0
|
||||
*
|
||||
*
|
||||
* NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).
|
||||
* https://openapi-generator.tech
|
||||
* Do not edit the class manually.
|
||||
*/
|
||||
|
||||
|
||||
|
||||
export interface SubAgentStatusClosed {
|
||||
'kind': SubAgentStatusClosedKindEnum;
|
||||
}
|
||||
|
||||
export const SubAgentStatusClosedKindEnum = {
|
||||
CLOSED: 'closed'
|
||||
} as const;
|
||||
|
||||
export type SubAgentStatusClosedKindEnum = typeof SubAgentStatusClosedKindEnum[keyof typeof SubAgentStatusClosedKindEnum];
|
||||
|
|
@ -1,27 +0,0 @@
|
|||
/* tslint:disable */
|
||||
/* eslint-disable */
|
||||
/**
|
||||
* Fabro Run API
|
||||
* HTTP API for managing Fabro workflow run executions.
|
||||
*
|
||||
* The version of the OpenAPI document: 0.2.0
|
||||
*
|
||||
*
|
||||
* NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).
|
||||
* https://openapi-generator.tech
|
||||
* Do not edit the class manually.
|
||||
*/
|
||||
|
||||
|
||||
|
||||
export interface SubAgentStatusCompleted {
|
||||
'kind': SubAgentStatusCompletedKindEnum;
|
||||
'success': boolean;
|
||||
'turns_used': number;
|
||||
}
|
||||
|
||||
export const SubAgentStatusCompletedKindEnum = {
|
||||
COMPLETED: 'completed'
|
||||
} as const;
|
||||
|
||||
export type SubAgentStatusCompletedKindEnum = typeof SubAgentStatusCompletedKindEnum[keyof typeof SubAgentStatusCompletedKindEnum];
|
||||
|
|
@ -1,26 +0,0 @@
|
|||
/* tslint:disable */
|
||||
/* eslint-disable */
|
||||
/**
|
||||
* Fabro Run API
|
||||
* HTTP API for managing Fabro workflow run executions.
|
||||
*
|
||||
* The version of the OpenAPI document: 0.2.0
|
||||
*
|
||||
*
|
||||
* NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).
|
||||
* https://openapi-generator.tech
|
||||
* Do not edit the class manually.
|
||||
*/
|
||||
|
||||
|
||||
|
||||
export interface SubAgentStatusFailed {
|
||||
'kind': SubAgentStatusFailedKindEnum;
|
||||
'error': any;
|
||||
}
|
||||
|
||||
export const SubAgentStatusFailedKindEnum = {
|
||||
FAILED: 'failed'
|
||||
} as const;
|
||||
|
||||
export type SubAgentStatusFailedKindEnum = typeof SubAgentStatusFailedKindEnum[keyof typeof SubAgentStatusFailedKindEnum];
|
||||
|
|
@ -1,25 +0,0 @@
|
|||
/* tslint:disable */
|
||||
/* eslint-disable */
|
||||
/**
|
||||
* Fabro Run API
|
||||
* HTTP API for managing Fabro workflow run executions.
|
||||
*
|
||||
* The version of the OpenAPI document: 0.2.0
|
||||
*
|
||||
*
|
||||
* NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).
|
||||
* https://openapi-generator.tech
|
||||
* Do not edit the class manually.
|
||||
*/
|
||||
|
||||
|
||||
|
||||
export interface SubAgentStatusRunning {
|
||||
'kind': SubAgentStatusRunningKindEnum;
|
||||
}
|
||||
|
||||
export const SubAgentStatusRunningKindEnum = {
|
||||
RUNNING: 'running'
|
||||
} as const;
|
||||
|
||||
export type SubAgentStatusRunningKindEnum = typeof SubAgentStatusRunningKindEnum[keyof typeof SubAgentStatusRunningKindEnum];
|
||||
|
|
@ -1,33 +0,0 @@
|
|||
/* tslint:disable */
|
||||
/* eslint-disable */
|
||||
/**
|
||||
* Fabro Run API
|
||||
* HTTP API for managing Fabro workflow run executions.
|
||||
*
|
||||
* The version of the OpenAPI document: 0.2.0
|
||||
*
|
||||
*
|
||||
* NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).
|
||||
* https://openapi-generator.tech
|
||||
* Do not edit the class manually.
|
||||
*/
|
||||
|
||||
|
||||
// May contain unused imports in some cases
|
||||
// @ts-ignore
|
||||
import type { SubAgentStatusClosed } from './sub-agent-status-closed';
|
||||
// May contain unused imports in some cases
|
||||
// @ts-ignore
|
||||
import type { SubAgentStatusCompleted } from './sub-agent-status-completed';
|
||||
// May contain unused imports in some cases
|
||||
// @ts-ignore
|
||||
import type { SubAgentStatusFailed } from './sub-agent-status-failed';
|
||||
// May contain unused imports in some cases
|
||||
// @ts-ignore
|
||||
import type { SubAgentStatusRunning } from './sub-agent-status-running';
|
||||
|
||||
/**
|
||||
* @type SubAgentStatus
|
||||
* Projected lifecycle status for a subagent.
|
||||
*/
|
||||
export type SubAgentStatus = { kind: 'closed' } & SubAgentStatusClosed | { kind: 'completed' } & SubAgentStatusCompleted | { kind: 'failed' } & SubAgentStatusFailed | { kind: 'running' } & SubAgentStatusRunning;
|
||||
Loading…
Add table
Reference in a new issue