pilotswarm-sdk 0.5.19 → 0.5.21
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/api/src/http-api-transport.js +4 -0
- package/api/src/protocol.js +2 -0
- package/dist/blob-store.d.ts +42 -11
- package/dist/blob-store.d.ts.map +1 -1
- package/dist/blob-store.js +163 -53
- package/dist/blob-store.js.map +1 -1
- package/dist/cms-migrations.d.ts.map +1 -1
- package/dist/cms-migrations.js +231 -0
- package/dist/cms-migrations.js.map +1 -1
- package/dist/cms.d.ts +62 -0
- package/dist/cms.d.ts.map +1 -1
- package/dist/cms.js +81 -4
- package/dist/cms.js.map +1 -1
- package/dist/distiller-tools.d.ts +9 -0
- package/dist/distiller-tools.d.ts.map +1 -0
- package/dist/distiller-tools.js +102 -0
- package/dist/distiller-tools.js.map +1 -0
- package/dist/footprint.d.ts +129 -0
- package/dist/footprint.d.ts.map +1 -0
- package/dist/footprint.js +254 -0
- package/dist/footprint.js.map +1 -0
- package/dist/inspect-tools.d.ts.map +1 -1
- package/dist/inspect-tools.js +63 -2
- package/dist/inspect-tools.js.map +1 -1
- package/dist/managed-session.d.ts.map +1 -1
- package/dist/managed-session.js +140 -14
- package/dist/managed-session.js.map +1 -1
- package/dist/management-client.d.ts +32 -0
- package/dist/management-client.d.ts.map +1 -1
- package/dist/management-client.js +93 -0
- package/dist/management-client.js.map +1 -1
- package/dist/orchestration/agents.d.ts +18 -0
- package/dist/orchestration/agents.d.ts.map +1 -1
- package/dist/orchestration/agents.js +63 -3
- package/dist/orchestration/agents.js.map +1 -1
- package/dist/orchestration/index.d.ts +2 -2
- package/dist/orchestration/index.js +1 -1
- package/dist/orchestration/lifecycle.d.ts +1 -0
- package/dist/orchestration/lifecycle.d.ts.map +1 -1
- package/dist/orchestration/lifecycle.js +349 -1
- package/dist/orchestration/lifecycle.js.map +1 -1
- package/dist/orchestration/queue.d.ts.map +1 -1
- package/dist/orchestration/queue.js +61 -2
- package/dist/orchestration/queue.js.map +1 -1
- package/dist/orchestration/runtime.d.ts +1 -1
- package/dist/orchestration/runtime.d.ts.map +1 -1
- package/dist/orchestration/runtime.js +31 -1
- package/dist/orchestration/runtime.js.map +1 -1
- package/dist/orchestration/state.d.ts +29 -0
- package/dist/orchestration/state.d.ts.map +1 -1
- package/dist/orchestration/state.js +11 -0
- package/dist/orchestration/state.js.map +1 -1
- package/dist/orchestration/turn.d.ts.map +1 -1
- package/dist/orchestration/turn.js +68 -1
- package/dist/orchestration/turn.js.map +1 -1
- package/dist/orchestration-registry.d.ts.map +1 -1
- package/dist/orchestration-registry.js +8 -2
- package/dist/orchestration-registry.js.map +1 -1
- package/dist/orchestration-version.d.ts +1 -1
- package/dist/orchestration-version.js +1 -1
- package/dist/orchestration.d.ts +2 -2
- package/dist/orchestration.js +1 -1
- package/dist/orchestration_1_0_46.d.ts +1 -1
- package/dist/orchestration_1_0_47.d.ts +1 -1
- package/dist/orchestration_1_0_48.d.ts +1 -1
- package/dist/orchestration_1_0_49.d.ts +1 -1
- package/dist/orchestration_1_0_50.d.ts +1 -1
- package/dist/orchestration_1_0_65/agents.d.ts +43 -0
- package/dist/orchestration_1_0_65/agents.d.ts.map +1 -0
- package/dist/orchestration_1_0_65/agents.js +780 -0
- package/dist/orchestration_1_0_65/agents.js.map +1 -0
- package/dist/orchestration_1_0_65/index.d.ts +24 -0
- package/dist/orchestration_1_0_65/index.d.ts.map +1 -0
- package/dist/orchestration_1_0_65/index.js +13 -0
- package/dist/orchestration_1_0_65/index.js.map +1 -0
- package/dist/orchestration_1_0_65/lifecycle.d.ts +48 -0
- package/dist/orchestration_1_0_65/lifecycle.d.ts.map +1 -0
- package/dist/orchestration_1_0_65/lifecycle.js +572 -0
- package/dist/orchestration_1_0_65/lifecycle.js.map +1 -0
- package/dist/orchestration_1_0_65/queue.d.ts +7 -0
- package/dist/orchestration_1_0_65/queue.d.ts.map +1 -0
- package/dist/orchestration_1_0_65/queue.js +745 -0
- package/dist/orchestration_1_0_65/queue.js.map +1 -0
- package/dist/orchestration_1_0_65/runtime.d.ts +29 -0
- package/dist/orchestration_1_0_65/runtime.d.ts.map +1 -0
- package/dist/orchestration_1_0_65/runtime.js +195 -0
- package/dist/orchestration_1_0_65/runtime.js.map +1 -0
- package/dist/orchestration_1_0_65/state.d.ts +147 -0
- package/dist/orchestration_1_0_65/state.d.ts.map +1 -0
- package/dist/orchestration_1_0_65/state.js +147 -0
- package/dist/orchestration_1_0_65/state.js.map +1 -0
- package/dist/orchestration_1_0_65/turn.d.ts +34 -0
- package/dist/orchestration_1_0_65/turn.d.ts.map +1 -0
- package/dist/orchestration_1_0_65/turn.js +1144 -0
- package/dist/orchestration_1_0_65/turn.js.map +1 -0
- package/dist/orchestration_1_0_65/utils.d.ts +39 -0
- package/dist/orchestration_1_0_65/utils.d.ts.map +1 -0
- package/dist/orchestration_1_0_65/utils.js +316 -0
- package/dist/orchestration_1_0_65/utils.js.map +1 -0
- package/dist/orchestration_1_0_66/agents.d.ts +52 -0
- package/dist/orchestration_1_0_66/agents.d.ts.map +1 -0
- package/dist/orchestration_1_0_66/agents.js +825 -0
- package/dist/orchestration_1_0_66/agents.js.map +1 -0
- package/dist/orchestration_1_0_66/index.d.ts +24 -0
- package/dist/orchestration_1_0_66/index.d.ts.map +1 -0
- package/dist/orchestration_1_0_66/index.js +13 -0
- package/dist/orchestration_1_0_66/index.js.map +1 -0
- package/dist/orchestration_1_0_66/lifecycle.d.ts +48 -0
- package/dist/orchestration_1_0_66/lifecycle.d.ts.map +1 -0
- package/dist/orchestration_1_0_66/lifecycle.js +573 -0
- package/dist/orchestration_1_0_66/lifecycle.js.map +1 -0
- package/dist/orchestration_1_0_66/queue.d.ts +7 -0
- package/dist/orchestration_1_0_66/queue.d.ts.map +1 -0
- package/dist/orchestration_1_0_66/queue.js +776 -0
- package/dist/orchestration_1_0_66/queue.js.map +1 -0
- package/dist/orchestration_1_0_66/runtime.d.ts +29 -0
- package/dist/orchestration_1_0_66/runtime.d.ts.map +1 -0
- package/dist/orchestration_1_0_66/runtime.js +198 -0
- package/dist/orchestration_1_0_66/runtime.js.map +1 -0
- package/dist/orchestration_1_0_66/state.d.ts +149 -0
- package/dist/orchestration_1_0_66/state.d.ts.map +1 -0
- package/dist/orchestration_1_0_66/state.js +148 -0
- package/dist/orchestration_1_0_66/state.js.map +1 -0
- package/dist/orchestration_1_0_66/turn.d.ts +34 -0
- package/dist/orchestration_1_0_66/turn.d.ts.map +1 -0
- package/dist/orchestration_1_0_66/turn.js +1169 -0
- package/dist/orchestration_1_0_66/turn.js.map +1 -0
- package/dist/orchestration_1_0_66/utils.d.ts +39 -0
- package/dist/orchestration_1_0_66/utils.d.ts.map +1 -0
- package/dist/orchestration_1_0_66/utils.js +316 -0
- package/dist/orchestration_1_0_66/utils.js.map +1 -0
- package/dist/orchestration_1_0_67/agents.d.ts +52 -0
- package/dist/orchestration_1_0_67/agents.d.ts.map +1 -0
- package/dist/orchestration_1_0_67/agents.js +825 -0
- package/dist/orchestration_1_0_67/agents.js.map +1 -0
- package/dist/orchestration_1_0_67/index.d.ts +28 -0
- package/dist/orchestration_1_0_67/index.d.ts.map +1 -0
- package/dist/orchestration_1_0_67/index.js +13 -0
- package/dist/orchestration_1_0_67/index.js.map +1 -0
- package/dist/orchestration_1_0_67/lifecycle.d.ts +49 -0
- package/dist/orchestration_1_0_67/lifecycle.d.ts.map +1 -0
- package/dist/orchestration_1_0_67/lifecycle.js +807 -0
- package/dist/orchestration_1_0_67/lifecycle.js.map +1 -0
- package/dist/orchestration_1_0_67/queue.d.ts +7 -0
- package/dist/orchestration_1_0_67/queue.d.ts.map +1 -0
- package/dist/orchestration_1_0_67/queue.js +804 -0
- package/dist/orchestration_1_0_67/queue.js.map +1 -0
- package/dist/orchestration_1_0_67/runtime.d.ts +29 -0
- package/dist/orchestration_1_0_67/runtime.d.ts.map +1 -0
- package/dist/orchestration_1_0_67/runtime.js +228 -0
- package/dist/orchestration_1_0_67/runtime.js.map +1 -0
- package/dist/orchestration_1_0_67/state.d.ts +176 -0
- package/dist/orchestration_1_0_67/state.d.ts.map +1 -0
- package/dist/orchestration_1_0_67/state.js +158 -0
- package/dist/orchestration_1_0_67/state.js.map +1 -0
- package/dist/orchestration_1_0_67/turn.d.ts +34 -0
- package/dist/orchestration_1_0_67/turn.d.ts.map +1 -0
- package/dist/orchestration_1_0_67/turn.js +1208 -0
- package/dist/orchestration_1_0_67/turn.js.map +1 -0
- package/dist/orchestration_1_0_67/utils.d.ts +39 -0
- package/dist/orchestration_1_0_67/utils.d.ts.map +1 -0
- package/dist/orchestration_1_0_67/utils.js +316 -0
- package/dist/orchestration_1_0_67/utils.js.map +1 -0
- package/dist/regen-worker.d.ts +167 -0
- package/dist/regen-worker.d.ts.map +1 -0
- package/dist/regen-worker.js +512 -0
- package/dist/regen-worker.js.map +1 -0
- package/dist/resourcemgr-tools.d.ts.map +1 -1
- package/dist/resourcemgr-tools.js +33 -2
- package/dist/resourcemgr-tools.js.map +1 -1
- package/dist/session-lifecycle.d.ts +8 -0
- package/dist/session-lifecycle.d.ts.map +1 -1
- package/dist/session-lifecycle.js +35 -17
- package/dist/session-lifecycle.js.map +1 -1
- package/dist/session-manager.d.ts +21 -0
- package/dist/session-manager.d.ts.map +1 -1
- package/dist/session-manager.js +104 -11
- package/dist/session-manager.js.map +1 -1
- package/dist/session-proxy.d.ts +34 -0
- package/dist/session-proxy.d.ts.map +1 -1
- package/dist/session-proxy.js +407 -5
- package/dist/session-proxy.js.map +1 -1
- package/dist/session-store.d.ts +50 -15
- package/dist/session-store.d.ts.map +1 -1
- package/dist/session-store.js +143 -30
- package/dist/session-store.js.map +1 -1
- package/dist/snapshot-protocol.d.ts +34 -3
- package/dist/snapshot-protocol.d.ts.map +1 -1
- package/dist/snapshot-protocol.js +25 -0
- package/dist/snapshot-protocol.js.map +1 -1
- package/dist/types.d.ts +101 -1
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js.map +1 -1
- package/dist/web/web-management-client.d.ts +11 -0
- package/dist/web/web-management-client.d.ts.map +1 -1
- package/dist/web/web-management-client.js +8 -0
- package/dist/web/web-management-client.js.map +1 -1
- package/dist/worker.d.ts.map +1 -1
- package/dist/worker.js +7 -0
- package/dist/worker.js.map +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1,825 @@
|
|
|
1
|
+
import { clearPendingChildDigest, publishStatus, queueFollowup, writeCommandResponse, } from "./lifecycle.js";
|
|
2
|
+
import { MAX_NESTING_LEVEL, MAX_SUB_AGENTS, SHUTDOWN_POLL_INTERVAL_MS, SHUTDOWN_TIMEOUT_MS, } from "./state.js";
|
|
3
|
+
// ─── Pure helpers ───────────────────────────────────────────
|
|
4
|
+
export function isSubAgentTerminalStatus(status) {
|
|
5
|
+
return status === "completed" || status === "failed" || status === "cancelled";
|
|
6
|
+
}
|
|
7
|
+
/**
|
|
8
|
+
* A status that satisfies a parent's wait: the child either finished
|
|
9
|
+
* (terminal) or will never speak again on its own — idle after answering, or
|
|
10
|
+
* blocked asking for input. "running" and "waiting" (child on a timer) stay
|
|
11
|
+
* pending: those children will still act without help. Without the idle
|
|
12
|
+
* mapping a wait_for_agents parent polls an idle child forever (observed
|
|
13
|
+
* live: 70+ minutes of 30s polls until a human poked).
|
|
14
|
+
*/
|
|
15
|
+
export function isAgentWaitSettledStatus(status) {
|
|
16
|
+
return isSubAgentTerminalStatus(status) || status === "idle" || status === "input_required";
|
|
17
|
+
}
|
|
18
|
+
export function parseChildUpdate(promptText) {
|
|
19
|
+
if (typeof promptText !== "string")
|
|
20
|
+
return null;
|
|
21
|
+
const match = promptText.match(/^\[CHILD_UPDATE\s+([^\]]+)\]/);
|
|
22
|
+
if (!match)
|
|
23
|
+
return null;
|
|
24
|
+
const fields = new Map();
|
|
25
|
+
for (const token of match[1].split(/\s+/)) {
|
|
26
|
+
const [key, ...rest] = token.split("=");
|
|
27
|
+
if (!key || rest.length === 0)
|
|
28
|
+
continue;
|
|
29
|
+
fields.set(key, rest.join("="));
|
|
30
|
+
}
|
|
31
|
+
const cycle = fields.get("cycle");
|
|
32
|
+
const status = fields.get("status");
|
|
33
|
+
const verdictField = fields.get("verdict");
|
|
34
|
+
const sessionId = fields.get("from") ?? "";
|
|
35
|
+
const updateType = fields.get("type") ?? "";
|
|
36
|
+
if (!sessionId || !updateType)
|
|
37
|
+
return null;
|
|
38
|
+
const cycleOrigin = cycle === "cron" || cycle === "cron_at" ? cycle : undefined;
|
|
39
|
+
const cycleStatus = status === "quiet" || status === "material" || status === "blocked" ? status : undefined;
|
|
40
|
+
const verdict = verdictField === "success" || verdictField === "partial" || verdictField === "blocked" || verdictField === "failed" || verdictField === "cancelled" || verdictField === "timed_out"
|
|
41
|
+
? verdictField
|
|
42
|
+
: undefined;
|
|
43
|
+
return {
|
|
44
|
+
sessionId,
|
|
45
|
+
updateType,
|
|
46
|
+
content: promptText.split("\n").slice(1).join("\n").trim(),
|
|
47
|
+
...(cycleOrigin ? { cycleOrigin } : {}),
|
|
48
|
+
...(cycleStatus ? { cycleStatus } : {}),
|
|
49
|
+
...(verdict ? { verdict } : {}),
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
export function defaultShutdownReason(mode) {
|
|
53
|
+
switch (mode) {
|
|
54
|
+
case "done":
|
|
55
|
+
return "Completed by user";
|
|
56
|
+
case "cancel":
|
|
57
|
+
return "Cancelled by user";
|
|
58
|
+
case "delete":
|
|
59
|
+
return "Deleted by user";
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
export function buildShutdownWaitReason(shutdown) {
|
|
63
|
+
switch (shutdown.mode) {
|
|
64
|
+
case "done":
|
|
65
|
+
return `Waiting for ${shutdown.targetAgentIds.length} child session(s) to complete before closing.`;
|
|
66
|
+
case "cancel":
|
|
67
|
+
return `Waiting for ${shutdown.targetAgentIds.length} child session(s) to cancel before closing.`;
|
|
68
|
+
case "delete":
|
|
69
|
+
return `Waiting for ${shutdown.targetAgentIds.length} child session(s) to cancel before deletion.`;
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
export function findTrackedAgentByOrchId(subAgents, orchId) {
|
|
73
|
+
return subAgents.find((agent) => agent.orchId === orchId);
|
|
74
|
+
}
|
|
75
|
+
export function areTrackedAgentsTerminal(subAgents, agentIds) {
|
|
76
|
+
return agentIds.every((agentId) => {
|
|
77
|
+
const agent = findTrackedAgentByOrchId(subAgents, agentId);
|
|
78
|
+
return Boolean(agent && isSubAgentTerminalStatus(agent.status));
|
|
79
|
+
});
|
|
80
|
+
}
|
|
81
|
+
export function getStillRunningAgentIds(subAgents, agentIds) {
|
|
82
|
+
return agentIds.filter((agentId) => {
|
|
83
|
+
const agent = findTrackedAgentByOrchId(subAgents, agentId);
|
|
84
|
+
return agent && !isSubAgentTerminalStatus(agent.status);
|
|
85
|
+
});
|
|
86
|
+
}
|
|
87
|
+
export function buildWaitForAgentsFollowup(subAgents, targetIds) {
|
|
88
|
+
const describeStatus = (agent) => {
|
|
89
|
+
if (agent.status === "idle") {
|
|
90
|
+
return "idle — went quiet after answering; treat its last message as its result (message_agent to re-task it, complete_agent to finish it)";
|
|
91
|
+
}
|
|
92
|
+
if (agent.status === "input_required") {
|
|
93
|
+
return "input_required — blocked waiting for an answer; relay the question to the user or answer it via message_agent";
|
|
94
|
+
}
|
|
95
|
+
return agent.status;
|
|
96
|
+
};
|
|
97
|
+
const summaries = targetIds
|
|
98
|
+
.map((targetId) => subAgents.find((agent) => agent.orchId === targetId))
|
|
99
|
+
.filter((agent) => Boolean(agent))
|
|
100
|
+
.map((agent) => ` - Agent ${agent.orchId}\n` +
|
|
101
|
+
` Task: "${agent.task.slice(0, 120)}"\n` +
|
|
102
|
+
` Status: ${describeStatus(agent)}\n` +
|
|
103
|
+
` Result: ${agent.result ?? "(no result)"}`);
|
|
104
|
+
if (summaries.length === 0) {
|
|
105
|
+
return `[SYSTEM: No tracked sub-agents produced a completion summary.]`;
|
|
106
|
+
}
|
|
107
|
+
if (summaries.length === 1) {
|
|
108
|
+
return `[SYSTEM: Sub-agent completed. If the user asked you to relay the child's final output, return the single sub-agent Result text verbatim.\n${summaries[0]}]`;
|
|
109
|
+
}
|
|
110
|
+
return `[SYSTEM: Sub-agents completed:\n${summaries.join("\n")}]`;
|
|
111
|
+
}
|
|
112
|
+
export function buildSubAgentSystemMessage(options) {
|
|
113
|
+
const { parentSessionId, childNestingLevel, maxNestingLevel, agentTask, agentIsSystem, parentSystemMessage, } = options;
|
|
114
|
+
const canSpawnMore = childNestingLevel < maxNestingLevel;
|
|
115
|
+
const timingInstruction = agentIsSystem
|
|
116
|
+
? `- For recurring or periodic work, use the \`cron\` or \`cron_at\` tool instead of ending every cycle with \`wait\`. ` +
|
|
117
|
+
`Call \`cron(seconds=<N>, reason="...")\` to start or update the durable recurring schedule, ` +
|
|
118
|
+
`or \`cron_at(minute=<M>, hour=<H>, tz="...", reason="...")\` for wall-clock schedules. ` +
|
|
119
|
+
`then finish turns normally so the orchestration wakes you automatically on each cron cycle. ` +
|
|
120
|
+
`Use \`wait\` only for one-shot delays inside a turn. ` +
|
|
121
|
+
`Call \`cron(action="cancel")\` or \`cron_at(action="cancel")\` only when you intentionally want to stop the recurring loop.\n`
|
|
122
|
+
: `- For ANY waiting, sleeping, delaying, or scheduling, you MUST use the \`wait\`, \`wait_on_worker\`, \`cron\`, or \`cron_at\` tools. ` +
|
|
123
|
+
`Use \`wait\` or \`wait_on_worker\` for one-shot delays. Use \`cron\` for fixed intervals and \`cron_at\` for wall-clock schedules. ` +
|
|
124
|
+
`Do NOT burn tokens polling inside one LLM turn; after a brief immediate re-check at most, yield with a durable timer. ` +
|
|
125
|
+
`NEVER use setTimeout, sleep, setInterval, or any other timing mechanism. ` +
|
|
126
|
+
`Durable waits survive process restarts.\n`;
|
|
127
|
+
const subAgentPreamble = `[SUB-AGENT CONTEXT]\n` +
|
|
128
|
+
`You are a sub-agent spawned by a parent session (ID: session-${parentSessionId}).\n` +
|
|
129
|
+
`Your nesting level: ${childNestingLevel} (max: ${maxNestingLevel}).\n` +
|
|
130
|
+
`Your task: "${agentTask.slice(0, 500)}"\n\n` +
|
|
131
|
+
`Instructions:\n` +
|
|
132
|
+
`- Focus exclusively on your assigned task.\n` +
|
|
133
|
+
`- Your final response will be automatically forwarded to the parent agent.\n` +
|
|
134
|
+
`- Be thorough but concise — the parent will synthesize results from multiple agents.\n` +
|
|
135
|
+
`- Do NOT ask the user for input — you are autonomous.\n` +
|
|
136
|
+
`- You are autonomous and goal-driven. If the task implies ongoing monitoring or follow-through until done, keep yourself alive with durable timers until the goal is complete or you can no longer make progress.\n` +
|
|
137
|
+
`- If it is ambiguous whether the task should become a long-running recurring workflow, report that ambiguity back to the parent instead of guessing or asking the user directly.\n` +
|
|
138
|
+
`- When your task is complete, provide a clear summary of your findings/results. Your final assistant message is automatically forwarded to the parent.\n` +
|
|
139
|
+
`- After you finish a task you stay ALIVE and idle, ready for the parent to send you a follow-up via \`message_agent\`. You are NOT auto-terminated when you produce a final answer.\n` +
|
|
140
|
+
`- Only the parent decides when you are no longer needed. The parent will close you with \`complete_agent\`, \`cancel_agent\`, or \`delete_agent\`. Do not assume you have been shut down just because you produced a final reply.\n` +
|
|
141
|
+
`- Prefer using \`store_fact\` for larger structured context handoffs across your spawn tree. Put the durable details in facts, then pass fact keys or \`read_facts\` pointers in messages/prompts instead of pasting large context blobs. Sibling and cousin agents under the same root can read your session-scoped facts directly via \`read_facts\` — you do NOT need to mark them \`shared=true\` just to share with peers.\n` +
|
|
142
|
+
`- FILESYSTEM ISOLATION: your parent, siblings, and sub-agents each run on their own worker pod — they can NEVER see your local files, and yours may vanish on the next turn, after a durable wait, or on worker restart. The artifact store is the ONLY shared byte channel. To hand off any file (especially binaries/archives): \`write_artifact({fromFile: "<path>"})\` on the producing side, \`read_artifact({toFile: "<path>"})\` on the consuming side. Bytes move server-side with a SHA-256 in every result — never read a file just to re-type its bytes as inline content, and never base64 payloads through messages.\n` +
|
|
143
|
+
`- \`write_artifact\` returns the artifact:// link — include it in your response whenever the user or your parent should see the file. Use \`store_fact\` for structured state, artifacts for files.\n` +
|
|
144
|
+
`- If you override a sub-agent model, you MUST first call list_available_models in this session and use only an exact provider:model value returned there. ` +
|
|
145
|
+
`NEVER invent, guess, shorten, or reuse a stale model name.\n` +
|
|
146
|
+
`- Worker-managed system agents are not valid spawn targets. If you expect one and it is missing, report that the workers likely need to be restarted.\n` +
|
|
147
|
+
timingInstruction +
|
|
148
|
+
(canSpawnMore
|
|
149
|
+
? `- If your parent task explicitly asks you to spawn sub-agents, delegate, fan out, or parallelize work, you SHOULD do so within runtime limits instead of collapsing the task into a direct answer. ` +
|
|
150
|
+
`If delegation was not explicitly requested, use your judgment and avoid unnecessary fan-out. ` +
|
|
151
|
+
`You have ${maxNestingLevel - childNestingLevel} level(s) of nesting remaining. ` +
|
|
152
|
+
`After spawning, finish the turn normally and let qualifying child updates wake you according to contract.wakeOn. ` +
|
|
153
|
+
`Do not schedule wait or cron solely to poll check_agents; use wait_for_agents only when you need an explicit synchronization barrier.\n`
|
|
154
|
+
: `- You CANNOT spawn sub-agents — you are at the maximum nesting depth. Handle everything directly.\n`);
|
|
155
|
+
return subAgentPreamble + (parentSystemMessage ? "\n\n" + parentSystemMessage : "");
|
|
156
|
+
}
|
|
157
|
+
// ─── Child agent tracking ───────────────────────────────────
|
|
158
|
+
export function* applyChildUpdate(runtime, update) {
|
|
159
|
+
runtime.ctx.traceInfo(`[orch] child update from=${update.sessionId} type=${update.updateType}`);
|
|
160
|
+
const agent = runtime.state.subAgents.find(a => a.sessionId === update.sessionId);
|
|
161
|
+
if (!agent) {
|
|
162
|
+
runtime.ctx.traceInfo(`[orch] ignoring child update from untracked session ${update.sessionId}`);
|
|
163
|
+
return false;
|
|
164
|
+
}
|
|
165
|
+
if (update.content) {
|
|
166
|
+
agent.result = update.content.slice(0, 2000);
|
|
167
|
+
}
|
|
168
|
+
// Any substantive update from the child satisfies the spawn-time
|
|
169
|
+
// report expectation.
|
|
170
|
+
agent.expectsReport = false;
|
|
171
|
+
if (update.updateType === "completed") {
|
|
172
|
+
agent.status = "completed";
|
|
173
|
+
}
|
|
174
|
+
else if (update.updateType === "cancelled" || update.updateType === "deleted") {
|
|
175
|
+
agent.status = "cancelled";
|
|
176
|
+
}
|
|
177
|
+
else if (update.updateType === "failed") {
|
|
178
|
+
agent.status = "failed";
|
|
179
|
+
}
|
|
180
|
+
try {
|
|
181
|
+
const rawStatus = yield runtime.manager.getSessionStatus(agent.sessionId);
|
|
182
|
+
const parsed = JSON.parse(rawStatus);
|
|
183
|
+
if (parsed.status === "failed") {
|
|
184
|
+
agent.status = "failed";
|
|
185
|
+
}
|
|
186
|
+
else if (parsed.status === "completed") {
|
|
187
|
+
agent.status = "completed";
|
|
188
|
+
}
|
|
189
|
+
else if (parsed.status === "cancelled") {
|
|
190
|
+
agent.status = "cancelled";
|
|
191
|
+
}
|
|
192
|
+
else if (parsed.status === "waiting" && update.updateType !== "completed") {
|
|
193
|
+
// A "completed" update means the child turn ended with a final
|
|
194
|
+
// answer; a concurrent "waiting" probe at that moment is almost
|
|
195
|
+
// always the auto-resumed remainder of a wait timer the parent's
|
|
196
|
+
// own message interrupted. Downgrading on it deadlocks
|
|
197
|
+
// wait_for_agents: the idle child may never speak again, and the
|
|
198
|
+
// fallback poll cannot re-derive "completed" from an idle probe.
|
|
199
|
+
// Deliberate continuation waits arrive as updateType "wait" and
|
|
200
|
+
// still downgrade here.
|
|
201
|
+
agent.status = "waiting";
|
|
202
|
+
}
|
|
203
|
+
else if (parsed.status === "idle" && !isSubAgentTerminalStatus(agent.status) && update.updateType !== "completed") {
|
|
204
|
+
// Quiescent child: answered and parked. Not terminal (it can be
|
|
205
|
+
// re-tasked), but it satisfies a parent wait.
|
|
206
|
+
agent.status = "idle";
|
|
207
|
+
}
|
|
208
|
+
else if (parsed.status === "input_required" && !isSubAgentTerminalStatus(agent.status)) {
|
|
209
|
+
// Blocked asking for input — it will never act unprompted; the
|
|
210
|
+
// parent must be told so it can answer or re-task.
|
|
211
|
+
agent.status = "input_required";
|
|
212
|
+
}
|
|
213
|
+
if (parsed.result && parsed.result !== "done") {
|
|
214
|
+
agent.result = parsed.result.slice(0, 2000);
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
catch { }
|
|
218
|
+
return true;
|
|
219
|
+
}
|
|
220
|
+
export function* refreshTrackedSubAgents(runtime, options = {}) {
|
|
221
|
+
const preserveTerminalTaskStatus = options.preserveTerminalTaskStatus !== false;
|
|
222
|
+
try {
|
|
223
|
+
const rawChildren = yield runtime.manager.listChildSessions(runtime.input.sessionId);
|
|
224
|
+
const directChildren = JSON.parse(rawChildren);
|
|
225
|
+
runtime.state.subAgents = directChildren
|
|
226
|
+
.filter(child => !child.isSystem)
|
|
227
|
+
.map((child) => {
|
|
228
|
+
const existing = runtime.state.subAgents.find(agent => agent.sessionId === child.sessionId || agent.orchId === child.orchId);
|
|
229
|
+
const contract = child.contract ?? existing?.contract;
|
|
230
|
+
const localStatus = existing?.status;
|
|
231
|
+
if (preserveTerminalTaskStatus && localStatus && isSubAgentTerminalStatus(localStatus)) {
|
|
232
|
+
return {
|
|
233
|
+
orchId: child.orchId,
|
|
234
|
+
sessionId: child.sessionId,
|
|
235
|
+
task: existing?.task ?? child.title ?? "(spawned sub-agent)",
|
|
236
|
+
status: localStatus,
|
|
237
|
+
result: child.result ?? existing?.result,
|
|
238
|
+
agentId: child.agentId ?? existing?.agentId,
|
|
239
|
+
contract,
|
|
240
|
+
};
|
|
241
|
+
}
|
|
242
|
+
const rawStatus = child.status ?? localStatus ?? "running";
|
|
243
|
+
const normalizedStatus = rawStatus === "failed" ? "failed"
|
|
244
|
+
: rawStatus === "cancelled" ? "cancelled"
|
|
245
|
+
: rawStatus === "waiting" ? "waiting"
|
|
246
|
+
: rawStatus === "completed" ? "completed"
|
|
247
|
+
: "running";
|
|
248
|
+
return {
|
|
249
|
+
orchId: child.orchId,
|
|
250
|
+
sessionId: child.sessionId,
|
|
251
|
+
task: existing?.task ?? child.title ?? "(spawned sub-agent)",
|
|
252
|
+
status: normalizedStatus,
|
|
253
|
+
result: child.result ?? existing?.result,
|
|
254
|
+
agentId: child.agentId ?? existing?.agentId,
|
|
255
|
+
contract,
|
|
256
|
+
};
|
|
257
|
+
});
|
|
258
|
+
}
|
|
259
|
+
catch (err) {
|
|
260
|
+
runtime.ctx.traceInfo(`[orch] refreshTrackedSubAgents failed (non-fatal): ${err.message ?? err}`);
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
// ─── Graceful shutdown cascade ──────────────────────────────
|
|
264
|
+
export function* notifyParentOfTerminalState(runtime, updateType, reason) {
|
|
265
|
+
if (!runtime.options.parentSessionId)
|
|
266
|
+
return;
|
|
267
|
+
try {
|
|
268
|
+
const verdict = updateType === "completed" ? "success" : "cancelled";
|
|
269
|
+
yield runtime.manager.sendToSession(runtime.options.parentSessionId, `[CHILD_UPDATE from=${runtime.input.sessionId} type=${updateType} iter=${runtime.state.iteration} verdict=${verdict}]\n${reason}`);
|
|
270
|
+
}
|
|
271
|
+
catch (err) {
|
|
272
|
+
runtime.ctx.traceInfo(`[orch] sendToSession(parent) on ${updateType} failed: ${err.message} (non-fatal)`);
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
export function* completeSession(runtime, reason, commandId) {
|
|
276
|
+
runtime.state.pendingShutdown = null;
|
|
277
|
+
runtime.state.waitingForAgentIds = null;
|
|
278
|
+
clearPendingChildDigest(runtime);
|
|
279
|
+
runtime.state.activeTimer = null;
|
|
280
|
+
yield runtime.manager.updateCmsState(runtime.input.sessionId, "completed", null, null);
|
|
281
|
+
publishStatus(runtime, "completed");
|
|
282
|
+
yield* notifyParentOfTerminalState(runtime, "completed", reason);
|
|
283
|
+
try {
|
|
284
|
+
yield runtime.session.destroy();
|
|
285
|
+
}
|
|
286
|
+
catch { }
|
|
287
|
+
if (commandId) {
|
|
288
|
+
const resp = {
|
|
289
|
+
id: commandId,
|
|
290
|
+
cmd: "done",
|
|
291
|
+
result: { ok: true, message: "Session completed" },
|
|
292
|
+
};
|
|
293
|
+
yield* writeCommandResponse(runtime, resp);
|
|
294
|
+
}
|
|
295
|
+
runtime.state.orchestrationResult = "done";
|
|
296
|
+
}
|
|
297
|
+
export function* cancelSession(runtime, reason, commandId, deleteAfterCancel = false) {
|
|
298
|
+
runtime.state.pendingShutdown = null;
|
|
299
|
+
runtime.state.waitingForAgentIds = null;
|
|
300
|
+
clearPendingChildDigest(runtime);
|
|
301
|
+
runtime.state.activeTimer = null;
|
|
302
|
+
const commandName = deleteAfterCancel ? "delete" : "cancel";
|
|
303
|
+
if (!deleteAfterCancel) {
|
|
304
|
+
yield runtime.manager.updateCmsState(runtime.input.sessionId, "cancelled", null, null);
|
|
305
|
+
publishStatus(runtime, "cancelled");
|
|
306
|
+
}
|
|
307
|
+
yield* notifyParentOfTerminalState(runtime, "cancelled", reason);
|
|
308
|
+
try {
|
|
309
|
+
yield runtime.session.destroy();
|
|
310
|
+
}
|
|
311
|
+
catch { }
|
|
312
|
+
if (commandId) {
|
|
313
|
+
const resp = {
|
|
314
|
+
id: commandId,
|
|
315
|
+
cmd: commandName,
|
|
316
|
+
result: {
|
|
317
|
+
ok: true,
|
|
318
|
+
message: deleteAfterCancel ? "Session deleted" : "Session cancelled",
|
|
319
|
+
},
|
|
320
|
+
};
|
|
321
|
+
yield* writeCommandResponse(runtime, resp);
|
|
322
|
+
}
|
|
323
|
+
if (deleteAfterCancel) {
|
|
324
|
+
const deleteReason = reason || "Deleted by user";
|
|
325
|
+
let descendants = [];
|
|
326
|
+
try {
|
|
327
|
+
descendants = yield runtime.manager.getDescendantSessionIds(runtime.input.sessionId);
|
|
328
|
+
}
|
|
329
|
+
catch (err) {
|
|
330
|
+
runtime.ctx.traceInfo(`[orch] delete: failed to enumerate descendants: ${err.message}`);
|
|
331
|
+
}
|
|
332
|
+
for (const descendantId of descendants) {
|
|
333
|
+
try {
|
|
334
|
+
yield runtime.manager.deleteSession(descendantId, `Ancestor ${runtime.input.sessionId} deleted: ${deleteReason}`);
|
|
335
|
+
}
|
|
336
|
+
catch (err) {
|
|
337
|
+
runtime.ctx.traceInfo(`[orch] delete: failed to delete descendant ${descendantId}: ${err.message} (non-fatal)`);
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
try {
|
|
341
|
+
yield runtime.manager.deleteSession(runtime.input.sessionId, deleteReason);
|
|
342
|
+
}
|
|
343
|
+
catch (err) {
|
|
344
|
+
runtime.ctx.traceInfo(`[orch] delete: failed to delete ${runtime.input.sessionId}: ${err.message}`);
|
|
345
|
+
}
|
|
346
|
+
runtime.state.orchestrationResult = "deleted";
|
|
347
|
+
return;
|
|
348
|
+
}
|
|
349
|
+
runtime.state.orchestrationResult = "cancelled";
|
|
350
|
+
}
|
|
351
|
+
export function* failPendingShutdown(runtime, errorMessage) {
|
|
352
|
+
const shutdown = runtime.state.pendingShutdown;
|
|
353
|
+
runtime.state.pendingShutdown = null;
|
|
354
|
+
runtime.state.waitingForAgentIds = null;
|
|
355
|
+
clearPendingChildDigest(runtime);
|
|
356
|
+
runtime.state.activeTimer = null;
|
|
357
|
+
try {
|
|
358
|
+
yield runtime.session.destroy();
|
|
359
|
+
}
|
|
360
|
+
catch { }
|
|
361
|
+
if (shutdown?.commandId) {
|
|
362
|
+
const resp = {
|
|
363
|
+
id: shutdown.commandId,
|
|
364
|
+
cmd: shutdown.mode,
|
|
365
|
+
error: errorMessage,
|
|
366
|
+
};
|
|
367
|
+
yield* writeCommandResponse(runtime, resp);
|
|
368
|
+
}
|
|
369
|
+
publishStatus(runtime, "failed", { error: errorMessage });
|
|
370
|
+
yield runtime.manager.updateCmsState(runtime.input.sessionId, "failed", errorMessage, null);
|
|
371
|
+
runtime.state.orchestrationResult = "failed";
|
|
372
|
+
}
|
|
373
|
+
export function* finalizePendingShutdown(runtime) {
|
|
374
|
+
if (!runtime.state.pendingShutdown)
|
|
375
|
+
return;
|
|
376
|
+
const shutdown = runtime.state.pendingShutdown;
|
|
377
|
+
if (shutdown.mode === "done") {
|
|
378
|
+
yield* completeSession(runtime, shutdown.reason, shutdown.commandId);
|
|
379
|
+
return;
|
|
380
|
+
}
|
|
381
|
+
yield* cancelSession(runtime, shutdown.reason, shutdown.commandId, shutdown.mode === "delete");
|
|
382
|
+
}
|
|
383
|
+
export function* maybeResolveAgentWaitCompletion(runtime) {
|
|
384
|
+
const { state } = runtime;
|
|
385
|
+
const allSettled = state.waitingForAgentIds?.every((targetId) => {
|
|
386
|
+
const agent = state.subAgents.find((entry) => entry.orchId === targetId);
|
|
387
|
+
return agent ? isAgentWaitSettledStatus(agent.status) : true;
|
|
388
|
+
});
|
|
389
|
+
if (!state.waitingForAgentIds || !allSettled) {
|
|
390
|
+
return false;
|
|
391
|
+
}
|
|
392
|
+
if (state.pendingShutdown) {
|
|
393
|
+
yield* finalizePendingShutdown(runtime);
|
|
394
|
+
return true;
|
|
395
|
+
}
|
|
396
|
+
queueFollowup(runtime, buildWaitForAgentsFollowup(state.subAgents, state.waitingForAgentIds));
|
|
397
|
+
state.waitingForAgentIds = null;
|
|
398
|
+
clearPendingChildDigest(runtime);
|
|
399
|
+
state.activeTimer = null;
|
|
400
|
+
return true;
|
|
401
|
+
}
|
|
402
|
+
export function* beginGracefulShutdown(runtime, mode, cmdMsg) {
|
|
403
|
+
const { state } = runtime;
|
|
404
|
+
if (state.pendingShutdown) {
|
|
405
|
+
const now = yield runtime.ctx.utcNow();
|
|
406
|
+
const resp = {
|
|
407
|
+
id: cmdMsg.id,
|
|
408
|
+
cmd: cmdMsg.cmd,
|
|
409
|
+
result: {
|
|
410
|
+
ok: true,
|
|
411
|
+
message: `Shutdown already in progress (${state.pendingShutdown.mode}).`,
|
|
412
|
+
},
|
|
413
|
+
};
|
|
414
|
+
yield* writeCommandResponse(runtime, resp);
|
|
415
|
+
publishStatus(runtime, "waiting", {
|
|
416
|
+
waitReason: buildShutdownWaitReason(state.pendingShutdown),
|
|
417
|
+
waitStartedAt: state.pendingShutdown.startedAtMs,
|
|
418
|
+
waitSeconds: Math.max(0, Math.ceil((state.pendingShutdown.deadlineAtMs - now) / 1000)),
|
|
419
|
+
});
|
|
420
|
+
return;
|
|
421
|
+
}
|
|
422
|
+
// Task completion does not make a child session terminal: non-system
|
|
423
|
+
// children stay alive for follow-up messages. During parent shutdown the
|
|
424
|
+
// CMS session lifecycle is authoritative, so an idle child must still
|
|
425
|
+
// receive done/cancel even if its last tracked task status was completed.
|
|
426
|
+
yield* refreshTrackedSubAgents(runtime, { preserveTerminalTaskStatus: false });
|
|
427
|
+
const shutdownReason = String(cmdMsg.args?.reason || defaultShutdownReason(mode));
|
|
428
|
+
const targetAgents = state.subAgents.filter((agent) => !isSubAgentTerminalStatus(agent.status));
|
|
429
|
+
if (targetAgents.length === 0) {
|
|
430
|
+
if (mode === "done") {
|
|
431
|
+
yield* completeSession(runtime, shutdownReason, cmdMsg.id);
|
|
432
|
+
return;
|
|
433
|
+
}
|
|
434
|
+
yield* cancelSession(runtime, shutdownReason, cmdMsg.id, mode === "delete");
|
|
435
|
+
return;
|
|
436
|
+
}
|
|
437
|
+
const childCmd = mode === "done" ? "done" : "cancel";
|
|
438
|
+
const childReason = mode === "done"
|
|
439
|
+
? "Parent session completing"
|
|
440
|
+
: shutdownReason;
|
|
441
|
+
runtime.ctx.traceInfo(`[orch] ${cmdMsg.cmd}: cascading ${childCmd} to ${targetAgents.length} child session(s)`);
|
|
442
|
+
for (const child of targetAgents) {
|
|
443
|
+
try {
|
|
444
|
+
const childCmdId = `${cmdMsg.cmd}-cascade-${state.iteration}-${child.sessionId.slice(0, 8)}`;
|
|
445
|
+
yield runtime.manager.sendCommandToSession(child.sessionId, { type: "cmd", cmd: childCmd, id: childCmdId, args: { reason: childReason } });
|
|
446
|
+
}
|
|
447
|
+
catch (err) {
|
|
448
|
+
runtime.ctx.traceInfo(`[orch] ${cmdMsg.cmd}: failed to signal child ${child.sessionId}: ${err.message} (non-fatal)`);
|
|
449
|
+
}
|
|
450
|
+
}
|
|
451
|
+
const startedAtMs = yield runtime.ctx.utcNow();
|
|
452
|
+
state.pendingShutdown = {
|
|
453
|
+
mode,
|
|
454
|
+
reason: shutdownReason,
|
|
455
|
+
startedAtMs,
|
|
456
|
+
deadlineAtMs: startedAtMs + SHUTDOWN_TIMEOUT_MS,
|
|
457
|
+
targetAgentIds: targetAgents.map((agent) => agent.orchId),
|
|
458
|
+
commandId: cmdMsg.id,
|
|
459
|
+
};
|
|
460
|
+
state.waitingForAgentIds = [...state.pendingShutdown.targetAgentIds];
|
|
461
|
+
clearPendingChildDigest(runtime);
|
|
462
|
+
state.activeTimer = {
|
|
463
|
+
deadlineMs: startedAtMs + SHUTDOWN_POLL_INTERVAL_MS,
|
|
464
|
+
originalDurationMs: SHUTDOWN_POLL_INTERVAL_MS,
|
|
465
|
+
reason: buildShutdownWaitReason(state.pendingShutdown),
|
|
466
|
+
type: "agent-poll",
|
|
467
|
+
agentIds: state.waitingForAgentIds,
|
|
468
|
+
};
|
|
469
|
+
publishStatus(runtime, "waiting", {
|
|
470
|
+
waitReason: buildShutdownWaitReason(state.pendingShutdown),
|
|
471
|
+
waitStartedAt: startedAtMs,
|
|
472
|
+
waitSeconds: Math.ceil(SHUTDOWN_TIMEOUT_MS / 1000),
|
|
473
|
+
});
|
|
474
|
+
}
|
|
475
|
+
// ─── Sub-agent tool actions (spawn/message/check/wait/etc.) ─
|
|
476
|
+
export function* handleSubAgentAction(runtime, result) {
|
|
477
|
+
const { ctx, state } = runtime;
|
|
478
|
+
switch (result.type) {
|
|
479
|
+
case "spawn_agent": {
|
|
480
|
+
const childNestingLevel = runtime.options.nestingLevel + 1;
|
|
481
|
+
if (childNestingLevel > MAX_NESTING_LEVEL) {
|
|
482
|
+
ctx.traceInfo(`[orch] spawn_agent denied: nesting level ${runtime.options.nestingLevel} is at max (${MAX_NESTING_LEVEL})`);
|
|
483
|
+
queueFollowup(runtime, `[SYSTEM: spawn_agent failed — you are already at nesting level ${runtime.options.nestingLevel} (max ${MAX_NESTING_LEVEL}). ` +
|
|
484
|
+
`Sub-agents at this depth cannot spawn further sub-agents. Handle the task directly instead.]`);
|
|
485
|
+
return true;
|
|
486
|
+
}
|
|
487
|
+
const activeCount = state.subAgents.filter(a => a.status === "running").length;
|
|
488
|
+
if (activeCount >= MAX_SUB_AGENTS) {
|
|
489
|
+
ctx.traceInfo(`[orch] spawn_agent denied: ${activeCount}/${MAX_SUB_AGENTS} agents running`);
|
|
490
|
+
queueFollowup(runtime, `[SYSTEM: spawn_agent failed — you already have ${activeCount} running sub-agents (max ${MAX_SUB_AGENTS}). ` +
|
|
491
|
+
`Wait for some to complete before spawning more.]`);
|
|
492
|
+
return true;
|
|
493
|
+
}
|
|
494
|
+
let agentTask = result.task;
|
|
495
|
+
let agentSystemMessage = result.systemMessage;
|
|
496
|
+
let agentToolNames = result.toolNames;
|
|
497
|
+
const agentModel = result.model;
|
|
498
|
+
const agentReasoningEffort = result.reasoningEffort;
|
|
499
|
+
let agentIsSystem = false;
|
|
500
|
+
const explicitAgentTitle = typeof result.title === "string" && result.title.trim() ? result.title.trim() : undefined;
|
|
501
|
+
let agentTitle = explicitAgentTitle;
|
|
502
|
+
let agentTitleIsExplicit = Boolean(explicitAgentTitle);
|
|
503
|
+
let agentId;
|
|
504
|
+
let agentSplash;
|
|
505
|
+
let boundAgentName;
|
|
506
|
+
let promptLayeringKind;
|
|
507
|
+
const resolvedAgentName = result.agentName;
|
|
508
|
+
const applyAgentDef = (agentDef, useDefinitionDefaults = false) => {
|
|
509
|
+
agentTask = useDefinitionDefaults
|
|
510
|
+
? (agentDef.initialPrompt || `You are the ${agentDef.name} agent. Begin your work.`)
|
|
511
|
+
: (result.task || agentDef.initialPrompt || `You are the ${agentDef.name} agent. Begin your work.`);
|
|
512
|
+
agentSystemMessage = useDefinitionDefaults ? undefined : result.systemMessage;
|
|
513
|
+
agentToolNames = useDefinitionDefaults
|
|
514
|
+
? (agentDef.tools ?? undefined)
|
|
515
|
+
: (result.toolNames ?? agentDef.tools ?? undefined);
|
|
516
|
+
agentIsSystem = agentDef.system ?? false;
|
|
517
|
+
if (!agentTitleIsExplicit)
|
|
518
|
+
agentTitle = agentDef.title;
|
|
519
|
+
agentId = agentDef.id ?? resolvedAgentName;
|
|
520
|
+
agentSplash = agentDef.splash;
|
|
521
|
+
boundAgentName = agentDef.name;
|
|
522
|
+
promptLayeringKind = agentDef.promptLayerKind
|
|
523
|
+
?? (agentDef.system
|
|
524
|
+
? ((agentDef.namespace || "pilotswarm") === "pilotswarm"
|
|
525
|
+
? "pilotswarm-system-agent"
|
|
526
|
+
: "app-system-agent")
|
|
527
|
+
: "app-agent");
|
|
528
|
+
};
|
|
529
|
+
if (resolvedAgentName) {
|
|
530
|
+
ctx.traceInfo(`[orch] resolving agent config for: ${resolvedAgentName}`);
|
|
531
|
+
const agentDef = yield runtime.manager.resolveAgentConfig(resolvedAgentName);
|
|
532
|
+
if (!agentDef) {
|
|
533
|
+
queueFollowup(runtime, `[SYSTEM: spawn_agent failed — agent "${resolvedAgentName}" not found. Use ps_list_agents to see available agents.]`);
|
|
534
|
+
return true;
|
|
535
|
+
}
|
|
536
|
+
if (agentDef.system && agentDef.creatable === false) {
|
|
537
|
+
queueFollowup(runtime, `[SYSTEM: spawn_agent failed — agent "${resolvedAgentName}" is a worker-managed system agent and cannot be spawned from a session. ` +
|
|
538
|
+
`If it is missing, the workers likely need to be restarted.]`);
|
|
539
|
+
return true;
|
|
540
|
+
}
|
|
541
|
+
applyAgentDef(agentDef, resolvedAgentName !== result.agentName);
|
|
542
|
+
}
|
|
543
|
+
// NOTE: a system parent does NOT make its children system.
|
|
544
|
+
// agentIsSystem stays purely definition-driven (agentDef.system —
|
|
545
|
+
// the worker-managed agents). Ad-hoc children of system sessions
|
|
546
|
+
// are ordinary deletable sessions that inherit the SYSTEM user as
|
|
547
|
+
// their OWNER instead (resolveEffectiveSpawnOwner in the
|
|
548
|
+
// spawnChildSession activity), which is how they reach the
|
|
549
|
+
// admin-stored System GitHub Copilot key.
|
|
550
|
+
if (agentModel && !agentModel.includes(":")) {
|
|
551
|
+
ctx.traceInfo(`[orch] spawn_agent denied: unqualified model override "${agentModel}"`);
|
|
552
|
+
queueFollowup(runtime, `[SYSTEM: spawn_agent failed — model "${agentModel}" is not allowed. ` +
|
|
553
|
+
`When overriding a sub-agent model, first call list_available_models and then use the exact provider:model value from that list. ` +
|
|
554
|
+
`If you are unsure, omit model so the sub-agent inherits your current model.]`);
|
|
555
|
+
return true;
|
|
556
|
+
}
|
|
557
|
+
if (!agentTitle && agentIsSystem) {
|
|
558
|
+
const text = agentTask || "";
|
|
559
|
+
const titleMatch = text.match(/You are the \*{0,2}([^*\n]+?)\*{0,2}\s*[—–-]/i)
|
|
560
|
+
|| text.match(/You are the \*{0,2}([^*\n]+?Agent)\*{0,2}/i);
|
|
561
|
+
if (titleMatch) {
|
|
562
|
+
agentTitle = titleMatch[1].trim();
|
|
563
|
+
}
|
|
564
|
+
}
|
|
565
|
+
ctx.traceInfo(`[orch] spawning sub-agent via SDK: task="${agentTask.slice(0, 80)}" model=${agentModel || "inherit"} agent=${resolvedAgentName || "custom"} nestingLevel=${childNestingLevel}`);
|
|
566
|
+
const { boundAgentName: _parentBoundAgentName, promptLayering: _parentPromptLayering, ...parentConfig } = state.config;
|
|
567
|
+
const childConfig = {
|
|
568
|
+
...parentConfig,
|
|
569
|
+
...(agentModel ? { model: agentModel } : {}),
|
|
570
|
+
...(agentReasoningEffort ? { reasoningEffort: agentReasoningEffort } : {}),
|
|
571
|
+
...(agentSystemMessage ? { systemMessage: agentSystemMessage } : {}),
|
|
572
|
+
...(boundAgentName ? { boundAgentName } : {}),
|
|
573
|
+
...(promptLayeringKind ? { promptLayering: { kind: promptLayeringKind } } : {}),
|
|
574
|
+
...(agentToolNames ? { toolNames: agentToolNames } : {}),
|
|
575
|
+
...(result.contract ? { childContract: result.contract } : {}),
|
|
576
|
+
};
|
|
577
|
+
const parentSystemMsg = typeof childConfig.systemMessage === "string"
|
|
578
|
+
? childConfig.systemMessage
|
|
579
|
+
: childConfig.systemMessage?.content ?? "";
|
|
580
|
+
childConfig.systemMessage = buildSubAgentSystemMessage({
|
|
581
|
+
parentSessionId: runtime.input.sessionId,
|
|
582
|
+
childNestingLevel,
|
|
583
|
+
maxNestingLevel: MAX_NESTING_LEVEL,
|
|
584
|
+
agentTask,
|
|
585
|
+
agentIsSystem,
|
|
586
|
+
parentSystemMessage: parentSystemMsg,
|
|
587
|
+
});
|
|
588
|
+
let childSessionId;
|
|
589
|
+
try {
|
|
590
|
+
childSessionId = yield runtime.manager.spawnChildSession(runtime.input.sessionId, childConfig, agentTask, childNestingLevel, agentIsSystem, agentTitle, agentId, agentSplash, agentTitleIsExplicit);
|
|
591
|
+
}
|
|
592
|
+
catch (err) {
|
|
593
|
+
ctx.traceInfo(`[orch] spawnChildSession failed: ${err.message}`);
|
|
594
|
+
queueFollowup(runtime, `[SYSTEM: spawn_agent failed: ${err.message}]`);
|
|
595
|
+
return true;
|
|
596
|
+
}
|
|
597
|
+
const childOrchId = `session-${childSessionId}`;
|
|
598
|
+
yield runtime.manager.recordSessionEvent(runtime.input.sessionId, [{
|
|
599
|
+
eventType: "session.agent_spawned",
|
|
600
|
+
data: { childSessionId, agentId: agentId || undefined, task: agentTask.slice(0, 500) },
|
|
601
|
+
}]);
|
|
602
|
+
state.subAgents.push({
|
|
603
|
+
orchId: childOrchId,
|
|
604
|
+
sessionId: childSessionId,
|
|
605
|
+
task: agentTask.slice(0, 500),
|
|
606
|
+
status: "running",
|
|
607
|
+
agentId: agentId || undefined,
|
|
608
|
+
contract: result.contract,
|
|
609
|
+
expectsReport: true,
|
|
610
|
+
});
|
|
611
|
+
queueFollowup(runtime, `[SYSTEM: Sub-agent spawned successfully.\n` +
|
|
612
|
+
` Agent ID: ${childOrchId}\n` +
|
|
613
|
+
` ${resolvedAgentName ? `Agent: ${resolvedAgentName}\n ` : ``}Task: "${agentTask.slice(0, 200)}"\n` +
|
|
614
|
+
` The agent is now running autonomously. Continue your work in this SAME turn and keep following the user's remaining steps. ` +
|
|
615
|
+
`Do NOT stop just because the child started. If you need to pause, call wait or wait_for_agents explicitly. ` +
|
|
616
|
+
`You can also use check_agents to poll status, ` +
|
|
617
|
+
`or message_agent to send instructions.]`);
|
|
618
|
+
return true;
|
|
619
|
+
}
|
|
620
|
+
case "message_agent": {
|
|
621
|
+
const targetOrchId = result.agentId;
|
|
622
|
+
const agentEntry = state.subAgents.find(a => a.orchId === targetOrchId);
|
|
623
|
+
if (!agentEntry) {
|
|
624
|
+
ctx.traceInfo(`[orch] message_agent: unknown agent ${targetOrchId}`);
|
|
625
|
+
queueFollowup(runtime, `[SYSTEM: message_agent failed — agent "${targetOrchId}" not found. ` +
|
|
626
|
+
`Known agents: ${state.subAgents.map(a => a.orchId).join(", ") || "none"}]`);
|
|
627
|
+
return true;
|
|
628
|
+
}
|
|
629
|
+
ctx.traceInfo(`[orch] message_agent via SDK: ${agentEntry.sessionId} msg="${result.message.slice(0, 60)}"`);
|
|
630
|
+
if (result.contractPatch && typeof result.contractPatch === "object") {
|
|
631
|
+
agentEntry.contract = {
|
|
632
|
+
...(agentEntry.contract ?? {}),
|
|
633
|
+
...result.contractPatch,
|
|
634
|
+
};
|
|
635
|
+
}
|
|
636
|
+
try {
|
|
637
|
+
yield runtime.manager.sendToSession(agentEntry.sessionId, result.message);
|
|
638
|
+
}
|
|
639
|
+
catch (err) {
|
|
640
|
+
ctx.traceInfo(`[orch] message_agent failed: ${err.message}`);
|
|
641
|
+
queueFollowup(runtime, `[SYSTEM: message_agent failed: ${err.message}]`);
|
|
642
|
+
return true;
|
|
643
|
+
}
|
|
644
|
+
queueFollowup(runtime, `[SYSTEM: Message sent to sub-agent ${targetOrchId}: "${result.message.slice(0, 200)}". ` +
|
|
645
|
+
`Continue your work in this SAME turn. If you are waiting on the child, call wait_for_agents explicitly rather than stopping here.]`);
|
|
646
|
+
return true;
|
|
647
|
+
}
|
|
648
|
+
case "check_agents": {
|
|
649
|
+
ctx.traceInfo(`[orch] check_agents: ${state.subAgents.length} agents tracked`);
|
|
650
|
+
if (state.subAgents.length === 0) {
|
|
651
|
+
queueFollowup(runtime, `[SYSTEM: No sub-agents have been spawned yet.]`);
|
|
652
|
+
return true;
|
|
653
|
+
}
|
|
654
|
+
const statusLines = [];
|
|
655
|
+
for (const agent of state.subAgents) {
|
|
656
|
+
try {
|
|
657
|
+
const rawStatus = yield runtime.manager.getSessionStatus(agent.sessionId);
|
|
658
|
+
const parsed = JSON.parse(rawStatus);
|
|
659
|
+
if (parsed.status === "completed" || parsed.status === "failed" || parsed.status === "idle") {
|
|
660
|
+
agent.status = parsed.status === "failed" ? "failed" : "completed";
|
|
661
|
+
if (parsed.result)
|
|
662
|
+
agent.result = parsed.result.slice(0, 1000);
|
|
663
|
+
}
|
|
664
|
+
statusLines.push(` - Agent ${agent.orchId}\n` +
|
|
665
|
+
` Task: "${agent.task.slice(0, 120)}"\n` +
|
|
666
|
+
` Status: ${parsed.status}\n` +
|
|
667
|
+
` Iterations: ${parsed.iterations ?? 0}\n` +
|
|
668
|
+
` Output: ${parsed.result ?? "(no output yet)"}`);
|
|
669
|
+
}
|
|
670
|
+
catch (err) {
|
|
671
|
+
statusLines.push(` - Agent ${agent.orchId}\n` +
|
|
672
|
+
` Task: "${agent.task.slice(0, 120)}"\n` +
|
|
673
|
+
` Status: unknown (error: ${err.message})`);
|
|
674
|
+
}
|
|
675
|
+
}
|
|
676
|
+
queueFollowup(runtime, `[SYSTEM: Sub-agent status report (${state.subAgents.length} agents):\n${statusLines.join("\n")}]`);
|
|
677
|
+
return true;
|
|
678
|
+
}
|
|
679
|
+
case "list_sessions": {
|
|
680
|
+
ctx.traceInfo(`[orch] list_sessions`);
|
|
681
|
+
const rawSessions = yield runtime.manager.listSessions({
|
|
682
|
+
includeSystem: result.includeSystem,
|
|
683
|
+
ownerQuery: result.ownerQuery,
|
|
684
|
+
ownerKind: result.ownerKind,
|
|
685
|
+
});
|
|
686
|
+
const sessions = JSON.parse(rawSessions);
|
|
687
|
+
if (!Array.isArray(sessions) || sessions.length === 0) {
|
|
688
|
+
queueFollowup(runtime, "[SYSTEM: Active sessions (0). No sessions matched the requested filters.]");
|
|
689
|
+
return true;
|
|
690
|
+
}
|
|
691
|
+
const lines = sessions.map((s) => ` - ${s.sessionId}${s.sessionId === runtime.input.sessionId ? " (this session)" : ""}\n` +
|
|
692
|
+
` Title: ${s.title ?? "(untitled)"}\n` +
|
|
693
|
+
` Owner: ${s.ownerKind === "system"
|
|
694
|
+
? "system"
|
|
695
|
+
: s.ownerKind === "unowned"
|
|
696
|
+
? "unowned"
|
|
697
|
+
: (s.owner?.displayName || s.owner?.email || [s.owner?.provider, s.owner?.subject].filter(Boolean).join(":") || "user")}\n` +
|
|
698
|
+
` Status: ${s.status}, Iterations: ${s.iterations ?? 0}\n` +
|
|
699
|
+
` Parent: ${s.parentSessionId ?? "none"}`);
|
|
700
|
+
queueFollowup(runtime, `[SYSTEM: Active sessions (${sessions.length}):\n${lines.join("\n")}]`);
|
|
701
|
+
return true;
|
|
702
|
+
}
|
|
703
|
+
case "wait_for_agents": {
|
|
704
|
+
let targetIds = result.agentIds;
|
|
705
|
+
if (!targetIds || targetIds.length === 0) {
|
|
706
|
+
const runningAgentIds = state.subAgents.filter(a => a.status === "running").map(a => a.orchId);
|
|
707
|
+
targetIds = runningAgentIds.length > 0
|
|
708
|
+
? runningAgentIds
|
|
709
|
+
: state.subAgents.map(a => a.orchId);
|
|
710
|
+
}
|
|
711
|
+
if (targetIds.length === 0) {
|
|
712
|
+
ctx.traceInfo(`[orch] wait_for_agents: no running agents to wait for`);
|
|
713
|
+
queueFollowup(runtime, `[SYSTEM: No running sub-agents to wait for. All agents have already completed.]`);
|
|
714
|
+
return true;
|
|
715
|
+
}
|
|
716
|
+
const stillRunning = targetIds.filter((id) => {
|
|
717
|
+
const agent = state.subAgents.find(a => a.orchId === id);
|
|
718
|
+
return agent && !isSubAgentTerminalStatus(agent.status);
|
|
719
|
+
});
|
|
720
|
+
if (stillRunning.length === 0) {
|
|
721
|
+
queueFollowup(runtime, buildWaitForAgentsFollowup(state.subAgents, targetIds));
|
|
722
|
+
return true;
|
|
723
|
+
}
|
|
724
|
+
ctx.traceInfo(`[orch] wait_for_agents: waiting for ${targetIds.length} agents`);
|
|
725
|
+
state.waitingForAgentIds = targetIds;
|
|
726
|
+
const agentPollNow = yield ctx.utcNow();
|
|
727
|
+
// The turn is over and the session is parked (interruptible — an
|
|
728
|
+
// interactive prompt preempts the poll), so surface "waiting" with
|
|
729
|
+
// a reason like every other durable wait. Publishing "running"
|
|
730
|
+
// here made the UI spin "Working…" for the whole agent wait.
|
|
731
|
+
publishStatus(runtime, "waiting", {
|
|
732
|
+
waitReason: `waiting for ${targetIds.length} agent(s)`,
|
|
733
|
+
waitStartedAt: agentPollNow,
|
|
734
|
+
});
|
|
735
|
+
state.activeTimer = {
|
|
736
|
+
deadlineMs: agentPollNow + 30_000,
|
|
737
|
+
originalDurationMs: 30_000,
|
|
738
|
+
reason: `waiting for ${targetIds.length} agent(s)`,
|
|
739
|
+
type: "agent-poll",
|
|
740
|
+
agentIds: targetIds,
|
|
741
|
+
};
|
|
742
|
+
return true;
|
|
743
|
+
}
|
|
744
|
+
case "complete_agent": {
|
|
745
|
+
const targetOrchId = result.agentId;
|
|
746
|
+
const agentEntry = state.subAgents.find(a => a.orchId === targetOrchId);
|
|
747
|
+
if (!agentEntry) {
|
|
748
|
+
ctx.traceInfo(`[orch] complete_agent: unknown agent ${targetOrchId}`);
|
|
749
|
+
queueFollowup(runtime, `[SYSTEM: complete_agent failed — agent "${targetOrchId}" not found. ` +
|
|
750
|
+
`Known agents: ${state.subAgents.map(a => a.orchId).join(", ") || "none"}]`);
|
|
751
|
+
return true;
|
|
752
|
+
}
|
|
753
|
+
ctx.traceInfo(`[orch] complete_agent: sending /done to ${agentEntry.sessionId}`);
|
|
754
|
+
try {
|
|
755
|
+
const cmdId = `done-${state.iteration}`;
|
|
756
|
+
yield runtime.manager.sendCommandToSession(agentEntry.sessionId, { type: "cmd", cmd: "done", id: cmdId, args: { reason: "Completed by parent" } });
|
|
757
|
+
}
|
|
758
|
+
catch (err) {
|
|
759
|
+
ctx.traceInfo(`[orch] complete_agent failed: ${err.message}`);
|
|
760
|
+
queueFollowup(runtime, `[SYSTEM: complete_agent failed: ${err.message}]`);
|
|
761
|
+
return true;
|
|
762
|
+
}
|
|
763
|
+
queueFollowup(runtime, `[SYSTEM: Graceful completion requested for sub-agent ${targetOrchId}. ` +
|
|
764
|
+
`Use check_agents or wait_for_agents to observe final completion.]`);
|
|
765
|
+
return true;
|
|
766
|
+
}
|
|
767
|
+
case "cancel_agent": {
|
|
768
|
+
const targetOrchId = result.agentId;
|
|
769
|
+
const agentEntry = state.subAgents.find(a => a.orchId === targetOrchId);
|
|
770
|
+
if (!agentEntry) {
|
|
771
|
+
ctx.traceInfo(`[orch] cancel_agent: unknown agent ${targetOrchId}`);
|
|
772
|
+
queueFollowup(runtime, `[SYSTEM: cancel_agent failed — agent "${targetOrchId}" not found. ` +
|
|
773
|
+
`Known agents: ${state.subAgents.map(a => a.orchId).join(", ") || "none"}]`);
|
|
774
|
+
return true;
|
|
775
|
+
}
|
|
776
|
+
const cancelReason = result.reason ?? "Cancelled by parent";
|
|
777
|
+
ctx.traceInfo(`[orch] cancel_agent: sending cancel to ${agentEntry.sessionId} reason="${cancelReason}"`);
|
|
778
|
+
try {
|
|
779
|
+
const cmdId = `cancel-${state.iteration}-${agentEntry.sessionId.slice(0, 8)}`;
|
|
780
|
+
yield runtime.manager.sendCommandToSession(agentEntry.sessionId, { type: "cmd", cmd: "cancel", id: cmdId, args: { reason: cancelReason } });
|
|
781
|
+
}
|
|
782
|
+
catch (err) {
|
|
783
|
+
ctx.traceInfo(`[orch] cancel_agent failed: ${err.message}`);
|
|
784
|
+
queueFollowup(runtime, `[SYSTEM: cancel_agent failed: ${err.message}]`);
|
|
785
|
+
return true;
|
|
786
|
+
}
|
|
787
|
+
queueFollowup(runtime, `[SYSTEM: Graceful cancellation requested for sub-agent ${targetOrchId}. ` +
|
|
788
|
+
`Use check_agents or wait_for_agents to observe final termination.${result.reason ? ` Reason: ${result.reason}` : ""}]`);
|
|
789
|
+
return true;
|
|
790
|
+
}
|
|
791
|
+
case "delete_agent": {
|
|
792
|
+
const targetOrchId = result.agentId;
|
|
793
|
+
const agentEntry = state.subAgents.find(a => a.orchId === targetOrchId);
|
|
794
|
+
if (!agentEntry) {
|
|
795
|
+
ctx.traceInfo(`[orch] delete_agent: unknown agent ${targetOrchId}`);
|
|
796
|
+
queueFollowup(runtime, `[SYSTEM: delete_agent failed — agent "${targetOrchId}" not found. ` +
|
|
797
|
+
`Known agents: ${state.subAgents.map(a => a.orchId).join(", ") || "none"}]`);
|
|
798
|
+
return true;
|
|
799
|
+
}
|
|
800
|
+
const deleteReason = result.reason ?? "Deleted by parent";
|
|
801
|
+
ctx.traceInfo(`[orch] delete_agent: deleting ${agentEntry.sessionId} reason="${deleteReason}"`);
|
|
802
|
+
try {
|
|
803
|
+
if (isSubAgentTerminalStatus(agentEntry.status)) {
|
|
804
|
+
yield runtime.manager.deleteSession(agentEntry.sessionId, deleteReason);
|
|
805
|
+
state.subAgents = state.subAgents.filter((agent) => agent.orchId !== targetOrchId);
|
|
806
|
+
queueFollowup(runtime, `[SYSTEM: Sub-agent ${targetOrchId} has been deleted.${result.reason ? ` Reason: ${result.reason}` : ""}]`);
|
|
807
|
+
return true;
|
|
808
|
+
}
|
|
809
|
+
const cmdId = `delete-${state.iteration}-${agentEntry.sessionId.slice(0, 8)}`;
|
|
810
|
+
yield runtime.manager.sendCommandToSession(agentEntry.sessionId, { type: "cmd", cmd: "delete", id: cmdId, args: { reason: deleteReason } });
|
|
811
|
+
}
|
|
812
|
+
catch (err) {
|
|
813
|
+
ctx.traceInfo(`[orch] delete_agent failed: ${err.message}`);
|
|
814
|
+
queueFollowup(runtime, `[SYSTEM: delete_agent failed: ${err.message}]`);
|
|
815
|
+
return true;
|
|
816
|
+
}
|
|
817
|
+
queueFollowup(runtime, `[SYSTEM: Graceful deletion requested for sub-agent ${targetOrchId}. ` +
|
|
818
|
+
`It will cancel its descendants first and then delete itself.${result.reason ? ` Reason: ${result.reason}` : ""}]`);
|
|
819
|
+
return true;
|
|
820
|
+
}
|
|
821
|
+
default:
|
|
822
|
+
return false;
|
|
823
|
+
}
|
|
824
|
+
}
|
|
825
|
+
//# sourceMappingURL=agents.js.map
|