pilotswarm-sdk 0.1.3
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/dist/agent-loader.d.ts +61 -0
- package/dist/agent-loader.d.ts.map +1 -0
- package/dist/agent-loader.js +212 -0
- package/dist/agent-loader.js.map +1 -0
- package/dist/artifact-tools.d.ts +31 -0
- package/dist/artifact-tools.d.ts.map +1 -0
- package/dist/artifact-tools.js +190 -0
- package/dist/artifact-tools.js.map +1 -0
- package/dist/blob-store.d.ts +73 -0
- package/dist/blob-store.d.ts.map +1 -0
- package/dist/blob-store.js +220 -0
- package/dist/blob-store.js.map +1 -0
- package/dist/client.d.ts +159 -0
- package/dist/client.d.ts.map +1 -0
- package/dist/client.js +676 -0
- package/dist/client.js.map +1 -0
- package/dist/cms.d.ts +129 -0
- package/dist/cms.d.ts.map +1 -0
- package/dist/cms.js +313 -0
- package/dist/cms.js.map +1 -0
- package/dist/index.d.ts +44 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +42 -0
- package/dist/index.js.map +1 -0
- package/dist/managed-session.d.ts +70 -0
- package/dist/managed-session.d.ts.map +1 -0
- package/dist/managed-session.js +717 -0
- package/dist/managed-session.js.map +1 -0
- package/dist/management-client.d.ts +171 -0
- package/dist/management-client.d.ts.map +1 -0
- package/dist/management-client.js +401 -0
- package/dist/management-client.js.map +1 -0
- package/dist/mcp-loader.d.ts +50 -0
- package/dist/mcp-loader.d.ts.map +1 -0
- package/dist/mcp-loader.js +83 -0
- package/dist/mcp-loader.js.map +1 -0
- package/dist/model-providers.d.ts +143 -0
- package/dist/model-providers.d.ts.map +1 -0
- package/dist/model-providers.js +228 -0
- package/dist/model-providers.js.map +1 -0
- package/dist/orchestration-registry.d.ts +7 -0
- package/dist/orchestration-registry.d.ts.map +1 -0
- package/dist/orchestration-registry.js +49 -0
- package/dist/orchestration-registry.js.map +1 -0
- package/dist/orchestration.d.ts +36 -0
- package/dist/orchestration.d.ts.map +1 -0
- package/dist/orchestration.js +1357 -0
- package/dist/orchestration.js.map +1 -0
- package/dist/orchestration_1_0_0.d.ts +20 -0
- package/dist/orchestration_1_0_0.d.ts.map +1 -0
- package/dist/orchestration_1_0_0.js +497 -0
- package/dist/orchestration_1_0_0.js.map +1 -0
- package/dist/orchestration_1_0_1.d.ts +19 -0
- package/dist/orchestration_1_0_1.d.ts.map +1 -0
- package/dist/orchestration_1_0_1.js +546 -0
- package/dist/orchestration_1_0_1.js.map +1 -0
- package/dist/orchestration_1_0_10.d.ts +36 -0
- package/dist/orchestration_1_0_10.d.ts.map +1 -0
- package/dist/orchestration_1_0_10.js +1253 -0
- package/dist/orchestration_1_0_10.js.map +1 -0
- package/dist/orchestration_1_0_11.d.ts +36 -0
- package/dist/orchestration_1_0_11.d.ts.map +1 -0
- package/dist/orchestration_1_0_11.js +1255 -0
- package/dist/orchestration_1_0_11.js.map +1 -0
- package/dist/orchestration_1_0_12.d.ts +36 -0
- package/dist/orchestration_1_0_12.d.ts.map +1 -0
- package/dist/orchestration_1_0_12.js +1250 -0
- package/dist/orchestration_1_0_12.js.map +1 -0
- package/dist/orchestration_1_0_13.d.ts +36 -0
- package/dist/orchestration_1_0_13.d.ts.map +1 -0
- package/dist/orchestration_1_0_13.js +1260 -0
- package/dist/orchestration_1_0_13.js.map +1 -0
- package/dist/orchestration_1_0_14.d.ts +36 -0
- package/dist/orchestration_1_0_14.d.ts.map +1 -0
- package/dist/orchestration_1_0_14.js +1258 -0
- package/dist/orchestration_1_0_14.js.map +1 -0
- package/dist/orchestration_1_0_15.d.ts +36 -0
- package/dist/orchestration_1_0_15.d.ts.map +1 -0
- package/dist/orchestration_1_0_15.js +1266 -0
- package/dist/orchestration_1_0_15.js.map +1 -0
- package/dist/orchestration_1_0_16.d.ts +36 -0
- package/dist/orchestration_1_0_16.d.ts.map +1 -0
- package/dist/orchestration_1_0_16.js +1275 -0
- package/dist/orchestration_1_0_16.js.map +1 -0
- package/dist/orchestration_1_0_17.d.ts +36 -0
- package/dist/orchestration_1_0_17.d.ts.map +1 -0
- package/dist/orchestration_1_0_17.js +1314 -0
- package/dist/orchestration_1_0_17.js.map +1 -0
- package/dist/orchestration_1_0_18.d.ts +36 -0
- package/dist/orchestration_1_0_18.d.ts.map +1 -0
- package/dist/orchestration_1_0_18.js +1328 -0
- package/dist/orchestration_1_0_18.js.map +1 -0
- package/dist/orchestration_1_0_19.d.ts +36 -0
- package/dist/orchestration_1_0_19.d.ts.map +1 -0
- package/dist/orchestration_1_0_19.js +1324 -0
- package/dist/orchestration_1_0_19.js.map +1 -0
- package/dist/orchestration_1_0_2.d.ts +19 -0
- package/dist/orchestration_1_0_2.d.ts.map +1 -0
- package/dist/orchestration_1_0_2.js +749 -0
- package/dist/orchestration_1_0_2.js.map +1 -0
- package/dist/orchestration_1_0_20.d.ts +36 -0
- package/dist/orchestration_1_0_20.d.ts.map +1 -0
- package/dist/orchestration_1_0_20.js +1347 -0
- package/dist/orchestration_1_0_20.js.map +1 -0
- package/dist/orchestration_1_0_3.d.ts +19 -0
- package/dist/orchestration_1_0_3.d.ts.map +1 -0
- package/dist/orchestration_1_0_3.js +826 -0
- package/dist/orchestration_1_0_3.js.map +1 -0
- package/dist/orchestration_1_0_4.d.ts +19 -0
- package/dist/orchestration_1_0_4.d.ts.map +1 -0
- package/dist/orchestration_1_0_4.js +1020 -0
- package/dist/orchestration_1_0_4.js.map +1 -0
- package/dist/orchestration_1_0_5.d.ts +19 -0
- package/dist/orchestration_1_0_5.d.ts.map +1 -0
- package/dist/orchestration_1_0_5.js +1027 -0
- package/dist/orchestration_1_0_5.js.map +1 -0
- package/dist/orchestration_1_0_6.d.ts +19 -0
- package/dist/orchestration_1_0_6.d.ts.map +1 -0
- package/dist/orchestration_1_0_6.js +1034 -0
- package/dist/orchestration_1_0_6.js.map +1 -0
- package/dist/orchestration_1_0_7.d.ts +19 -0
- package/dist/orchestration_1_0_7.d.ts.map +1 -0
- package/dist/orchestration_1_0_7.js +1085 -0
- package/dist/orchestration_1_0_7.js.map +1 -0
- package/dist/orchestration_1_0_8.d.ts +36 -0
- package/dist/orchestration_1_0_8.d.ts.map +1 -0
- package/dist/orchestration_1_0_8.js +1106 -0
- package/dist/orchestration_1_0_8.js.map +1 -0
- package/dist/orchestration_1_0_9.d.ts +36 -0
- package/dist/orchestration_1_0_9.d.ts.map +1 -0
- package/dist/orchestration_1_0_9.js +1207 -0
- package/dist/orchestration_1_0_9.js.map +1 -0
- package/dist/prompt-layering.d.ts +16 -0
- package/dist/prompt-layering.d.ts.map +1 -0
- package/dist/prompt-layering.js +60 -0
- package/dist/prompt-layering.js.map +1 -0
- package/dist/resourcemgr-tools.d.ts +27 -0
- package/dist/resourcemgr-tools.d.ts.map +1 -0
- package/dist/resourcemgr-tools.js +638 -0
- package/dist/resourcemgr-tools.js.map +1 -0
- package/dist/session-dumper.d.ts +26 -0
- package/dist/session-dumper.d.ts.map +1 -0
- package/dist/session-dumper.js +272 -0
- package/dist/session-dumper.js.map +1 -0
- package/dist/session-manager.d.ts +152 -0
- package/dist/session-manager.d.ts.map +1 -0
- package/dist/session-manager.js +493 -0
- package/dist/session-manager.js.map +1 -0
- package/dist/session-proxy.d.ts +68 -0
- package/dist/session-proxy.d.ts.map +1 -0
- package/dist/session-proxy.js +665 -0
- package/dist/session-proxy.js.map +1 -0
- package/dist/session-store.d.ts +35 -0
- package/dist/session-store.d.ts.map +1 -0
- package/dist/session-store.js +88 -0
- package/dist/session-store.js.map +1 -0
- package/dist/skills.d.ts +31 -0
- package/dist/skills.d.ts.map +1 -0
- package/dist/skills.js +93 -0
- package/dist/skills.js.map +1 -0
- package/dist/sweeper-tools.d.ts +28 -0
- package/dist/sweeper-tools.d.ts.map +1 -0
- package/dist/sweeper-tools.js +332 -0
- package/dist/sweeper-tools.js.map +1 -0
- package/dist/types.d.ts +498 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +9 -0
- package/dist/types.js.map +1 -0
- package/dist/worker.d.ts +128 -0
- package/dist/worker.d.ts.map +1 -0
- package/dist/worker.js +562 -0
- package/dist/worker.js.map +1 -0
- package/package.json +74 -0
- package/plugins/mgmt/agents/pilotswarm.agent.md +59 -0
- package/plugins/mgmt/agents/resourcemgr.agent.md +111 -0
- package/plugins/mgmt/agents/sweeper.agent.md +67 -0
- package/plugins/mgmt/skills/resourcemgr/SKILL.md +41 -0
- package/plugins/mgmt/skills/resourcemgr/tools.json +1 -0
- package/plugins/mgmt/skills/sweeper/SKILL.md +44 -0
- package/plugins/mgmt/skills/sweeper/tools.json +1 -0
- package/plugins/system/agents/default.agent.md +58 -0
- package/plugins/system/skills/durable-timers/SKILL.md +39 -0
- package/plugins/system/skills/sub-agents/SKILL.md +75 -0
|
@@ -0,0 +1,749 @@
|
|
|
1
|
+
import { createSessionProxy, createSessionManagerProxy as _createSessionManagerProxy } from "./session-proxy.js";
|
|
2
|
+
// Cast to `any` so this frozen orchestration doesn't break when
|
|
3
|
+
// the live proxy shape evolves. The old activity names (getChildStatus,
|
|
4
|
+
// messageChild, registerChildSession) are still registered on the worker
|
|
5
|
+
// for backward compatibility during the transition period.
|
|
6
|
+
const createSessionManagerProxy = _createSessionManagerProxy;
|
|
7
|
+
/**
|
|
8
|
+
* Set custom status as a JSON blob of session state.
|
|
9
|
+
* Clients read this via waitForStatusChange() or getStatus().
|
|
10
|
+
* @internal
|
|
11
|
+
*/
|
|
12
|
+
function setStatus(ctx, status, extra) {
|
|
13
|
+
ctx.setCustomStatus(JSON.stringify({ status, ...extra }));
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Long-lived durable session orchestration.
|
|
17
|
+
*
|
|
18
|
+
* One orchestration per copilot session. Uses:
|
|
19
|
+
* - SessionProxy for session-scoped operations (runTurn, dehydrate, hydrate, destroy)
|
|
20
|
+
* - SessionManagerProxy for global operations (listModels)
|
|
21
|
+
* - A single FIFO event queue ("messages") for all client→orchestration communication
|
|
22
|
+
*
|
|
23
|
+
* Main loop:
|
|
24
|
+
* 1. Dequeue message from "messages" queue
|
|
25
|
+
* 2. session.hydrate() if needed
|
|
26
|
+
* 3. session.runTurn(prompt) — returns TurnResult
|
|
27
|
+
* 4. Handle result: completed → idle wait, wait → timer, input → wait for answer
|
|
28
|
+
*
|
|
29
|
+
* @internal
|
|
30
|
+
*/
|
|
31
|
+
export function* durableSessionOrchestration_1_0_2(ctx, input) {
|
|
32
|
+
const dehydrateThreshold = input.dehydrateThreshold ?? 30;
|
|
33
|
+
const idleTimeout = input.idleTimeout ?? 30;
|
|
34
|
+
const inputGracePeriod = input.inputGracePeriod ?? 30;
|
|
35
|
+
const checkpointInterval = input.checkpointInterval ?? -1; // seconds, -1 = disabled
|
|
36
|
+
const rehydrationMessage = input.rehydrationMessage;
|
|
37
|
+
const blobEnabled = input.blobEnabled ?? false;
|
|
38
|
+
let needsHydration = input.needsHydration ?? false;
|
|
39
|
+
let affinityKey = input.affinityKey ?? input.sessionId;
|
|
40
|
+
let iteration = input.iteration ?? 0;
|
|
41
|
+
let config = { ...input.config };
|
|
42
|
+
let retryCount = input.retryCount ?? 0;
|
|
43
|
+
let taskContext = input.taskContext;
|
|
44
|
+
const baseSystemMessage = input.baseSystemMessage ?? config.systemMessage;
|
|
45
|
+
const MAX_RETRIES = 3;
|
|
46
|
+
const MAX_SUB_AGENTS = 5;
|
|
47
|
+
// ─── Sub-agent tracking ──────────────────────────────────
|
|
48
|
+
let subAgents = input.subAgents ? [...input.subAgents] : [];
|
|
49
|
+
const parentOrchId = input.parentOrchId;
|
|
50
|
+
// If we have a captured task context, inject it into the system message
|
|
51
|
+
// so it survives LLM conversation truncation (BasicTruncator never drops system messages).
|
|
52
|
+
if (taskContext) {
|
|
53
|
+
const base = typeof baseSystemMessage === 'string'
|
|
54
|
+
? baseSystemMessage ?? ''
|
|
55
|
+
: baseSystemMessage?.content ?? '';
|
|
56
|
+
config.systemMessage = base + (base ? '\n\n' : '') +
|
|
57
|
+
'[RECURRING TASK]\n' +
|
|
58
|
+
'Original user request (always remember, even if conversation history is truncated):\n"' +
|
|
59
|
+
taskContext + '"';
|
|
60
|
+
}
|
|
61
|
+
// ─── Title summarization timer ───────────────────────────
|
|
62
|
+
// First summarize at iteration 0 + 60s, then every 300s.
|
|
63
|
+
// We track the target timestamp (epoch ms) across continueAsNew.
|
|
64
|
+
// 0 means "schedule on first turn completion".
|
|
65
|
+
let nextSummarizeAt = input.nextSummarizeAt ?? 0;
|
|
66
|
+
// ─── Create proxies ──────────────────────────────────────
|
|
67
|
+
const manager = createSessionManagerProxy(ctx);
|
|
68
|
+
let session = createSessionProxy(ctx, input.sessionId, affinityKey, config);
|
|
69
|
+
// ─── Helper: wrap prompt with resume context after dehydration ──
|
|
70
|
+
function wrapWithResumeContext(userPrompt, extra) {
|
|
71
|
+
const base = rehydrationMessage ??
|
|
72
|
+
`The session was dehydrated and has been rehydrated on a new worker. ` +
|
|
73
|
+
`The LLM conversation history is preserved, but you should acknowledge the context switch. ` +
|
|
74
|
+
`After responding to the user's message below, resume exactly what you were doing before. ` +
|
|
75
|
+
`If you were in the middle of a recurring task, continue it.`;
|
|
76
|
+
const parts = [`[SYSTEM: ${base}`];
|
|
77
|
+
if (extra)
|
|
78
|
+
parts.push(extra);
|
|
79
|
+
parts.push(`]`);
|
|
80
|
+
parts.push(``);
|
|
81
|
+
parts.push(userPrompt);
|
|
82
|
+
return parts.join('\n');
|
|
83
|
+
}
|
|
84
|
+
// ─── Shared continueAsNew input builder ──────────────────
|
|
85
|
+
function continueInput(overrides = {}) {
|
|
86
|
+
return {
|
|
87
|
+
sessionId: input.sessionId,
|
|
88
|
+
config,
|
|
89
|
+
iteration,
|
|
90
|
+
affinityKey,
|
|
91
|
+
needsHydration,
|
|
92
|
+
blobEnabled,
|
|
93
|
+
dehydrateThreshold,
|
|
94
|
+
idleTimeout,
|
|
95
|
+
inputGracePeriod,
|
|
96
|
+
checkpointInterval,
|
|
97
|
+
rehydrationMessage,
|
|
98
|
+
nextSummarizeAt,
|
|
99
|
+
taskContext,
|
|
100
|
+
baseSystemMessage,
|
|
101
|
+
subAgents,
|
|
102
|
+
parentOrchId,
|
|
103
|
+
retryCount: 0, // reset by default; overrides can set it
|
|
104
|
+
...overrides,
|
|
105
|
+
};
|
|
106
|
+
}
|
|
107
|
+
// ─── Helper: dehydrate + reset affinity ──────────────────
|
|
108
|
+
function* dehydrateAndReset(reason) {
|
|
109
|
+
ctx.traceInfo(`[orch] dehydrating session (reason=${reason})`);
|
|
110
|
+
yield session.dehydrate(reason);
|
|
111
|
+
needsHydration = true;
|
|
112
|
+
affinityKey = yield ctx.newGuid();
|
|
113
|
+
session = createSessionProxy(ctx, input.sessionId, affinityKey, config);
|
|
114
|
+
}
|
|
115
|
+
// ─── Helper: checkpoint without releasing pin ────────────
|
|
116
|
+
function* maybeCheckpoint() {
|
|
117
|
+
if (!blobEnabled || checkpointInterval < 0)
|
|
118
|
+
return;
|
|
119
|
+
try {
|
|
120
|
+
ctx.traceInfo(`[orch] checkpoint (iteration=${iteration})`);
|
|
121
|
+
yield session.checkpoint();
|
|
122
|
+
}
|
|
123
|
+
catch (err) {
|
|
124
|
+
ctx.traceInfo(`[orch] checkpoint failed: ${err.message ?? err}`);
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
// ─── Helper: summarize session title if due ──────────────
|
|
128
|
+
const FIRST_SUMMARIZE_DELAY = 60_000; // 1 minute
|
|
129
|
+
const REPEAT_SUMMARIZE_DELAY = 300_000; // 5 minutes
|
|
130
|
+
function* maybeSummarize() {
|
|
131
|
+
const now = yield ctx.utcNow();
|
|
132
|
+
// Schedule first summarize 60s after session start
|
|
133
|
+
if (nextSummarizeAt === 0) {
|
|
134
|
+
nextSummarizeAt = now + FIRST_SUMMARIZE_DELAY;
|
|
135
|
+
return;
|
|
136
|
+
}
|
|
137
|
+
if (now < nextSummarizeAt)
|
|
138
|
+
return;
|
|
139
|
+
// Time to summarize — fire and forget (best effort)
|
|
140
|
+
try {
|
|
141
|
+
ctx.traceInfo(`[orch] summarizing session title`);
|
|
142
|
+
yield manager.summarizeSession(input.sessionId);
|
|
143
|
+
}
|
|
144
|
+
catch (err) {
|
|
145
|
+
ctx.traceInfo(`[orch] summarize failed: ${err.message}`);
|
|
146
|
+
}
|
|
147
|
+
nextSummarizeAt = now + REPEAT_SUMMARIZE_DELAY;
|
|
148
|
+
}
|
|
149
|
+
// ─── Prompt carried from continueAsNew ───────────────────
|
|
150
|
+
let pendingPrompt = input.prompt;
|
|
151
|
+
/** Set by the "completed" handler so the dequeue loop doesn't overwrite it. */
|
|
152
|
+
let lastTurnResult = undefined;
|
|
153
|
+
ctx.traceInfo(`[orch] start: iter=${iteration} pending=${pendingPrompt ? `"${pendingPrompt.slice(0, 40)}"` : 'NONE'} hydrate=${needsHydration} blob=${blobEnabled}`);
|
|
154
|
+
// ─── MAIN LOOP ──────────────────────────────────────────
|
|
155
|
+
while (true) {
|
|
156
|
+
// ① GET NEXT PROMPT
|
|
157
|
+
let prompt = "";
|
|
158
|
+
if (pendingPrompt) {
|
|
159
|
+
prompt = pendingPrompt;
|
|
160
|
+
pendingPrompt = undefined;
|
|
161
|
+
}
|
|
162
|
+
else {
|
|
163
|
+
// If we have a completed turnResult, include it in the idle status
|
|
164
|
+
// so clients can read it via waitForStatusChange. Without this,
|
|
165
|
+
// a bare setStatus("idle") between yields would overwrite it.
|
|
166
|
+
if (lastTurnResult) {
|
|
167
|
+
setStatus(ctx, "idle", { iteration, turnResult: lastTurnResult });
|
|
168
|
+
}
|
|
169
|
+
else {
|
|
170
|
+
setStatus(ctx, "idle", { iteration });
|
|
171
|
+
}
|
|
172
|
+
let gotPrompt = false;
|
|
173
|
+
while (!gotPrompt) {
|
|
174
|
+
const msg = yield ctx.dequeueEvent("messages");
|
|
175
|
+
const msgData = typeof msg === "string" ? JSON.parse(msg) : msg;
|
|
176
|
+
// ── Command dispatch ─────────────────────────
|
|
177
|
+
if (msgData.type === "cmd") {
|
|
178
|
+
const cmdMsg = msgData;
|
|
179
|
+
ctx.traceInfo(`[orch-cmd] ${cmdMsg.cmd} id=${cmdMsg.id}`);
|
|
180
|
+
switch (cmdMsg.cmd) {
|
|
181
|
+
case "set_model": {
|
|
182
|
+
const newModel = String(cmdMsg.args?.model || "");
|
|
183
|
+
const oldModel = config.model || "(default)";
|
|
184
|
+
config = { ...config, model: newModel };
|
|
185
|
+
const resp = {
|
|
186
|
+
id: cmdMsg.id,
|
|
187
|
+
cmd: cmdMsg.cmd,
|
|
188
|
+
result: { ok: true, oldModel, newModel },
|
|
189
|
+
};
|
|
190
|
+
setStatus(ctx, "idle", { iteration, cmdResponse: resp });
|
|
191
|
+
yield ctx.continueAsNew(continueInput());
|
|
192
|
+
return "";
|
|
193
|
+
}
|
|
194
|
+
case "list_models": {
|
|
195
|
+
setStatus(ctx, "idle", { iteration, cmdProcessing: cmdMsg.id });
|
|
196
|
+
let models;
|
|
197
|
+
try {
|
|
198
|
+
const raw = yield manager.listModels();
|
|
199
|
+
models = typeof raw === "string" ? JSON.parse(raw) : raw;
|
|
200
|
+
}
|
|
201
|
+
catch (err) {
|
|
202
|
+
const resp = {
|
|
203
|
+
id: cmdMsg.id,
|
|
204
|
+
cmd: cmdMsg.cmd,
|
|
205
|
+
error: err.message || String(err),
|
|
206
|
+
};
|
|
207
|
+
setStatus(ctx, "idle", { iteration, cmdResponse: resp });
|
|
208
|
+
continue;
|
|
209
|
+
}
|
|
210
|
+
const resp = {
|
|
211
|
+
id: cmdMsg.id,
|
|
212
|
+
cmd: cmdMsg.cmd,
|
|
213
|
+
result: { models, currentModel: config.model },
|
|
214
|
+
};
|
|
215
|
+
setStatus(ctx, "idle", { iteration, cmdResponse: resp });
|
|
216
|
+
continue;
|
|
217
|
+
}
|
|
218
|
+
case "get_info": {
|
|
219
|
+
const resp = {
|
|
220
|
+
id: cmdMsg.id,
|
|
221
|
+
cmd: cmdMsg.cmd,
|
|
222
|
+
result: {
|
|
223
|
+
model: config.model || "(default)",
|
|
224
|
+
iteration,
|
|
225
|
+
sessionId: input.sessionId,
|
|
226
|
+
affinityKey: affinityKey?.slice(0, 8),
|
|
227
|
+
needsHydration,
|
|
228
|
+
blobEnabled,
|
|
229
|
+
},
|
|
230
|
+
};
|
|
231
|
+
setStatus(ctx, "idle", { iteration, cmdResponse: resp });
|
|
232
|
+
continue;
|
|
233
|
+
}
|
|
234
|
+
default: {
|
|
235
|
+
const resp = {
|
|
236
|
+
id: cmdMsg.id,
|
|
237
|
+
cmd: cmdMsg.cmd,
|
|
238
|
+
error: `Unknown command: ${cmdMsg.cmd}`,
|
|
239
|
+
};
|
|
240
|
+
setStatus(ctx, "idle", { iteration, cmdResponse: resp });
|
|
241
|
+
continue;
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
prompt = msgData.prompt;
|
|
246
|
+
gotPrompt = true;
|
|
247
|
+
lastTurnResult = undefined; // Clear after new prompt arrives
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
// If the session needs hydration, the LLM lost in-memory context.
|
|
251
|
+
// Wrap the user's prompt with resume instructions so the LLM picks up where it left off.
|
|
252
|
+
if (needsHydration && blobEnabled && prompt) {
|
|
253
|
+
prompt = wrapWithResumeContext(prompt);
|
|
254
|
+
}
|
|
255
|
+
ctx.traceInfo(`[turn ${iteration}] session=${input.sessionId} prompt="${prompt.slice(0, 80)}"`);
|
|
256
|
+
// ② HYDRATE if session was dehydrated (with retry)
|
|
257
|
+
if (needsHydration && blobEnabled) {
|
|
258
|
+
let hydrateAttempts = 0;
|
|
259
|
+
while (true) {
|
|
260
|
+
try {
|
|
261
|
+
affinityKey = yield ctx.newGuid();
|
|
262
|
+
session = createSessionProxy(ctx, input.sessionId, affinityKey, config);
|
|
263
|
+
yield session.hydrate();
|
|
264
|
+
needsHydration = false;
|
|
265
|
+
break;
|
|
266
|
+
}
|
|
267
|
+
catch (hydrateErr) {
|
|
268
|
+
hydrateAttempts++;
|
|
269
|
+
const hMsg = hydrateErr.message || String(hydrateErr);
|
|
270
|
+
ctx.traceInfo(`[orch] hydrate FAILED (attempt ${hydrateAttempts}/${MAX_RETRIES}): ${hMsg}`);
|
|
271
|
+
if (hydrateAttempts >= MAX_RETRIES) {
|
|
272
|
+
setStatus(ctx, "error", {
|
|
273
|
+
iteration,
|
|
274
|
+
error: `Hydrate failed after ${MAX_RETRIES} attempts: ${hMsg}`,
|
|
275
|
+
retriesExhausted: true,
|
|
276
|
+
});
|
|
277
|
+
// Can't proceed without hydration — wait for next user message to retry
|
|
278
|
+
break;
|
|
279
|
+
}
|
|
280
|
+
const hydrateDelay = 10 * Math.pow(2, hydrateAttempts - 1);
|
|
281
|
+
setStatus(ctx, "error", {
|
|
282
|
+
iteration,
|
|
283
|
+
error: `Hydrate failed: ${hMsg} (retry ${hydrateAttempts}/${MAX_RETRIES} in ${hydrateDelay}s)`,
|
|
284
|
+
});
|
|
285
|
+
yield ctx.scheduleTimer(hydrateDelay * 1000);
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
if (needsHydration)
|
|
289
|
+
continue; // hydrate exhausted retries — go back to dequeue
|
|
290
|
+
}
|
|
291
|
+
// ③ RUN TURN via SessionProxy (with retry on failure)
|
|
292
|
+
setStatus(ctx, "running", { iteration });
|
|
293
|
+
let turnResult;
|
|
294
|
+
try {
|
|
295
|
+
turnResult = yield session.runTurn(prompt);
|
|
296
|
+
}
|
|
297
|
+
catch (err) {
|
|
298
|
+
// Activity failed (e.g. Copilot timeout, network error).
|
|
299
|
+
const errorMsg = err.message || String(err);
|
|
300
|
+
retryCount++;
|
|
301
|
+
ctx.traceInfo(`[orch] runTurn FAILED (attempt ${retryCount}/${MAX_RETRIES}): ${errorMsg}`);
|
|
302
|
+
if (retryCount >= MAX_RETRIES) {
|
|
303
|
+
// Exhausted retries — park in error state but don't crash.
|
|
304
|
+
// The orchestration stays alive and will retry on the next user message.
|
|
305
|
+
ctx.traceInfo(`[orch] max retries exhausted, waiting for user input`);
|
|
306
|
+
setStatus(ctx, "error", {
|
|
307
|
+
iteration,
|
|
308
|
+
error: `Failed after ${MAX_RETRIES} attempts: ${errorMsg}`,
|
|
309
|
+
retriesExhausted: true,
|
|
310
|
+
});
|
|
311
|
+
// Reset retry count and wait for next user message
|
|
312
|
+
retryCount = 0;
|
|
313
|
+
continue;
|
|
314
|
+
}
|
|
315
|
+
setStatus(ctx, "error", {
|
|
316
|
+
iteration,
|
|
317
|
+
error: `${errorMsg} (retry ${retryCount}/${MAX_RETRIES} in 15s)`,
|
|
318
|
+
});
|
|
319
|
+
// Exponential backoff: 15s, 30s, 60s
|
|
320
|
+
const retryDelay = 15 * Math.pow(2, retryCount - 1);
|
|
321
|
+
ctx.traceInfo(`[orch] retrying in ${retryDelay}s`);
|
|
322
|
+
if (blobEnabled) {
|
|
323
|
+
yield* dehydrateAndReset("error");
|
|
324
|
+
}
|
|
325
|
+
yield ctx.scheduleTimer(retryDelay * 1000);
|
|
326
|
+
yield ctx.continueAsNew(continueInput({
|
|
327
|
+
prompt,
|
|
328
|
+
retryCount,
|
|
329
|
+
needsHydration: blobEnabled ? true : needsHydration,
|
|
330
|
+
}));
|
|
331
|
+
return "";
|
|
332
|
+
}
|
|
333
|
+
// Successful activity — reset retry counter
|
|
334
|
+
retryCount = 0;
|
|
335
|
+
const result = typeof turnResult === "string"
|
|
336
|
+
? JSON.parse(turnResult) : turnResult;
|
|
337
|
+
iteration++;
|
|
338
|
+
// Strip events from result before putting in customStatus (events go to CMS, not status)
|
|
339
|
+
const { events: _events, ...statusResult } = result;
|
|
340
|
+
// ── Summarize title if due ──────────────────────────
|
|
341
|
+
yield* maybeSummarize();
|
|
342
|
+
// ④ HANDLE RESULT
|
|
343
|
+
switch (result.type) {
|
|
344
|
+
case "completed":
|
|
345
|
+
ctx.traceInfo(`[response] ${result.content}`);
|
|
346
|
+
if (!blobEnabled || idleTimeout < 0) {
|
|
347
|
+
// Store the result so the dequeue-idle setStatus includes it
|
|
348
|
+
lastTurnResult = statusResult;
|
|
349
|
+
// Checkpoint while idle (no dehydration path)
|
|
350
|
+
yield* maybeCheckpoint();
|
|
351
|
+
continue;
|
|
352
|
+
}
|
|
353
|
+
// Race: next message vs idle timeout
|
|
354
|
+
{
|
|
355
|
+
setStatus(ctx, "idle", { iteration, turnResult: statusResult });
|
|
356
|
+
yield* maybeCheckpoint();
|
|
357
|
+
const nextMsg = ctx.dequeueEvent("messages");
|
|
358
|
+
const idleTimer = ctx.scheduleTimer(idleTimeout * 1000);
|
|
359
|
+
const raceResult = yield ctx.race(nextMsg, idleTimer);
|
|
360
|
+
if (raceResult.index === 0) {
|
|
361
|
+
ctx.traceInfo("[session] user responded within idle window");
|
|
362
|
+
const raceMsg = typeof raceResult.value === "string"
|
|
363
|
+
? JSON.parse(raceResult.value) : (raceResult.value ?? {});
|
|
364
|
+
if (raceMsg.prompt) {
|
|
365
|
+
yield ctx.continueAsNew(continueInput({ prompt: raceMsg.prompt }));
|
|
366
|
+
}
|
|
367
|
+
else {
|
|
368
|
+
yield ctx.continueAsNew(continueInput());
|
|
369
|
+
}
|
|
370
|
+
return "";
|
|
371
|
+
}
|
|
372
|
+
// Idle timeout → dehydrate. Next message will need resume context.
|
|
373
|
+
ctx.traceInfo("[session] idle timeout, dehydrating");
|
|
374
|
+
yield* dehydrateAndReset("idle");
|
|
375
|
+
// Don't continueAsNew with a prompt — wait for the next user message,
|
|
376
|
+
// which will be wrapped with resume context because needsHydration=true.
|
|
377
|
+
yield ctx.continueAsNew(continueInput());
|
|
378
|
+
return "";
|
|
379
|
+
}
|
|
380
|
+
case "wait":
|
|
381
|
+
// Capture original user prompt as task context for recurring tasks.
|
|
382
|
+
// This ensures the LLM remembers its task even after conversation truncation.
|
|
383
|
+
if (!taskContext) {
|
|
384
|
+
taskContext = prompt.slice(0, 2000);
|
|
385
|
+
const base = typeof baseSystemMessage === 'string'
|
|
386
|
+
? baseSystemMessage ?? ''
|
|
387
|
+
: baseSystemMessage?.content ?? '';
|
|
388
|
+
config.systemMessage = base + (base ? '\n\n' : '') +
|
|
389
|
+
'[RECURRING TASK]\n' +
|
|
390
|
+
'Original user request (always remember, even if conversation history is truncated):\n"' +
|
|
391
|
+
taskContext + '"';
|
|
392
|
+
}
|
|
393
|
+
if (result.content) {
|
|
394
|
+
setStatus(ctx, "running", { iteration, intermediateContent: result.content });
|
|
395
|
+
ctx.traceInfo(`[orch] intermediate: ${result.content.slice(0, 80)}`);
|
|
396
|
+
}
|
|
397
|
+
ctx.traceInfo(`[orch] durable timer: ${result.seconds}s (${result.reason})`);
|
|
398
|
+
{
|
|
399
|
+
const shouldDehydrate = blobEnabled && result.seconds > dehydrateThreshold;
|
|
400
|
+
if (shouldDehydrate) {
|
|
401
|
+
yield* dehydrateAndReset("timer");
|
|
402
|
+
}
|
|
403
|
+
const waitStartedAt = yield ctx.utcNow();
|
|
404
|
+
setStatus(ctx, "waiting", {
|
|
405
|
+
iteration,
|
|
406
|
+
waitSeconds: result.seconds,
|
|
407
|
+
waitReason: result.reason,
|
|
408
|
+
waitStartedAt,
|
|
409
|
+
...(result.content ? { turnResult: { type: "completed", content: result.content } } : {}),
|
|
410
|
+
});
|
|
411
|
+
// Checkpoint before the blocking wait
|
|
412
|
+
if (!shouldDehydrate)
|
|
413
|
+
yield* maybeCheckpoint();
|
|
414
|
+
const timerTask = ctx.scheduleTimer(result.seconds * 1000);
|
|
415
|
+
const interruptMsg = ctx.dequeueEvent("messages");
|
|
416
|
+
const timerRace = yield ctx.race(timerTask, interruptMsg);
|
|
417
|
+
if (timerRace.index === 1) {
|
|
418
|
+
const interruptData = typeof timerRace.value === "string"
|
|
419
|
+
? JSON.parse(timerRace.value) : (timerRace.value ?? {});
|
|
420
|
+
ctx.traceInfo(`[session] wait interrupted: "${(interruptData.prompt || "").slice(0, 60)}"`);
|
|
421
|
+
// Calculate remaining time for resume context
|
|
422
|
+
const interruptedAt = yield ctx.utcNow();
|
|
423
|
+
const elapsedSec = Math.round((interruptedAt - waitStartedAt) / 1000);
|
|
424
|
+
const remainingSec = Math.max(0, result.seconds - elapsedSec);
|
|
425
|
+
const userPrompt = interruptData.prompt || "";
|
|
426
|
+
let finalPrompt;
|
|
427
|
+
if (shouldDehydrate && userPrompt) {
|
|
428
|
+
finalPrompt = wrapWithResumeContext(userPrompt, `You were waiting on a ${result.seconds}s timer (reason: "${result.reason}"). ` +
|
|
429
|
+
`${elapsedSec}s have elapsed, ${remainingSec}s remain. ` +
|
|
430
|
+
`After handling the user's message, restart the wait using the wait tool for the remaining ${remainingSec} seconds only.`);
|
|
431
|
+
}
|
|
432
|
+
else if (userPrompt) {
|
|
433
|
+
// Not dehydrated but still interrupted — give timing context
|
|
434
|
+
finalPrompt = `${userPrompt}\n\n` +
|
|
435
|
+
`[SYSTEM: You were waiting on a ${result.seconds}s timer (reason: "${result.reason}"). ` +
|
|
436
|
+
`${elapsedSec}s elapsed, ${remainingSec}s remain. ` +
|
|
437
|
+
`After handling this message, restart the wait using the wait tool for the remaining ${remainingSec} seconds only.]`;
|
|
438
|
+
}
|
|
439
|
+
else {
|
|
440
|
+
finalPrompt = userPrompt;
|
|
441
|
+
}
|
|
442
|
+
yield ctx.continueAsNew(continueInput({
|
|
443
|
+
prompt: finalPrompt,
|
|
444
|
+
needsHydration: shouldDehydrate ? true : needsHydration,
|
|
445
|
+
}));
|
|
446
|
+
return "";
|
|
447
|
+
}
|
|
448
|
+
const timerPrompt = `The ${result.seconds} second wait is now complete. Continue with your task.`;
|
|
449
|
+
yield ctx.continueAsNew(continueInput({
|
|
450
|
+
prompt: timerPrompt,
|
|
451
|
+
needsHydration: shouldDehydrate ? true : needsHydration,
|
|
452
|
+
}));
|
|
453
|
+
return "";
|
|
454
|
+
}
|
|
455
|
+
case "input_required":
|
|
456
|
+
ctx.traceInfo(`[orch] waiting for user input: ${result.question}`);
|
|
457
|
+
if (!blobEnabled || inputGracePeriod < 0) {
|
|
458
|
+
setStatus(ctx, "input_required", {
|
|
459
|
+
iteration,
|
|
460
|
+
turnResult: statusResult,
|
|
461
|
+
pendingQuestion: result.question,
|
|
462
|
+
choices: result.choices,
|
|
463
|
+
allowFreeform: result.allowFreeform,
|
|
464
|
+
});
|
|
465
|
+
yield* maybeCheckpoint();
|
|
466
|
+
const answerMsg = yield ctx.dequeueEvent("messages");
|
|
467
|
+
const answerData = typeof answerMsg === "string"
|
|
468
|
+
? JSON.parse(answerMsg) : answerMsg;
|
|
469
|
+
yield ctx.continueAsNew(continueInput({
|
|
470
|
+
prompt: `The user was asked: "${result.question}"\nThe user responded: "${answerData.answer}"`,
|
|
471
|
+
needsHydration: false,
|
|
472
|
+
}));
|
|
473
|
+
return "";
|
|
474
|
+
}
|
|
475
|
+
if (inputGracePeriod === 0) {
|
|
476
|
+
setStatus(ctx, "input_required", {
|
|
477
|
+
iteration,
|
|
478
|
+
turnResult: statusResult,
|
|
479
|
+
pendingQuestion: result.question,
|
|
480
|
+
});
|
|
481
|
+
yield* dehydrateAndReset("input_required");
|
|
482
|
+
const answerMsg = yield ctx.dequeueEvent("messages");
|
|
483
|
+
const answerData = typeof answerMsg === "string"
|
|
484
|
+
? JSON.parse(answerMsg) : answerMsg;
|
|
485
|
+
yield ctx.continueAsNew(continueInput({
|
|
486
|
+
prompt: `The user was asked: "${result.question}"\nThe user responded: "${answerData.answer}"`,
|
|
487
|
+
}));
|
|
488
|
+
return "";
|
|
489
|
+
}
|
|
490
|
+
// Race: user answer vs grace period
|
|
491
|
+
{
|
|
492
|
+
setStatus(ctx, "input_required", {
|
|
493
|
+
iteration,
|
|
494
|
+
turnResult: statusResult,
|
|
495
|
+
pendingQuestion: result.question,
|
|
496
|
+
choices: result.choices,
|
|
497
|
+
allowFreeform: result.allowFreeform,
|
|
498
|
+
});
|
|
499
|
+
const answerEvt = ctx.dequeueEvent("messages");
|
|
500
|
+
const graceTimer = ctx.scheduleTimer(inputGracePeriod * 1000);
|
|
501
|
+
const raceResult = yield ctx.race(answerEvt, graceTimer);
|
|
502
|
+
if (raceResult.index === 0) {
|
|
503
|
+
const answerData = typeof raceResult.value === "string"
|
|
504
|
+
? JSON.parse(raceResult.value) : (raceResult.value ?? {});
|
|
505
|
+
yield ctx.continueAsNew(continueInput({
|
|
506
|
+
prompt: `The user was asked: "${result.question}"\nThe user responded: "${answerData.answer}"`,
|
|
507
|
+
needsHydration: false,
|
|
508
|
+
}));
|
|
509
|
+
return "";
|
|
510
|
+
}
|
|
511
|
+
yield* dehydrateAndReset("input_required");
|
|
512
|
+
const answerMsg = yield ctx.dequeueEvent("messages");
|
|
513
|
+
const answerData = typeof answerMsg === "string"
|
|
514
|
+
? JSON.parse(answerMsg) : answerMsg;
|
|
515
|
+
yield ctx.continueAsNew(continueInput({
|
|
516
|
+
prompt: `The user was asked: "${result.question}"\nThe user responded: "${answerData.answer}"`,
|
|
517
|
+
}));
|
|
518
|
+
return "";
|
|
519
|
+
}
|
|
520
|
+
case "cancelled":
|
|
521
|
+
ctx.traceInfo("[session] turn cancelled");
|
|
522
|
+
continue;
|
|
523
|
+
// ─── Sub-Agent Result Handlers ───────────────────
|
|
524
|
+
case "spawn_agent": {
|
|
525
|
+
// Enforce max sub-agents
|
|
526
|
+
const activeCount = subAgents.filter(a => a.status === "running").length;
|
|
527
|
+
if (activeCount >= MAX_SUB_AGENTS) {
|
|
528
|
+
ctx.traceInfo(`[orch] spawn_agent denied: ${activeCount}/${MAX_SUB_AGENTS} agents running`);
|
|
529
|
+
// Feed error back to the LLM
|
|
530
|
+
yield ctx.continueAsNew(continueInput({
|
|
531
|
+
prompt: `[SYSTEM: spawn_agent failed — you already have ${activeCount} running sub-agents (max ${MAX_SUB_AGENTS}). ` +
|
|
532
|
+
`Wait for some to complete before spawning more.]`,
|
|
533
|
+
}));
|
|
534
|
+
return "";
|
|
535
|
+
}
|
|
536
|
+
// Generate deterministic child IDs
|
|
537
|
+
const childGuid = yield ctx.newGuid();
|
|
538
|
+
const childSessionId = `${input.sessionId}:sub-${childGuid.slice(0, 8)}`;
|
|
539
|
+
const childOrchId = `session-${childSessionId}`;
|
|
540
|
+
ctx.traceInfo(`[orch] spawning sub-agent: id=${childOrchId} task="${result.task.slice(0, 80)}"`);
|
|
541
|
+
// Build child config — inherit parent's config with optional overrides
|
|
542
|
+
const childConfig = {
|
|
543
|
+
...config,
|
|
544
|
+
...(result.systemMessage ? { systemMessage: result.systemMessage } : {}),
|
|
545
|
+
...(result.toolNames ? { toolNames: result.toolNames } : {}),
|
|
546
|
+
};
|
|
547
|
+
// Build child orchestration input
|
|
548
|
+
const childInput = {
|
|
549
|
+
sessionId: childSessionId,
|
|
550
|
+
config: childConfig,
|
|
551
|
+
blobEnabled,
|
|
552
|
+
dehydrateThreshold,
|
|
553
|
+
idleTimeout: -1, // sub-agents don't idle-dehydrate — they just run
|
|
554
|
+
inputGracePeriod: -1,
|
|
555
|
+
checkpointInterval,
|
|
556
|
+
rehydrationMessage,
|
|
557
|
+
parentOrchId: `session-${input.sessionId}`,
|
|
558
|
+
prompt: result.task,
|
|
559
|
+
};
|
|
560
|
+
// Fire-and-forget: start the child orchestration
|
|
561
|
+
yield ctx.startOrchestrationVersioned("durable-session-v2", "1.0.2", childOrchId, childInput);
|
|
562
|
+
// Track the sub-agent
|
|
563
|
+
subAgents.push({
|
|
564
|
+
orchId: childOrchId,
|
|
565
|
+
sessionId: childSessionId,
|
|
566
|
+
task: result.task.slice(0, 500),
|
|
567
|
+
status: "running",
|
|
568
|
+
});
|
|
569
|
+
// Feed confirmation back to the LLM
|
|
570
|
+
const spawnMsg = `[SYSTEM: Sub-agent spawned successfully.\n` +
|
|
571
|
+
` Agent ID: ${childOrchId}\n` +
|
|
572
|
+
` Task: "${result.task.slice(0, 200)}"\n` +
|
|
573
|
+
` The agent is now running autonomously. Use check_agents to monitor progress, ` +
|
|
574
|
+
`message_agent to send instructions, or wait_for_agents to block until completion.]`;
|
|
575
|
+
yield ctx.continueAsNew(continueInput({ prompt: spawnMsg }));
|
|
576
|
+
return "";
|
|
577
|
+
}
|
|
578
|
+
case "message_agent": {
|
|
579
|
+
const targetOrchId = result.agentId;
|
|
580
|
+
const agentEntry = subAgents.find(a => a.orchId === targetOrchId);
|
|
581
|
+
if (!agentEntry) {
|
|
582
|
+
ctx.traceInfo(`[orch] message_agent: unknown agent ${targetOrchId}`);
|
|
583
|
+
yield ctx.continueAsNew(continueInput({
|
|
584
|
+
prompt: `[SYSTEM: message_agent failed — agent "${targetOrchId}" not found. ` +
|
|
585
|
+
`Known agents: ${subAgents.map(a => a.orchId).join(", ") || "none"}]`,
|
|
586
|
+
}));
|
|
587
|
+
return "";
|
|
588
|
+
}
|
|
589
|
+
ctx.traceInfo(`[orch] message_agent: ${targetOrchId} msg="${result.message.slice(0, 60)}"`);
|
|
590
|
+
try {
|
|
591
|
+
yield manager.messageChild(targetOrchId, result.message);
|
|
592
|
+
}
|
|
593
|
+
catch (err) {
|
|
594
|
+
ctx.traceInfo(`[orch] message_agent failed: ${err.message}`);
|
|
595
|
+
yield ctx.continueAsNew(continueInput({
|
|
596
|
+
prompt: `[SYSTEM: message_agent failed: ${err.message}]`,
|
|
597
|
+
}));
|
|
598
|
+
return "";
|
|
599
|
+
}
|
|
600
|
+
yield ctx.continueAsNew(continueInput({
|
|
601
|
+
prompt: `[SYSTEM: Message sent to sub-agent ${targetOrchId}: "${result.message.slice(0, 200)}"]`,
|
|
602
|
+
}));
|
|
603
|
+
return "";
|
|
604
|
+
}
|
|
605
|
+
case "check_agents": {
|
|
606
|
+
ctx.traceInfo(`[orch] check_agents: ${subAgents.length} agents tracked`);
|
|
607
|
+
if (subAgents.length === 0) {
|
|
608
|
+
yield ctx.continueAsNew(continueInput({
|
|
609
|
+
prompt: `[SYSTEM: No sub-agents have been spawned yet.]`,
|
|
610
|
+
}));
|
|
611
|
+
return "";
|
|
612
|
+
}
|
|
613
|
+
// Poll fresh status for each agent
|
|
614
|
+
const statusLines = [];
|
|
615
|
+
for (const agent of subAgents) {
|
|
616
|
+
try {
|
|
617
|
+
const rawStatus = yield manager.getChildStatus(agent.orchId);
|
|
618
|
+
const parsed = JSON.parse(rawStatus);
|
|
619
|
+
const childCustom = parsed.customStatus;
|
|
620
|
+
const runtimeStatus = parsed.runtimeStatus ?? "unknown";
|
|
621
|
+
// Update local tracking
|
|
622
|
+
if (runtimeStatus === "Completed" || runtimeStatus === "Failed" || runtimeStatus === "Terminated") {
|
|
623
|
+
agent.status = runtimeStatus === "Completed" ? "completed" : "failed";
|
|
624
|
+
if (childCustom?.turnResult?.content) {
|
|
625
|
+
agent.result = childCustom.turnResult.content.slice(0, 1000);
|
|
626
|
+
}
|
|
627
|
+
}
|
|
628
|
+
const statusStr = childCustom?.status ?? runtimeStatus;
|
|
629
|
+
const content = childCustom?.turnResult?.content
|
|
630
|
+
?? childCustom?.intermediateContent
|
|
631
|
+
?? "(no output yet)";
|
|
632
|
+
statusLines.push(` - Agent ${agent.orchId}\n` +
|
|
633
|
+
` Task: "${agent.task.slice(0, 120)}"\n` +
|
|
634
|
+
` Status: ${statusStr} (runtime: ${runtimeStatus})\n` +
|
|
635
|
+
` Output: ${String(content).slice(0, 500)}`);
|
|
636
|
+
}
|
|
637
|
+
catch (err) {
|
|
638
|
+
statusLines.push(` - Agent ${agent.orchId}\n` +
|
|
639
|
+
` Task: "${agent.task.slice(0, 120)}"\n` +
|
|
640
|
+
` Status: unknown (error: ${err.message})`);
|
|
641
|
+
}
|
|
642
|
+
}
|
|
643
|
+
yield ctx.continueAsNew(continueInput({
|
|
644
|
+
prompt: `[SYSTEM: Sub-agent status report (${subAgents.length} agents):\n${statusLines.join("\n")}]`,
|
|
645
|
+
}));
|
|
646
|
+
return "";
|
|
647
|
+
}
|
|
648
|
+
case "wait_for_agents": {
|
|
649
|
+
let targetIds = result.agentIds;
|
|
650
|
+
// If empty, wait for all running agents
|
|
651
|
+
if (!targetIds || targetIds.length === 0) {
|
|
652
|
+
targetIds = subAgents.filter(a => a.status === "running").map(a => a.orchId);
|
|
653
|
+
}
|
|
654
|
+
if (targetIds.length === 0) {
|
|
655
|
+
ctx.traceInfo(`[orch] wait_for_agents: no running agents to wait for`);
|
|
656
|
+
yield ctx.continueAsNew(continueInput({
|
|
657
|
+
prompt: `[SYSTEM: No running sub-agents to wait for. All agents have already completed.]`,
|
|
658
|
+
}));
|
|
659
|
+
return "";
|
|
660
|
+
}
|
|
661
|
+
ctx.traceInfo(`[orch] wait_for_agents: waiting for ${targetIds.length} agents`);
|
|
662
|
+
setStatus(ctx, "running", {
|
|
663
|
+
iteration,
|
|
664
|
+
waitingForAgents: targetIds,
|
|
665
|
+
});
|
|
666
|
+
// Poll until all target agents are done (10s intervals)
|
|
667
|
+
const MAX_POLL_ATTEMPTS = 180; // 30 minutes max
|
|
668
|
+
for (let pollAttempt = 0; pollAttempt < MAX_POLL_ATTEMPTS; pollAttempt++) {
|
|
669
|
+
let allDone = true;
|
|
670
|
+
for (const targetId of targetIds) {
|
|
671
|
+
const agent = subAgents.find(a => a.orchId === targetId);
|
|
672
|
+
if (!agent || agent.status !== "running")
|
|
673
|
+
continue;
|
|
674
|
+
try {
|
|
675
|
+
const rawStatus = yield manager.getChildStatus(targetId);
|
|
676
|
+
const parsed = JSON.parse(rawStatus);
|
|
677
|
+
const runtimeStatus = parsed.runtimeStatus ?? "unknown";
|
|
678
|
+
const childCustom = parsed.customStatus;
|
|
679
|
+
if (runtimeStatus === "Completed" || runtimeStatus === "Failed" || runtimeStatus === "Terminated") {
|
|
680
|
+
agent.status = runtimeStatus === "Completed" ? "completed" : "failed";
|
|
681
|
+
if (childCustom?.turnResult?.content) {
|
|
682
|
+
agent.result = childCustom.turnResult.content.slice(0, 2000);
|
|
683
|
+
}
|
|
684
|
+
ctx.traceInfo(`[orch] agent ${targetId} finished: ${agent.status}`);
|
|
685
|
+
}
|
|
686
|
+
else {
|
|
687
|
+
allDone = false;
|
|
688
|
+
}
|
|
689
|
+
}
|
|
690
|
+
catch {
|
|
691
|
+
allDone = false;
|
|
692
|
+
}
|
|
693
|
+
}
|
|
694
|
+
if (allDone)
|
|
695
|
+
break;
|
|
696
|
+
// Wait 10 seconds before polling again
|
|
697
|
+
yield ctx.scheduleTimer(10_000);
|
|
698
|
+
}
|
|
699
|
+
// Build results summary
|
|
700
|
+
const resultLines = [];
|
|
701
|
+
for (const targetId of targetIds) {
|
|
702
|
+
const agent = subAgents.find(a => a.orchId === targetId);
|
|
703
|
+
if (!agent)
|
|
704
|
+
continue;
|
|
705
|
+
resultLines.push(` - Agent ${agent.orchId}\n` +
|
|
706
|
+
` Task: "${agent.task.slice(0, 120)}"\n` +
|
|
707
|
+
` Status: ${agent.status}\n` +
|
|
708
|
+
` Result: ${agent.result ?? "(no result)"}`);
|
|
709
|
+
}
|
|
710
|
+
yield ctx.continueAsNew(continueInput({
|
|
711
|
+
prompt: `[SYSTEM: Sub-agents completed:\n${resultLines.join("\n")}]`,
|
|
712
|
+
}));
|
|
713
|
+
return "";
|
|
714
|
+
}
|
|
715
|
+
case "error": {
|
|
716
|
+
// Treat like an activity failure — retry with backoff.
|
|
717
|
+
retryCount++;
|
|
718
|
+
ctx.traceInfo(`[orch] turn returned error (attempt ${retryCount}/${MAX_RETRIES}): ${result.message}`);
|
|
719
|
+
if (retryCount >= MAX_RETRIES) {
|
|
720
|
+
ctx.traceInfo(`[orch] max retries exhausted for turn error, waiting for user input`);
|
|
721
|
+
setStatus(ctx, "error", {
|
|
722
|
+
iteration,
|
|
723
|
+
error: `Failed after ${MAX_RETRIES} attempts: ${result.message}`,
|
|
724
|
+
retriesExhausted: true,
|
|
725
|
+
});
|
|
726
|
+
retryCount = 0;
|
|
727
|
+
continue;
|
|
728
|
+
}
|
|
729
|
+
setStatus(ctx, "error", {
|
|
730
|
+
iteration,
|
|
731
|
+
error: `${result.message} (retry ${retryCount}/${MAX_RETRIES})`,
|
|
732
|
+
});
|
|
733
|
+
const errorRetryDelay = 15 * Math.pow(2, retryCount - 1);
|
|
734
|
+
ctx.traceInfo(`[orch] retrying in ${errorRetryDelay}s after turn error`);
|
|
735
|
+
if (blobEnabled) {
|
|
736
|
+
yield* dehydrateAndReset("error");
|
|
737
|
+
}
|
|
738
|
+
yield ctx.scheduleTimer(errorRetryDelay * 1000);
|
|
739
|
+
yield ctx.continueAsNew(continueInput({
|
|
740
|
+
prompt,
|
|
741
|
+
retryCount,
|
|
742
|
+
needsHydration: blobEnabled ? true : needsHydration,
|
|
743
|
+
}));
|
|
744
|
+
return "";
|
|
745
|
+
}
|
|
746
|
+
}
|
|
747
|
+
}
|
|
748
|
+
}
|
|
749
|
+
//# sourceMappingURL=orchestration_1_0_2.js.map
|