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.
Files changed (183) hide show
  1. package/dist/agent-loader.d.ts +61 -0
  2. package/dist/agent-loader.d.ts.map +1 -0
  3. package/dist/agent-loader.js +212 -0
  4. package/dist/agent-loader.js.map +1 -0
  5. package/dist/artifact-tools.d.ts +31 -0
  6. package/dist/artifact-tools.d.ts.map +1 -0
  7. package/dist/artifact-tools.js +190 -0
  8. package/dist/artifact-tools.js.map +1 -0
  9. package/dist/blob-store.d.ts +73 -0
  10. package/dist/blob-store.d.ts.map +1 -0
  11. package/dist/blob-store.js +220 -0
  12. package/dist/blob-store.js.map +1 -0
  13. package/dist/client.d.ts +159 -0
  14. package/dist/client.d.ts.map +1 -0
  15. package/dist/client.js +676 -0
  16. package/dist/client.js.map +1 -0
  17. package/dist/cms.d.ts +129 -0
  18. package/dist/cms.d.ts.map +1 -0
  19. package/dist/cms.js +313 -0
  20. package/dist/cms.js.map +1 -0
  21. package/dist/index.d.ts +44 -0
  22. package/dist/index.d.ts.map +1 -0
  23. package/dist/index.js +42 -0
  24. package/dist/index.js.map +1 -0
  25. package/dist/managed-session.d.ts +70 -0
  26. package/dist/managed-session.d.ts.map +1 -0
  27. package/dist/managed-session.js +717 -0
  28. package/dist/managed-session.js.map +1 -0
  29. package/dist/management-client.d.ts +171 -0
  30. package/dist/management-client.d.ts.map +1 -0
  31. package/dist/management-client.js +401 -0
  32. package/dist/management-client.js.map +1 -0
  33. package/dist/mcp-loader.d.ts +50 -0
  34. package/dist/mcp-loader.d.ts.map +1 -0
  35. package/dist/mcp-loader.js +83 -0
  36. package/dist/mcp-loader.js.map +1 -0
  37. package/dist/model-providers.d.ts +143 -0
  38. package/dist/model-providers.d.ts.map +1 -0
  39. package/dist/model-providers.js +228 -0
  40. package/dist/model-providers.js.map +1 -0
  41. package/dist/orchestration-registry.d.ts +7 -0
  42. package/dist/orchestration-registry.d.ts.map +1 -0
  43. package/dist/orchestration-registry.js +49 -0
  44. package/dist/orchestration-registry.js.map +1 -0
  45. package/dist/orchestration.d.ts +36 -0
  46. package/dist/orchestration.d.ts.map +1 -0
  47. package/dist/orchestration.js +1357 -0
  48. package/dist/orchestration.js.map +1 -0
  49. package/dist/orchestration_1_0_0.d.ts +20 -0
  50. package/dist/orchestration_1_0_0.d.ts.map +1 -0
  51. package/dist/orchestration_1_0_0.js +497 -0
  52. package/dist/orchestration_1_0_0.js.map +1 -0
  53. package/dist/orchestration_1_0_1.d.ts +19 -0
  54. package/dist/orchestration_1_0_1.d.ts.map +1 -0
  55. package/dist/orchestration_1_0_1.js +546 -0
  56. package/dist/orchestration_1_0_1.js.map +1 -0
  57. package/dist/orchestration_1_0_10.d.ts +36 -0
  58. package/dist/orchestration_1_0_10.d.ts.map +1 -0
  59. package/dist/orchestration_1_0_10.js +1253 -0
  60. package/dist/orchestration_1_0_10.js.map +1 -0
  61. package/dist/orchestration_1_0_11.d.ts +36 -0
  62. package/dist/orchestration_1_0_11.d.ts.map +1 -0
  63. package/dist/orchestration_1_0_11.js +1255 -0
  64. package/dist/orchestration_1_0_11.js.map +1 -0
  65. package/dist/orchestration_1_0_12.d.ts +36 -0
  66. package/dist/orchestration_1_0_12.d.ts.map +1 -0
  67. package/dist/orchestration_1_0_12.js +1250 -0
  68. package/dist/orchestration_1_0_12.js.map +1 -0
  69. package/dist/orchestration_1_0_13.d.ts +36 -0
  70. package/dist/orchestration_1_0_13.d.ts.map +1 -0
  71. package/dist/orchestration_1_0_13.js +1260 -0
  72. package/dist/orchestration_1_0_13.js.map +1 -0
  73. package/dist/orchestration_1_0_14.d.ts +36 -0
  74. package/dist/orchestration_1_0_14.d.ts.map +1 -0
  75. package/dist/orchestration_1_0_14.js +1258 -0
  76. package/dist/orchestration_1_0_14.js.map +1 -0
  77. package/dist/orchestration_1_0_15.d.ts +36 -0
  78. package/dist/orchestration_1_0_15.d.ts.map +1 -0
  79. package/dist/orchestration_1_0_15.js +1266 -0
  80. package/dist/orchestration_1_0_15.js.map +1 -0
  81. package/dist/orchestration_1_0_16.d.ts +36 -0
  82. package/dist/orchestration_1_0_16.d.ts.map +1 -0
  83. package/dist/orchestration_1_0_16.js +1275 -0
  84. package/dist/orchestration_1_0_16.js.map +1 -0
  85. package/dist/orchestration_1_0_17.d.ts +36 -0
  86. package/dist/orchestration_1_0_17.d.ts.map +1 -0
  87. package/dist/orchestration_1_0_17.js +1314 -0
  88. package/dist/orchestration_1_0_17.js.map +1 -0
  89. package/dist/orchestration_1_0_18.d.ts +36 -0
  90. package/dist/orchestration_1_0_18.d.ts.map +1 -0
  91. package/dist/orchestration_1_0_18.js +1328 -0
  92. package/dist/orchestration_1_0_18.js.map +1 -0
  93. package/dist/orchestration_1_0_19.d.ts +36 -0
  94. package/dist/orchestration_1_0_19.d.ts.map +1 -0
  95. package/dist/orchestration_1_0_19.js +1324 -0
  96. package/dist/orchestration_1_0_19.js.map +1 -0
  97. package/dist/orchestration_1_0_2.d.ts +19 -0
  98. package/dist/orchestration_1_0_2.d.ts.map +1 -0
  99. package/dist/orchestration_1_0_2.js +749 -0
  100. package/dist/orchestration_1_0_2.js.map +1 -0
  101. package/dist/orchestration_1_0_20.d.ts +36 -0
  102. package/dist/orchestration_1_0_20.d.ts.map +1 -0
  103. package/dist/orchestration_1_0_20.js +1347 -0
  104. package/dist/orchestration_1_0_20.js.map +1 -0
  105. package/dist/orchestration_1_0_3.d.ts +19 -0
  106. package/dist/orchestration_1_0_3.d.ts.map +1 -0
  107. package/dist/orchestration_1_0_3.js +826 -0
  108. package/dist/orchestration_1_0_3.js.map +1 -0
  109. package/dist/orchestration_1_0_4.d.ts +19 -0
  110. package/dist/orchestration_1_0_4.d.ts.map +1 -0
  111. package/dist/orchestration_1_0_4.js +1020 -0
  112. package/dist/orchestration_1_0_4.js.map +1 -0
  113. package/dist/orchestration_1_0_5.d.ts +19 -0
  114. package/dist/orchestration_1_0_5.d.ts.map +1 -0
  115. package/dist/orchestration_1_0_5.js +1027 -0
  116. package/dist/orchestration_1_0_5.js.map +1 -0
  117. package/dist/orchestration_1_0_6.d.ts +19 -0
  118. package/dist/orchestration_1_0_6.d.ts.map +1 -0
  119. package/dist/orchestration_1_0_6.js +1034 -0
  120. package/dist/orchestration_1_0_6.js.map +1 -0
  121. package/dist/orchestration_1_0_7.d.ts +19 -0
  122. package/dist/orchestration_1_0_7.d.ts.map +1 -0
  123. package/dist/orchestration_1_0_7.js +1085 -0
  124. package/dist/orchestration_1_0_7.js.map +1 -0
  125. package/dist/orchestration_1_0_8.d.ts +36 -0
  126. package/dist/orchestration_1_0_8.d.ts.map +1 -0
  127. package/dist/orchestration_1_0_8.js +1106 -0
  128. package/dist/orchestration_1_0_8.js.map +1 -0
  129. package/dist/orchestration_1_0_9.d.ts +36 -0
  130. package/dist/orchestration_1_0_9.d.ts.map +1 -0
  131. package/dist/orchestration_1_0_9.js +1207 -0
  132. package/dist/orchestration_1_0_9.js.map +1 -0
  133. package/dist/prompt-layering.d.ts +16 -0
  134. package/dist/prompt-layering.d.ts.map +1 -0
  135. package/dist/prompt-layering.js +60 -0
  136. package/dist/prompt-layering.js.map +1 -0
  137. package/dist/resourcemgr-tools.d.ts +27 -0
  138. package/dist/resourcemgr-tools.d.ts.map +1 -0
  139. package/dist/resourcemgr-tools.js +638 -0
  140. package/dist/resourcemgr-tools.js.map +1 -0
  141. package/dist/session-dumper.d.ts +26 -0
  142. package/dist/session-dumper.d.ts.map +1 -0
  143. package/dist/session-dumper.js +272 -0
  144. package/dist/session-dumper.js.map +1 -0
  145. package/dist/session-manager.d.ts +152 -0
  146. package/dist/session-manager.d.ts.map +1 -0
  147. package/dist/session-manager.js +493 -0
  148. package/dist/session-manager.js.map +1 -0
  149. package/dist/session-proxy.d.ts +68 -0
  150. package/dist/session-proxy.d.ts.map +1 -0
  151. package/dist/session-proxy.js +665 -0
  152. package/dist/session-proxy.js.map +1 -0
  153. package/dist/session-store.d.ts +35 -0
  154. package/dist/session-store.d.ts.map +1 -0
  155. package/dist/session-store.js +88 -0
  156. package/dist/session-store.js.map +1 -0
  157. package/dist/skills.d.ts +31 -0
  158. package/dist/skills.d.ts.map +1 -0
  159. package/dist/skills.js +93 -0
  160. package/dist/skills.js.map +1 -0
  161. package/dist/sweeper-tools.d.ts +28 -0
  162. package/dist/sweeper-tools.d.ts.map +1 -0
  163. package/dist/sweeper-tools.js +332 -0
  164. package/dist/sweeper-tools.js.map +1 -0
  165. package/dist/types.d.ts +498 -0
  166. package/dist/types.d.ts.map +1 -0
  167. package/dist/types.js +9 -0
  168. package/dist/types.js.map +1 -0
  169. package/dist/worker.d.ts +128 -0
  170. package/dist/worker.d.ts.map +1 -0
  171. package/dist/worker.js +562 -0
  172. package/dist/worker.js.map +1 -0
  173. package/package.json +74 -0
  174. package/plugins/mgmt/agents/pilotswarm.agent.md +59 -0
  175. package/plugins/mgmt/agents/resourcemgr.agent.md +111 -0
  176. package/plugins/mgmt/agents/sweeper.agent.md +67 -0
  177. package/plugins/mgmt/skills/resourcemgr/SKILL.md +41 -0
  178. package/plugins/mgmt/skills/resourcemgr/tools.json +1 -0
  179. package/plugins/mgmt/skills/sweeper/SKILL.md +44 -0
  180. package/plugins/mgmt/skills/sweeper/tools.json +1 -0
  181. package/plugins/system/agents/default.agent.md +58 -0
  182. package/plugins/system/skills/durable-timers/SKILL.md +39 -0
  183. 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