pilotswarm-sdk 0.5.19 → 0.5.21

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (201) hide show
  1. package/api/src/http-api-transport.js +4 -0
  2. package/api/src/protocol.js +2 -0
  3. package/dist/blob-store.d.ts +42 -11
  4. package/dist/blob-store.d.ts.map +1 -1
  5. package/dist/blob-store.js +163 -53
  6. package/dist/blob-store.js.map +1 -1
  7. package/dist/cms-migrations.d.ts.map +1 -1
  8. package/dist/cms-migrations.js +231 -0
  9. package/dist/cms-migrations.js.map +1 -1
  10. package/dist/cms.d.ts +62 -0
  11. package/dist/cms.d.ts.map +1 -1
  12. package/dist/cms.js +81 -4
  13. package/dist/cms.js.map +1 -1
  14. package/dist/distiller-tools.d.ts +9 -0
  15. package/dist/distiller-tools.d.ts.map +1 -0
  16. package/dist/distiller-tools.js +102 -0
  17. package/dist/distiller-tools.js.map +1 -0
  18. package/dist/footprint.d.ts +129 -0
  19. package/dist/footprint.d.ts.map +1 -0
  20. package/dist/footprint.js +254 -0
  21. package/dist/footprint.js.map +1 -0
  22. package/dist/inspect-tools.d.ts.map +1 -1
  23. package/dist/inspect-tools.js +63 -2
  24. package/dist/inspect-tools.js.map +1 -1
  25. package/dist/managed-session.d.ts.map +1 -1
  26. package/dist/managed-session.js +140 -14
  27. package/dist/managed-session.js.map +1 -1
  28. package/dist/management-client.d.ts +32 -0
  29. package/dist/management-client.d.ts.map +1 -1
  30. package/dist/management-client.js +93 -0
  31. package/dist/management-client.js.map +1 -1
  32. package/dist/orchestration/agents.d.ts +18 -0
  33. package/dist/orchestration/agents.d.ts.map +1 -1
  34. package/dist/orchestration/agents.js +63 -3
  35. package/dist/orchestration/agents.js.map +1 -1
  36. package/dist/orchestration/index.d.ts +2 -2
  37. package/dist/orchestration/index.js +1 -1
  38. package/dist/orchestration/lifecycle.d.ts +1 -0
  39. package/dist/orchestration/lifecycle.d.ts.map +1 -1
  40. package/dist/orchestration/lifecycle.js +349 -1
  41. package/dist/orchestration/lifecycle.js.map +1 -1
  42. package/dist/orchestration/queue.d.ts.map +1 -1
  43. package/dist/orchestration/queue.js +61 -2
  44. package/dist/orchestration/queue.js.map +1 -1
  45. package/dist/orchestration/runtime.d.ts +1 -1
  46. package/dist/orchestration/runtime.d.ts.map +1 -1
  47. package/dist/orchestration/runtime.js +31 -1
  48. package/dist/orchestration/runtime.js.map +1 -1
  49. package/dist/orchestration/state.d.ts +29 -0
  50. package/dist/orchestration/state.d.ts.map +1 -1
  51. package/dist/orchestration/state.js +11 -0
  52. package/dist/orchestration/state.js.map +1 -1
  53. package/dist/orchestration/turn.d.ts.map +1 -1
  54. package/dist/orchestration/turn.js +68 -1
  55. package/dist/orchestration/turn.js.map +1 -1
  56. package/dist/orchestration-registry.d.ts.map +1 -1
  57. package/dist/orchestration-registry.js +8 -2
  58. package/dist/orchestration-registry.js.map +1 -1
  59. package/dist/orchestration-version.d.ts +1 -1
  60. package/dist/orchestration-version.js +1 -1
  61. package/dist/orchestration.d.ts +2 -2
  62. package/dist/orchestration.js +1 -1
  63. package/dist/orchestration_1_0_46.d.ts +1 -1
  64. package/dist/orchestration_1_0_47.d.ts +1 -1
  65. package/dist/orchestration_1_0_48.d.ts +1 -1
  66. package/dist/orchestration_1_0_49.d.ts +1 -1
  67. package/dist/orchestration_1_0_50.d.ts +1 -1
  68. package/dist/orchestration_1_0_65/agents.d.ts +43 -0
  69. package/dist/orchestration_1_0_65/agents.d.ts.map +1 -0
  70. package/dist/orchestration_1_0_65/agents.js +780 -0
  71. package/dist/orchestration_1_0_65/agents.js.map +1 -0
  72. package/dist/orchestration_1_0_65/index.d.ts +24 -0
  73. package/dist/orchestration_1_0_65/index.d.ts.map +1 -0
  74. package/dist/orchestration_1_0_65/index.js +13 -0
  75. package/dist/orchestration_1_0_65/index.js.map +1 -0
  76. package/dist/orchestration_1_0_65/lifecycle.d.ts +48 -0
  77. package/dist/orchestration_1_0_65/lifecycle.d.ts.map +1 -0
  78. package/dist/orchestration_1_0_65/lifecycle.js +572 -0
  79. package/dist/orchestration_1_0_65/lifecycle.js.map +1 -0
  80. package/dist/orchestration_1_0_65/queue.d.ts +7 -0
  81. package/dist/orchestration_1_0_65/queue.d.ts.map +1 -0
  82. package/dist/orchestration_1_0_65/queue.js +745 -0
  83. package/dist/orchestration_1_0_65/queue.js.map +1 -0
  84. package/dist/orchestration_1_0_65/runtime.d.ts +29 -0
  85. package/dist/orchestration_1_0_65/runtime.d.ts.map +1 -0
  86. package/dist/orchestration_1_0_65/runtime.js +195 -0
  87. package/dist/orchestration_1_0_65/runtime.js.map +1 -0
  88. package/dist/orchestration_1_0_65/state.d.ts +147 -0
  89. package/dist/orchestration_1_0_65/state.d.ts.map +1 -0
  90. package/dist/orchestration_1_0_65/state.js +147 -0
  91. package/dist/orchestration_1_0_65/state.js.map +1 -0
  92. package/dist/orchestration_1_0_65/turn.d.ts +34 -0
  93. package/dist/orchestration_1_0_65/turn.d.ts.map +1 -0
  94. package/dist/orchestration_1_0_65/turn.js +1144 -0
  95. package/dist/orchestration_1_0_65/turn.js.map +1 -0
  96. package/dist/orchestration_1_0_65/utils.d.ts +39 -0
  97. package/dist/orchestration_1_0_65/utils.d.ts.map +1 -0
  98. package/dist/orchestration_1_0_65/utils.js +316 -0
  99. package/dist/orchestration_1_0_65/utils.js.map +1 -0
  100. package/dist/orchestration_1_0_66/agents.d.ts +52 -0
  101. package/dist/orchestration_1_0_66/agents.d.ts.map +1 -0
  102. package/dist/orchestration_1_0_66/agents.js +825 -0
  103. package/dist/orchestration_1_0_66/agents.js.map +1 -0
  104. package/dist/orchestration_1_0_66/index.d.ts +24 -0
  105. package/dist/orchestration_1_0_66/index.d.ts.map +1 -0
  106. package/dist/orchestration_1_0_66/index.js +13 -0
  107. package/dist/orchestration_1_0_66/index.js.map +1 -0
  108. package/dist/orchestration_1_0_66/lifecycle.d.ts +48 -0
  109. package/dist/orchestration_1_0_66/lifecycle.d.ts.map +1 -0
  110. package/dist/orchestration_1_0_66/lifecycle.js +573 -0
  111. package/dist/orchestration_1_0_66/lifecycle.js.map +1 -0
  112. package/dist/orchestration_1_0_66/queue.d.ts +7 -0
  113. package/dist/orchestration_1_0_66/queue.d.ts.map +1 -0
  114. package/dist/orchestration_1_0_66/queue.js +776 -0
  115. package/dist/orchestration_1_0_66/queue.js.map +1 -0
  116. package/dist/orchestration_1_0_66/runtime.d.ts +29 -0
  117. package/dist/orchestration_1_0_66/runtime.d.ts.map +1 -0
  118. package/dist/orchestration_1_0_66/runtime.js +198 -0
  119. package/dist/orchestration_1_0_66/runtime.js.map +1 -0
  120. package/dist/orchestration_1_0_66/state.d.ts +149 -0
  121. package/dist/orchestration_1_0_66/state.d.ts.map +1 -0
  122. package/dist/orchestration_1_0_66/state.js +148 -0
  123. package/dist/orchestration_1_0_66/state.js.map +1 -0
  124. package/dist/orchestration_1_0_66/turn.d.ts +34 -0
  125. package/dist/orchestration_1_0_66/turn.d.ts.map +1 -0
  126. package/dist/orchestration_1_0_66/turn.js +1169 -0
  127. package/dist/orchestration_1_0_66/turn.js.map +1 -0
  128. package/dist/orchestration_1_0_66/utils.d.ts +39 -0
  129. package/dist/orchestration_1_0_66/utils.d.ts.map +1 -0
  130. package/dist/orchestration_1_0_66/utils.js +316 -0
  131. package/dist/orchestration_1_0_66/utils.js.map +1 -0
  132. package/dist/orchestration_1_0_67/agents.d.ts +52 -0
  133. package/dist/orchestration_1_0_67/agents.d.ts.map +1 -0
  134. package/dist/orchestration_1_0_67/agents.js +825 -0
  135. package/dist/orchestration_1_0_67/agents.js.map +1 -0
  136. package/dist/orchestration_1_0_67/index.d.ts +28 -0
  137. package/dist/orchestration_1_0_67/index.d.ts.map +1 -0
  138. package/dist/orchestration_1_0_67/index.js +13 -0
  139. package/dist/orchestration_1_0_67/index.js.map +1 -0
  140. package/dist/orchestration_1_0_67/lifecycle.d.ts +49 -0
  141. package/dist/orchestration_1_0_67/lifecycle.d.ts.map +1 -0
  142. package/dist/orchestration_1_0_67/lifecycle.js +807 -0
  143. package/dist/orchestration_1_0_67/lifecycle.js.map +1 -0
  144. package/dist/orchestration_1_0_67/queue.d.ts +7 -0
  145. package/dist/orchestration_1_0_67/queue.d.ts.map +1 -0
  146. package/dist/orchestration_1_0_67/queue.js +804 -0
  147. package/dist/orchestration_1_0_67/queue.js.map +1 -0
  148. package/dist/orchestration_1_0_67/runtime.d.ts +29 -0
  149. package/dist/orchestration_1_0_67/runtime.d.ts.map +1 -0
  150. package/dist/orchestration_1_0_67/runtime.js +228 -0
  151. package/dist/orchestration_1_0_67/runtime.js.map +1 -0
  152. package/dist/orchestration_1_0_67/state.d.ts +176 -0
  153. package/dist/orchestration_1_0_67/state.d.ts.map +1 -0
  154. package/dist/orchestration_1_0_67/state.js +158 -0
  155. package/dist/orchestration_1_0_67/state.js.map +1 -0
  156. package/dist/orchestration_1_0_67/turn.d.ts +34 -0
  157. package/dist/orchestration_1_0_67/turn.d.ts.map +1 -0
  158. package/dist/orchestration_1_0_67/turn.js +1208 -0
  159. package/dist/orchestration_1_0_67/turn.js.map +1 -0
  160. package/dist/orchestration_1_0_67/utils.d.ts +39 -0
  161. package/dist/orchestration_1_0_67/utils.d.ts.map +1 -0
  162. package/dist/orchestration_1_0_67/utils.js +316 -0
  163. package/dist/orchestration_1_0_67/utils.js.map +1 -0
  164. package/dist/regen-worker.d.ts +167 -0
  165. package/dist/regen-worker.d.ts.map +1 -0
  166. package/dist/regen-worker.js +512 -0
  167. package/dist/regen-worker.js.map +1 -0
  168. package/dist/resourcemgr-tools.d.ts.map +1 -1
  169. package/dist/resourcemgr-tools.js +33 -2
  170. package/dist/resourcemgr-tools.js.map +1 -1
  171. package/dist/session-lifecycle.d.ts +8 -0
  172. package/dist/session-lifecycle.d.ts.map +1 -1
  173. package/dist/session-lifecycle.js +35 -17
  174. package/dist/session-lifecycle.js.map +1 -1
  175. package/dist/session-manager.d.ts +21 -0
  176. package/dist/session-manager.d.ts.map +1 -1
  177. package/dist/session-manager.js +104 -11
  178. package/dist/session-manager.js.map +1 -1
  179. package/dist/session-proxy.d.ts +34 -0
  180. package/dist/session-proxy.d.ts.map +1 -1
  181. package/dist/session-proxy.js +407 -5
  182. package/dist/session-proxy.js.map +1 -1
  183. package/dist/session-store.d.ts +50 -15
  184. package/dist/session-store.d.ts.map +1 -1
  185. package/dist/session-store.js +143 -30
  186. package/dist/session-store.js.map +1 -1
  187. package/dist/snapshot-protocol.d.ts +34 -3
  188. package/dist/snapshot-protocol.d.ts.map +1 -1
  189. package/dist/snapshot-protocol.js +25 -0
  190. package/dist/snapshot-protocol.js.map +1 -1
  191. package/dist/types.d.ts +101 -1
  192. package/dist/types.d.ts.map +1 -1
  193. package/dist/types.js.map +1 -1
  194. package/dist/web/web-management-client.d.ts +11 -0
  195. package/dist/web/web-management-client.d.ts.map +1 -1
  196. package/dist/web/web-management-client.js +8 -0
  197. package/dist/web/web-management-client.js.map +1 -1
  198. package/dist/worker.d.ts.map +1 -1
  199. package/dist/worker.js +7 -0
  200. package/dist/worker.js.map +1 -1
  201. package/package.json +1 -1
@@ -0,0 +1,776 @@
1
+ import { sanitizePromptAttachmentRefs, ATTACHMENTS_MAX_COUNT } from "../types.js";
2
+ import { messageSenderKey } from "../message-sender.js";
3
+ import { applyChildUpdate, maybeResolveAgentWaitCompletion, parseChildUpdate, isAgentWaitSettledStatus, } from "./agents.js";
4
+ import { bufferChildUpdate, drainLeadingQueuedScheduleActions, flushPendingChildDigestIntoPrompt, handleCommand, promptIdsIntersectCancellation, publishStatus, queueFollowup, recordCancelledMessageIds, wrapWithResumeContext, } from "./lifecycle.js";
5
+ import { shouldWakeParentForChildDigest } from "../child-notifications.js";
6
+ import { CHILD_UPDATE_BATCH_MS, FIFO_BUCKET_COUNT, MAX_BUCKET_BYTES, MAX_DRAIN_PER_TURN, MAX_PREDISPATCH_SWEEP, NON_BLOCKING_TIMER_MS, PREDISPATCH_CANCEL_SWEEP_MS, touchRecentClientMessageIds, } from "./state.js";
7
+ import { handleTurnResult, processPrompt, processTimer } from "./turn.js";
8
+ import { validClientMessageIds, noteMessageSender, applySenderAttribution, maybeQueueSharedPreamble } from "./utils.js";
9
+ // ─── KV FIFO bucket primitives ──────────────────────────────
10
+ function fifoBucketKey(index) {
11
+ return `fifo.${index}`;
12
+ }
13
+ function readFifoBucket(ctx, index) {
14
+ const raw = ctx.getValue(fifoBucketKey(index));
15
+ if (!raw)
16
+ return [];
17
+ try {
18
+ return JSON.parse(raw);
19
+ }
20
+ catch {
21
+ return [];
22
+ }
23
+ }
24
+ function writeFifoBucket(ctx, index, items) {
25
+ if (items.length === 0) {
26
+ ctx.clearValue(fifoBucketKey(index));
27
+ }
28
+ else {
29
+ ctx.setValue(fifoBucketKey(index), JSON.stringify(items));
30
+ }
31
+ }
32
+ export function appendToFifo(runtime, newItems) {
33
+ const { ctx } = runtime;
34
+ let writeBucketIdx = 0;
35
+ for (let i = FIFO_BUCKET_COUNT - 1; i >= 0; i--) {
36
+ if (readFifoBucket(ctx, i).length > 0) {
37
+ writeBucketIdx = i;
38
+ break;
39
+ }
40
+ }
41
+ for (const item of newItems) {
42
+ const bucket = readFifoBucket(ctx, writeBucketIdx);
43
+ bucket.push(item);
44
+ const serialized = JSON.stringify(bucket);
45
+ if (serialized.length > MAX_BUCKET_BYTES) {
46
+ bucket.pop();
47
+ writeFifoBucket(ctx, writeBucketIdx, bucket);
48
+ writeBucketIdx++;
49
+ if (writeBucketIdx >= FIFO_BUCKET_COUNT) {
50
+ ctx.traceInfo(`[fifo] overflow — ${newItems.length} item(s) may rely on carry-forward`);
51
+ return;
52
+ }
53
+ writeFifoBucket(ctx, writeBucketIdx, [item]);
54
+ }
55
+ else {
56
+ writeFifoBucket(ctx, writeBucketIdx, bucket);
57
+ }
58
+ }
59
+ }
60
+ function popFifoItem(runtime) {
61
+ const { ctx } = runtime;
62
+ for (let i = 0; i < FIFO_BUCKET_COUNT; i++) {
63
+ const items = readFifoBucket(ctx, i);
64
+ if (items.length > 0) {
65
+ const [first, ...rest] = items;
66
+ writeFifoBucket(ctx, i, rest);
67
+ return first;
68
+ }
69
+ }
70
+ return null;
71
+ }
72
+ function popFirstFifoItemMatching(runtime, predicate) {
73
+ const { ctx } = runtime;
74
+ for (let i = 0; i < FIFO_BUCKET_COUNT; i++) {
75
+ const items = readFifoBucket(ctx, i);
76
+ const index = items.findIndex(predicate);
77
+ if (index >= 0) {
78
+ const [item] = items.splice(index, 1);
79
+ writeFifoBucket(ctx, i, items);
80
+ return item;
81
+ }
82
+ }
83
+ return null;
84
+ }
85
+ function popNextDispatchFifoItem(runtime) {
86
+ const interactive = popFirstFifoItemMatching(runtime, (item) => item?.kind === "prompt" || item?.kind === "answer");
87
+ if (interactive) {
88
+ runtime.ctx.traceInfo(`[fifo] dispatching interactive ${interactive.kind} before queued timers`);
89
+ return interactive;
90
+ }
91
+ return popFifoItem(runtime);
92
+ }
93
+ function hasFifoItems(runtime) {
94
+ const { ctx } = runtime;
95
+ for (let i = 0; i < FIFO_BUCKET_COUNT; i++) {
96
+ if (readFifoBucket(ctx, i).length > 0)
97
+ return true;
98
+ }
99
+ return false;
100
+ }
101
+ function appendPromptStashToFifo(runtime, stash) {
102
+ appendToFifo(runtime, stash);
103
+ for (const item of stash) {
104
+ if (item?.kind !== "prompt")
105
+ continue;
106
+ const ids = validClientMessageIds(item.clientMessageIds);
107
+ touchRecentClientMessageIds(runtime.state, ids);
108
+ }
109
+ }
110
+ function duplicateClientMessageIds(runtime, ids, pendingIds) {
111
+ if (ids.length === 0)
112
+ return [];
113
+ const recent = new Set(runtime.state.recentClientMessageIds);
114
+ return ids.filter((id) => recent.has(id) || pendingIds.has(id));
115
+ }
116
+ function* recordDuplicatePrompt(runtime, ids, duplicateIds, source) {
117
+ const recent = new Set(runtime.state.recentClientMessageIds);
118
+ touchRecentClientMessageIds(runtime.state, duplicateIds.filter((id) => recent.has(id)));
119
+ runtime.ctx.traceInfo(`[${source}] suppressing duplicate prompt (duplicateIds=${duplicateIds.join(",")})`);
120
+ yield runtime.manager.recordSessionEvent(runtime.input.sessionId, [{
121
+ eventType: "session.message_duplicate_suppressed",
122
+ data: {
123
+ clientMessageIds: ids,
124
+ duplicateClientMessageIds: duplicateIds,
125
+ windowSize: 20,
126
+ source,
127
+ },
128
+ }]);
129
+ }
130
+ // ─── Timer race candidate selection ─────────────────────────
131
+ function nextTimerCandidate(activeTimer, pendingChildDigest, now) {
132
+ const candidates = [];
133
+ if (activeTimer) {
134
+ candidates.push({
135
+ kind: "active",
136
+ remainingMs: Math.max(0, activeTimer.deadlineMs - now),
137
+ timer: activeTimer,
138
+ });
139
+ }
140
+ if (pendingChildDigest && !pendingChildDigest.ready && pendingChildDigest.updates.length > 0) {
141
+ candidates.push({
142
+ kind: "child-digest",
143
+ remainingMs: Math.max(0, pendingChildDigest.startedAtMs + CHILD_UPDATE_BATCH_MS - now),
144
+ });
145
+ }
146
+ if (candidates.length === 0)
147
+ return null;
148
+ candidates.sort((left, right) => left.remainingMs - right.remainingMs);
149
+ return candidates[0];
150
+ }
151
+ // ─── drain: greedily move queue events + timer fires into KV FIFO ──
152
+ function hasReadyPendingChildDigest(runtime) {
153
+ const digest = runtime.state.pendingChildDigest;
154
+ return Boolean(digest?.ready && digest.updates.length > 0);
155
+ }
156
+ function needsBlockingDequeue(runtime) {
157
+ const { state } = runtime;
158
+ return (state.legacyPendingMessage === undefined &&
159
+ !state.activeTimer &&
160
+ !hasReadyPendingChildDigest(runtime) &&
161
+ state.pendingToolActions.length === 0 &&
162
+ !state.pendingPrompt &&
163
+ !hasFifoItems(runtime));
164
+ }
165
+ export function* drain(runtime) {
166
+ const { ctx, state } = runtime;
167
+ const stash = [];
168
+ const seenChildUpdates = new Set();
169
+ const cancelledThisDrain = new Set();
170
+ const pendingClientMessageIds = new Set();
171
+ for (let i = 0; i < MAX_DRAIN_PER_TURN; i++) {
172
+ let msg = null;
173
+ if (state.legacyPendingMessage !== undefined) {
174
+ msg = state.legacyPendingMessage;
175
+ state.legacyPendingMessage = undefined;
176
+ }
177
+ else if (state.activeTimer || (state.pendingChildDigest && !state.pendingChildDigest.ready)) {
178
+ const now = yield ctx.utcNow();
179
+ const candidate = nextTimerCandidate(state.activeTimer, state.pendingChildDigest, now);
180
+ if (!candidate)
181
+ continue;
182
+ if (candidate.remainingMs === 0) {
183
+ if (candidate.kind === "active" && candidate.timer) {
184
+ stash.push({ kind: "timer", timer: { ...candidate.timer }, firedAtMs: now });
185
+ state.activeTimer = null;
186
+ }
187
+ else if (state.pendingChildDigest && state.pendingChildDigest.updates.length > 0) {
188
+ state.pendingChildDigest.ready = true;
189
+ break;
190
+ }
191
+ continue;
192
+ }
193
+ const msgTask = ctx.dequeueEvent("messages");
194
+ const timerTask = ctx.scheduleTimer(candidate.remainingMs);
195
+ const race = yield ctx.race(msgTask, timerTask);
196
+ if (race.index === 1) {
197
+ if (candidate.kind === "active" && candidate.timer) {
198
+ const firedAt = yield ctx.utcNow();
199
+ stash.push({ kind: "timer", timer: { ...candidate.timer }, firedAtMs: firedAt });
200
+ state.activeTimer = null;
201
+ }
202
+ else if (state.pendingChildDigest && state.pendingChildDigest.updates.length > 0) {
203
+ state.pendingChildDigest.ready = true;
204
+ break;
205
+ }
206
+ continue;
207
+ }
208
+ msg = typeof race.value === "string" ? JSON.parse(race.value) : race.value;
209
+ }
210
+ else if (needsBlockingDequeue(runtime)) {
211
+ if (i > 0)
212
+ break;
213
+ if (state.pendingInputQuestion) {
214
+ publishStatus(runtime, "input_required");
215
+ }
216
+ else if (state.blockedError) {
217
+ publishStatus(runtime, "error", {
218
+ error: state.blockedError.message,
219
+ retriesExhausted: true,
220
+ ...(state.blockedError.authFailure ? { authFailure: true } : {}),
221
+ });
222
+ }
223
+ else {
224
+ publishStatus(runtime, "idle");
225
+ }
226
+ const rawMsg = yield ctx.dequeueEvent("messages");
227
+ msg = typeof rawMsg === "string" ? JSON.parse(rawMsg) : rawMsg;
228
+ }
229
+ else {
230
+ const msgTask = ctx.dequeueEvent("messages");
231
+ const timerTask = ctx.scheduleTimer(NON_BLOCKING_TIMER_MS);
232
+ const race = yield ctx.race(msgTask, timerTask);
233
+ if (race.index === 1)
234
+ break;
235
+ msg = typeof race.value === "string" ? JSON.parse(race.value) : race.value;
236
+ }
237
+ if (!msg)
238
+ continue;
239
+ if (msg && Array.isArray(msg.cancelPending) && msg.cancelPending.length > 0) {
240
+ const validCancelIds = [];
241
+ for (const id of msg.cancelPending) {
242
+ if (typeof id === "string" && id) {
243
+ validCancelIds.push(id);
244
+ cancelledThisDrain.add(id);
245
+ state.cancelledMessageIds.add(id);
246
+ }
247
+ }
248
+ if (validCancelIds.length > 0) {
249
+ ctx.traceInfo(`[drain] received cancel tombstone (ids=${validCancelIds.join(",")})`);
250
+ }
251
+ for (let s = stash.length - 1; s >= 0; s--) {
252
+ const item = stash[s];
253
+ if (item?.kind !== "prompt")
254
+ continue;
255
+ const ids = Array.isArray(item.clientMessageIds) ? item.clientMessageIds : [];
256
+ if (ids.some((id) => cancelledThisDrain.has(id))) {
257
+ ctx.traceInfo(`[drain] dropping stashed prompt cancelled by tombstone (ids=${ids.join(",")})`);
258
+ yield* recordCancelledMessageIds(runtime, ids, "drain-stash");
259
+ stash.splice(s, 1);
260
+ }
261
+ }
262
+ continue;
263
+ }
264
+ if (msg.type === "cmd") {
265
+ if (stash.length > 0) {
266
+ appendPromptStashToFifo(runtime, stash);
267
+ stash.length = 0;
268
+ }
269
+ yield* handleCommand(runtime, msg);
270
+ if (state.orchestrationResult !== null)
271
+ return;
272
+ continue;
273
+ }
274
+ const childUpdate = parseChildUpdate(msg.prompt);
275
+ if (childUpdate) {
276
+ const key = `${childUpdate.sessionId}|${childUpdate.updateType}|${childUpdate.content ?? ""}`;
277
+ if (!seenChildUpdates.has(key)) {
278
+ seenChildUpdates.add(key);
279
+ const wasExpectingReport = state.subAgents.find((agent) => agent.sessionId === childUpdate.sessionId)?.expectsReport === true;
280
+ const tracked = yield* applyChildUpdate(runtime, childUpdate);
281
+ if (tracked && !state.pendingShutdown) {
282
+ const childObservedAt = yield ctx.utcNow();
283
+ bufferChildUpdate(runtime, childUpdate, childObservedAt);
284
+ // Fast-path only on a FIRST report from a child spawned with
285
+ // an outstanding expectation — routine later updates keep
286
+ // the normal batch window.
287
+ if (wasExpectingReport)
288
+ markDigestReadyIfAllAgentsSettled(runtime);
289
+ }
290
+ if (tracked && state.waitingForAgentIds) {
291
+ yield* maybeResolveAgentWaitCompletion(runtime);
292
+ }
293
+ }
294
+ continue;
295
+ }
296
+ if (msg.answer !== undefined) {
297
+ const interruptsInputHold = Boolean(state.pendingInputQuestion)
298
+ && (state.activeTimer?.type === "input-grace" || state.activeTimer?.type === "idle");
299
+ if (interruptsInputHold) {
300
+ ctx.traceInfo(`[drain] answer interrupted ${state.activeTimer.type} timer`);
301
+ state.activeTimer = null;
302
+ }
303
+ stash.push({ kind: "answer", answer: msg.answer, wasFreeform: msg.wasFreeform, ...(msg.sender && typeof msg.sender === "object" ? { sender: msg.sender } : {}) });
304
+ if (interruptsInputHold)
305
+ break;
306
+ continue;
307
+ }
308
+ if (msg.prompt) {
309
+ const incomingClientMessageIds = validClientMessageIds(msg.clientMessageIds);
310
+ if (promptIdsIntersectCancellation(runtime, incomingClientMessageIds)) {
311
+ ctx.traceInfo(`[drain] dropping incoming prompt cancelled by tombstone (ids=${incomingClientMessageIds.join(",")})`);
312
+ yield* recordCancelledMessageIds(runtime, incomingClientMessageIds, "drain-incoming");
313
+ continue;
314
+ }
315
+ const duplicateIds = duplicateClientMessageIds(runtime, incomingClientMessageIds, pendingClientMessageIds);
316
+ if (duplicateIds.length > 0) {
317
+ yield* recordDuplicatePrompt(runtime, incomingClientMessageIds, duplicateIds, "drain");
318
+ continue;
319
+ }
320
+ let userPrompt = msg.prompt;
321
+ state.blockedError = undefined;
322
+ if (state.activeTimer?.type === "wait") {
323
+ const now = yield ctx.utcNow();
324
+ const remainingMs = Math.max(0, state.activeTimer.deadlineMs - now);
325
+ const remainingSec = Math.round(remainingMs / 1000);
326
+ const elapsedMs = state.activeTimer.originalDurationMs - remainingMs;
327
+ const elapsedSec = Math.round(elapsedMs / 1000);
328
+ const totalSec = Math.round(state.activeTimer.originalDurationMs / 1000);
329
+ ctx.traceInfo(`[drain] user prompt interrupted wait timer, ${remainingSec}s remain — orchestration will auto-resume`);
330
+ state.interruptedWaitTimer = {
331
+ remainingSec,
332
+ reason: state.activeTimer.reason,
333
+ shouldRehydrate: state.activeTimer.shouldRehydrate ?? false,
334
+ waitPlan: state.activeTimer.waitPlan,
335
+ interruptKind: "user",
336
+ };
337
+ if (state.activeTimer.shouldRehydrate && userPrompt) {
338
+ userPrompt = wrapWithResumeContext(runtime, userPrompt, `Your ${totalSec}s timer (reason: "${state.activeTimer.reason}") was interrupted by the above message. ` +
339
+ `${elapsedSec}s elapsed, ${remainingSec}s remain. ` +
340
+ `Reply to the message. The timer will be automatically resumed after your reply.`);
341
+ }
342
+ else if (userPrompt) {
343
+ userPrompt = `${userPrompt}\n\n` +
344
+ `[SYSTEM: The above is a message that interrupted your ${totalSec}s timer (reason: "${state.activeTimer.reason}"). ` +
345
+ `${elapsedSec}s elapsed, ${remainingSec}s remain. ` +
346
+ `Reply to the message. The timer will be automatically resumed after your reply.]`;
347
+ }
348
+ state.activeTimer = null;
349
+ }
350
+ else if (state.activeTimer?.type === "cron") {
351
+ const activeCron = state.cronSchedule;
352
+ const now = yield ctx.utcNow();
353
+ const remainingMs = Math.max(0, state.activeTimer.deadlineMs - now);
354
+ state.interruptedCronTimer = {
355
+ remainingMs,
356
+ reason: state.activeTimer.reason,
357
+ originalDurationMs: state.activeTimer.originalDurationMs,
358
+ ...(state.activeTimer.shouldRehydrate ? { shouldRehydrate: true } : {}),
359
+ };
360
+ const cronResumeNote = `This is an internal recurring schedule, not a new user prompt. ` +
361
+ `There is an active recurring schedule every ${activeCron?.intervalSeconds ?? "?"} seconds for "${activeCron?.reason ?? state.activeTimer.reason}". ` +
362
+ `The next cron wake-up will keep the original schedule and resume after the remaining ${Math.round(remainingMs / 1000)} seconds unless you explicitly reset cron. ` +
363
+ `Do NOT call wait() just to keep the recurring loop alive. ` +
364
+ `Call cron(action="cancel") only if you need to stop it.`;
365
+ if (state.activeTimer.shouldRehydrate && userPrompt) {
366
+ userPrompt = wrapWithResumeContext(runtime, userPrompt, cronResumeNote);
367
+ }
368
+ else if (userPrompt) {
369
+ userPrompt = `${userPrompt}\n\n[SYSTEM: ${cronResumeNote}]`;
370
+ }
371
+ ctx.traceInfo(`[drain] user prompt interrupted cron timer`);
372
+ state.activeTimer = null;
373
+ }
374
+ else if (state.activeTimer?.type === "cron_at") {
375
+ const activeCronAt = state.cronAtSchedule;
376
+ const now = yield ctx.utcNow();
377
+ const remainingMs = Math.max(0, state.activeTimer.deadlineMs - now);
378
+ const scheduledAt = activeCronAt?.nextFireAtMs ? new Date(activeCronAt.nextFireAtMs).toISOString() : "unknown";
379
+ const cronAtResumeNote = `This is an internal wall-clock recurring schedule, not a new user prompt. ` +
380
+ `There is an active wall-clock schedule for "${activeCronAt?.reason ?? state.activeTimer.reason}". ` +
381
+ `The pending scheduled fire (${scheduledAt}) is preserved and will run after this turn completes; ` +
382
+ `if the scheduled time passes while you respond, it will fire immediately afterward unless you explicitly cancel or reset the schedule. ` +
383
+ `Do NOT call wait() just to keep the recurring loop alive. ` +
384
+ `Call cron_at(action="cancel") only if you need to stop it.`;
385
+ if (state.activeTimer.shouldRehydrate && userPrompt) {
386
+ userPrompt = wrapWithResumeContext(runtime, userPrompt, cronAtResumeNote);
387
+ }
388
+ else if (userPrompt) {
389
+ userPrompt = `${userPrompt}\n\n[SYSTEM: ${cronAtResumeNote}]`;
390
+ }
391
+ ctx.traceInfo(`[drain] user prompt interrupted cron_at timer (${Math.round(remainingMs / 1000)}s remain)`);
392
+ state.activeTimer = null;
393
+ }
394
+ else if (state.activeTimer?.type === "idle") {
395
+ ctx.traceInfo(`[drain] user prompt within idle window, cancelling idle timer`);
396
+ state.activeTimer = null;
397
+ }
398
+ else if (state.activeTimer?.type === "agent-poll") {
399
+ ctx.traceInfo(`[drain] user prompt interrupted agent wait`);
400
+ state.waitingForAgentIds = null;
401
+ state.activeTimer = null;
402
+ }
403
+ if (state.pendingChildDigest?.updates.length) {
404
+ userPrompt = flushPendingChildDigestIntoPrompt(runtime, userPrompt);
405
+ }
406
+ const incomingAttachments = sanitizePromptAttachmentRefs(msg.attachments);
407
+ stash.push({
408
+ kind: "prompt",
409
+ prompt: userPrompt,
410
+ bootstrap: Boolean(msg.bootstrap),
411
+ ...(msg.requiredTool ? { requiredTool: msg.requiredTool } : {}),
412
+ ...(incomingClientMessageIds.length > 0 ? { clientMessageIds: incomingClientMessageIds } : {}),
413
+ ...(msg.sender && typeof msg.sender === "object" ? { sender: msg.sender } : {}),
414
+ ...(incomingAttachments.length > 0 ? { attachments: incomingAttachments } : {}),
415
+ });
416
+ for (const id of incomingClientMessageIds)
417
+ pendingClientMessageIds.add(id);
418
+ continue;
419
+ }
420
+ ctx.traceInfo(`[drain] skipping unknown: ${JSON.stringify(msg).slice(0, 120)}`);
421
+ }
422
+ if (stash.length > 0)
423
+ appendPromptStashToFifo(runtime, stash);
424
+ }
425
+ // ─── Pre-dispatch sweep: grab any pending cancel tombstone ──
426
+ function* sweepMessagesBeforePromptDispatch(runtime) {
427
+ const { ctx, state } = runtime;
428
+ const stash = [];
429
+ const seenChildUpdates = new Set();
430
+ const pendingClientMessageIds = new Set();
431
+ for (let i = 0; i < MAX_PREDISPATCH_SWEEP; i++) {
432
+ const msgTask = ctx.dequeueEvent("messages");
433
+ const timerTask = ctx.scheduleTimer(PREDISPATCH_CANCEL_SWEEP_MS);
434
+ const race = yield ctx.race(msgTask, timerTask);
435
+ if (race.index === 1)
436
+ break;
437
+ const msg = typeof race.value === "string" ? JSON.parse(race.value) : race.value;
438
+ if (!msg)
439
+ continue;
440
+ if (msg && Array.isArray(msg.cancelPending) && msg.cancelPending.length > 0) {
441
+ const validCancelIds = validClientMessageIds(msg.cancelPending);
442
+ for (const id of validCancelIds)
443
+ state.cancelledMessageIds.add(id);
444
+ if (validCancelIds.length > 0) {
445
+ ctx.traceInfo(`[predispatch] received cancel tombstone (ids=${validCancelIds.join(",")})`);
446
+ for (let s = stash.length - 1; s >= 0; s--) {
447
+ const item = stash[s];
448
+ if (item?.kind !== "prompt")
449
+ continue;
450
+ const ids = Array.isArray(item.clientMessageIds) ? item.clientMessageIds : [];
451
+ if (ids.some((id) => validCancelIds.includes(id))) {
452
+ ctx.traceInfo(`[predispatch] dropping stashed prompt cancelled by tombstone (ids=${ids.join(",")})`);
453
+ yield* recordCancelledMessageIds(runtime, ids, "predispatch-stash");
454
+ stash.splice(s, 1);
455
+ }
456
+ }
457
+ }
458
+ continue;
459
+ }
460
+ if (msg.type === "cmd") {
461
+ if (stash.length > 0) {
462
+ appendPromptStashToFifo(runtime, stash);
463
+ stash.length = 0;
464
+ }
465
+ yield* handleCommand(runtime, msg);
466
+ if (state.orchestrationResult !== null)
467
+ return;
468
+ continue;
469
+ }
470
+ const childUpdate = parseChildUpdate(msg.prompt);
471
+ if (childUpdate) {
472
+ const key = `${childUpdate.sessionId}|${childUpdate.updateType}|${childUpdate.content ?? ""}`;
473
+ if (!seenChildUpdates.has(key)) {
474
+ seenChildUpdates.add(key);
475
+ const wasExpectingReport = state.subAgents.find((agent) => agent.sessionId === childUpdate.sessionId)?.expectsReport === true;
476
+ const tracked = yield* applyChildUpdate(runtime, childUpdate);
477
+ if (tracked && !state.pendingShutdown) {
478
+ const childObservedAt = yield ctx.utcNow();
479
+ bufferChildUpdate(runtime, childUpdate, childObservedAt);
480
+ // Fast-path only on a FIRST report from a child spawned with
481
+ // an outstanding expectation — routine later updates keep
482
+ // the normal batch window.
483
+ if (wasExpectingReport)
484
+ markDigestReadyIfAllAgentsSettled(runtime);
485
+ }
486
+ if (tracked && state.waitingForAgentIds) {
487
+ yield* maybeResolveAgentWaitCompletion(runtime);
488
+ }
489
+ }
490
+ continue;
491
+ }
492
+ if (msg.answer !== undefined) {
493
+ stash.push({ kind: "answer", answer: msg.answer, wasFreeform: msg.wasFreeform, ...(msg.sender && typeof msg.sender === "object" ? { sender: msg.sender } : {}) });
494
+ continue;
495
+ }
496
+ if (msg.prompt) {
497
+ const incomingClientMessageIds = validClientMessageIds(msg.clientMessageIds);
498
+ if (promptIdsIntersectCancellation(runtime, incomingClientMessageIds)) {
499
+ ctx.traceInfo(`[predispatch] dropping incoming prompt cancelled by tombstone (ids=${incomingClientMessageIds.join(",")})`);
500
+ yield* recordCancelledMessageIds(runtime, incomingClientMessageIds, "predispatch-incoming");
501
+ continue;
502
+ }
503
+ const duplicateIds = duplicateClientMessageIds(runtime, incomingClientMessageIds, pendingClientMessageIds);
504
+ if (duplicateIds.length > 0) {
505
+ yield* recordDuplicatePrompt(runtime, incomingClientMessageIds, duplicateIds, "predispatch");
506
+ continue;
507
+ }
508
+ const sweepAttachments = sanitizePromptAttachmentRefs(msg.attachments);
509
+ stash.push({
510
+ kind: "prompt",
511
+ prompt: msg.prompt,
512
+ bootstrap: Boolean(msg.bootstrap),
513
+ ...(msg.requiredTool ? { requiredTool: msg.requiredTool } : {}),
514
+ ...(incomingClientMessageIds.length > 0 ? { clientMessageIds: incomingClientMessageIds } : {}),
515
+ ...(msg.sender && typeof msg.sender === "object" ? { sender: msg.sender } : {}),
516
+ ...(sweepAttachments.length > 0 ? { attachments: sweepAttachments } : {}),
517
+ });
518
+ for (const id of incomingClientMessageIds)
519
+ pendingClientMessageIds.add(id);
520
+ continue;
521
+ }
522
+ ctx.traceInfo(`[predispatch] skipping unknown: ${JSON.stringify(msg).slice(0, 120)}`);
523
+ }
524
+ if (stash.length > 0)
525
+ appendPromptStashToFifo(runtime, stash);
526
+ }
527
+ // ─── decide: pop and process one item from FIFO ─────────────
528
+ function* processAnswer(runtime, answerItem) {
529
+ const question = runtime.state.pendingInputQuestion?.question ?? "a question";
530
+ runtime.state.pendingInputQuestion = null;
531
+ // Any writer may answer (security model); attribution shows who did.
532
+ const sender = noteMessageSender(runtime, answerItem.sender);
533
+ const answeredBy = runtime.state.multiWriter && sender?.display ? ` (answered by ${sender.display})` : "";
534
+ const answerPrompt = `The user was asked: "${question}"\nThe user responded${answeredBy}: "${answerItem.answer}"`;
535
+ maybeQueueSharedPreamble(runtime);
536
+ yield* processPrompt(runtime, answerPrompt, false, undefined, undefined, undefined, sender);
537
+ }
538
+ export function* decide(runtime) {
539
+ const { ctx, state } = runtime;
540
+ // Priority 1: pending tool actions (in-memory, replay carry-forward).
541
+ yield* drainLeadingQueuedScheduleActions(runtime);
542
+ if (state.pendingToolActions.length > 0) {
543
+ const action = state.pendingToolActions.shift();
544
+ ctx.traceInfo(`[orch] replaying queued action: ${action.type} remaining=${state.pendingToolActions.length}`);
545
+ yield* handleTurnResult(runtime, action, "");
546
+ return true;
547
+ }
548
+ // Priority 2: pending prompt (CAN carry-forward or queueFollowup).
549
+ // Hold while waiting for agents — let confirmations accumulate and merge
550
+ // with the agents-done summary for one combined LLM turn.
551
+ if (state.pendingPrompt && !state.waitingForAgentIds) {
552
+ const prompt = state.pendingPrompt;
553
+ const isBootstrap = state.bootstrapPrompt;
554
+ const requiredTool = state.pendingRequiredTool;
555
+ const cycleOrigin = state.pendingCycleOrigin;
556
+ const pendingAttachments = state.pendingAttachments;
557
+ state.pendingPrompt = undefined;
558
+ state.bootstrapPrompt = false;
559
+ state.pendingRequiredTool = undefined;
560
+ state.pendingCycleOrigin = undefined;
561
+ state.pendingAttachments = undefined;
562
+ yield* processPrompt(runtime, prompt, isBootstrap, requiredTool, undefined, cycleOrigin, undefined, pendingAttachments && pendingAttachments.length > 0 ? pendingAttachments : undefined);
563
+ return true;
564
+ }
565
+ // Priority 3: FIFO — next item in arrival order, with prompt batching.
566
+ const item = popNextDispatchFifoItem(runtime);
567
+ if (item) {
568
+ switch (item.kind) {
569
+ case "prompt": {
570
+ const ids = Array.isArray(item.clientMessageIds) ? item.clientMessageIds : [];
571
+ if (ids.length > 0) {
572
+ yield* sweepMessagesBeforePromptDispatch(runtime);
573
+ if (state.orchestrationResult !== null)
574
+ return true;
575
+ }
576
+ if (promptIdsIntersectCancellation(runtime, ids)) {
577
+ ctx.traceInfo(`[decide] dropping FIFO prompt cancelled by tombstone (ids=${ids.join(",")})`);
578
+ yield* recordCancelledMessageIds(runtime, ids, "decide-fifo");
579
+ return true;
580
+ }
581
+ // Batch consecutive prompt FIFO items into a single Copilot turn.
582
+ // Multi-writer attribution: note each item's sender (may flip
583
+ // the session to multi-writer), prefix segments with [FROM:]
584
+ // once flipped, and keep a single turn-level sender only when
585
+ // every merged segment came from the same identity.
586
+ const firstSender = noteMessageSender(runtime, item.sender);
587
+ let mergedPrompt = applySenderAttribution(runtime, firstSender, String(item.prompt || ""));
588
+ let mergedBootstrap = item.bootstrap ?? false;
589
+ let mergedRequiredTool = item.requiredTool;
590
+ const mergedClientMessageIds = [...ids];
591
+ const mergedAttachments = sanitizePromptAttachmentRefs(item.attachments);
592
+ let turnSender = firstSender;
593
+ let mixedSenders = false;
594
+ while (true) {
595
+ const peek = popFifoItem(runtime);
596
+ if (!peek)
597
+ break;
598
+ if (peek.kind !== "prompt") {
599
+ appendToFifo(runtime, [peek]);
600
+ break;
601
+ }
602
+ const peekIds = Array.isArray(peek.clientMessageIds) ? peek.clientMessageIds : [];
603
+ if (promptIdsIntersectCancellation(runtime, peekIds)) {
604
+ ctx.traceInfo(`[decide] dropping merged FIFO prompt cancelled by tombstone (ids=${peekIds.join(",")})`);
605
+ yield* recordCancelledMessageIds(runtime, peekIds, "decide-merge");
606
+ continue;
607
+ }
608
+ const peekSender = noteMessageSender(runtime, peek.sender);
609
+ if (messageSenderKey(peekSender ?? null) !== messageSenderKey(turnSender ?? null)) {
610
+ mixedSenders = true;
611
+ }
612
+ mergedPrompt = `${mergedPrompt}\n\n${applySenderAttribution(runtime, peekSender, String(peek.prompt || ""))}`;
613
+ mergedBootstrap = mergedBootstrap || (peek.bootstrap ?? false);
614
+ if (!mergedRequiredTool && peek.requiredTool)
615
+ mergedRequiredTool = peek.requiredTool;
616
+ for (const id of peekIds)
617
+ mergedClientMessageIds.push(id);
618
+ // Merged messages pool their image attachments in arrival
619
+ // order, bounded by the per-turn cap (overflow is dropped
620
+ // here deterministically rather than failing the turn).
621
+ for (const ref of sanitizePromptAttachmentRefs(peek.attachments)) {
622
+ if (mergedAttachments.length >= ATTACHMENTS_MAX_COUNT)
623
+ break;
624
+ mergedAttachments.push(ref);
625
+ }
626
+ }
627
+ maybeQueueSharedPreamble(runtime);
628
+ yield* processPrompt(runtime, mergedPrompt, mergedBootstrap, mergedRequiredTool, mergedClientMessageIds.length > 0 ? mergedClientMessageIds : undefined, undefined, mixedSenders ? undefined : turnSender, mergedAttachments.length > 0 ? mergedAttachments : undefined);
629
+ break;
630
+ }
631
+ case "answer":
632
+ yield* processAnswer(runtime, item);
633
+ break;
634
+ case "timer":
635
+ yield* processTimer(runtime, item);
636
+ break;
637
+ case "agents-done":
638
+ queueFollowup(runtime, item.summary);
639
+ break;
640
+ default:
641
+ ctx.traceInfo(`[decide] unknown FIFO item kind: ${item.kind}`);
642
+ }
643
+ return true;
644
+ }
645
+ // Priority 4: buffered child digest — only after user/FIFO work is drained.
646
+ if (state.pendingChildDigest?.ready && state.pendingChildDigest.updates.length > 0 && !state.waitingForAgentIds) {
647
+ yield* processPendingChildDigest(runtime);
648
+ return true;
649
+ }
650
+ return false;
651
+ }
652
+ // ─── pending child digest dispatch (timer-aware) ────────────
653
+ import { buildPendingChildDigestSystemPrompt, clearPendingChildDigest, } from "./lifecycle.js";
654
+ /**
655
+ * All-quiet fast path: a freshly buffered child update revealed that EVERY
656
+ * tracked sub-agent is settled (terminal, idle, or blocked on input) — nothing
657
+ * will ever act again unprompted. Deliver the digest immediately instead of
658
+ * waiting out the remainder of the batch window so the parent looks at its
659
+ * children NOW rather than after a human pokes it.
660
+ */
661
+ function markDigestReadyIfAllAgentsSettled(runtime) {
662
+ const { state } = runtime;
663
+ if (state.waitingForAgentIds)
664
+ return; // wait resolution handles this case
665
+ if (!state.pendingChildDigest || state.pendingChildDigest.updates.length === 0)
666
+ return;
667
+ if (state.subAgents.length === 0)
668
+ return;
669
+ if (!state.subAgents.every((agent) => isAgentWaitSettledStatus(agent.status)))
670
+ return;
671
+ state.pendingChildDigest.ready = true;
672
+ }
673
+ function* processPendingChildDigest(runtime) {
674
+ const { ctx, state } = runtime;
675
+ const digestPrompt = buildPendingChildDigestSystemPrompt(runtime);
676
+ if (!digestPrompt) {
677
+ clearPendingChildDigest(runtime);
678
+ return;
679
+ }
680
+ const digestDecision = shouldWakeParentForChildDigest(state.pendingChildDigest.updates.map((update) => {
681
+ const agent = state.subAgents.find((entry) => entry.sessionId === update.sessionId);
682
+ return {
683
+ update: {
684
+ kind: update.updateType === "wait"
685
+ ? "wait"
686
+ : update.updateType === "failed"
687
+ ? "error"
688
+ : update.updateType === "cancelled" || update.updateType === "deleted"
689
+ ? "cancelled"
690
+ : update.updateType === "completed"
691
+ ? "completed"
692
+ : "progress",
693
+ summary: update.content,
694
+ ...(update.cycleOrigin ? { cyclic: true } : {}),
695
+ ...(update.cycleStatus === "material" || update.cycleStatus === "blocked"
696
+ ? { material: true }
697
+ : update.cycleStatus === "quiet"
698
+ ? { material: false }
699
+ : {}),
700
+ ...(update.verdict ? { result: { verdict: update.verdict } } : update.cycleStatus === "blocked" ? { result: { verdict: "blocked" } } : {}),
701
+ },
702
+ contract: agent?.contract,
703
+ };
704
+ }));
705
+ if (!digestDecision.wake) {
706
+ yield runtime.manager.recordSessionEvent(runtime.input.sessionId, [{
707
+ eventType: "session.child_update_suppressed",
708
+ data: {
709
+ reason: digestDecision.reason,
710
+ policy: digestDecision.policy,
711
+ classification: digestDecision.classification,
712
+ updateCount: state.pendingChildDigest.updates.length,
713
+ },
714
+ }]);
715
+ clearPendingChildDigest(runtime);
716
+ return;
717
+ }
718
+ if (state.activeTimer?.type === "wait") {
719
+ const now = yield ctx.utcNow();
720
+ const remainingMs = Math.max(0, state.activeTimer.deadlineMs - now);
721
+ const remainingSec = Math.round(remainingMs / 1000);
722
+ const elapsedMs = state.activeTimer.originalDurationMs - remainingMs;
723
+ const elapsedSec = Math.round(elapsedMs / 1000);
724
+ const totalSec = Math.round(state.activeTimer.originalDurationMs / 1000);
725
+ state.interruptedWaitTimer = {
726
+ remainingSec,
727
+ reason: state.activeTimer.reason,
728
+ shouldRehydrate: state.activeTimer.shouldRehydrate ?? false,
729
+ waitPlan: state.activeTimer.waitPlan,
730
+ interruptKind: "child",
731
+ };
732
+ state.activeTimer = null;
733
+ clearPendingChildDigest(runtime);
734
+ yield* processPrompt(runtime, `[SYSTEM: Buffered child updates interrupted your ${totalSec}s timer (reason: "${state.interruptedWaitTimer.reason}"). ` +
735
+ `${elapsedSec}s elapsed, ${remainingSec}s remain. ` +
736
+ `Review the updates and continue your task now. The remaining wait will be resumed automatically after this turn completes.\n\n${digestPrompt}]`, true);
737
+ return;
738
+ }
739
+ if (state.activeTimer?.type === "cron") {
740
+ const activeCron = state.cronSchedule;
741
+ const now = yield ctx.utcNow();
742
+ const remainingMs = Math.max(0, state.activeTimer.deadlineMs - now);
743
+ state.interruptedCronTimer = {
744
+ remainingMs,
745
+ reason: state.activeTimer.reason,
746
+ originalDurationMs: state.activeTimer.originalDurationMs,
747
+ ...(state.activeTimer.shouldRehydrate ? { shouldRehydrate: true } : {}),
748
+ };
749
+ state.activeTimer = null;
750
+ clearPendingChildDigest(runtime);
751
+ yield* processPrompt(runtime, `[SYSTEM: This is an internal orchestration wake-up caused by child session updates; the user did not send a new message. ` +
752
+ `Buffered child updates arrived while your recurring schedule was waiting for the next wake-up${activeCron ? ` ("${activeCron.reason}")` : ""}. ` +
753
+ `Review the updates and continue your task now. The recurring cron schedule remains active and will be re-armed automatically after this turn completes.\n\n${digestPrompt}]`, true);
754
+ return;
755
+ }
756
+ if (state.activeTimer?.type === "cron_at") {
757
+ const activeCronAt = state.cronAtSchedule;
758
+ const scheduledAt = activeCronAt?.nextFireAtMs ? new Date(activeCronAt.nextFireAtMs).toISOString() : "unknown";
759
+ state.activeTimer = null;
760
+ clearPendingChildDigest(runtime);
761
+ yield* processPrompt(runtime, `[SYSTEM: This is an internal orchestration wake-up caused by child session updates; the user did not send a new message. ` +
762
+ `Buffered child updates arrived while your wall-clock schedule was waiting for its next fire${activeCronAt ? ` ("${activeCronAt.reason}", scheduled ${scheduledAt})` : ""}. ` +
763
+ `Review the updates and continue your task now. The wall-clock cron schedule remains active and will be re-armed automatically after this turn completes.\n\n${digestPrompt}]`, true);
764
+ return;
765
+ }
766
+ if (state.activeTimer?.type === "idle") {
767
+ state.activeTimer = null;
768
+ }
769
+ else if (state.activeTimer?.type === "agent-poll") {
770
+ state.waitingForAgentIds = null;
771
+ state.activeTimer = null;
772
+ }
773
+ clearPendingChildDigest(runtime);
774
+ yield* processPrompt(runtime, `[SYSTEM: ${digestPrompt}]`, true);
775
+ }
776
+ //# sourceMappingURL=queue.js.map