feat(agent): Pi subagents via agent.spawn / agent.await
- agent_run gains depth / spawn_reason (0029); agent_run_event types
subagent_spawned / subagent_event / subagent_completed
- agent.spawn (propose, medium, replay never): allowlist ⊆ parent, mode not
above parent, budget carved from parent's remainder, idempotent per run+input;
settings.pi.subagents {enabled, maxConcurrent, maxDepth} (subagentsEnabled kept as alias)
- child runs claim a concurrency slot under an advisory lock (waitForFact while
queued); agent.await polls business tables without calling the model
- parent abort cascades to all descendants; parent runtime budget counts
descendant usage; child events mirrored into the parent stream by parent seq
- authorize caps every child call by all ancestors' allowlists/kinds
- session page shows parent link, inherited info and the subagent tree
- e2e E11; runbook section on subagents
Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
1 parent
daa85a8bb4
commit
459991421e
27 files changed
+1720
-32
No files matched your search
@@ -34,6 +34,7 @@ import {
|
||||
RunStatusBadge,
|
||||
runPlanIds,
|
||||
} from "./agent-conversation.tsx";
|
||||
import { SubagentTree } from "./agent-subagent-tree.tsx";
|
||||
import { useAgentStream } from "./use-agent-stream.ts";
|
||||
|
||||
const TRIGGER_LABEL = { user: "用户发起", event: "事件触发", schedule: "定时", subagent: "子代理" } as const;
|
||||
@@ -181,11 +182,22 @@ export function AgentRunPanel({
|
||||
const audit = useRunInvocationAudit(runId, toolCount > 0, toolCount);
|
||||
const approvals = useRunApprovals(runPlanIds(live.items, run.data?.report ?? null));
|
||||
// run 状态变化(queued → running → 终态)经 SSE 推送:到达即刷新快照,不等 3 秒轮询
|
||||
// 子会话:本会话收到子会话提示(agent.tool_call / agent.spawn)或任一子会话 / 父会话状态变化时刷新会话树
|
||||
const related = new Set([
|
||||
runId,
|
||||
...(run.data?.children ?? []).map((c) => c.id),
|
||||
...(run.data?.parent ? [run.data.parent.id] : []),
|
||||
]);
|
||||
useSse(
|
||||
[EventType.agentRunStatus],
|
||||
[EventType.agentRunStatus, EventType.agentToolCall],
|
||||
(e) => {
|
||||
if ((e.payload as { runId?: string } | null)?.runId === runId)
|
||||
void qc.invalidateQueries({ queryKey: agentKeys.run(runId) });
|
||||
const p = e.payload as { runId?: string; capabilityId?: string } | null;
|
||||
if (!p?.runId) return;
|
||||
const hit =
|
||||
e.type === EventType.agentRunStatus
|
||||
? related.has(p.runId)
|
||||
: p.runId === runId && p.capabilityId === "agent.spawn";
|
||||
if (hit) void qc.invalidateQueries({ queryKey: agentKeys.run(runId) });
|
||||
},
|
||||
!agentMockEnabled(),
|
||||
);
|
||||
@@ -245,6 +257,8 @@ export function AgentRunPanel({
|
||||
<BudgetBar run={r} live={live} />
|
||||
</div>
|
||||
|
||||
<SubagentTree snapshot={run.data} />
|
||||
|
||||
{mode === "plan_only" ? <PlanSteps live={live} active={streaming} /> : null}
|
||||
|
||||
{streaming && sseStatus === "reconnecting" ? (
|
||||
|
||||
Reference in new issue
Block a user