pilotswarm-sdk 0.4.0 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/api/src/http-api-transport.js +4 -4
- package/api/src/protocol.js +10 -3
- package/dist/agent-loader.d.ts +2 -0
- package/dist/agent-loader.d.ts.map +1 -1
- package/dist/agent-loader.js +7 -1
- package/dist/agent-loader.js.map +1 -1
- package/dist/blob-store.d.ts +10 -1
- package/dist/blob-store.d.ts.map +1 -1
- package/dist/blob-store.js +275 -13
- package/dist/blob-store.js.map +1 -1
- package/dist/client.d.ts +1 -0
- package/dist/client.d.ts.map +1 -1
- package/dist/client.js +5 -1
- package/dist/client.js.map +1 -1
- package/dist/cms-migrations.d.ts.map +1 -1
- package/dist/cms-migrations.js +544 -1
- package/dist/cms-migrations.js.map +1 -1
- package/dist/cms.d.ts +18 -4
- package/dist/cms.d.ts.map +1 -1
- package/dist/cms.js +62 -3
- package/dist/cms.js.map +1 -1
- package/dist/fault-injection.d.ts +2 -0
- package/dist/fault-injection.d.ts.map +1 -0
- package/dist/fault-injection.js +45 -0
- package/dist/fault-injection.js.map +1 -0
- package/dist/management-client.d.ts +6 -2
- package/dist/management-client.d.ts.map +1 -1
- package/dist/management-client.js +8 -4
- package/dist/management-client.js.map +1 -1
- package/dist/orchestration/index.d.ts +3 -3
- package/dist/orchestration/index.js +1 -1
- package/dist/orchestration/lifecycle.d.ts +9 -2
- package/dist/orchestration/lifecycle.d.ts.map +1 -1
- package/dist/orchestration/lifecycle.js +21 -55
- package/dist/orchestration/lifecycle.js.map +1 -1
- package/dist/orchestration/runtime.d.ts +1 -1
- package/dist/orchestration/state.d.ts +7 -3
- package/dist/orchestration/state.d.ts.map +1 -1
- package/dist/orchestration/state.js +13 -4
- package/dist/orchestration/state.js.map +1 -1
- package/dist/orchestration/turn.d.ts.map +1 -1
- package/dist/orchestration/turn.js +98 -66
- package/dist/orchestration/turn.js.map +1 -1
- package/dist/orchestration-registry.d.ts.map +1 -1
- package/dist/orchestration-registry.js +6 -2
- package/dist/orchestration-registry.js.map +1 -1
- package/dist/orchestration-version.d.ts +1 -1
- package/dist/orchestration-version.js +1 -1
- package/dist/orchestration.d.ts +2 -2
- package/dist/orchestration.js +4 -4
- package/dist/orchestration_1_0_46.d.ts +1 -1
- package/dist/orchestration_1_0_47.d.ts +1 -1
- package/dist/orchestration_1_0_48.d.ts +1 -1
- package/dist/orchestration_1_0_49.d.ts +1 -1
- package/dist/orchestration_1_0_50.d.ts +1 -1
- package/dist/orchestration_1_0_56/agents.d.ts +41 -0
- package/dist/orchestration_1_0_56/agents.d.ts.map +1 -0
- package/dist/orchestration_1_0_56/agents.js +758 -0
- package/dist/orchestration_1_0_56/agents.js.map +1 -0
- package/dist/orchestration_1_0_56/index.d.ts +24 -0
- package/dist/orchestration_1_0_56/index.d.ts.map +1 -0
- package/dist/orchestration_1_0_56/index.js +13 -0
- package/dist/orchestration_1_0_56/index.js.map +1 -0
- package/dist/orchestration_1_0_56/lifecycle.d.ts +41 -0
- package/dist/orchestration_1_0_56/lifecycle.d.ts.map +1 -0
- package/dist/orchestration_1_0_56/lifecycle.js +580 -0
- package/dist/orchestration_1_0_56/lifecycle.js.map +1 -0
- package/dist/orchestration_1_0_56/queue.d.ts +7 -0
- package/dist/orchestration_1_0_56/queue.d.ts.map +1 -0
- package/dist/orchestration_1_0_56/queue.js +644 -0
- package/dist/orchestration_1_0_56/queue.js.map +1 -0
- package/dist/orchestration_1_0_56/runtime.d.ts +29 -0
- package/dist/orchestration_1_0_56/runtime.d.ts.map +1 -0
- package/dist/orchestration_1_0_56/runtime.js +194 -0
- package/dist/orchestration_1_0_56/runtime.js.map +1 -0
- package/dist/orchestration_1_0_56/state.d.ts +126 -0
- package/dist/orchestration_1_0_56/state.d.ts.map +1 -0
- package/dist/orchestration_1_0_56/state.js +106 -0
- package/dist/orchestration_1_0_56/state.js.map +1 -0
- package/dist/orchestration_1_0_56/turn.d.ts +30 -0
- package/dist/orchestration_1_0_56/turn.d.ts.map +1 -0
- package/dist/orchestration_1_0_56/turn.js +1075 -0
- package/dist/orchestration_1_0_56/turn.js.map +1 -0
- package/dist/orchestration_1_0_56/utils.d.ts +22 -0
- package/dist/orchestration_1_0_56/utils.d.ts.map +1 -0
- package/dist/orchestration_1_0_56/utils.js +226 -0
- package/dist/orchestration_1_0_56/utils.js.map +1 -0
- package/dist/orchestration_1_0_57/agents.d.ts +41 -0
- package/dist/orchestration_1_0_57/agents.d.ts.map +1 -0
- package/dist/orchestration_1_0_57/agents.js +758 -0
- package/dist/orchestration_1_0_57/agents.js.map +1 -0
- package/dist/orchestration_1_0_57/index.d.ts +24 -0
- package/dist/orchestration_1_0_57/index.d.ts.map +1 -0
- package/dist/orchestration_1_0_57/index.js +13 -0
- package/dist/orchestration_1_0_57/index.js.map +1 -0
- package/dist/orchestration_1_0_57/lifecycle.d.ts +49 -0
- package/dist/orchestration_1_0_57/lifecycle.d.ts.map +1 -0
- package/dist/orchestration_1_0_57/lifecycle.js +583 -0
- package/dist/orchestration_1_0_57/lifecycle.js.map +1 -0
- package/dist/orchestration_1_0_57/queue.d.ts +7 -0
- package/dist/orchestration_1_0_57/queue.d.ts.map +1 -0
- package/dist/orchestration_1_0_57/queue.js +644 -0
- package/dist/orchestration_1_0_57/queue.js.map +1 -0
- package/dist/orchestration_1_0_57/runtime.d.ts +29 -0
- package/dist/orchestration_1_0_57/runtime.d.ts.map +1 -0
- package/dist/orchestration_1_0_57/runtime.js +198 -0
- package/dist/orchestration_1_0_57/runtime.js.map +1 -0
- package/dist/orchestration_1_0_57/state.d.ts +133 -0
- package/dist/orchestration_1_0_57/state.d.ts.map +1 -0
- package/dist/orchestration_1_0_57/state.js +115 -0
- package/dist/orchestration_1_0_57/state.js.map +1 -0
- package/dist/orchestration_1_0_57/turn.d.ts +30 -0
- package/dist/orchestration_1_0_57/turn.d.ts.map +1 -0
- package/dist/orchestration_1_0_57/turn.js +1110 -0
- package/dist/orchestration_1_0_57/turn.js.map +1 -0
- package/dist/orchestration_1_0_57/utils.d.ts +22 -0
- package/dist/orchestration_1_0_57/utils.d.ts.map +1 -0
- package/dist/orchestration_1_0_57/utils.js +226 -0
- package/dist/orchestration_1_0_57/utils.js.map +1 -0
- package/dist/session-lifecycle.d.ts +80 -0
- package/dist/session-lifecycle.d.ts.map +1 -0
- package/dist/session-lifecycle.js +257 -0
- package/dist/session-lifecycle.js.map +1 -0
- package/dist/session-manager.d.ts +21 -0
- package/dist/session-manager.d.ts.map +1 -1
- package/dist/session-manager.js +137 -1
- package/dist/session-manager.js.map +1 -1
- package/dist/session-proxy.d.ts +6 -1
- package/dist/session-proxy.d.ts.map +1 -1
- package/dist/session-proxy.js +1123 -953
- package/dist/session-proxy.js.map +1 -1
- package/dist/session-store.d.ts +48 -3
- package/dist/session-store.d.ts.map +1 -1
- package/dist/session-store.js +394 -28
- package/dist/session-store.js.map +1 -1
- package/dist/snapshot-protocol.d.ts +101 -0
- package/dist/snapshot-protocol.d.ts.map +1 -0
- package/dist/snapshot-protocol.js +120 -0
- package/dist/snapshot-protocol.js.map +1 -0
- package/dist/types.d.ts +26 -1
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js.map +1 -1
- package/dist/wait-affinity.d.ts +11 -0
- package/dist/wait-affinity.d.ts.map +1 -1
- package/dist/wait-affinity.js +6 -0
- package/dist/wait-affinity.js.map +1 -1
- package/dist/web/web-client.d.ts +1 -0
- package/dist/web/web-client.d.ts.map +1 -1
- package/dist/web/web-client.js +1 -0
- package/dist/web/web-client.js.map +1 -1
- package/dist/web/web-management-client.d.ts +30 -11
- package/dist/web/web-management-client.d.ts.map +1 -1
- package/dist/web/web-management-client.js +34 -19
- package/dist/web/web-management-client.js.map +1 -1
- package/dist/worker.d.ts +16 -1
- package/dist/worker.d.ts.map +1 -1
- package/dist/worker.js +92 -13
- package/dist/worker.js.map +1 -1
- package/package.json +7 -4
- package/plugins/mgmt/agents/agent-tuner.agent.md +6 -0
- package/plugins/mgmt/agents/facts-manager.agent.md +6 -0
- package/plugins/mgmt/agents/pilotswarm.agent.md +6 -0
- package/plugins/mgmt/agents/resourcemgr.agent.md +6 -0
- package/plugins/mgmt/agents/sweeper.agent.md +6 -0
|
@@ -0,0 +1,644 @@
|
|
|
1
|
+
import { applyChildUpdate, maybeResolveAgentWaitCompletion, parseChildUpdate, } from "./agents.js";
|
|
2
|
+
import { bufferChildUpdate, drainLeadingQueuedScheduleActions, flushPendingChildDigestIntoPrompt, handleCommand, promptIdsIntersectCancellation, publishStatus, queueFollowup, recordCancelledMessageIds, wrapWithResumeContext, } from "./lifecycle.js";
|
|
3
|
+
import { shouldWakeParentForChildDigest } from "../child-notifications.js";
|
|
4
|
+
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, } from "./state.js";
|
|
5
|
+
import { handleTurnResult, processPrompt, processTimer } from "./turn.js";
|
|
6
|
+
import { validClientMessageIds } from "./utils.js";
|
|
7
|
+
// ─── KV FIFO bucket primitives ──────────────────────────────
|
|
8
|
+
function fifoBucketKey(index) {
|
|
9
|
+
return `fifo.${index}`;
|
|
10
|
+
}
|
|
11
|
+
function readFifoBucket(ctx, index) {
|
|
12
|
+
const raw = ctx.getValue(fifoBucketKey(index));
|
|
13
|
+
if (!raw)
|
|
14
|
+
return [];
|
|
15
|
+
try {
|
|
16
|
+
return JSON.parse(raw);
|
|
17
|
+
}
|
|
18
|
+
catch {
|
|
19
|
+
return [];
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
function writeFifoBucket(ctx, index, items) {
|
|
23
|
+
if (items.length === 0) {
|
|
24
|
+
ctx.clearValue(fifoBucketKey(index));
|
|
25
|
+
}
|
|
26
|
+
else {
|
|
27
|
+
ctx.setValue(fifoBucketKey(index), JSON.stringify(items));
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
export function appendToFifo(runtime, newItems) {
|
|
31
|
+
const { ctx } = runtime;
|
|
32
|
+
let writeBucketIdx = 0;
|
|
33
|
+
for (let i = FIFO_BUCKET_COUNT - 1; i >= 0; i--) {
|
|
34
|
+
if (readFifoBucket(ctx, i).length > 0) {
|
|
35
|
+
writeBucketIdx = i;
|
|
36
|
+
break;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
for (const item of newItems) {
|
|
40
|
+
const bucket = readFifoBucket(ctx, writeBucketIdx);
|
|
41
|
+
bucket.push(item);
|
|
42
|
+
const serialized = JSON.stringify(bucket);
|
|
43
|
+
if (serialized.length > MAX_BUCKET_BYTES) {
|
|
44
|
+
bucket.pop();
|
|
45
|
+
writeFifoBucket(ctx, writeBucketIdx, bucket);
|
|
46
|
+
writeBucketIdx++;
|
|
47
|
+
if (writeBucketIdx >= FIFO_BUCKET_COUNT) {
|
|
48
|
+
ctx.traceInfo(`[fifo] overflow — ${newItems.length} item(s) may rely on carry-forward`);
|
|
49
|
+
return;
|
|
50
|
+
}
|
|
51
|
+
writeFifoBucket(ctx, writeBucketIdx, [item]);
|
|
52
|
+
}
|
|
53
|
+
else {
|
|
54
|
+
writeFifoBucket(ctx, writeBucketIdx, bucket);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
function popFifoItem(runtime) {
|
|
59
|
+
const { ctx } = runtime;
|
|
60
|
+
for (let i = 0; i < FIFO_BUCKET_COUNT; i++) {
|
|
61
|
+
const items = readFifoBucket(ctx, i);
|
|
62
|
+
if (items.length > 0) {
|
|
63
|
+
const [first, ...rest] = items;
|
|
64
|
+
writeFifoBucket(ctx, i, rest);
|
|
65
|
+
return first;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
return null;
|
|
69
|
+
}
|
|
70
|
+
function popFirstFifoItemMatching(runtime, predicate) {
|
|
71
|
+
const { ctx } = runtime;
|
|
72
|
+
for (let i = 0; i < FIFO_BUCKET_COUNT; i++) {
|
|
73
|
+
const items = readFifoBucket(ctx, i);
|
|
74
|
+
const index = items.findIndex(predicate);
|
|
75
|
+
if (index >= 0) {
|
|
76
|
+
const [item] = items.splice(index, 1);
|
|
77
|
+
writeFifoBucket(ctx, i, items);
|
|
78
|
+
return item;
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
return null;
|
|
82
|
+
}
|
|
83
|
+
function popNextDispatchFifoItem(runtime) {
|
|
84
|
+
const interactive = popFirstFifoItemMatching(runtime, (item) => item?.kind === "prompt" || item?.kind === "answer");
|
|
85
|
+
if (interactive) {
|
|
86
|
+
runtime.ctx.traceInfo(`[fifo] dispatching interactive ${interactive.kind} before queued timers`);
|
|
87
|
+
return interactive;
|
|
88
|
+
}
|
|
89
|
+
return popFifoItem(runtime);
|
|
90
|
+
}
|
|
91
|
+
function hasFifoItems(runtime) {
|
|
92
|
+
const { ctx } = runtime;
|
|
93
|
+
for (let i = 0; i < FIFO_BUCKET_COUNT; i++) {
|
|
94
|
+
if (readFifoBucket(ctx, i).length > 0)
|
|
95
|
+
return true;
|
|
96
|
+
}
|
|
97
|
+
return false;
|
|
98
|
+
}
|
|
99
|
+
// ─── Timer race candidate selection ─────────────────────────
|
|
100
|
+
function nextTimerCandidate(activeTimer, pendingChildDigest, now) {
|
|
101
|
+
const candidates = [];
|
|
102
|
+
if (activeTimer) {
|
|
103
|
+
candidates.push({
|
|
104
|
+
kind: "active",
|
|
105
|
+
remainingMs: Math.max(0, activeTimer.deadlineMs - now),
|
|
106
|
+
timer: activeTimer,
|
|
107
|
+
});
|
|
108
|
+
}
|
|
109
|
+
if (pendingChildDigest && !pendingChildDigest.ready && pendingChildDigest.updates.length > 0) {
|
|
110
|
+
candidates.push({
|
|
111
|
+
kind: "child-digest",
|
|
112
|
+
remainingMs: Math.max(0, pendingChildDigest.startedAtMs + CHILD_UPDATE_BATCH_MS - now),
|
|
113
|
+
});
|
|
114
|
+
}
|
|
115
|
+
if (candidates.length === 0)
|
|
116
|
+
return null;
|
|
117
|
+
candidates.sort((left, right) => left.remainingMs - right.remainingMs);
|
|
118
|
+
return candidates[0];
|
|
119
|
+
}
|
|
120
|
+
// ─── drain: greedily move queue events + timer fires into KV FIFO ──
|
|
121
|
+
function hasReadyPendingChildDigest(runtime) {
|
|
122
|
+
const digest = runtime.state.pendingChildDigest;
|
|
123
|
+
return Boolean(digest?.ready && digest.updates.length > 0);
|
|
124
|
+
}
|
|
125
|
+
function needsBlockingDequeue(runtime) {
|
|
126
|
+
const { state } = runtime;
|
|
127
|
+
return (state.legacyPendingMessage === undefined &&
|
|
128
|
+
!state.activeTimer &&
|
|
129
|
+
!hasReadyPendingChildDigest(runtime) &&
|
|
130
|
+
state.pendingToolActions.length === 0 &&
|
|
131
|
+
!state.pendingPrompt &&
|
|
132
|
+
!hasFifoItems(runtime));
|
|
133
|
+
}
|
|
134
|
+
export function* drain(runtime) {
|
|
135
|
+
const { ctx, state } = runtime;
|
|
136
|
+
const stash = [];
|
|
137
|
+
const seenChildUpdates = new Set();
|
|
138
|
+
const cancelledThisDrain = new Set();
|
|
139
|
+
for (let i = 0; i < MAX_DRAIN_PER_TURN; i++) {
|
|
140
|
+
let msg = null;
|
|
141
|
+
if (state.legacyPendingMessage !== undefined) {
|
|
142
|
+
msg = state.legacyPendingMessage;
|
|
143
|
+
state.legacyPendingMessage = undefined;
|
|
144
|
+
}
|
|
145
|
+
else if (state.activeTimer || (state.pendingChildDigest && !state.pendingChildDigest.ready)) {
|
|
146
|
+
const now = yield ctx.utcNow();
|
|
147
|
+
const candidate = nextTimerCandidate(state.activeTimer, state.pendingChildDigest, now);
|
|
148
|
+
if (!candidate)
|
|
149
|
+
continue;
|
|
150
|
+
if (candidate.remainingMs === 0) {
|
|
151
|
+
if (candidate.kind === "active" && candidate.timer) {
|
|
152
|
+
stash.push({ kind: "timer", timer: { ...candidate.timer }, firedAtMs: now });
|
|
153
|
+
state.activeTimer = null;
|
|
154
|
+
}
|
|
155
|
+
else if (state.pendingChildDigest && state.pendingChildDigest.updates.length > 0) {
|
|
156
|
+
state.pendingChildDigest.ready = true;
|
|
157
|
+
break;
|
|
158
|
+
}
|
|
159
|
+
continue;
|
|
160
|
+
}
|
|
161
|
+
const msgTask = ctx.dequeueEvent("messages");
|
|
162
|
+
const timerTask = ctx.scheduleTimer(candidate.remainingMs);
|
|
163
|
+
const race = yield ctx.race(msgTask, timerTask);
|
|
164
|
+
if (race.index === 1) {
|
|
165
|
+
if (candidate.kind === "active" && candidate.timer) {
|
|
166
|
+
const firedAt = yield ctx.utcNow();
|
|
167
|
+
stash.push({ kind: "timer", timer: { ...candidate.timer }, firedAtMs: firedAt });
|
|
168
|
+
state.activeTimer = null;
|
|
169
|
+
}
|
|
170
|
+
else if (state.pendingChildDigest && state.pendingChildDigest.updates.length > 0) {
|
|
171
|
+
state.pendingChildDigest.ready = true;
|
|
172
|
+
break;
|
|
173
|
+
}
|
|
174
|
+
continue;
|
|
175
|
+
}
|
|
176
|
+
msg = typeof race.value === "string" ? JSON.parse(race.value) : race.value;
|
|
177
|
+
}
|
|
178
|
+
else if (needsBlockingDequeue(runtime)) {
|
|
179
|
+
if (i > 0)
|
|
180
|
+
break;
|
|
181
|
+
publishStatus(runtime, state.pendingInputQuestion ? "input_required" : "idle");
|
|
182
|
+
const rawMsg = yield ctx.dequeueEvent("messages");
|
|
183
|
+
msg = typeof rawMsg === "string" ? JSON.parse(rawMsg) : rawMsg;
|
|
184
|
+
}
|
|
185
|
+
else {
|
|
186
|
+
const msgTask = ctx.dequeueEvent("messages");
|
|
187
|
+
const timerTask = ctx.scheduleTimer(NON_BLOCKING_TIMER_MS);
|
|
188
|
+
const race = yield ctx.race(msgTask, timerTask);
|
|
189
|
+
if (race.index === 1)
|
|
190
|
+
break;
|
|
191
|
+
msg = typeof race.value === "string" ? JSON.parse(race.value) : race.value;
|
|
192
|
+
}
|
|
193
|
+
if (!msg)
|
|
194
|
+
continue;
|
|
195
|
+
if (msg && Array.isArray(msg.cancelPending) && msg.cancelPending.length > 0) {
|
|
196
|
+
const validCancelIds = [];
|
|
197
|
+
for (const id of msg.cancelPending) {
|
|
198
|
+
if (typeof id === "string" && id) {
|
|
199
|
+
validCancelIds.push(id);
|
|
200
|
+
cancelledThisDrain.add(id);
|
|
201
|
+
state.cancelledMessageIds.add(id);
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
if (validCancelIds.length > 0) {
|
|
205
|
+
ctx.traceInfo(`[drain] received cancel tombstone (ids=${validCancelIds.join(",")})`);
|
|
206
|
+
}
|
|
207
|
+
for (let s = stash.length - 1; s >= 0; s--) {
|
|
208
|
+
const item = stash[s];
|
|
209
|
+
if (item?.kind !== "prompt")
|
|
210
|
+
continue;
|
|
211
|
+
const ids = Array.isArray(item.clientMessageIds) ? item.clientMessageIds : [];
|
|
212
|
+
if (ids.some((id) => cancelledThisDrain.has(id))) {
|
|
213
|
+
ctx.traceInfo(`[drain] dropping stashed prompt cancelled by tombstone (ids=${ids.join(",")})`);
|
|
214
|
+
yield* recordCancelledMessageIds(runtime, ids, "drain-stash");
|
|
215
|
+
stash.splice(s, 1);
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
continue;
|
|
219
|
+
}
|
|
220
|
+
if (msg.type === "cmd") {
|
|
221
|
+
if (stash.length > 0) {
|
|
222
|
+
appendToFifo(runtime, stash);
|
|
223
|
+
stash.length = 0;
|
|
224
|
+
}
|
|
225
|
+
yield* handleCommand(runtime, msg);
|
|
226
|
+
if (state.orchestrationResult !== null)
|
|
227
|
+
return;
|
|
228
|
+
continue;
|
|
229
|
+
}
|
|
230
|
+
const childUpdate = parseChildUpdate(msg.prompt);
|
|
231
|
+
if (childUpdate) {
|
|
232
|
+
const key = `${childUpdate.sessionId}|${childUpdate.updateType}|${childUpdate.content ?? ""}`;
|
|
233
|
+
if (!seenChildUpdates.has(key)) {
|
|
234
|
+
seenChildUpdates.add(key);
|
|
235
|
+
const tracked = yield* applyChildUpdate(runtime, childUpdate);
|
|
236
|
+
if (tracked && !state.pendingShutdown) {
|
|
237
|
+
const childObservedAt = yield ctx.utcNow();
|
|
238
|
+
bufferChildUpdate(runtime, childUpdate, childObservedAt);
|
|
239
|
+
}
|
|
240
|
+
if (tracked && state.waitingForAgentIds) {
|
|
241
|
+
yield* maybeResolveAgentWaitCompletion(runtime);
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
continue;
|
|
245
|
+
}
|
|
246
|
+
if (msg.answer !== undefined) {
|
|
247
|
+
stash.push({ kind: "answer", answer: msg.answer, wasFreeform: msg.wasFreeform });
|
|
248
|
+
continue;
|
|
249
|
+
}
|
|
250
|
+
if (msg.prompt) {
|
|
251
|
+
let userPrompt = msg.prompt;
|
|
252
|
+
if (state.activeTimer?.type === "wait") {
|
|
253
|
+
const now = yield ctx.utcNow();
|
|
254
|
+
const remainingMs = Math.max(0, state.activeTimer.deadlineMs - now);
|
|
255
|
+
const remainingSec = Math.round(remainingMs / 1000);
|
|
256
|
+
const elapsedMs = state.activeTimer.originalDurationMs - remainingMs;
|
|
257
|
+
const elapsedSec = Math.round(elapsedMs / 1000);
|
|
258
|
+
const totalSec = Math.round(state.activeTimer.originalDurationMs / 1000);
|
|
259
|
+
ctx.traceInfo(`[drain] user prompt interrupted wait timer, ${remainingSec}s remain — orchestration will auto-resume`);
|
|
260
|
+
state.interruptedWaitTimer = {
|
|
261
|
+
remainingSec,
|
|
262
|
+
reason: state.activeTimer.reason,
|
|
263
|
+
shouldRehydrate: state.activeTimer.shouldRehydrate ?? false,
|
|
264
|
+
waitPlan: state.activeTimer.waitPlan,
|
|
265
|
+
interruptKind: "user",
|
|
266
|
+
};
|
|
267
|
+
if (state.activeTimer.shouldRehydrate && userPrompt) {
|
|
268
|
+
userPrompt = wrapWithResumeContext(runtime, userPrompt, `Your ${totalSec}s timer (reason: "${state.activeTimer.reason}") was interrupted by the above message. ` +
|
|
269
|
+
`${elapsedSec}s elapsed, ${remainingSec}s remain. ` +
|
|
270
|
+
`Reply to the message. The timer will be automatically resumed after your reply.`);
|
|
271
|
+
}
|
|
272
|
+
else if (userPrompt) {
|
|
273
|
+
userPrompt = `${userPrompt}\n\n` +
|
|
274
|
+
`[SYSTEM: The above is a message that interrupted your ${totalSec}s timer (reason: "${state.activeTimer.reason}"). ` +
|
|
275
|
+
`${elapsedSec}s elapsed, ${remainingSec}s remain. ` +
|
|
276
|
+
`Reply to the message. The timer will be automatically resumed after your reply.]`;
|
|
277
|
+
}
|
|
278
|
+
state.activeTimer = null;
|
|
279
|
+
}
|
|
280
|
+
else if (state.activeTimer?.type === "cron") {
|
|
281
|
+
const activeCron = state.cronSchedule;
|
|
282
|
+
const now = yield ctx.utcNow();
|
|
283
|
+
const remainingMs = Math.max(0, state.activeTimer.deadlineMs - now);
|
|
284
|
+
state.interruptedCronTimer = {
|
|
285
|
+
remainingMs,
|
|
286
|
+
reason: state.activeTimer.reason,
|
|
287
|
+
originalDurationMs: state.activeTimer.originalDurationMs,
|
|
288
|
+
...(state.activeTimer.shouldRehydrate ? { shouldRehydrate: true } : {}),
|
|
289
|
+
};
|
|
290
|
+
const cronResumeNote = `This is an internal recurring schedule, not a new user prompt. ` +
|
|
291
|
+
`There is an active recurring schedule every ${activeCron?.intervalSeconds ?? "?"} seconds for "${activeCron?.reason ?? state.activeTimer.reason}". ` +
|
|
292
|
+
`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. ` +
|
|
293
|
+
`Do NOT call wait() just to keep the recurring loop alive. ` +
|
|
294
|
+
`Call cron(action="cancel") only if you need to stop it.`;
|
|
295
|
+
if (state.activeTimer.shouldRehydrate && userPrompt) {
|
|
296
|
+
userPrompt = wrapWithResumeContext(runtime, userPrompt, cronResumeNote);
|
|
297
|
+
}
|
|
298
|
+
else if (userPrompt) {
|
|
299
|
+
userPrompt = `${userPrompt}\n\n[SYSTEM: ${cronResumeNote}]`;
|
|
300
|
+
}
|
|
301
|
+
ctx.traceInfo(`[drain] user prompt interrupted cron timer`);
|
|
302
|
+
state.activeTimer = null;
|
|
303
|
+
}
|
|
304
|
+
else if (state.activeTimer?.type === "cron_at") {
|
|
305
|
+
const activeCronAt = state.cronAtSchedule;
|
|
306
|
+
const now = yield ctx.utcNow();
|
|
307
|
+
const remainingMs = Math.max(0, state.activeTimer.deadlineMs - now);
|
|
308
|
+
const scheduledAt = activeCronAt?.nextFireAtMs ? new Date(activeCronAt.nextFireAtMs).toISOString() : "unknown";
|
|
309
|
+
const cronAtResumeNote = `This is an internal wall-clock recurring schedule, not a new user prompt. ` +
|
|
310
|
+
`There is an active wall-clock schedule for "${activeCronAt?.reason ?? state.activeTimer.reason}". ` +
|
|
311
|
+
`The pending scheduled fire (${scheduledAt}) is preserved and will run after this turn completes; ` +
|
|
312
|
+
`if the scheduled time passes while you respond, it will fire immediately afterward unless you explicitly cancel or reset the schedule. ` +
|
|
313
|
+
`Do NOT call wait() just to keep the recurring loop alive. ` +
|
|
314
|
+
`Call cron_at(action="cancel") only if you need to stop it.`;
|
|
315
|
+
if (state.activeTimer.shouldRehydrate && userPrompt) {
|
|
316
|
+
userPrompt = wrapWithResumeContext(runtime, userPrompt, cronAtResumeNote);
|
|
317
|
+
}
|
|
318
|
+
else if (userPrompt) {
|
|
319
|
+
userPrompt = `${userPrompt}\n\n[SYSTEM: ${cronAtResumeNote}]`;
|
|
320
|
+
}
|
|
321
|
+
ctx.traceInfo(`[drain] user prompt interrupted cron_at timer (${Math.round(remainingMs / 1000)}s remain)`);
|
|
322
|
+
state.activeTimer = null;
|
|
323
|
+
}
|
|
324
|
+
else if (state.activeTimer?.type === "idle") {
|
|
325
|
+
ctx.traceInfo(`[drain] user prompt within idle window, cancelling idle timer`);
|
|
326
|
+
state.activeTimer = null;
|
|
327
|
+
}
|
|
328
|
+
else if (state.activeTimer?.type === "agent-poll") {
|
|
329
|
+
ctx.traceInfo(`[drain] user prompt interrupted agent wait`);
|
|
330
|
+
state.waitingForAgentIds = null;
|
|
331
|
+
state.activeTimer = null;
|
|
332
|
+
}
|
|
333
|
+
if (state.pendingChildDigest?.updates.length) {
|
|
334
|
+
userPrompt = flushPendingChildDigestIntoPrompt(runtime, userPrompt);
|
|
335
|
+
}
|
|
336
|
+
const incomingClientMessageIds = validClientMessageIds(msg.clientMessageIds);
|
|
337
|
+
if (promptIdsIntersectCancellation(runtime, incomingClientMessageIds)) {
|
|
338
|
+
ctx.traceInfo(`[drain] dropping incoming prompt cancelled by tombstone (ids=${incomingClientMessageIds.join(",")})`);
|
|
339
|
+
yield* recordCancelledMessageIds(runtime, incomingClientMessageIds, "drain-incoming");
|
|
340
|
+
continue;
|
|
341
|
+
}
|
|
342
|
+
stash.push({
|
|
343
|
+
kind: "prompt",
|
|
344
|
+
prompt: userPrompt,
|
|
345
|
+
bootstrap: Boolean(msg.bootstrap),
|
|
346
|
+
...(msg.requiredTool ? { requiredTool: msg.requiredTool } : {}),
|
|
347
|
+
...(incomingClientMessageIds.length > 0 ? { clientMessageIds: incomingClientMessageIds } : {}),
|
|
348
|
+
});
|
|
349
|
+
continue;
|
|
350
|
+
}
|
|
351
|
+
ctx.traceInfo(`[drain] skipping unknown: ${JSON.stringify(msg).slice(0, 120)}`);
|
|
352
|
+
}
|
|
353
|
+
if (stash.length > 0)
|
|
354
|
+
appendToFifo(runtime, stash);
|
|
355
|
+
}
|
|
356
|
+
// ─── Pre-dispatch sweep: grab any pending cancel tombstone ──
|
|
357
|
+
function* sweepMessagesBeforePromptDispatch(runtime) {
|
|
358
|
+
const { ctx, state } = runtime;
|
|
359
|
+
const stash = [];
|
|
360
|
+
const seenChildUpdates = new Set();
|
|
361
|
+
for (let i = 0; i < MAX_PREDISPATCH_SWEEP; i++) {
|
|
362
|
+
const msgTask = ctx.dequeueEvent("messages");
|
|
363
|
+
const timerTask = ctx.scheduleTimer(PREDISPATCH_CANCEL_SWEEP_MS);
|
|
364
|
+
const race = yield ctx.race(msgTask, timerTask);
|
|
365
|
+
if (race.index === 1)
|
|
366
|
+
break;
|
|
367
|
+
const msg = typeof race.value === "string" ? JSON.parse(race.value) : race.value;
|
|
368
|
+
if (!msg)
|
|
369
|
+
continue;
|
|
370
|
+
if (msg && Array.isArray(msg.cancelPending) && msg.cancelPending.length > 0) {
|
|
371
|
+
const validCancelIds = validClientMessageIds(msg.cancelPending);
|
|
372
|
+
for (const id of validCancelIds)
|
|
373
|
+
state.cancelledMessageIds.add(id);
|
|
374
|
+
if (validCancelIds.length > 0) {
|
|
375
|
+
ctx.traceInfo(`[predispatch] received cancel tombstone (ids=${validCancelIds.join(",")})`);
|
|
376
|
+
for (let s = stash.length - 1; s >= 0; s--) {
|
|
377
|
+
const item = stash[s];
|
|
378
|
+
if (item?.kind !== "prompt")
|
|
379
|
+
continue;
|
|
380
|
+
const ids = Array.isArray(item.clientMessageIds) ? item.clientMessageIds : [];
|
|
381
|
+
if (ids.some((id) => validCancelIds.includes(id))) {
|
|
382
|
+
ctx.traceInfo(`[predispatch] dropping stashed prompt cancelled by tombstone (ids=${ids.join(",")})`);
|
|
383
|
+
yield* recordCancelledMessageIds(runtime, ids, "predispatch-stash");
|
|
384
|
+
stash.splice(s, 1);
|
|
385
|
+
}
|
|
386
|
+
}
|
|
387
|
+
}
|
|
388
|
+
continue;
|
|
389
|
+
}
|
|
390
|
+
if (msg.type === "cmd") {
|
|
391
|
+
if (stash.length > 0) {
|
|
392
|
+
appendToFifo(runtime, stash);
|
|
393
|
+
stash.length = 0;
|
|
394
|
+
}
|
|
395
|
+
yield* handleCommand(runtime, msg);
|
|
396
|
+
if (state.orchestrationResult !== null)
|
|
397
|
+
return;
|
|
398
|
+
continue;
|
|
399
|
+
}
|
|
400
|
+
const childUpdate = parseChildUpdate(msg.prompt);
|
|
401
|
+
if (childUpdate) {
|
|
402
|
+
const key = `${childUpdate.sessionId}|${childUpdate.updateType}|${childUpdate.content ?? ""}`;
|
|
403
|
+
if (!seenChildUpdates.has(key)) {
|
|
404
|
+
seenChildUpdates.add(key);
|
|
405
|
+
const tracked = yield* applyChildUpdate(runtime, childUpdate);
|
|
406
|
+
if (tracked && !state.pendingShutdown) {
|
|
407
|
+
const childObservedAt = yield ctx.utcNow();
|
|
408
|
+
bufferChildUpdate(runtime, childUpdate, childObservedAt);
|
|
409
|
+
}
|
|
410
|
+
if (tracked && state.waitingForAgentIds) {
|
|
411
|
+
yield* maybeResolveAgentWaitCompletion(runtime);
|
|
412
|
+
}
|
|
413
|
+
}
|
|
414
|
+
continue;
|
|
415
|
+
}
|
|
416
|
+
if (msg.answer !== undefined) {
|
|
417
|
+
stash.push({ kind: "answer", answer: msg.answer, wasFreeform: msg.wasFreeform });
|
|
418
|
+
continue;
|
|
419
|
+
}
|
|
420
|
+
if (msg.prompt) {
|
|
421
|
+
const incomingClientMessageIds = validClientMessageIds(msg.clientMessageIds);
|
|
422
|
+
if (promptIdsIntersectCancellation(runtime, incomingClientMessageIds)) {
|
|
423
|
+
ctx.traceInfo(`[predispatch] dropping incoming prompt cancelled by tombstone (ids=${incomingClientMessageIds.join(",")})`);
|
|
424
|
+
yield* recordCancelledMessageIds(runtime, incomingClientMessageIds, "predispatch-incoming");
|
|
425
|
+
continue;
|
|
426
|
+
}
|
|
427
|
+
stash.push({
|
|
428
|
+
kind: "prompt",
|
|
429
|
+
prompt: msg.prompt,
|
|
430
|
+
bootstrap: Boolean(msg.bootstrap),
|
|
431
|
+
...(msg.requiredTool ? { requiredTool: msg.requiredTool } : {}),
|
|
432
|
+
...(incomingClientMessageIds.length > 0 ? { clientMessageIds: incomingClientMessageIds } : {}),
|
|
433
|
+
});
|
|
434
|
+
continue;
|
|
435
|
+
}
|
|
436
|
+
ctx.traceInfo(`[predispatch] skipping unknown: ${JSON.stringify(msg).slice(0, 120)}`);
|
|
437
|
+
}
|
|
438
|
+
if (stash.length > 0)
|
|
439
|
+
appendToFifo(runtime, stash);
|
|
440
|
+
}
|
|
441
|
+
// ─── decide: pop and process one item from FIFO ─────────────
|
|
442
|
+
function* processAnswer(runtime, answerItem) {
|
|
443
|
+
const question = runtime.state.pendingInputQuestion?.question ?? "a question";
|
|
444
|
+
runtime.state.pendingInputQuestion = null;
|
|
445
|
+
const answerPrompt = `The user was asked: "${question}"\nThe user responded: "${answerItem.answer}"`;
|
|
446
|
+
yield* processPrompt(runtime, answerPrompt, false);
|
|
447
|
+
}
|
|
448
|
+
export function* decide(runtime) {
|
|
449
|
+
const { ctx, state } = runtime;
|
|
450
|
+
// Priority 1: pending tool actions (in-memory, replay carry-forward).
|
|
451
|
+
yield* drainLeadingQueuedScheduleActions(runtime);
|
|
452
|
+
if (state.pendingToolActions.length > 0) {
|
|
453
|
+
const action = state.pendingToolActions.shift();
|
|
454
|
+
ctx.traceInfo(`[orch] replaying queued action: ${action.type} remaining=${state.pendingToolActions.length}`);
|
|
455
|
+
yield* handleTurnResult(runtime, action, "");
|
|
456
|
+
return true;
|
|
457
|
+
}
|
|
458
|
+
// Priority 2: pending prompt (CAN carry-forward or queueFollowup).
|
|
459
|
+
// Hold while waiting for agents — let confirmations accumulate and merge
|
|
460
|
+
// with the agents-done summary for one combined LLM turn.
|
|
461
|
+
if (state.pendingPrompt && !state.waitingForAgentIds) {
|
|
462
|
+
const prompt = state.pendingPrompt;
|
|
463
|
+
const isBootstrap = state.bootstrapPrompt;
|
|
464
|
+
const requiredTool = state.pendingRequiredTool;
|
|
465
|
+
const cycleOrigin = state.pendingCycleOrigin;
|
|
466
|
+
state.pendingPrompt = undefined;
|
|
467
|
+
state.bootstrapPrompt = false;
|
|
468
|
+
state.pendingRequiredTool = undefined;
|
|
469
|
+
state.pendingCycleOrigin = undefined;
|
|
470
|
+
yield* processPrompt(runtime, prompt, isBootstrap, requiredTool, undefined, cycleOrigin);
|
|
471
|
+
return true;
|
|
472
|
+
}
|
|
473
|
+
// Priority 3: FIFO — next item in arrival order, with prompt batching.
|
|
474
|
+
const item = popNextDispatchFifoItem(runtime);
|
|
475
|
+
if (item) {
|
|
476
|
+
switch (item.kind) {
|
|
477
|
+
case "prompt": {
|
|
478
|
+
const ids = Array.isArray(item.clientMessageIds) ? item.clientMessageIds : [];
|
|
479
|
+
if (ids.length > 0) {
|
|
480
|
+
yield* sweepMessagesBeforePromptDispatch(runtime);
|
|
481
|
+
if (state.orchestrationResult !== null)
|
|
482
|
+
return true;
|
|
483
|
+
}
|
|
484
|
+
if (promptIdsIntersectCancellation(runtime, ids)) {
|
|
485
|
+
ctx.traceInfo(`[decide] dropping FIFO prompt cancelled by tombstone (ids=${ids.join(",")})`);
|
|
486
|
+
yield* recordCancelledMessageIds(runtime, ids, "decide-fifo");
|
|
487
|
+
return true;
|
|
488
|
+
}
|
|
489
|
+
// Batch consecutive prompt FIFO items into a single Copilot turn.
|
|
490
|
+
let mergedPrompt = String(item.prompt || "");
|
|
491
|
+
let mergedBootstrap = item.bootstrap ?? false;
|
|
492
|
+
let mergedRequiredTool = item.requiredTool;
|
|
493
|
+
const mergedClientMessageIds = [...ids];
|
|
494
|
+
while (true) {
|
|
495
|
+
const peek = popFifoItem(runtime);
|
|
496
|
+
if (!peek)
|
|
497
|
+
break;
|
|
498
|
+
if (peek.kind !== "prompt") {
|
|
499
|
+
appendToFifo(runtime, [peek]);
|
|
500
|
+
break;
|
|
501
|
+
}
|
|
502
|
+
const peekIds = Array.isArray(peek.clientMessageIds) ? peek.clientMessageIds : [];
|
|
503
|
+
if (promptIdsIntersectCancellation(runtime, peekIds)) {
|
|
504
|
+
ctx.traceInfo(`[decide] dropping merged FIFO prompt cancelled by tombstone (ids=${peekIds.join(",")})`);
|
|
505
|
+
yield* recordCancelledMessageIds(runtime, peekIds, "decide-merge");
|
|
506
|
+
continue;
|
|
507
|
+
}
|
|
508
|
+
mergedPrompt = `${mergedPrompt}\n\n${String(peek.prompt || "")}`;
|
|
509
|
+
mergedBootstrap = mergedBootstrap || (peek.bootstrap ?? false);
|
|
510
|
+
if (!mergedRequiredTool && peek.requiredTool)
|
|
511
|
+
mergedRequiredTool = peek.requiredTool;
|
|
512
|
+
for (const id of peekIds)
|
|
513
|
+
mergedClientMessageIds.push(id);
|
|
514
|
+
}
|
|
515
|
+
yield* processPrompt(runtime, mergedPrompt, mergedBootstrap, mergedRequiredTool, mergedClientMessageIds.length > 0 ? mergedClientMessageIds : undefined);
|
|
516
|
+
break;
|
|
517
|
+
}
|
|
518
|
+
case "answer":
|
|
519
|
+
yield* processAnswer(runtime, item);
|
|
520
|
+
break;
|
|
521
|
+
case "timer":
|
|
522
|
+
yield* processTimer(runtime, item);
|
|
523
|
+
break;
|
|
524
|
+
case "agents-done":
|
|
525
|
+
queueFollowup(runtime, item.summary);
|
|
526
|
+
break;
|
|
527
|
+
default:
|
|
528
|
+
ctx.traceInfo(`[decide] unknown FIFO item kind: ${item.kind}`);
|
|
529
|
+
}
|
|
530
|
+
return true;
|
|
531
|
+
}
|
|
532
|
+
// Priority 4: buffered child digest — only after user/FIFO work is drained.
|
|
533
|
+
if (state.pendingChildDigest?.ready && state.pendingChildDigest.updates.length > 0 && !state.waitingForAgentIds) {
|
|
534
|
+
yield* processPendingChildDigest(runtime);
|
|
535
|
+
return true;
|
|
536
|
+
}
|
|
537
|
+
return false;
|
|
538
|
+
}
|
|
539
|
+
// ─── pending child digest dispatch (timer-aware) ────────────
|
|
540
|
+
import { buildPendingChildDigestSystemPrompt, clearPendingChildDigest, } from "./lifecycle.js";
|
|
541
|
+
function* processPendingChildDigest(runtime) {
|
|
542
|
+
const { ctx, state } = runtime;
|
|
543
|
+
const digestPrompt = buildPendingChildDigestSystemPrompt(runtime);
|
|
544
|
+
if (!digestPrompt) {
|
|
545
|
+
clearPendingChildDigest(runtime);
|
|
546
|
+
return;
|
|
547
|
+
}
|
|
548
|
+
const digestDecision = shouldWakeParentForChildDigest(state.pendingChildDigest.updates.map((update) => {
|
|
549
|
+
const agent = state.subAgents.find((entry) => entry.sessionId === update.sessionId);
|
|
550
|
+
return {
|
|
551
|
+
update: {
|
|
552
|
+
kind: update.updateType === "wait"
|
|
553
|
+
? "wait"
|
|
554
|
+
: update.updateType === "failed"
|
|
555
|
+
? "error"
|
|
556
|
+
: update.updateType === "cancelled" || update.updateType === "deleted"
|
|
557
|
+
? "cancelled"
|
|
558
|
+
: update.updateType === "completed"
|
|
559
|
+
? "completed"
|
|
560
|
+
: "progress",
|
|
561
|
+
summary: update.content,
|
|
562
|
+
...(update.cycleOrigin ? { cyclic: true } : {}),
|
|
563
|
+
...(update.cycleStatus === "material" || update.cycleStatus === "blocked"
|
|
564
|
+
? { material: true }
|
|
565
|
+
: update.cycleStatus === "quiet"
|
|
566
|
+
? { material: false }
|
|
567
|
+
: {}),
|
|
568
|
+
...(update.verdict ? { result: { verdict: update.verdict } } : update.cycleStatus === "blocked" ? { result: { verdict: "blocked" } } : {}),
|
|
569
|
+
},
|
|
570
|
+
contract: agent?.contract,
|
|
571
|
+
};
|
|
572
|
+
}));
|
|
573
|
+
if (!digestDecision.wake) {
|
|
574
|
+
yield runtime.manager.recordSessionEvent(runtime.input.sessionId, [{
|
|
575
|
+
eventType: "session.child_update_suppressed",
|
|
576
|
+
data: {
|
|
577
|
+
reason: digestDecision.reason,
|
|
578
|
+
policy: digestDecision.policy,
|
|
579
|
+
classification: digestDecision.classification,
|
|
580
|
+
updateCount: state.pendingChildDigest.updates.length,
|
|
581
|
+
},
|
|
582
|
+
}]);
|
|
583
|
+
clearPendingChildDigest(runtime);
|
|
584
|
+
return;
|
|
585
|
+
}
|
|
586
|
+
if (state.activeTimer?.type === "wait") {
|
|
587
|
+
const now = yield ctx.utcNow();
|
|
588
|
+
const remainingMs = Math.max(0, state.activeTimer.deadlineMs - now);
|
|
589
|
+
const remainingSec = Math.round(remainingMs / 1000);
|
|
590
|
+
const elapsedMs = state.activeTimer.originalDurationMs - remainingMs;
|
|
591
|
+
const elapsedSec = Math.round(elapsedMs / 1000);
|
|
592
|
+
const totalSec = Math.round(state.activeTimer.originalDurationMs / 1000);
|
|
593
|
+
state.interruptedWaitTimer = {
|
|
594
|
+
remainingSec,
|
|
595
|
+
reason: state.activeTimer.reason,
|
|
596
|
+
shouldRehydrate: state.activeTimer.shouldRehydrate ?? false,
|
|
597
|
+
waitPlan: state.activeTimer.waitPlan,
|
|
598
|
+
interruptKind: "child",
|
|
599
|
+
};
|
|
600
|
+
state.activeTimer = null;
|
|
601
|
+
clearPendingChildDigest(runtime);
|
|
602
|
+
yield* processPrompt(runtime, `[SYSTEM: Buffered child updates interrupted your ${totalSec}s timer (reason: "${state.interruptedWaitTimer.reason}"). ` +
|
|
603
|
+
`${elapsedSec}s elapsed, ${remainingSec}s remain. ` +
|
|
604
|
+
`Review the updates and continue your task now. The remaining wait will be resumed automatically after this turn completes.\n\n${digestPrompt}]`, true);
|
|
605
|
+
return;
|
|
606
|
+
}
|
|
607
|
+
if (state.activeTimer?.type === "cron") {
|
|
608
|
+
const activeCron = state.cronSchedule;
|
|
609
|
+
const now = yield ctx.utcNow();
|
|
610
|
+
const remainingMs = Math.max(0, state.activeTimer.deadlineMs - now);
|
|
611
|
+
state.interruptedCronTimer = {
|
|
612
|
+
remainingMs,
|
|
613
|
+
reason: state.activeTimer.reason,
|
|
614
|
+
originalDurationMs: state.activeTimer.originalDurationMs,
|
|
615
|
+
...(state.activeTimer.shouldRehydrate ? { shouldRehydrate: true } : {}),
|
|
616
|
+
};
|
|
617
|
+
state.activeTimer = null;
|
|
618
|
+
clearPendingChildDigest(runtime);
|
|
619
|
+
yield* processPrompt(runtime, `[SYSTEM: This is an internal orchestration wake-up caused by child session updates; the user did not send a new message. ` +
|
|
620
|
+
`Buffered child updates arrived while your recurring schedule was waiting for the next wake-up${activeCron ? ` ("${activeCron.reason}")` : ""}. ` +
|
|
621
|
+
`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);
|
|
622
|
+
return;
|
|
623
|
+
}
|
|
624
|
+
if (state.activeTimer?.type === "cron_at") {
|
|
625
|
+
const activeCronAt = state.cronAtSchedule;
|
|
626
|
+
const scheduledAt = activeCronAt?.nextFireAtMs ? new Date(activeCronAt.nextFireAtMs).toISOString() : "unknown";
|
|
627
|
+
state.activeTimer = null;
|
|
628
|
+
clearPendingChildDigest(runtime);
|
|
629
|
+
yield* processPrompt(runtime, `[SYSTEM: This is an internal orchestration wake-up caused by child session updates; the user did not send a new message. ` +
|
|
630
|
+
`Buffered child updates arrived while your wall-clock schedule was waiting for its next fire${activeCronAt ? ` ("${activeCronAt.reason}", scheduled ${scheduledAt})` : ""}. ` +
|
|
631
|
+
`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);
|
|
632
|
+
return;
|
|
633
|
+
}
|
|
634
|
+
if (state.activeTimer?.type === "idle") {
|
|
635
|
+
state.activeTimer = null;
|
|
636
|
+
}
|
|
637
|
+
else if (state.activeTimer?.type === "agent-poll") {
|
|
638
|
+
state.waitingForAgentIds = null;
|
|
639
|
+
state.activeTimer = null;
|
|
640
|
+
}
|
|
641
|
+
clearPendingChildDigest(runtime);
|
|
642
|
+
yield* processPrompt(runtime, `[SYSTEM: ${digestPrompt}]`, true);
|
|
643
|
+
}
|
|
644
|
+
//# sourceMappingURL=queue.js.map
|