pilotswarm-sdk 0.1.35 → 0.2.1

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 (173) hide show
  1. package/dist/agent-loader.d.ts +9 -0
  2. package/dist/agent-loader.d.ts.map +1 -1
  3. package/dist/agent-loader.js +3 -0
  4. package/dist/agent-loader.js.map +1 -1
  5. package/dist/child-notifications.d.ts +10 -0
  6. package/dist/child-notifications.d.ts.map +1 -1
  7. package/dist/child-notifications.js +15 -1
  8. package/dist/child-notifications.js.map +1 -1
  9. package/dist/client.d.ts +2 -2
  10. package/dist/client.d.ts.map +1 -1
  11. package/dist/client.js +14 -20
  12. package/dist/client.js.map +1 -1
  13. package/dist/cms-migrations.d.ts.map +1 -1
  14. package/dist/cms-migrations.js +321 -0
  15. package/dist/cms-migrations.js.map +1 -1
  16. package/dist/cms.d.ts +132 -8
  17. package/dist/cms.d.ts.map +1 -1
  18. package/dist/cms.js +142 -6
  19. package/dist/cms.js.map +1 -1
  20. package/dist/duroxide-schema-migration.d.ts +19 -0
  21. package/dist/duroxide-schema-migration.d.ts.map +1 -0
  22. package/dist/duroxide-schema-migration.js +122 -0
  23. package/dist/duroxide-schema-migration.js.map +1 -0
  24. package/dist/facts-migrations.d.ts.map +1 -1
  25. package/dist/facts-migrations.js +923 -0
  26. package/dist/facts-migrations.js.map +1 -1
  27. package/dist/facts-store.d.ts +278 -28
  28. package/dist/facts-store.d.ts.map +1 -1
  29. package/dist/facts-store.js +262 -32
  30. package/dist/facts-store.js.map +1 -1
  31. package/dist/facts-tools.d.ts +8 -1
  32. package/dist/facts-tools.d.ts.map +1 -1
  33. package/dist/facts-tools.js +458 -25
  34. package/dist/facts-tools.js.map +1 -1
  35. package/dist/graph-store.d.ts +243 -0
  36. package/dist/graph-store.d.ts.map +1 -0
  37. package/dist/graph-store.js +63 -0
  38. package/dist/graph-store.js.map +1 -0
  39. package/dist/graph-tools.d.ts +47 -0
  40. package/dist/graph-tools.d.ts.map +1 -0
  41. package/dist/graph-tools.js +554 -0
  42. package/dist/graph-tools.js.map +1 -0
  43. package/dist/horizon-env.d.ts +61 -0
  44. package/dist/horizon-env.d.ts.map +1 -0
  45. package/dist/horizon-env.js +120 -0
  46. package/dist/horizon-env.js.map +1 -0
  47. package/dist/index.d.ts +15 -4
  48. package/dist/index.d.ts.map +1 -1
  49. package/dist/index.js +10 -2
  50. package/dist/index.js.map +1 -1
  51. package/dist/inspect-tools.d.ts +2 -2
  52. package/dist/inspect-tools.d.ts.map +1 -1
  53. package/dist/inspect-tools.js +303 -6
  54. package/dist/inspect-tools.js.map +1 -1
  55. package/dist/knowledge-index.d.ts +33 -2
  56. package/dist/knowledge-index.d.ts.map +1 -1
  57. package/dist/knowledge-index.js +90 -19
  58. package/dist/knowledge-index.js.map +1 -1
  59. package/dist/managed-session.d.ts.map +1 -1
  60. package/dist/managed-session.js +61 -2
  61. package/dist/managed-session.js.map +1 -1
  62. package/dist/management-client.d.ts +81 -3
  63. package/dist/management-client.d.ts.map +1 -1
  64. package/dist/management-client.js +97 -20
  65. package/dist/management-client.js.map +1 -1
  66. package/dist/orchestration/agents.d.ts +4 -1
  67. package/dist/orchestration/agents.d.ts.map +1 -1
  68. package/dist/orchestration/agents.js +27 -4
  69. package/dist/orchestration/agents.js.map +1 -1
  70. package/dist/orchestration/index.d.ts +2 -2
  71. package/dist/orchestration/index.js +1 -1
  72. package/dist/orchestration/lifecycle.d.ts +3 -0
  73. package/dist/orchestration/lifecycle.d.ts.map +1 -1
  74. package/dist/orchestration/lifecycle.js +6 -1
  75. package/dist/orchestration/lifecycle.js.map +1 -1
  76. package/dist/orchestration/queue.d.ts.map +1 -1
  77. package/dist/orchestration/queue.js +10 -1
  78. package/dist/orchestration/queue.js.map +1 -1
  79. package/dist/orchestration/runtime.d.ts +1 -1
  80. package/dist/orchestration/state.d.ts +1 -0
  81. package/dist/orchestration/state.d.ts.map +1 -1
  82. package/dist/orchestration/state.js +1 -0
  83. package/dist/orchestration/state.js.map +1 -1
  84. package/dist/orchestration/turn.d.ts +2 -2
  85. package/dist/orchestration/turn.d.ts.map +1 -1
  86. package/dist/orchestration/turn.js +42 -12
  87. package/dist/orchestration/turn.js.map +1 -1
  88. package/dist/orchestration-registry.d.ts.map +1 -1
  89. package/dist/orchestration-registry.js +4 -2
  90. package/dist/orchestration-registry.js.map +1 -1
  91. package/dist/orchestration-version.d.ts +1 -1
  92. package/dist/orchestration-version.js +1 -1
  93. package/dist/orchestration.d.ts +2 -2
  94. package/dist/orchestration.js +1 -1
  95. package/dist/orchestration_1_0_46.d.ts +1 -1
  96. package/dist/orchestration_1_0_47.d.ts +1 -1
  97. package/dist/orchestration_1_0_48.d.ts +1 -1
  98. package/dist/orchestration_1_0_49.d.ts +1 -1
  99. package/dist/orchestration_1_0_50.d.ts +1 -1
  100. package/dist/orchestration_1_0_52/agents.d.ts +38 -0
  101. package/dist/orchestration_1_0_52/agents.d.ts.map +1 -0
  102. package/dist/orchestration_1_0_52/agents.js +735 -0
  103. package/dist/orchestration_1_0_52/agents.js.map +1 -0
  104. package/dist/orchestration_1_0_52/index.d.ts +24 -0
  105. package/dist/orchestration_1_0_52/index.d.ts.map +1 -0
  106. package/dist/orchestration_1_0_52/index.js +13 -0
  107. package/dist/orchestration_1_0_52/index.js.map +1 -0
  108. package/dist/orchestration_1_0_52/lifecycle.d.ts +38 -0
  109. package/dist/orchestration_1_0_52/lifecycle.d.ts.map +1 -0
  110. package/dist/orchestration_1_0_52/lifecycle.js +504 -0
  111. package/dist/orchestration_1_0_52/lifecycle.js.map +1 -0
  112. package/dist/orchestration_1_0_52/queue.d.ts +7 -0
  113. package/dist/orchestration_1_0_52/queue.d.ts.map +1 -0
  114. package/dist/orchestration_1_0_52/queue.js +635 -0
  115. package/dist/orchestration_1_0_52/queue.js.map +1 -0
  116. package/dist/orchestration_1_0_52/runtime.d.ts +29 -0
  117. package/dist/orchestration_1_0_52/runtime.d.ts.map +1 -0
  118. package/dist/orchestration_1_0_52/runtime.js +190 -0
  119. package/dist/orchestration_1_0_52/runtime.js.map +1 -0
  120. package/dist/orchestration_1_0_52/state.d.ts +124 -0
  121. package/dist/orchestration_1_0_52/state.d.ts.map +1 -0
  122. package/dist/orchestration_1_0_52/state.js +104 -0
  123. package/dist/orchestration_1_0_52/state.js.map +1 -0
  124. package/dist/orchestration_1_0_52/turn.d.ts +6 -0
  125. package/dist/orchestration_1_0_52/turn.d.ts.map +1 -0
  126. package/dist/orchestration_1_0_52/turn.js +834 -0
  127. package/dist/orchestration_1_0_52/turn.js.map +1 -0
  128. package/dist/orchestration_1_0_52/utils.d.ts +22 -0
  129. package/dist/orchestration_1_0_52/utils.d.ts.map +1 -0
  130. package/dist/orchestration_1_0_52/utils.js +226 -0
  131. package/dist/orchestration_1_0_52/utils.js.map +1 -0
  132. package/dist/resourcemgr-tools.d.ts +2 -2
  133. package/dist/resourcemgr-tools.d.ts.map +1 -1
  134. package/dist/resourcemgr-tools.js +1 -1
  135. package/dist/resourcemgr-tools.js.map +1 -1
  136. package/dist/session-dumper.d.ts +2 -2
  137. package/dist/session-dumper.d.ts.map +1 -1
  138. package/dist/session-dumper.js.map +1 -1
  139. package/dist/session-manager.d.ts +8 -2
  140. package/dist/session-manager.d.ts.map +1 -1
  141. package/dist/session-manager.js +86 -4
  142. package/dist/session-manager.js.map +1 -1
  143. package/dist/session-messages.d.ts +2 -2
  144. package/dist/session-messages.d.ts.map +1 -1
  145. package/dist/session-messages.js +0 -6
  146. package/dist/session-messages.js.map +1 -1
  147. package/dist/session-proxy.d.ts +47 -2
  148. package/dist/session-proxy.d.ts.map +1 -1
  149. package/dist/session-proxy.js +79 -59
  150. package/dist/session-proxy.js.map +1 -1
  151. package/dist/storage-config.d.ts +62 -0
  152. package/dist/storage-config.d.ts.map +1 -0
  153. package/dist/storage-config.js +138 -0
  154. package/dist/storage-config.js.map +1 -0
  155. package/dist/storage-providers.d.ts +25 -0
  156. package/dist/storage-providers.d.ts.map +1 -0
  157. package/dist/storage-providers.js +150 -0
  158. package/dist/storage-providers.js.map +1 -0
  159. package/dist/sweeper-tools.d.ts +2 -2
  160. package/dist/sweeper-tools.d.ts.map +1 -1
  161. package/dist/system-agents.d.ts +2 -2
  162. package/dist/system-agents.d.ts.map +1 -1
  163. package/dist/types.d.ts +103 -2
  164. package/dist/types.d.ts.map +1 -1
  165. package/dist/types.js.map +1 -1
  166. package/dist/worker.d.ts +5 -4
  167. package/dist/worker.d.ts.map +1 -1
  168. package/dist/worker.js +108 -33
  169. package/dist/worker.js.map +1 -1
  170. package/package.json +3 -1
  171. package/plugins/mgmt/agents/agent-tuner.agent.md +56 -4
  172. package/plugins/mgmt/agents/facts-manager.agent.md +81 -3
  173. package/plugins/mgmt/skills/graph-debug/SKILL.md +133 -0
@@ -0,0 +1,834 @@
1
+ import { SESSION_STATE_MISSING_PREFIX } from "../types.js";
2
+ import { createSessionProxy } from "../session-proxy.js";
3
+ import { planWaitHandling } from "../wait-affinity.js";
4
+ import { buildShutdownWaitReason, failPendingShutdown, getStillRunningAgentIds, handleSubAgentAction, isSubAgentTerminalStatus, maybeResolveAgentWaitCompletion, refreshTrackedSubAgents, } from "./agents.js";
5
+ import { applyCronAtAction, applyCronAction, continueInput, continueInputWithPrompt, dehydrateForNextTurn, drainLeadingQueuedScheduleActions, ensureTaskContext, maybeCheckpoint, maybeSummarize, publishStatus, versionedContinueAsNew, wrapWithResumeContext, writeLatestResponse, } from "./lifecycle.js";
6
+ import { describeCronAt } from "../cron-at.js";
7
+ import { shouldWakeParentForChildUpdate } from "../child-notifications.js";
8
+ import { INTERNAL_SYSTEM_TURN_PROMPT, MAX_RETRIES, SHUTDOWN_POLL_INTERVAL_MS, SHUTDOWN_TIMEOUT_MS, } from "./state.js";
9
+ import { AUTH_FAILURE_USER_HINT, COPILOT_CONNECTION_CLOSED_MAX_RETRIES, COPILOT_CONNECTION_CLOSED_RETRY_DELAY_SECONDS, appendSystemContext, buildConnectionClosedRetryDetail, buildLossyHandoffRehydrationMessage, buildLossyHandoffSummary, extractPromptSystemContext, isAuthFailureError, isCopilotConnectionClosedError, mergePrompt, updateContextUsageFromEvents, } from "./utils.js";
10
+ function* handleConnectionClosedRetry(runtime, errorMessage, rc) {
11
+ const { state } = runtime;
12
+ if (state.retryCount <= COPILOT_CONNECTION_CLOSED_MAX_RETRIES) {
13
+ const retryDetail = buildConnectionClosedRetryDetail(state.retryCount);
14
+ publishStatus(runtime, "error", {
15
+ error: `${errorMessage} (${retryDetail})`,
16
+ recoverableTransportLoss: true,
17
+ });
18
+ runtime.ctx.traceInfo(`[orch] live Copilot connection lost; retrying in ${COPILOT_CONNECTION_CLOSED_RETRY_DELAY_SECONDS}s`);
19
+ if (state.blobEnabled) {
20
+ yield* dehydrateForNextTurn(runtime, "error", true, {
21
+ detail: retryDetail,
22
+ error: errorMessage,
23
+ phase: rc.phase,
24
+ retryAttempt: state.retryCount,
25
+ maxRetries: COPILOT_CONNECTION_CLOSED_MAX_RETRIES,
26
+ retryDelaySeconds: COPILOT_CONNECTION_CLOSED_RETRY_DELAY_SECONDS,
27
+ });
28
+ }
29
+ yield runtime.ctx.scheduleTimer(COPILOT_CONNECTION_CLOSED_RETRY_DELAY_SECONDS * 1000);
30
+ yield* versionedContinueAsNew(runtime, continueInput(runtime, retryContinueOverrides(state, rc)));
31
+ return;
32
+ }
33
+ const handoffMessage = buildLossyHandoffSummary(errorMessage);
34
+ runtime.ctx.traceInfo(`[orch] ${handoffMessage}`);
35
+ publishStatus(runtime, "error", {
36
+ error: handoffMessage,
37
+ retriesExhausted: true,
38
+ lossyHandoff: true,
39
+ });
40
+ yield runtime.manager.recordSessionEvent(runtime.input.sessionId, [{
41
+ eventType: "session.lossy_handoff",
42
+ data: {
43
+ message: handoffMessage,
44
+ error: errorMessage,
45
+ phase: rc.phase,
46
+ retries: COPILOT_CONNECTION_CLOSED_MAX_RETRIES,
47
+ retryDelaySeconds: COPILOT_CONNECTION_CLOSED_RETRY_DELAY_SECONDS,
48
+ nextStep: "dehydrate_and_resume_on_new_worker",
49
+ },
50
+ }]);
51
+ if (state.blobEnabled) {
52
+ yield* dehydrateForNextTurn(runtime, "lossy_handoff", true, {
53
+ detail: handoffMessage,
54
+ error: errorMessage,
55
+ phase: rc.phase,
56
+ retries: COPILOT_CONNECTION_CLOSED_MAX_RETRIES,
57
+ retryDelaySeconds: COPILOT_CONNECTION_CLOSED_RETRY_DELAY_SECONDS,
58
+ nextStep: "dehydrate_and_resume_on_new_worker",
59
+ });
60
+ yield* versionedContinueAsNew(runtime, continueInput(runtime, {
61
+ ...retryContinueOverrides(state, rc),
62
+ retryCount: 0,
63
+ needsHydration: true,
64
+ rehydrationMessage: buildLossyHandoffRehydrationMessage(errorMessage),
65
+ }));
66
+ return;
67
+ }
68
+ publishStatus(runtime, "error", {
69
+ error: `${handoffMessage} Durable handoff is unavailable because blob persistence is disabled.`,
70
+ retriesExhausted: true,
71
+ lossyHandoff: false,
72
+ });
73
+ state.retryCount = 0;
74
+ }
75
+ function retryContinueOverrides(state, rc) {
76
+ if (rc.phase === "turn.result.error") {
77
+ return {
78
+ prompt: rc.sourcePrompt,
79
+ retryCount: state.retryCount,
80
+ needsHydration: state.blobEnabled ? true : state.needsHydration,
81
+ };
82
+ }
83
+ return {
84
+ ...(rc.systemOnlyTurn ? {} : { prompt: rc.sourcePrompt }),
85
+ ...(rc.requiredTool ? { requiredTool: rc.requiredTool } : {}),
86
+ ...(rc.turnSystemPrompt ? { systemPrompt: rc.turnSystemPrompt } : {}),
87
+ retryCount: state.retryCount,
88
+ needsHydration: state.blobEnabled ? true : state.needsHydration,
89
+ };
90
+ }
91
+ function* handleGenericRetry(runtime, errorMessage, rc) {
92
+ const { state } = runtime;
93
+ if (state.retryCount >= MAX_RETRIES) {
94
+ runtime.ctx.traceInfo(`[orch] max retries exhausted, waiting for user input`);
95
+ publishStatus(runtime, "error", {
96
+ error: `Failed after ${MAX_RETRIES} attempts: ${errorMessage}`,
97
+ retriesExhausted: true,
98
+ });
99
+ state.retryCount = 0;
100
+ return;
101
+ }
102
+ const retryDelay = 15 * Math.pow(2, state.retryCount - 1);
103
+ publishStatus(runtime, "error", {
104
+ error: `${errorMessage} (retry ${state.retryCount}/${MAX_RETRIES} in ${retryDelay}s)`,
105
+ });
106
+ runtime.ctx.traceInfo(`[orch] retrying in ${retryDelay}s${rc.phase === "turn.result.error" ? " after turn error" : ""}`);
107
+ if (state.blobEnabled) {
108
+ yield* dehydrateForNextTurn(runtime, "error", true, {
109
+ detail: errorMessage,
110
+ error: errorMessage,
111
+ phase: rc.phase,
112
+ retryAttempt: state.retryCount,
113
+ maxRetries: MAX_RETRIES,
114
+ retryDelaySeconds: retryDelay,
115
+ });
116
+ }
117
+ yield runtime.ctx.scheduleTimer(retryDelay * 1000);
118
+ yield* versionedContinueAsNew(runtime, continueInput(runtime, retryContinueOverrides(state, rc)));
119
+ }
120
+ // ─── processPrompt: hydrate → runTurn → handleTurnResult ────
121
+ export function* processPrompt(runtime, promptText, isBootstrap, requiredTool, clientMessageIds) {
122
+ const { ctx, state } = runtime;
123
+ let prompt = promptText;
124
+ let promptIsBootstrap = isBootstrap;
125
+ if (state.blobEnabled && !state.needsHydration) {
126
+ try {
127
+ state.needsHydration = yield runtime.session.needsHydration();
128
+ }
129
+ catch (err) {
130
+ ctx.traceInfo(`[orch] needsHydration probe failed: ${err.message ?? err}`);
131
+ }
132
+ }
133
+ if (state.needsHydration && state.blobEnabled && prompt) {
134
+ prompt = wrapWithResumeContext(runtime, prompt);
135
+ }
136
+ let turnSystemPrompt = state.pendingSystemPrompt;
137
+ state.pendingSystemPrompt = undefined;
138
+ const extractedPrompt = extractPromptSystemContext(prompt);
139
+ prompt = extractedPrompt.prompt ?? "";
140
+ turnSystemPrompt = mergePrompt(turnSystemPrompt, extractedPrompt.systemPrompt);
141
+ const systemOnlyTurn = !prompt && !!turnSystemPrompt;
142
+ if (systemOnlyTurn) {
143
+ prompt = INTERNAL_SYSTEM_TURN_PROMPT;
144
+ promptIsBootstrap = true;
145
+ }
146
+ state.config.turnSystemPrompt = turnSystemPrompt;
147
+ ctx.traceInfo(`[turn ${state.iteration}] session=${runtime.input.sessionId} prompt="${prompt.slice(0, 80)}"`);
148
+ if (state.needsHydration && state.blobEnabled) {
149
+ let hydrateAttempts = 0;
150
+ while (true) {
151
+ try {
152
+ if (!state.preserveAffinityOnHydrate) {
153
+ state.affinityKey = yield ctx.newGuid();
154
+ }
155
+ runtime.session = createSessionProxy(ctx, runtime.input.sessionId, state.affinityKey, state.config);
156
+ yield runtime.session.hydrate();
157
+ state.needsHydration = false;
158
+ state.preserveAffinityOnHydrate = false;
159
+ state.lastLiveSessionAction = "session-activity";
160
+ break;
161
+ }
162
+ catch (hydrateErr) {
163
+ const hMsg = hydrateErr.message || String(hydrateErr);
164
+ if (hMsg.includes("blob does not exist")
165
+ || hMsg.includes("BlobNotFound")
166
+ || hMsg.includes("Session archive not found")
167
+ || hMsg.includes("404")) {
168
+ ctx.traceInfo(`[orch] hydrate skipped — blob not found, starting fresh session`);
169
+ state.needsHydration = false;
170
+ state.preserveAffinityOnHydrate = false;
171
+ break;
172
+ }
173
+ hydrateAttempts++;
174
+ ctx.traceInfo(`[orch] hydrate FAILED (attempt ${hydrateAttempts}/${MAX_RETRIES}): ${hMsg}`);
175
+ if (hydrateAttempts >= MAX_RETRIES) {
176
+ publishStatus(runtime, "error", {
177
+ error: `Hydrate failed after ${MAX_RETRIES} attempts: ${hMsg}`,
178
+ retriesExhausted: true,
179
+ });
180
+ break;
181
+ }
182
+ const hydrateDelay = 10 * Math.pow(2, hydrateAttempts - 1);
183
+ publishStatus(runtime, "error", {
184
+ error: `Hydrate failed: ${hMsg} (retry ${hydrateAttempts}/${MAX_RETRIES} in ${hydrateDelay}s)`,
185
+ });
186
+ yield ctx.scheduleTimer(hydrateDelay * 1000);
187
+ }
188
+ }
189
+ if (state.needsHydration)
190
+ return;
191
+ }
192
+ if (state.config.agentIdentity !== "facts-manager") {
193
+ try {
194
+ yield runtime.manager.loadKnowledgeIndex();
195
+ }
196
+ catch (knErr) {
197
+ ctx.traceInfo(`[orch] loadKnowledgeIndex failed (non-fatal): ${knErr.message || knErr}`);
198
+ }
199
+ }
200
+ publishStatus(runtime, "running", { iteration: state.iteration + 1 });
201
+ let turnResult;
202
+ try {
203
+ state.lastLiveSessionAction = "session-activity";
204
+ turnResult = yield runtime.session.runTurn(prompt, promptIsBootstrap, state.iteration, {
205
+ ...(runtime.options.parentSessionId ? { parentSessionId: runtime.options.parentSessionId } : {}),
206
+ nestingLevel: runtime.options.nestingLevel,
207
+ ...(requiredTool ? { requiredTool } : {}),
208
+ retryCount: state.retryCount,
209
+ ...(clientMessageIds && clientMessageIds.length > 0 ? { clientMessageIds } : {}),
210
+ });
211
+ }
212
+ catch (err) {
213
+ state.config.turnSystemPrompt = undefined;
214
+ const errorMsg = err.message || String(err);
215
+ const missingStateIndex = errorMsg.indexOf(SESSION_STATE_MISSING_PREFIX);
216
+ if (missingStateIndex >= 0) {
217
+ const fatalError = errorMsg.slice(missingStateIndex + SESSION_STATE_MISSING_PREFIX.length).trim();
218
+ ctx.traceInfo(`[orch] fatal missing session state: ${fatalError}`);
219
+ publishStatus(runtime, "failed", { error: fatalError, fatal: true });
220
+ yield runtime.manager.updateCmsState(runtime.input.sessionId, "failed", fatalError);
221
+ throw new Error(fatalError);
222
+ }
223
+ if (isAuthFailureError(errorMsg)) {
224
+ const blockedDetail = `${errorMsg} — ${AUTH_FAILURE_USER_HINT}`;
225
+ ctx.traceInfo(`[orch] runTurn FAILED with auth error; not retrying: ${errorMsg}`);
226
+ publishStatus(runtime, "error", {
227
+ error: blockedDetail,
228
+ retriesExhausted: true,
229
+ authFailure: true,
230
+ });
231
+ state.retryCount = 0;
232
+ return;
233
+ }
234
+ state.retryCount++;
235
+ ctx.traceInfo(`[orch] runTurn FAILED (attempt ${state.retryCount}/${MAX_RETRIES}): ${errorMsg}`);
236
+ const rc = {
237
+ sourcePrompt: prompt,
238
+ systemOnlyTurn,
239
+ requiredTool,
240
+ turnSystemPrompt,
241
+ phase: "runTurn.throw",
242
+ };
243
+ if (isCopilotConnectionClosedError(errorMsg)) {
244
+ yield* handleConnectionClosedRetry(runtime, errorMsg, rc);
245
+ return;
246
+ }
247
+ yield* handleGenericRetry(runtime, errorMsg, rc);
248
+ return;
249
+ }
250
+ state.config.turnSystemPrompt = undefined;
251
+ state.retryCount = 0;
252
+ const result = typeof turnResult === "string" ? JSON.parse(turnResult) : turnResult;
253
+ const observedAt = yield ctx.utcNow();
254
+ state.contextUsage = updateContextUsageFromEvents(state.contextUsage, result?.events, observedAt);
255
+ state.iteration++;
256
+ yield* maybeSummarize(runtime);
257
+ yield* refreshTrackedSubAgents(runtime);
258
+ if ("queuedActions" in result && Array.isArray(result.queuedActions) && result.queuedActions.length > 0) {
259
+ state.pendingToolActions.push(...result.queuedActions);
260
+ ctx.traceInfo(`[orch] queued ${result.queuedActions.length} extra action(s) from turn`);
261
+ }
262
+ yield* drainLeadingQueuedScheduleActions(runtime, prompt);
263
+ yield* handleTurnResult(runtime, result, prompt);
264
+ }
265
+ // ─── handleTurnResult: dispatch on TurnResult variant ───────
266
+ function coerceChildQuestionToWait(runtime, result) {
267
+ if (result.type === "completed"
268
+ && runtime.options.parentSessionId
269
+ && typeof result.content === "string"
270
+ && /^QUESTION FOR PARENT:/i.test(result.content.trim())) {
271
+ runtime.ctx.traceInfo("[orch] coercing child QUESTION FOR PARENT result into durable wait");
272
+ return {
273
+ type: "wait",
274
+ seconds: 60,
275
+ reason: "waiting for parent answer",
276
+ content: result.content.trim(),
277
+ model: result.model,
278
+ };
279
+ }
280
+ return result;
281
+ }
282
+ function* synthesizeWaitInterruptReplyIfNeeded(runtime, result) {
283
+ if (runtime.state.interruptedWaitTimer?.interruptKind === "user"
284
+ && (result.type === "completed" || result.type === "wait")
285
+ && !(typeof result.content === "string" && result.content.trim())) {
286
+ const content = "I'm here. Resuming the timer.";
287
+ const next = { ...result, content };
288
+ yield runtime.manager.recordSessionEvent(runtime.input.sessionId, [{
289
+ eventType: "assistant.message",
290
+ data: {
291
+ content,
292
+ synthetic: true,
293
+ reason: "wait_interrupt_empty_reply",
294
+ },
295
+ }]);
296
+ runtime.ctx.traceInfo("[orch] synthesized visible assistant reply for wait interrupt");
297
+ return next;
298
+ }
299
+ return result;
300
+ }
301
+ export function* handleTurnResult(runtime, result, sourcePrompt) {
302
+ const { ctx, state, options } = runtime;
303
+ result = coerceChildQuestionToWait(runtime, result);
304
+ result = yield* synthesizeWaitInterruptReplyIfNeeded(runtime, result);
305
+ switch (result.type) {
306
+ case "completed": {
307
+ ctx.traceInfo(`[response] ${result.content}`);
308
+ yield* writeLatestResponse(runtime, {
309
+ iteration: state.iteration,
310
+ type: "completed",
311
+ content: result.content,
312
+ model: result.model,
313
+ });
314
+ if (options.parentSessionId) {
315
+ const wakeDecision = shouldWakeParentForChildUpdate({
316
+ update: { kind: "completed", summary: result.content },
317
+ contract: state.config.childContract,
318
+ });
319
+ if (wakeDecision.wake) {
320
+ try {
321
+ yield runtime.manager.sendToSession(options.parentSessionId, `[CHILD_UPDATE from=${runtime.input.sessionId} type=completed iter=${state.iteration}]\n${result.content.slice(0, 2000)}`);
322
+ }
323
+ catch (err) {
324
+ ctx.traceInfo(`[orch] sendToSession(parent) failed: ${err.message} (non-fatal)`);
325
+ }
326
+ }
327
+ else {
328
+ yield runtime.manager.recordSessionEvent(runtime.input.sessionId, [{
329
+ eventType: "session.child_update_suppressed",
330
+ data: { direction: "child_to_parent", updateType: "completed", ...wakeDecision },
331
+ }]);
332
+ }
333
+ if (runtime.input.isSystem && !state.cronSchedule && !state.cronAtSchedule) {
334
+ ctx.traceInfo(`[orch] system sub-agent completed turn, continuing loop`);
335
+ yield* maybeCheckpoint(runtime);
336
+ return;
337
+ }
338
+ }
339
+ // Forgotten-timer safety net
340
+ {
341
+ const runningAgents = state.subAgents.filter(a => a.status === "running");
342
+ if (runningAgents.length > 0 && !runtime.input.forgottenTimerNudged && !state.cronSchedule && !state.cronAtSchedule) {
343
+ const names = runningAgents.map(a => a.task?.slice(0, 40) || a.orchId).join(", ");
344
+ ctx.traceInfo(`[orch] forgotten-timer safety: ${runningAgents.length} agents still running, nudging LLM`);
345
+ yield* versionedContinueAsNew(runtime, continueInputWithPrompt(runtime, `[SYSTEM: You ended your turn without calling wait(), but you have ${runningAgents.length} sub-agent(s) still running: ${names}. ` +
346
+ `Without a wait() call, your monitoring/polling loop is DEAD — the orchestration will NOT wake you up automatically. ` +
347
+ `You MUST call wait() now to schedule your next check-in. Call wait() with an appropriate interval to continue your loop.]`, { forgottenTimerNudged: true }));
348
+ return;
349
+ }
350
+ }
351
+ if (state.interruptedWaitTimer && state.interruptedWaitTimer.remainingSec > 0) {
352
+ const saved = state.interruptedWaitTimer;
353
+ state.interruptedWaitTimer = null;
354
+ ctx.traceInfo(`[orch] auto-resuming interrupted wait: ${saved.remainingSec}s (${saved.reason})`);
355
+ if (saved.shouldRehydrate) {
356
+ yield* dehydrateForNextTurn(runtime, "timer", saved.waitPlan?.resetAffinityOnDehydrate ?? true);
357
+ }
358
+ const resumeNow = yield ctx.utcNow();
359
+ publishStatus(runtime, "waiting", {
360
+ waitSeconds: saved.remainingSec,
361
+ waitReason: saved.reason,
362
+ waitStartedAt: resumeNow,
363
+ });
364
+ if (!saved.shouldRehydrate)
365
+ yield* maybeCheckpoint(runtime);
366
+ state.activeTimer = {
367
+ deadlineMs: resumeNow + saved.remainingSec * 1000,
368
+ originalDurationMs: saved.remainingSec * 1000,
369
+ reason: saved.reason,
370
+ type: "wait",
371
+ shouldRehydrate: saved.shouldRehydrate,
372
+ waitPlan: saved.waitPlan,
373
+ };
374
+ return;
375
+ }
376
+ if (state.interruptedCronTimer && state.interruptedCronTimer.remainingMs > 0) {
377
+ const saved = state.interruptedCronTimer;
378
+ state.interruptedCronTimer = null;
379
+ const remainingMs = Math.max(0, saved.remainingMs);
380
+ const remainingSec = Math.max(1, Math.round(remainingMs / 1000));
381
+ ctx.traceInfo(`[orch] auto-resuming interrupted cron: ${remainingSec}s remain (${saved.reason})`);
382
+ const cronResumePlan = planWaitHandling({
383
+ blobEnabled: state.blobEnabled,
384
+ seconds: remainingSec,
385
+ dehydrateThreshold: options.dehydrateThreshold,
386
+ });
387
+ if (cronResumePlan.shouldDehydrate) {
388
+ yield* dehydrateForNextTurn(runtime, "cron", cronResumePlan.resetAffinityOnDehydrate);
389
+ }
390
+ const resumeNow = yield ctx.utcNow();
391
+ publishStatus(runtime, "waiting", {
392
+ waitSeconds: remainingSec,
393
+ waitReason: saved.reason,
394
+ waitStartedAt: resumeNow,
395
+ });
396
+ if (!cronResumePlan.shouldDehydrate)
397
+ yield* maybeCheckpoint(runtime);
398
+ state.activeTimer = {
399
+ deadlineMs: resumeNow + remainingMs,
400
+ originalDurationMs: remainingMs,
401
+ reason: saved.reason,
402
+ type: "cron",
403
+ shouldRehydrate: cronResumePlan.shouldDehydrate,
404
+ };
405
+ return;
406
+ }
407
+ if (state.cronSchedule) {
408
+ const activeCron = { ...state.cronSchedule };
409
+ const cronPlan = planWaitHandling({
410
+ blobEnabled: state.blobEnabled,
411
+ seconds: activeCron.intervalSeconds,
412
+ dehydrateThreshold: options.dehydrateThreshold,
413
+ });
414
+ if (cronPlan.shouldDehydrate) {
415
+ yield* dehydrateForNextTurn(runtime, "cron", cronPlan.resetAffinityOnDehydrate);
416
+ }
417
+ yield runtime.manager.recordSessionEvent(runtime.input.sessionId, [{
418
+ eventType: "session.cron_started",
419
+ data: { intervalSeconds: activeCron.intervalSeconds, reason: activeCron.reason },
420
+ }]);
421
+ const cronStartedAt = yield ctx.utcNow();
422
+ ctx.traceInfo(`[orch] cron timer: ${activeCron.intervalSeconds}s (${activeCron.reason})`);
423
+ publishStatus(runtime, "waiting", {
424
+ waitSeconds: activeCron.intervalSeconds,
425
+ waitReason: activeCron.reason,
426
+ waitStartedAt: cronStartedAt,
427
+ });
428
+ if (!cronPlan.shouldDehydrate)
429
+ yield* maybeCheckpoint(runtime);
430
+ state.activeTimer = {
431
+ deadlineMs: cronStartedAt + activeCron.intervalSeconds * 1000,
432
+ originalDurationMs: activeCron.intervalSeconds * 1000,
433
+ reason: activeCron.reason,
434
+ type: "cron",
435
+ shouldRehydrate: cronPlan.shouldDehydrate,
436
+ };
437
+ return;
438
+ }
439
+ if (state.cronAtSchedule) {
440
+ const activeCronAt = { ...state.cronAtSchedule };
441
+ if (activeCronAt.maxFires !== undefined && activeCronAt.firesCompleted >= activeCronAt.maxFires) {
442
+ state.cronAtSchedule = undefined;
443
+ yield runtime.manager.recordSessionEvent(runtime.input.sessionId, [{
444
+ eventType: "session.cron_at_completed",
445
+ data: { reason: activeCronAt.reason, firesCompleted: activeCronAt.firesCompleted, maxFires: activeCronAt.maxFires },
446
+ }]);
447
+ return;
448
+ }
449
+ const nowMs = yield ctx.utcNow();
450
+ let nextFireAtMs = activeCronAt.nextFireAtMs;
451
+ let nextOccurrenceKey = activeCronAt.nextOccurrenceKey;
452
+ if (!nextFireAtMs || !nextOccurrenceKey) {
453
+ const nextFire = yield runtime.manager.computeCronAtNextFire(activeCronAt, nowMs, activeCronAt.lastOccurrenceKey);
454
+ nextFireAtMs = nextFire.nextFireAtMs;
455
+ nextOccurrenceKey = nextFire.occurrenceKey;
456
+ state.cronAtSchedule = {
457
+ ...activeCronAt,
458
+ nextFireAtMs,
459
+ nextOccurrenceKey,
460
+ };
461
+ }
462
+ if (nextFireAtMs === undefined || !nextOccurrenceKey) {
463
+ throw new Error("cron_at next-fire computation did not return a fire time");
464
+ }
465
+ const waitMs = Math.max(0, nextFireAtMs - nowMs);
466
+ const waitSeconds = Math.max(0, Math.ceil(waitMs / 1000));
467
+ const cronAtPlan = planWaitHandling({
468
+ blobEnabled: state.blobEnabled,
469
+ seconds: waitSeconds,
470
+ dehydrateThreshold: options.dehydrateThreshold,
471
+ });
472
+ if (cronAtPlan.shouldDehydrate) {
473
+ yield* dehydrateForNextTurn(runtime, "cron_at", cronAtPlan.resetAffinityOnDehydrate);
474
+ }
475
+ yield runtime.manager.recordSessionEvent(runtime.input.sessionId, [{
476
+ eventType: "session.cron_at_started",
477
+ data: {
478
+ ...state.cronAtSchedule,
479
+ nextFireAt: new Date(nextFireAtMs).toISOString(),
480
+ },
481
+ }]);
482
+ publishStatus(runtime, "waiting", {
483
+ waitSeconds,
484
+ waitReason: activeCronAt.reason,
485
+ waitStartedAt: nowMs,
486
+ });
487
+ if (!cronAtPlan.shouldDehydrate)
488
+ yield* maybeCheckpoint(runtime);
489
+ state.activeTimer = {
490
+ deadlineMs: nowMs + waitMs,
491
+ originalDurationMs: waitMs,
492
+ reason: activeCronAt.reason,
493
+ type: "cron_at",
494
+ shouldRehydrate: cronAtPlan.shouldDehydrate,
495
+ };
496
+ return;
497
+ }
498
+ if (!state.blobEnabled || options.idleTimeout < 0) {
499
+ yield* maybeCheckpoint(runtime);
500
+ return;
501
+ }
502
+ publishStatus(runtime, "idle");
503
+ yield* maybeCheckpoint(runtime);
504
+ const idleNow = yield ctx.utcNow();
505
+ state.activeTimer = {
506
+ deadlineMs: idleNow + options.idleTimeout * 1000,
507
+ originalDurationMs: options.idleTimeout * 1000,
508
+ reason: "idle timeout",
509
+ type: "idle",
510
+ };
511
+ return;
512
+ }
513
+ case "cron":
514
+ applyCronAction(runtime, result, sourcePrompt);
515
+ return;
516
+ case "cron_at":
517
+ yield* applyCronAtAction(runtime, result, sourcePrompt);
518
+ return;
519
+ case "wait": {
520
+ state.interruptedWaitTimer = null;
521
+ ensureTaskContext(runtime, sourcePrompt);
522
+ if (options.parentSessionId) {
523
+ const notifyContent = result.content
524
+ ? result.content.slice(0, 2000)
525
+ : `[wait: ${result.reason} (${result.seconds}s)]`;
526
+ const wakeDecision = shouldWakeParentForChildUpdate({
527
+ update: { kind: "wait", summary: notifyContent },
528
+ contract: state.config.childContract,
529
+ });
530
+ if (wakeDecision.wake) {
531
+ try {
532
+ yield runtime.manager.sendToSession(options.parentSessionId, `[CHILD_UPDATE from=${runtime.input.sessionId} type=wait iter=${state.iteration}]\n${notifyContent}`);
533
+ }
534
+ catch (err) {
535
+ ctx.traceInfo(`[orch] sendToSession(parent) wait failed: ${err.message} (non-fatal)`);
536
+ }
537
+ }
538
+ else {
539
+ yield runtime.manager.recordSessionEvent(runtime.input.sessionId, [{
540
+ eventType: "session.child_update_suppressed",
541
+ data: { direction: "child_to_parent", updateType: "wait", ...wakeDecision },
542
+ }]);
543
+ }
544
+ }
545
+ ctx.traceInfo(`[orch] durable timer: ${result.seconds}s (${result.reason})`);
546
+ const waitPlan = planWaitHandling({
547
+ blobEnabled: state.blobEnabled,
548
+ seconds: result.seconds,
549
+ dehydrateThreshold: options.dehydrateThreshold,
550
+ preserveWorkerAffinity: result.preserveWorkerAffinity,
551
+ });
552
+ if (waitPlan.shouldDehydrate) {
553
+ yield* dehydrateForNextTurn(runtime, "timer", waitPlan.resetAffinityOnDehydrate);
554
+ }
555
+ const waitStartedAt = yield ctx.utcNow();
556
+ if (result.content) {
557
+ yield* writeLatestResponse(runtime, {
558
+ iteration: state.iteration,
559
+ type: "wait",
560
+ content: result.content,
561
+ waitReason: result.reason,
562
+ waitSeconds: result.seconds,
563
+ waitStartedAt,
564
+ model: result.model,
565
+ });
566
+ ctx.traceInfo(`[orch] intermediate: ${result.content.slice(0, 80)}`);
567
+ }
568
+ publishStatus(runtime, "waiting", {
569
+ waitSeconds: result.seconds,
570
+ waitReason: result.reason,
571
+ waitStartedAt,
572
+ preserveWorkerAffinity: waitPlan.preserveAffinityOnHydrate,
573
+ });
574
+ if (!waitPlan.shouldDehydrate)
575
+ yield* maybeCheckpoint(runtime);
576
+ yield runtime.manager.recordSessionEvent(runtime.input.sessionId, [{
577
+ eventType: "session.wait_started",
578
+ data: { seconds: result.seconds, reason: result.reason, preserveAffinity: waitPlan.preserveAffinityOnHydrate },
579
+ }]);
580
+ state.activeTimer = {
581
+ deadlineMs: waitStartedAt + result.seconds * 1000,
582
+ originalDurationMs: result.seconds * 1000,
583
+ reason: result.reason,
584
+ type: "wait",
585
+ shouldRehydrate: waitPlan.shouldDehydrate,
586
+ waitPlan,
587
+ content: result.content,
588
+ };
589
+ return;
590
+ }
591
+ case "input_required": {
592
+ ctx.traceInfo(`[orch] waiting for user input: ${result.question}`);
593
+ yield* writeLatestResponse(runtime, {
594
+ iteration: state.iteration,
595
+ type: "input_required",
596
+ question: result.question,
597
+ choices: result.choices,
598
+ allowFreeform: result.allowFreeform,
599
+ model: result.model,
600
+ });
601
+ state.pendingInputQuestion = {
602
+ question: result.question,
603
+ choices: result.choices,
604
+ allowFreeform: result.allowFreeform,
605
+ };
606
+ publishStatus(runtime, "input_required");
607
+ if (!state.blobEnabled || options.inputGracePeriod < 0) {
608
+ yield* maybeCheckpoint(runtime);
609
+ return;
610
+ }
611
+ if (options.inputGracePeriod === 0) {
612
+ yield* dehydrateForNextTurn(runtime, "input_required");
613
+ return;
614
+ }
615
+ const graceNow = yield ctx.utcNow();
616
+ state.activeTimer = {
617
+ deadlineMs: graceNow + options.inputGracePeriod * 1000,
618
+ originalDurationMs: options.inputGracePeriod * 1000,
619
+ reason: "input grace period",
620
+ type: "input-grace",
621
+ question: result.question,
622
+ choices: result.choices,
623
+ allowFreeform: result.allowFreeform,
624
+ };
625
+ return;
626
+ }
627
+ case "cancelled":
628
+ ctx.traceInfo("[session] turn cancelled");
629
+ return;
630
+ case "spawn_agent":
631
+ case "message_agent":
632
+ case "check_agents":
633
+ case "list_sessions":
634
+ case "wait_for_agents":
635
+ case "complete_agent":
636
+ case "cancel_agent":
637
+ case "delete_agent":
638
+ yield* handleSubAgentAction(runtime, result);
639
+ return;
640
+ case "error": {
641
+ const missingStateIndex = result.message.indexOf(SESSION_STATE_MISSING_PREFIX);
642
+ if (missingStateIndex >= 0) {
643
+ const fatalError = result.message.slice(missingStateIndex + SESSION_STATE_MISSING_PREFIX.length).trim();
644
+ ctx.traceInfo(`[orch] fatal missing session state: ${fatalError}`);
645
+ publishStatus(runtime, "failed", { error: fatalError, fatal: true });
646
+ yield runtime.manager.updateCmsState(runtime.input.sessionId, "failed", fatalError);
647
+ throw new Error(fatalError);
648
+ }
649
+ state.retryCount++;
650
+ ctx.traceInfo(`[orch] turn returned error (attempt ${state.retryCount}/${MAX_RETRIES}): ${result.message}`);
651
+ const rc = {
652
+ sourcePrompt,
653
+ systemOnlyTurn: false,
654
+ phase: "turn.result.error",
655
+ };
656
+ if (isCopilotConnectionClosedError(result.message)) {
657
+ yield* handleConnectionClosedRetry(runtime, result.message, rc);
658
+ return;
659
+ }
660
+ yield* handleGenericRetry(runtime, result.message, rc);
661
+ return;
662
+ }
663
+ }
664
+ }
665
+ // ─── processTimer: handle fired timers by type ──────────────
666
+ export function* processTimer(runtime, timerItem) {
667
+ const { ctx, state } = runtime;
668
+ const timer = timerItem.timer;
669
+ switch (timer.type) {
670
+ case "wait": {
671
+ const seconds = Math.round(timer.originalDurationMs / 1000);
672
+ yield runtime.manager.recordSessionEvent(runtime.input.sessionId, [{
673
+ eventType: "session.wait_completed",
674
+ data: { seconds },
675
+ }]);
676
+ const timerPrompt = `The ${seconds} second wait is now complete. Continue with your task.`;
677
+ const resumeSystemPrompt = [
678
+ timer.reason ? `Wait reason: "${timer.reason}".` : undefined,
679
+ state.taskContext ? `Original user request: "${state.taskContext}".` : undefined,
680
+ "Resume the interrupted task now.",
681
+ "Do not treat this as a new unrelated user request.",
682
+ "Do not call wait() again for the delay that already finished.",
683
+ ].filter(Boolean).join(" ");
684
+ yield* processPrompt(runtime, appendSystemContext(timerPrompt, resumeSystemPrompt) ?? timerPrompt, false);
685
+ return;
686
+ }
687
+ case "cron": {
688
+ yield runtime.manager.recordSessionEvent(runtime.input.sessionId, [{
689
+ eventType: "session.cron_fired",
690
+ data: {},
691
+ }]);
692
+ const activeCron = state.cronSchedule;
693
+ const cronPrompt = `[SYSTEM: Scheduled cron wake-up for: "${activeCron.reason}". Resume your recurring task.]`;
694
+ if (timer.shouldRehydrate) {
695
+ yield* processPrompt(runtime, wrapWithResumeContext(runtime, "Resume your recurring task.", `Scheduled cron wake-up for: "${activeCron.reason}".`), true);
696
+ }
697
+ else {
698
+ yield* processPrompt(runtime, cronPrompt, true);
699
+ }
700
+ return;
701
+ }
702
+ case "cron_at": {
703
+ const activeCronAt = state.cronAtSchedule;
704
+ if (!activeCronAt) {
705
+ ctx.traceInfo("[orch] cron_at timer fired but no active cronAtSchedule exists");
706
+ return;
707
+ }
708
+ const scheduledAtMs = activeCronAt.nextFireAtMs ?? timer.deadlineMs;
709
+ const occurrenceKey = activeCronAt.nextOccurrenceKey;
710
+ yield runtime.manager.recordSessionEvent(runtime.input.sessionId, [{
711
+ eventType: "session.cron_at_fired",
712
+ data: {
713
+ scheduledAt: new Date(scheduledAtMs).toISOString(),
714
+ occurrenceKey,
715
+ tz: activeCronAt.tz,
716
+ minute: activeCronAt.minute,
717
+ hour: activeCronAt.hour,
718
+ dayOfWeek: activeCronAt.dayOfWeek,
719
+ dayOfMonth: activeCronAt.dayOfMonth,
720
+ firesCompleted: activeCronAt.firesCompleted + 1,
721
+ },
722
+ }]);
723
+ const firedSchedule = {
724
+ ...activeCronAt,
725
+ firesCompleted: activeCronAt.firesCompleted + 1,
726
+ ...(occurrenceKey ? { lastOccurrenceKey: occurrenceKey } : {}),
727
+ nextFireAtMs: undefined,
728
+ nextOccurrenceKey: undefined,
729
+ };
730
+ const finalFire = firedSchedule.maxFires !== undefined && firedSchedule.firesCompleted >= firedSchedule.maxFires;
731
+ state.cronAtSchedule = finalFire ? undefined : firedSchedule;
732
+ if (finalFire) {
733
+ yield runtime.manager.recordSessionEvent(runtime.input.sessionId, [{
734
+ eventType: "session.cron_at_completed",
735
+ data: { reason: firedSchedule.reason, firesCompleted: firedSchedule.firesCompleted, maxFires: firedSchedule.maxFires },
736
+ }]);
737
+ }
738
+ const description = describeCronAt(activeCronAt);
739
+ const cronAtPrompt = `[SYSTEM: Scheduled wall-clock cron wake-up for "${activeCronAt.reason}". ` +
740
+ `Schedule: ${description}. Scheduled fire: ${new Date(scheduledAtMs).toISOString()}. ` +
741
+ `Resume your recurring task now.]`;
742
+ if (timer.shouldRehydrate) {
743
+ yield* processPrompt(runtime, wrapWithResumeContext(runtime, "Resume your recurring task.", `Scheduled wall-clock cron wake-up for "${activeCronAt.reason}". ` +
744
+ `Schedule: ${description}. Scheduled fire: ${new Date(scheduledAtMs).toISOString()}.`), true);
745
+ }
746
+ else {
747
+ yield* processPrompt(runtime, cronAtPrompt, true);
748
+ }
749
+ return;
750
+ }
751
+ case "idle": {
752
+ ctx.traceInfo("[session] idle timeout, dehydrating");
753
+ yield* dehydrateForNextTurn(runtime, "idle");
754
+ return;
755
+ }
756
+ case "agent-poll": {
757
+ if (state.waitingForAgentIds) {
758
+ const stillRunning = state.waitingForAgentIds.filter(id => {
759
+ const agent = state.subAgents.find(a => a.orchId === id);
760
+ return agent && !isSubAgentTerminalStatus(agent.status);
761
+ });
762
+ ctx.traceInfo(`[orch] wait_for_agents: fallback poll, checking ${stillRunning.length} agents`);
763
+ for (const targetId of stillRunning) {
764
+ const agent = state.subAgents.find(a => a.orchId === targetId);
765
+ if (!agent || isSubAgentTerminalStatus(agent.status))
766
+ continue;
767
+ try {
768
+ const rawStatus = yield runtime.manager.getSessionStatus(agent.sessionId);
769
+ const parsed = JSON.parse(rawStatus);
770
+ if (parsed.status === "failed") {
771
+ agent.status = "failed";
772
+ }
773
+ else if (parsed.status === "completed") {
774
+ agent.status = "completed";
775
+ }
776
+ else if (parsed.status === "cancelled") {
777
+ agent.status = "cancelled";
778
+ }
779
+ else if (parsed.status === "waiting") {
780
+ agent.status = "waiting";
781
+ }
782
+ if (parsed.result) {
783
+ agent.result = parsed.result.slice(0, 2000);
784
+ }
785
+ }
786
+ catch { }
787
+ }
788
+ if (yield* maybeResolveAgentWaitCompletion(runtime)) {
789
+ return;
790
+ }
791
+ const nowRunning = getStillRunningAgentIds(state.subAgents, state.waitingForAgentIds);
792
+ if (state.pendingShutdown) {
793
+ const now = yield ctx.utcNow();
794
+ if (now >= state.pendingShutdown.deadlineAtMs) {
795
+ const timeoutMessage = `Graceful ${state.pendingShutdown.mode} timed out after ${Math.round(SHUTDOWN_TIMEOUT_MS / 1000)}s ` +
796
+ `waiting for ${nowRunning.length} child session(s): ${nowRunning.join(", ") || "unknown"}`;
797
+ yield* failPendingShutdown(runtime, timeoutMessage);
798
+ return;
799
+ }
800
+ const remainingMs = Math.max(0, state.pendingShutdown.deadlineAtMs - now);
801
+ const nextPollMs = Math.min(SHUTDOWN_POLL_INTERVAL_MS, remainingMs);
802
+ state.activeTimer = {
803
+ deadlineMs: now + nextPollMs,
804
+ originalDurationMs: nextPollMs,
805
+ reason: buildShutdownWaitReason(state.pendingShutdown),
806
+ type: "agent-poll",
807
+ agentIds: state.waitingForAgentIds,
808
+ };
809
+ publishStatus(runtime, "waiting", {
810
+ waitReason: buildShutdownWaitReason(state.pendingShutdown),
811
+ waitStartedAt: state.pendingShutdown.startedAtMs,
812
+ waitSeconds: Math.ceil(remainingMs / 1000),
813
+ });
814
+ }
815
+ else {
816
+ const now = yield ctx.utcNow();
817
+ state.activeTimer = {
818
+ deadlineMs: now + 30_000,
819
+ originalDurationMs: 30_000,
820
+ reason: `waiting for ${nowRunning.length} agent(s)`,
821
+ type: "agent-poll",
822
+ agentIds: state.waitingForAgentIds,
823
+ };
824
+ }
825
+ }
826
+ return;
827
+ }
828
+ case "input-grace": {
829
+ yield* dehydrateForNextTurn(runtime, "input_required");
830
+ return;
831
+ }
832
+ }
833
+ }
834
+ //# sourceMappingURL=turn.js.map