maka-agent 0.2.0-dev.23.20260906 → 0.2.0-dev.24.20260907
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/acp/maka-acp-agent.js +2 -1
- package/dist/acp/session-configuration.js +143 -0
- package/dist/acp/session-registry.js +90 -4
- package/dist/cli-core.js +2 -0
- package/dist/pi-transcript.js +5 -12
- package/dist/pi-tui-runner.js +74 -4
- package/dist/runtime-host-cli-context.js +167 -78
- package/dist/runtime-host-cli-installation.js +8 -2
- package/dist/runtime-host-cli.js +31 -1
- package/dist/runtime-host-handoff-surface.js +81 -0
- package/dist/runtime-host-installed-update-bootstrap.js +13 -0
- package/dist/runtime-host-installed-update-coordinator.js +33 -0
- package/dist/runtime-host-lifecycle-transaction.js +9 -1
- package/dist/runtime-host-local-handoff.js +37 -4
- package/dist/runtime-host-local-managed-activation.js +84 -0
- package/dist/runtime-host-local-source-retirement.js +18 -2
- package/dist/runtime-host-managed-lifecycle-manager.js +1 -0
- package/dist/runtime-host-operator-retirement.js +50 -0
- package/dist/runtime-host-service-manager.js +3 -0
- package/dist/runtime-host-session-driver.js +4 -25
- package/dist/runtime-host-session-update.js +62 -0
- package/dist/runtime-host-tui-command.js +38 -73
- package/dist/runtime-host-tui-context.js +1 -0
- package/dist/runtime-host-tui-owner.js +226 -0
- package/dist/tui-copy-catalog.js +140 -3
- package/dist/tui-host-owner-copy.js +24 -0
- package/native/runtime-host-windows-task-launcher/prebuilds/win32-x64/maka-runtime-host-task-launcher.exe +0 -0
- package/node_modules/@maka/core/dist/canonical-runtime-event.js +3 -1
- package/node_modules/@maka/core/dist/model-failure.js +40 -0
- package/node_modules/@maka/core/dist/model-metadata.generated.js +4 -4
- package/node_modules/@maka/core/dist/model-metadata.js +3 -3
- package/node_modules/@maka/core/dist/model-web-search.js +5 -4
- package/node_modules/@maka/core/dist/provider-registry.js +45 -4
- package/node_modules/@maka/core/dist/record-schema.js +9 -2
- package/node_modules/@maka/core/dist/runtime-boundary.js +31 -11
- package/node_modules/@maka/core/dist/runtime-event.js +37 -6
- package/node_modules/@maka/core/dist/runtime-handoff.js +98 -0
- package/node_modules/@maka/core/dist/runtime-invocation.js +1 -1
- package/node_modules/@maka/core/dist/runtime-logical-execution.js +112 -0
- package/node_modules/@maka/core/dist/session.js +54 -52
- package/node_modules/@maka/core/dist/slash-command-catalog.js +2 -0
- package/node_modules/@maka/core/dist/tool-result-archive-evidence.js +20 -0
- package/node_modules/@maka/core/dist/usage-record-schema.js +26 -35
- package/node_modules/@maka/core/package.json +4 -0
- package/node_modules/@maka/mcp/dist/index.js +131 -1
- package/node_modules/@maka/runtime/dist/agent-run.js +272 -105
- package/node_modules/@maka/runtime/dist/ai-sdk-backend.js +13 -5
- package/node_modules/@maka/runtime/dist/ai-sdk-compaction.js +109 -34
- package/node_modules/@maka/runtime/dist/ai-sdk-turn.js +127 -133
- package/node_modules/@maka/runtime/dist/context-budget.js +14 -9
- package/node_modules/@maka/runtime/dist/conversation-copy.js +5 -4
- package/node_modules/@maka/runtime/dist/goal-continuation.js +36 -4
- package/node_modules/@maka/runtime/dist/history-compact-checkpoint.js +18 -9
- package/node_modules/@maka/runtime/dist/history-compact-summarizer.js +30 -22
- package/node_modules/@maka/runtime/dist/history-compaction.js +29 -4
- package/node_modules/@maka/runtime/dist/ledger-tool-result-archive-reader.js +106 -0
- package/node_modules/@maka/runtime/dist/message-authority.js +9 -0
- package/node_modules/@maka/runtime/dist/model-adapter.js +19 -98
- package/node_modules/@maka/runtime/dist/model-factory.js +19 -81
- package/node_modules/@maka/runtime/dist/model-fetcher.js +7 -0
- package/node_modules/@maka/runtime/dist/model-runtime.js +85 -111
- package/node_modules/@maka/runtime/dist/native-web-search-tool.js +2 -1
- package/node_modules/@maka/runtime/dist/openai-codex-history-compactor.js +2 -2
- package/node_modules/@maka/runtime/dist/opencode-session-header.js +4 -2
- package/node_modules/@maka/runtime/dist/provider-error-classification.js +84 -123
- package/node_modules/@maka/runtime/dist/provider-request-telemetry.js +68 -108
- package/node_modules/@maka/runtime/dist/run-handoff-gate.js +97 -0
- package/node_modules/@maka/runtime/dist/runtime-event-backfill.js +82 -54
- package/node_modules/@maka/runtime/dist/runtime-event-read-model.js +117 -159
- package/node_modules/@maka/runtime/dist/runtime-kernel.js +150 -112
- package/node_modules/@maka/runtime/dist/runtime-ledger-repair.js +160 -79
- package/node_modules/@maka/runtime/dist/runtime-read-model.js +30 -89
- package/node_modules/@maka/runtime/dist/runtime-resume.js +80 -4
- package/node_modules/@maka/runtime/dist/session-event-runtime-mapper.js +5 -0
- package/node_modules/@maka/runtime/dist/session-manager.js +171 -288
- package/node_modules/@maka/runtime/dist/session-projection-helpers.js +0 -27
- package/node_modules/@maka/runtime/dist/subscription-model-fetch.js +3 -4
- package/node_modules/@maka/runtime/dist/test-connection.js +16 -16
- package/node_modules/@maka/runtime/dist/tool-result-archive-encoding.js +38 -0
- package/node_modules/@maka/runtime/dist/tool-runtime.js +3 -61
- package/node_modules/@maka/runtime/package.json +1 -0
- package/node_modules/@maka/runtime-host/dist/adapter/session-projector.js +35 -0
- package/node_modules/@maka/runtime-host/dist/client/connect-or-spawn.js +9 -1
- package/node_modules/@maka/runtime-host/dist/client/connection.js +3 -0
- package/node_modules/@maka/runtime-host/dist/client/host-handoff-copy.js +156 -0
- package/node_modules/@maka/runtime-host/dist/client/host-handoff.js +273 -0
- package/node_modules/@maka/runtime-host/dist/client/host-retirement.js +26 -5
- package/node_modules/@maka/runtime-host/dist/client/index.js +2 -0
- package/node_modules/@maka/runtime-host/dist/client/launcher.js +3 -0
- package/node_modules/@maka/runtime-host/dist/client/remote-compatibility-error.js +2 -0
- package/node_modules/@maka/runtime-host/dist/client/session-subscription.js +27 -5
- package/node_modules/@maka/runtime-host/dist/operator/index.js +2 -1
- package/node_modules/@maka/runtime-host/dist/operator/retirement-cancellation.js +20 -0
- package/node_modules/@maka/runtime-host/dist/protocol/host-status.js +21 -12
- package/node_modules/@maka/runtime-host/dist/protocol/index.js +25 -1
- package/node_modules/@maka/runtime-host/dist/protocol/operations.js +1 -0
- package/node_modules/@maka/runtime-host/dist/protocol/session-continuity.js +40 -20
- package/node_modules/@maka/runtime-host/dist/protocol/session-transcript.js +0 -5
- package/node_modules/@maka/runtime-host/dist/protocol/session-turns.js +29 -58
- package/node_modules/@maka/runtime-host/dist/protocol/turn.js +11 -1
- package/node_modules/@maka/runtime-host/dist/server/access-credential-store.js +1 -0
- package/node_modules/@maka/runtime-host/dist/server/canonical-turn-snapshot.js +19 -4
- package/node_modules/@maka/runtime-host/dist/server/connection-session.js +7 -6
- package/node_modules/@maka/runtime-host/dist/server/execution-composition.js +100 -24
- package/node_modules/@maka/runtime-host/dist/server/execution-model-authority.js +5 -5
- package/node_modules/@maka/runtime-host/dist/server/execution-model-composition.js +3 -2
- package/node_modules/@maka/runtime-host/dist/server/goal-coordinator.js +40 -1
- package/node_modules/@maka/runtime-host/dist/server/host-kernel.js +96 -17
- package/node_modules/@maka/runtime-host/dist/server/host-residency-registry.js +13 -7
- package/node_modules/@maka/runtime-host/dist/server/hosted-execution-admission-registry.js +3 -1
- package/node_modules/@maka/runtime-host/dist/server/hosted-execution-recovery.js +124 -62
- package/node_modules/@maka/runtime-host/dist/server/message-coordinator.js +76 -6
- package/node_modules/@maka/runtime-host/dist/server/root-turn-coordinator.js +325 -73
- package/node_modules/@maka/runtime-host/dist/server/scheduled-task-coordinator.js +37 -2
- package/node_modules/@maka/runtime-host/dist/server/serial-outbound-writer.js +45 -3
- package/node_modules/@maka/runtime-host/dist/server/session-catalog-coordinator.js +50 -7
- package/node_modules/@maka/runtime-host/dist/server/session-continuity-coordinator.js +87 -25
- package/node_modules/@maka/runtime-host/dist/server/session-revision-coordinator.js +8 -55
- package/node_modules/@maka/runtime-host/dist/server/session-sidecar-purge.js +1 -16
- package/node_modules/@maka/runtime-host/dist/server/session-transcript-pager.js +0 -1
- package/node_modules/@maka/runtime-host/dist/server/session-transcript-reader.js +460 -70
- package/node_modules/@maka/runtime-host/dist/server/shared-session-transcript.js +1 -1
- package/node_modules/@maka/runtime-host/dist/server/storage-maintenance.js +109 -0
- package/node_modules/@maka/runtime-host/dist/server/workhub-coordination-coordinator.js +0 -1
- package/node_modules/@maka/storage/dist/artifact-store.js +21 -10
- package/node_modules/@maka/storage/dist/artifact-stores.js +1 -1
- package/node_modules/@maka/storage/dist/claude-code-session-adapter.js +0 -4
- package/node_modules/@maka/storage/dist/codex-session-adapter.js +0 -2
- package/node_modules/@maka/storage/dist/context-offload-snapshot.js +325 -0
- package/node_modules/@maka/storage/dist/execution-stores.js +9 -7
- package/node_modules/@maka/storage/dist/file-session-repository.js +1012 -0
- package/node_modules/@maka/storage/dist/opencode-session-adapter.js +0 -4
- package/node_modules/@maka/storage/dist/operational-state-backup.js +9 -5
- package/node_modules/@maka/storage/dist/runtime-event-persistence.js +1 -0
- package/node_modules/@maka/storage/dist/runtime-transcript-query.js +308 -0
- package/node_modules/@maka/storage/dist/session-bundle-policy.js +24 -4
- package/node_modules/@maka/storage/dist/session-message-projection.js +15 -0
- package/node_modules/@maka/storage/dist/session-repository.js +767 -0
- package/node_modules/@maka/storage/dist/session-store.js +25 -99
- package/node_modules/@maka/storage/dist/sqlite-artifact-metadata.js +10 -3
- package/node_modules/@maka/storage/dist/sqlite-context-offload-store.js +24 -8
- package/node_modules/@maka/storage/dist/sqlite-core-execution-schema.js +7 -1
- package/node_modules/@maka/storage/dist/sqlite-runtime-schema.js +44 -1
- package/node_modules/@maka/storage/dist/sqlite-runtime-store.js +129 -25
- package/node_modules/@maka/storage/dist/sqlite-session-metadata-store.js +109 -892
- package/node_modules/@maka/storage/dist/tool-result-archive-evidence.js +111 -0
- package/node_modules/@maka/storage/package.json +2 -0
- package/package.json +1 -1
|
@@ -18,6 +18,8 @@
|
|
|
18
18
|
*/
|
|
19
19
|
import { createHash, randomUUID } from 'node:crypto';
|
|
20
20
|
import { isDeepStrictEqual } from 'node:util';
|
|
21
|
+
import { readLogicalRuntimeExecution, readLogicalRuntimeExecutionForRun, } from '@maka/core/runtime-logical-execution';
|
|
22
|
+
import { runtimeHandoffPause } from '@maka/core/runtime-handoff';
|
|
21
23
|
import { INLINE_REFERENCE_MAX_COUNT, messageContentDigest, messageContentsEqual, normalizeMessageContent, } from '@maka/core/events';
|
|
22
24
|
import { isWorkHubCoordinationSessionId } from '@maka/core/session';
|
|
23
25
|
import { resolveEffectiveOrchestration } from '@maka/core/orchestration';
|
|
@@ -58,6 +60,8 @@ export class RootTurnCoordinator {
|
|
|
58
60
|
};
|
|
59
61
|
#executions = new HostedExecutionRegistry();
|
|
60
62
|
#admissions;
|
|
63
|
+
#handoffHold;
|
|
64
|
+
#cancelHandoff;
|
|
61
65
|
#recoveryPlansBySession = new Map();
|
|
62
66
|
stores;
|
|
63
67
|
executionProjection;
|
|
@@ -117,6 +121,8 @@ export class RootTurnCoordinator {
|
|
|
117
121
|
}
|
|
118
122
|
}
|
|
119
123
|
else {
|
|
124
|
+
if (plan.rootReplayAdmission === admission)
|
|
125
|
+
continue;
|
|
120
126
|
if (admission.execution.kind !== 'safe_boundary_continuation') {
|
|
121
127
|
throw new Error(`Startup recovery left Turn ${admission.turnId} non-terminal`);
|
|
122
128
|
}
|
|
@@ -127,7 +133,11 @@ export class RootTurnCoordinator {
|
|
|
127
133
|
if (!admission)
|
|
128
134
|
continue;
|
|
129
135
|
// Session recovery may have materialized this Run after the replay plan was prepared.
|
|
130
|
-
|
|
136
|
+
const existingRun = await this.readRunIfPresent(sessionId, admission.runId);
|
|
137
|
+
const pendingHandoff = existingRun &&
|
|
138
|
+
(await readLogicalRuntimeExecution(this.stores.runtimeEventStore, admission, existingRun))
|
|
139
|
+
?.pendingHandoff;
|
|
140
|
+
if (existingRun && !pendingHandoff)
|
|
131
141
|
continue;
|
|
132
142
|
const input = activationInputForAdmission(admission);
|
|
133
143
|
const disposition = await this.sessionAdmission.run(sessionId, async (lease) => {
|
|
@@ -138,7 +148,7 @@ export class RootTurnCoordinator {
|
|
|
138
148
|
return undefined;
|
|
139
149
|
}
|
|
140
150
|
}
|
|
141
|
-
const continuation = admission.execution.kind === 'safe_boundary_continuation'
|
|
151
|
+
const continuation = admission.execution.kind === 'safe_boundary_continuation' && !pendingHandoff
|
|
142
152
|
? await this.reconstructAdmittedContinuation(admission)
|
|
143
153
|
: undefined;
|
|
144
154
|
if (continuation?.disposition === 'parked') {
|
|
@@ -169,6 +179,7 @@ export class RootTurnCoordinator {
|
|
|
169
179
|
this.#recoveryPlansBySession.clear();
|
|
170
180
|
}
|
|
171
181
|
async close() {
|
|
182
|
+
this.#cancelHandoff?.();
|
|
172
183
|
this.beginDrain();
|
|
173
184
|
await this.#admissions.waitForSettledAdmissions();
|
|
174
185
|
const errors = [];
|
|
@@ -221,7 +232,9 @@ export class RootTurnCoordinator {
|
|
|
221
232
|
runId: active.runId,
|
|
222
233
|
};
|
|
223
234
|
}
|
|
224
|
-
return this.#admissions.has(sessionId)
|
|
235
|
+
return this.#handoffHold || this.#admissions.has(sessionId)
|
|
236
|
+
? { kind: 'reserved' }
|
|
237
|
+
: { kind: 'idle' };
|
|
225
238
|
}
|
|
226
239
|
/** Returns the newest Host-admitted continuation descended from one root execution. */
|
|
227
240
|
async readLatestRootTurnLineage(identity) {
|
|
@@ -231,7 +244,11 @@ export class RootTurnCoordinator {
|
|
|
231
244
|
}
|
|
232
245
|
let latest = origin;
|
|
233
246
|
while (true) {
|
|
234
|
-
const
|
|
247
|
+
const run = await this.readRunIfPresent(latest.sessionId, latest.runId);
|
|
248
|
+
const physical = run?.terminalEvent && runtimeHandoffPause(run.terminalEvent)
|
|
249
|
+
? (await readLogicalRuntimeExecution(this.stores.runtimeEventStore, latest, run))?.tip
|
|
250
|
+
: run;
|
|
251
|
+
const continuation = await this.stores.agentRunStore.readRootTurnContinuationAdmission(identity.sessionId, latest.turnId, physical?.runId ?? latest.runId);
|
|
235
252
|
if (!continuation)
|
|
236
253
|
break;
|
|
237
254
|
latest = continuation;
|
|
@@ -249,12 +266,16 @@ export class RootTurnCoordinator {
|
|
|
249
266
|
}, context);
|
|
250
267
|
}
|
|
251
268
|
reserveRootTurn(sessionId) {
|
|
269
|
+
if (this.#handoffHold)
|
|
270
|
+
return undefined;
|
|
252
271
|
return this.#admissions.reserve(sessionId);
|
|
253
272
|
}
|
|
254
273
|
parkContinuationAdmission(admission) {
|
|
255
274
|
this.#admissions.park(admission);
|
|
256
275
|
}
|
|
257
276
|
takeParkedContinuationReservation(admission) {
|
|
277
|
+
if (this.#handoffHold)
|
|
278
|
+
return undefined;
|
|
258
279
|
return this.#admissions.takeParked(admission);
|
|
259
280
|
}
|
|
260
281
|
clearParkedContinuationAdmission(admission) {
|
|
@@ -272,6 +293,163 @@ export class RootTurnCoordinator {
|
|
|
272
293
|
beginDrain() {
|
|
273
294
|
this.#admissions.beginDrain();
|
|
274
295
|
}
|
|
296
|
+
async prepareHandoff(hostEpoch, signal) {
|
|
297
|
+
if (this.#handoffHold || this.#admissions.isDraining || signal.aborted)
|
|
298
|
+
return undefined;
|
|
299
|
+
const hold = deferred();
|
|
300
|
+
this.#handoffHold = hold;
|
|
301
|
+
const held = new Map();
|
|
302
|
+
let sealed = false;
|
|
303
|
+
let detached = false;
|
|
304
|
+
const releaseHold = () => {
|
|
305
|
+
if (this.#handoffHold === hold) {
|
|
306
|
+
this.#handoffHold = undefined;
|
|
307
|
+
this.#cancelHandoff = undefined;
|
|
308
|
+
}
|
|
309
|
+
hold.resolve();
|
|
310
|
+
signal.removeEventListener('abort', cancel);
|
|
311
|
+
};
|
|
312
|
+
const cancel = () => {
|
|
313
|
+
if (detached)
|
|
314
|
+
return;
|
|
315
|
+
sealed = false;
|
|
316
|
+
for (const [active, { request, attachment }] of held) {
|
|
317
|
+
request.cancel();
|
|
318
|
+
attachment.arrived.resolve();
|
|
319
|
+
attachment.decision.resolve('resume');
|
|
320
|
+
if (active.handoff === attachment)
|
|
321
|
+
active.handoff = undefined;
|
|
322
|
+
}
|
|
323
|
+
releaseHold();
|
|
324
|
+
};
|
|
325
|
+
this.#cancelHandoff = cancel;
|
|
326
|
+
signal.addEventListener('abort', cancel, { once: true });
|
|
327
|
+
try {
|
|
328
|
+
await waitForHostedExecutionIdleOrAbort(this.#admissions.waitForSettledAdmissions(), signal);
|
|
329
|
+
// Natural completion can hand a queued follow-up to a fresh root while
|
|
330
|
+
// gates converge. Bound that convergence; never chase an endless queue.
|
|
331
|
+
for (let pass = 0; pass < 3; pass += 1) {
|
|
332
|
+
for (const [, active] of this.#executions.entries()) {
|
|
333
|
+
if (held.has(active))
|
|
334
|
+
continue;
|
|
335
|
+
await waitForHostedExecutionIdleOrAbort(active.startSettled.promise, signal);
|
|
336
|
+
if (this.#executions.get(active.sessionId) !== active)
|
|
337
|
+
continue;
|
|
338
|
+
const request = this.manager.requestRunHandoff(active.sessionId, active.continuation?.runId ?? active.runId, {
|
|
339
|
+
protocol: 'runtime_handoff_pause_v1',
|
|
340
|
+
hostEpoch,
|
|
341
|
+
handoffId: randomUUID(),
|
|
342
|
+
rootRunId: active.runId,
|
|
343
|
+
successorRunId: randomUUID(),
|
|
344
|
+
successorInvocationId: randomUUID(),
|
|
345
|
+
claimId: randomUUID(),
|
|
346
|
+
}, signal);
|
|
347
|
+
if (!request) {
|
|
348
|
+
cancel();
|
|
349
|
+
return undefined;
|
|
350
|
+
}
|
|
351
|
+
const attachment = {
|
|
352
|
+
decision: valueDeferred(),
|
|
353
|
+
arrived: deferred(),
|
|
354
|
+
};
|
|
355
|
+
active.handoff = attachment;
|
|
356
|
+
held.set(active, { request, attachment });
|
|
357
|
+
}
|
|
358
|
+
for (const [active, item] of held) {
|
|
359
|
+
if (await waitForAbortableValue(item.request.ready, signal))
|
|
360
|
+
continue;
|
|
361
|
+
const snapshot = await this.readCanonicalSnapshot(active.sessionId, active.turnId, active.runId);
|
|
362
|
+
if (!isTerminalSnapshot(snapshot)) {
|
|
363
|
+
cancel();
|
|
364
|
+
return undefined;
|
|
365
|
+
}
|
|
366
|
+
item.attachment.decision.resolve('resume');
|
|
367
|
+
if (active.handoff === item.attachment)
|
|
368
|
+
active.handoff = undefined;
|
|
369
|
+
await waitForHostedExecutionIdleOrAbort(active.done, signal);
|
|
370
|
+
held.delete(active);
|
|
371
|
+
}
|
|
372
|
+
if ([...this.#executions.entries()].every(([, active]) => held.has(active)))
|
|
373
|
+
break;
|
|
374
|
+
}
|
|
375
|
+
if (signal.aborted ||
|
|
376
|
+
[...this.#executions.entries()].some(([, active]) => !held.has(active))) {
|
|
377
|
+
cancel();
|
|
378
|
+
return undefined;
|
|
379
|
+
}
|
|
380
|
+
return {
|
|
381
|
+
executions: [...held.keys()].map(({ sessionId, turnId, runId }) => ({
|
|
382
|
+
sessionId,
|
|
383
|
+
turnId,
|
|
384
|
+
runId,
|
|
385
|
+
})),
|
|
386
|
+
seal: async () => {
|
|
387
|
+
if (signal.aborted || this.#handoffHold !== hold || detached)
|
|
388
|
+
return false;
|
|
389
|
+
if (sealed)
|
|
390
|
+
return true;
|
|
391
|
+
for (const [active, { request }] of held) {
|
|
392
|
+
if (this.#executions.get(active.sessionId) !== active || !request.commit()) {
|
|
393
|
+
cancel();
|
|
394
|
+
return false;
|
|
395
|
+
}
|
|
396
|
+
}
|
|
397
|
+
try {
|
|
398
|
+
const results = await Promise.all([...held.values()].map(({ request }) => request.sealed));
|
|
399
|
+
sealed = !signal.aborted && this.#handoffHold === hold && results.every(Boolean);
|
|
400
|
+
if (sealed) {
|
|
401
|
+
await Promise.all([...held].map(([active, { attachment }]) => Promise.race([
|
|
402
|
+
attachment.arrived.promise,
|
|
403
|
+
active.done.then(() => {
|
|
404
|
+
throw new Error('Sealed execution ended before its attachment handoff');
|
|
405
|
+
}),
|
|
406
|
+
])));
|
|
407
|
+
}
|
|
408
|
+
}
|
|
409
|
+
catch (error) {
|
|
410
|
+
cancel();
|
|
411
|
+
throw error;
|
|
412
|
+
}
|
|
413
|
+
if (!sealed)
|
|
414
|
+
cancel();
|
|
415
|
+
return sealed;
|
|
416
|
+
},
|
|
417
|
+
residencies: async () => {
|
|
418
|
+
if (!sealed || signal.aborted || this.#handoffHold !== hold || detached)
|
|
419
|
+
return undefined;
|
|
420
|
+
const residencies = [];
|
|
421
|
+
for (const [active, { attachment }] of held) {
|
|
422
|
+
if (this.#executions.get(active.sessionId) !== active ||
|
|
423
|
+
active.handoff !== attachment ||
|
|
424
|
+
active.stopRequested)
|
|
425
|
+
return undefined;
|
|
426
|
+
const messages = await this.sessionAdmission.run(active.sessionId, (lease) => this.messages.handoffResidencies(active, lease));
|
|
427
|
+
if (!messages)
|
|
428
|
+
return undefined;
|
|
429
|
+
residencies.push(active.residency, ...messages);
|
|
430
|
+
}
|
|
431
|
+
return residencies;
|
|
432
|
+
},
|
|
433
|
+
detach: async () => {
|
|
434
|
+
if (!sealed || signal.aborted || this.#handoffHold !== hold || detached) {
|
|
435
|
+
throw new Error('Cannot detach a handoff without all durable seals');
|
|
436
|
+
}
|
|
437
|
+
detached = true;
|
|
438
|
+
for (const { attachment } of held.values())
|
|
439
|
+
attachment.decision.resolve('detach');
|
|
440
|
+
await Promise.all([...held.keys()].map((active) => active.done));
|
|
441
|
+
releaseHold();
|
|
442
|
+
},
|
|
443
|
+
cancel,
|
|
444
|
+
};
|
|
445
|
+
}
|
|
446
|
+
catch (error) {
|
|
447
|
+
cancel();
|
|
448
|
+
if (signal.aborted)
|
|
449
|
+
return undefined;
|
|
450
|
+
throw error;
|
|
451
|
+
}
|
|
452
|
+
}
|
|
275
453
|
async runExclusiveSessionOperation(input, operation) {
|
|
276
454
|
const { sessionId, abortSignal } = input;
|
|
277
455
|
let reservation;
|
|
@@ -287,7 +465,7 @@ export class RootTurnCoordinator {
|
|
|
287
465
|
}
|
|
288
466
|
const active = this.#executions.get(sessionId);
|
|
289
467
|
const pending = this.#admissions.get(sessionId);
|
|
290
|
-
const whenIdle = active?.done ?? pending?.whenIdle.promise;
|
|
468
|
+
const whenIdle = active?.done ?? pending?.whenIdle.promise ?? this.#handoffHold?.promise;
|
|
291
469
|
if (whenIdle) {
|
|
292
470
|
await waitForHostedExecutionIdleOrAbort(whenIdle, abortSignal);
|
|
293
471
|
}
|
|
@@ -336,6 +514,8 @@ export class RootTurnCoordinator {
|
|
|
336
514
|
reason: 'Runtime Host execution authority is draining.',
|
|
337
515
|
};
|
|
338
516
|
}
|
|
517
|
+
if (this.#handoffHold)
|
|
518
|
+
return { kind: 'busy', whenIdle: this.#handoffHold.promise };
|
|
339
519
|
const active = this.#executions.get(sessionId);
|
|
340
520
|
if (active) {
|
|
341
521
|
return {
|
|
@@ -528,7 +708,9 @@ export class RootTurnCoordinator {
|
|
|
528
708
|
return this.#executions.subscribe(listener);
|
|
529
709
|
}
|
|
530
710
|
whenIdle(sessionId) {
|
|
531
|
-
return (this.#executions.get(sessionId)?.done ??
|
|
711
|
+
return (this.#executions.get(sessionId)?.done ??
|
|
712
|
+
this.#admissions.get(sessionId)?.whenIdle.promise ??
|
|
713
|
+
this.#handoffHold?.promise);
|
|
532
714
|
}
|
|
533
715
|
stopRoot(identity, input = {}) {
|
|
534
716
|
normalizeStopSessionSource(input.source, input.workHubActionId);
|
|
@@ -1330,9 +1512,19 @@ export class RootTurnCoordinator {
|
|
|
1330
1512
|
});
|
|
1331
1513
|
}
|
|
1332
1514
|
async planTurnResume(input) {
|
|
1333
|
-
|
|
1515
|
+
let sourceRunId = input.sourceRunId;
|
|
1516
|
+
if (sourceRunId) {
|
|
1517
|
+
const run = await this.readRunIfPresent(input.sessionId, sourceRunId);
|
|
1518
|
+
if (run?.terminalEvent && runtimeHandoffPause(run.terminalEvent)) {
|
|
1519
|
+
const logical = await readLogicalRuntimeExecutionForRun(this.stores.runtimeEventStore, run);
|
|
1520
|
+
// Queries may name the stable public root. The returned plan and the
|
|
1521
|
+
// subsequent start must still bind the exact physical source/high-water.
|
|
1522
|
+
sourceRunId = logical?.tip.runId ?? sourceRunId;
|
|
1523
|
+
}
|
|
1524
|
+
}
|
|
1525
|
+
const plan = sourceRunId
|
|
1334
1526
|
? await this.manager.planAuthoritativeSafeBoundaryContinuation(input.sessionId, {
|
|
1335
|
-
sourceRunId
|
|
1527
|
+
sourceRunId,
|
|
1336
1528
|
...(input.expectedRuntimeEventHighWater !== undefined
|
|
1337
1529
|
? {
|
|
1338
1530
|
expectedRuntimeEventHighWater: input.expectedRuntimeEventHighWater,
|
|
@@ -1391,9 +1583,9 @@ export class RootTurnCoordinator {
|
|
|
1391
1583
|
}
|
|
1392
1584
|
const fence = commitQueueFence();
|
|
1393
1585
|
await this.messages.cancelMessages(input.sessionId, fence.retracted.map((message) => message.messageId));
|
|
1394
|
-
await this.interactions.claimRunClosure(input, 'turn_stopped', admission);
|
|
1586
|
+
await this.interactions.claimRunClosure({ ...input, runId: active.continuation?.runId ?? active.runId }, 'turn_stopped', admission);
|
|
1395
1587
|
const shouldDeliverStop = !active.stopRequested;
|
|
1396
|
-
active.stopRequested =
|
|
1588
|
+
active.stopRequested = stopInput;
|
|
1397
1589
|
return {
|
|
1398
1590
|
active,
|
|
1399
1591
|
deliverStop: () => shouldDeliverStop
|
|
@@ -1418,7 +1610,7 @@ export class RootTurnCoordinator {
|
|
|
1418
1610
|
if (active?.turnId === input.turnId && active.runId === input.runId) {
|
|
1419
1611
|
const fence = commitQueueFence();
|
|
1420
1612
|
await this.messages.cancelMessages(input.sessionId, fence.retracted.map((message) => message.messageId));
|
|
1421
|
-
active.stopRequested
|
|
1613
|
+
active.stopRequested ??= { source: 'stop_button' };
|
|
1422
1614
|
return { kind: 'await_terminal', active };
|
|
1423
1615
|
}
|
|
1424
1616
|
return { kind: 'complete', outcome: { ok: true, result: snapshot } };
|
|
@@ -1441,9 +1633,9 @@ export class RootTurnCoordinator {
|
|
|
1441
1633
|
}
|
|
1442
1634
|
const fence = commitQueueFence();
|
|
1443
1635
|
await this.messages.cancelMessages(input.sessionId, fence.retracted.map((message) => message.messageId));
|
|
1444
|
-
await this.interactions.claimRunClosure(input, 'turn_stopped', admissionLease);
|
|
1636
|
+
await this.interactions.claimRunClosure({ ...input, runId: active.continuation?.runId ?? active.runId }, 'turn_stopped', admissionLease);
|
|
1445
1637
|
const shouldRequestStop = !active.stopRequested;
|
|
1446
|
-
active.stopRequested
|
|
1638
|
+
active.stopRequested ??= { source: 'stop_button' };
|
|
1447
1639
|
return shouldRequestStop
|
|
1448
1640
|
? { kind: 'request_stop', active }
|
|
1449
1641
|
: { kind: 'await_terminal', active };
|
|
@@ -1484,7 +1676,19 @@ export class RootTurnCoordinator {
|
|
|
1484
1676
|
}
|
|
1485
1677
|
if (active)
|
|
1486
1678
|
return completedStart(sessionBusy('Session already has an active root Turn'));
|
|
1487
|
-
|
|
1679
|
+
const logical = await readLogicalRuntimeExecution(this.stores.runtimeEventStore, admission, existingRun);
|
|
1680
|
+
if (!logical?.pendingHandoff) {
|
|
1681
|
+
throw new Error('Admitted non-terminal Turn has no active Runtime Host execution');
|
|
1682
|
+
}
|
|
1683
|
+
const plan = await this.manager.planAuthoritativeSafeBoundaryContinuation(input.sessionId, {
|
|
1684
|
+
sourceRunId: logical.tip.runId,
|
|
1685
|
+
purpose: 'handoff',
|
|
1686
|
+
});
|
|
1687
|
+
if (!plan.continuation) {
|
|
1688
|
+
this.parkContinuationAdmission(admission);
|
|
1689
|
+
return completedStart(operationUnavailable(`Sealed handoff cannot resume: ${plan.rejectionReasons.join(', ')}`));
|
|
1690
|
+
}
|
|
1691
|
+
continuation = plan.continuation;
|
|
1488
1692
|
}
|
|
1489
1693
|
const active = this.#executions.get(input.sessionId);
|
|
1490
1694
|
const currentReservation = this.#admissions.get(input.sessionId);
|
|
@@ -1517,6 +1721,9 @@ export class RootTurnCoordinator {
|
|
|
1517
1721
|
try {
|
|
1518
1722
|
this.messages.reserveRootTurn(messageIdentity);
|
|
1519
1723
|
messageReserved = true;
|
|
1724
|
+
if (continuation?.handoffRootRunId !== undefined) {
|
|
1725
|
+
await this.messages.advanceHandoffRun(messageIdentity, continuation.runId, admissionLease);
|
|
1726
|
+
}
|
|
1520
1727
|
await this.continuity.holdTerminalPublication(input.sessionId, input.turnId, runId, admissionLease);
|
|
1521
1728
|
}
|
|
1522
1729
|
catch (error) {
|
|
@@ -1547,7 +1754,7 @@ export class RootTurnCoordinator {
|
|
|
1547
1754
|
startSettled,
|
|
1548
1755
|
done: Promise.resolve(),
|
|
1549
1756
|
residency,
|
|
1550
|
-
stopRequested:
|
|
1757
|
+
stopRequested: undefined,
|
|
1551
1758
|
messageTransitionCommitted: false,
|
|
1552
1759
|
};
|
|
1553
1760
|
if (replacing && this.#executions.get(input.sessionId) !== replacing) {
|
|
@@ -1613,6 +1820,7 @@ export class RootTurnCoordinator {
|
|
|
1613
1820
|
}
|
|
1614
1821
|
async drainTurn(input, active, startSettled) {
|
|
1615
1822
|
let terminalTransitionStarted = false;
|
|
1823
|
+
let detached = false;
|
|
1616
1824
|
try {
|
|
1617
1825
|
const messageOrigin = hostedExecutionMessageOrigin(active.descriptor);
|
|
1618
1826
|
const onRunStarted = async () => {
|
|
@@ -1620,46 +1828,80 @@ export class RootTurnCoordinator {
|
|
|
1620
1828
|
await this.continuity.refreshCanonical(input.sessionId);
|
|
1621
1829
|
startSettled.resolve();
|
|
1622
1830
|
};
|
|
1623
|
-
|
|
1624
|
-
?
|
|
1625
|
-
|
|
1626
|
-
|
|
1627
|
-
onRunStarted: async () => {
|
|
1628
|
-
await onRunStarted();
|
|
1629
|
-
await active.execution?.onReady?.();
|
|
1630
|
-
},
|
|
1831
|
+
let stream = active.continuation
|
|
1832
|
+
? this.manager.resumeSafeBoundaryContinuation(active.continuation, {
|
|
1833
|
+
onRunStarted,
|
|
1834
|
+
stopBeforeDispatch: () => active.stopRequested,
|
|
1631
1835
|
})
|
|
1632
|
-
: active.
|
|
1633
|
-
?
|
|
1634
|
-
|
|
1635
|
-
|
|
1636
|
-
|
|
1637
|
-
onRunStarted
|
|
1836
|
+
: active.execution
|
|
1837
|
+
? active.execution.start({
|
|
1838
|
+
runId: active.runId,
|
|
1839
|
+
userMessageId: active.userMessageId,
|
|
1840
|
+
onRunStarted: async () => {
|
|
1841
|
+
await onRunStarted();
|
|
1842
|
+
await active.execution?.onReady?.();
|
|
1638
1843
|
},
|
|
1639
1844
|
})
|
|
1640
|
-
: active.
|
|
1641
|
-
? this.manager.
|
|
1642
|
-
|
|
1845
|
+
: active.descriptor.kind === 'context_compact'
|
|
1846
|
+
? this.manager.compactSession(input.sessionId, {
|
|
1847
|
+
turnId: input.turnId,
|
|
1848
|
+
hostedRoot: {
|
|
1849
|
+
runId: active.runId,
|
|
1850
|
+
onRunStarted,
|
|
1851
|
+
},
|
|
1643
1852
|
})
|
|
1644
|
-
:
|
|
1645
|
-
|
|
1646
|
-
|
|
1647
|
-
|
|
1648
|
-
|
|
1853
|
+
: active.continuation
|
|
1854
|
+
? this.manager.resumeSafeBoundaryContinuation(active.continuation, {
|
|
1855
|
+
onRunStarted,
|
|
1856
|
+
})
|
|
1857
|
+
: this.startRootMessageTurn(input, active, normalizeMessageContent(requireRootMessageContent(input)), messageOrigin, onRunStarted);
|
|
1858
|
+
for (;;) {
|
|
1859
|
+
for await (const event of stream) {
|
|
1860
|
+
if (active.execution?.onEvent) {
|
|
1861
|
+
try {
|
|
1862
|
+
active.execution.onEvent(event);
|
|
1863
|
+
}
|
|
1864
|
+
catch {
|
|
1865
|
+
// Presentation observers do not participate in execution authority.
|
|
1866
|
+
}
|
|
1649
1867
|
}
|
|
1650
|
-
|
|
1651
|
-
|
|
1868
|
+
if (isRuntimeSessionForwardedEvent(event)) {
|
|
1869
|
+
await this.continuity.acceptRuntimeEvent(input.sessionId, active.runId, event);
|
|
1870
|
+
}
|
|
1871
|
+
else if (isInteractionAnswerAck(event)) {
|
|
1872
|
+
await this.continuity.refreshCanonical(input.sessionId);
|
|
1873
|
+
}
|
|
1874
|
+
else if (event.type === 'user_question_request' || event.type === 'form_request') {
|
|
1875
|
+
this.continuity.enqueueCanonicalRefresh(input.sessionId);
|
|
1652
1876
|
}
|
|
1653
1877
|
}
|
|
1654
|
-
|
|
1655
|
-
|
|
1656
|
-
|
|
1657
|
-
|
|
1658
|
-
|
|
1659
|
-
|
|
1660
|
-
|
|
1661
|
-
|
|
1662
|
-
|
|
1878
|
+
const logical = await readLogicalRuntimeExecution(this.stores.runtimeEventStore, active);
|
|
1879
|
+
if (!logical?.pendingHandoff)
|
|
1880
|
+
break;
|
|
1881
|
+
const handoff = active.handoff;
|
|
1882
|
+
handoff?.arrived.resolve();
|
|
1883
|
+
if (handoff && (await handoff.decision.promise) === 'detach') {
|
|
1884
|
+
await this.sessionAdmission.run(input.sessionId, (lease) => this.messages.detachHandoffRoot(active, lease));
|
|
1885
|
+
detached = true;
|
|
1886
|
+
return;
|
|
1887
|
+
}
|
|
1888
|
+
await this.sessionAdmission.run(input.sessionId, async (lease) => {
|
|
1889
|
+
if (this.#executions.get(input.sessionId) !== active) {
|
|
1890
|
+
throw new RuntimeMessageAuthorityInvariantError('Sealed handoff lost its original root owner');
|
|
1891
|
+
}
|
|
1892
|
+
const plan = await this.manager.planAuthoritativeSafeBoundaryContinuation(input.sessionId, {
|
|
1893
|
+
sourceRunId: logical.tip.runId,
|
|
1894
|
+
purpose: 'handoff',
|
|
1895
|
+
});
|
|
1896
|
+
if (!plan.continuation)
|
|
1897
|
+
throw new Error(`Sealed handoff cannot resume: ${plan.rejectionReasons.join(', ')}`);
|
|
1898
|
+
await this.messages.advanceHandoffRun(active, plan.continuation.runId, lease);
|
|
1899
|
+
active.continuation = plan.continuation;
|
|
1900
|
+
});
|
|
1901
|
+
stream = this.manager.resumeSafeBoundaryContinuation(active.continuation, {
|
|
1902
|
+
onRunStarted,
|
|
1903
|
+
stopBeforeDispatch: () => active.stopRequested,
|
|
1904
|
+
});
|
|
1663
1905
|
}
|
|
1664
1906
|
const snapshot = await this.readCanonicalSnapshot(input.sessionId, input.turnId, active.runId);
|
|
1665
1907
|
await this.assertCompletedExecutionIdentity(input, active);
|
|
@@ -1702,7 +1944,7 @@ export class RootTurnCoordinator {
|
|
|
1702
1944
|
((!active.stopRequested &&
|
|
1703
1945
|
snapshot.status === 'failed' &&
|
|
1704
1946
|
isContainableRunFailure(error)) ||
|
|
1705
|
-
(active.stopRequested &&
|
|
1947
|
+
(Boolean(active.stopRequested) &&
|
|
1706
1948
|
snapshot.status === 'cancelled' &&
|
|
1707
1949
|
isStoppedInteractionAdmission(error)));
|
|
1708
1950
|
}
|
|
@@ -1725,32 +1967,38 @@ export class RootTurnCoordinator {
|
|
|
1725
1967
|
throw commandFailure;
|
|
1726
1968
|
}
|
|
1727
1969
|
finally {
|
|
1728
|
-
|
|
1729
|
-
kind: 'authority_error',
|
|
1730
|
-
execution: active,
|
|
1731
|
-
reason: 'Runtime root Turn ended without a canonical completion.',
|
|
1732
|
-
});
|
|
1733
|
-
await active.observationSettled?.catch(() => this.requestHostDrain());
|
|
1734
|
-
let releaseRootOwnership = active.messageTransitionCommitted;
|
|
1735
|
-
if (!active.messageTransitionCommitted) {
|
|
1736
|
-
try {
|
|
1737
|
-
this.messages.abandonRootReservation({
|
|
1738
|
-
sessionId: input.sessionId,
|
|
1739
|
-
turnId: active.turnId,
|
|
1740
|
-
runId: active.runId,
|
|
1741
|
-
});
|
|
1742
|
-
releaseRootOwnership = true;
|
|
1743
|
-
}
|
|
1744
|
-
catch {
|
|
1745
|
-
this.requestHostDrain();
|
|
1746
|
-
}
|
|
1747
|
-
}
|
|
1748
|
-
if (releaseRootOwnership) {
|
|
1970
|
+
if (detached) {
|
|
1749
1971
|
this.#executions.release(active);
|
|
1750
1972
|
active.residency.release();
|
|
1751
1973
|
}
|
|
1752
|
-
|
|
1753
|
-
|
|
1974
|
+
else {
|
|
1975
|
+
this.observeExecutionCompletion(active, {
|
|
1976
|
+
kind: 'authority_error',
|
|
1977
|
+
execution: active,
|
|
1978
|
+
reason: 'Runtime root Turn ended without a canonical completion.',
|
|
1979
|
+
});
|
|
1980
|
+
await active.observationSettled?.catch(() => this.requestHostDrain());
|
|
1981
|
+
let releaseRootOwnership = active.messageTransitionCommitted;
|
|
1982
|
+
if (!active.messageTransitionCommitted) {
|
|
1983
|
+
try {
|
|
1984
|
+
this.messages.abandonRootReservation({
|
|
1985
|
+
sessionId: input.sessionId,
|
|
1986
|
+
turnId: active.turnId,
|
|
1987
|
+
runId: active.runId,
|
|
1988
|
+
});
|
|
1989
|
+
releaseRootOwnership = true;
|
|
1990
|
+
}
|
|
1991
|
+
catch {
|
|
1992
|
+
this.requestHostDrain();
|
|
1993
|
+
}
|
|
1994
|
+
}
|
|
1995
|
+
if (releaseRootOwnership) {
|
|
1996
|
+
this.#executions.release(active);
|
|
1997
|
+
active.residency.release();
|
|
1998
|
+
}
|
|
1999
|
+
active.completion.resolve(active.observedCompletion);
|
|
2000
|
+
this.#executions.publish(active);
|
|
2001
|
+
}
|
|
1754
2002
|
}
|
|
1755
2003
|
}
|
|
1756
2004
|
async materializeAdmittedMessageSources(active) {
|
|
@@ -1789,7 +2037,7 @@ export class RootTurnCoordinator {
|
|
|
1789
2037
|
turnId: active.turnId,
|
|
1790
2038
|
runId: active.runId,
|
|
1791
2039
|
};
|
|
1792
|
-
await this.interactions.assertTerminalFence(identity, lease);
|
|
2040
|
+
await this.interactions.assertTerminalFence({ ...identity, runId: active.continuation?.runId ?? active.runId }, lease);
|
|
1793
2041
|
const batch = this.messages.beginTerminalTransition(identity);
|
|
1794
2042
|
await this.continuity.publishTerminalProjection(sessionId, active.turnId, active.runId, lease);
|
|
1795
2043
|
if (batch.sources.length === 0) {
|
|
@@ -1914,6 +2162,10 @@ export class RootTurnCoordinator {
|
|
|
1914
2162
|
}
|
|
1915
2163
|
}
|
|
1916
2164
|
}
|
|
2165
|
+
async function waitForAbortableValue(value, signal) {
|
|
2166
|
+
await waitForHostedExecutionIdleOrAbort(value.then(() => undefined), signal);
|
|
2167
|
+
return value;
|
|
2168
|
+
}
|
|
1917
2169
|
class HostedRootAdmissionGateError extends Error {
|
|
1918
2170
|
cause;
|
|
1919
2171
|
name = 'HostedRootAdmissionGateError';
|
|
@@ -71,6 +71,7 @@ export class HostScheduledTaskCoordinator {
|
|
|
71
71
|
#prepared = false;
|
|
72
72
|
#started = false;
|
|
73
73
|
#draining = false;
|
|
74
|
+
#handoffHeld = false;
|
|
74
75
|
#closed = false;
|
|
75
76
|
#retiringSessions = new Set();
|
|
76
77
|
constructor(input) {
|
|
@@ -156,6 +157,34 @@ export class HostScheduledTaskCoordinator {
|
|
|
156
157
|
this.#started = true;
|
|
157
158
|
void this.#refresh().catch((error) => this.#fatal(error));
|
|
158
159
|
}
|
|
160
|
+
holdForHandoff() {
|
|
161
|
+
if (this.#draining || this.#handoffHeld)
|
|
162
|
+
return undefined;
|
|
163
|
+
this.#handoffHeld = true;
|
|
164
|
+
this.#stopTimer();
|
|
165
|
+
let released = false;
|
|
166
|
+
return {
|
|
167
|
+
settled: async () => {
|
|
168
|
+
// An admitted native effect or Agent fire finishes normally. No new
|
|
169
|
+
// fire may start while the handoff waits for this lane.
|
|
170
|
+
for (;;) {
|
|
171
|
+
const lane = this.#lane;
|
|
172
|
+
await lane;
|
|
173
|
+
if (lane === this.#lane)
|
|
174
|
+
return;
|
|
175
|
+
}
|
|
176
|
+
},
|
|
177
|
+
residencies: () => (this.#residency ? [this.#residency] : []),
|
|
178
|
+
release: () => {
|
|
179
|
+
if (released)
|
|
180
|
+
return;
|
|
181
|
+
released = true;
|
|
182
|
+
this.#handoffHeld = false;
|
|
183
|
+
if (!this.#draining)
|
|
184
|
+
void this.#refresh().catch((error) => this.#fatal(error));
|
|
185
|
+
},
|
|
186
|
+
};
|
|
187
|
+
}
|
|
159
188
|
beginDrain() {
|
|
160
189
|
if (this.#draining)
|
|
161
190
|
return;
|
|
@@ -390,12 +419,16 @@ export class HostScheduledTaskCoordinator {
|
|
|
390
419
|
}
|
|
391
420
|
async #refresh() {
|
|
392
421
|
await this.#exclusive(async () => {
|
|
422
|
+
if (this.#handoffHeld || this.#draining)
|
|
423
|
+
return;
|
|
393
424
|
for (const claim of await this.#store.listPendingFires()) {
|
|
425
|
+
if (this.#handoffHeld || this.#draining)
|
|
426
|
+
break;
|
|
394
427
|
if (claim.nativeState === 'waiting_for_provider') {
|
|
395
428
|
await this.#fulfill(claim, true);
|
|
396
429
|
}
|
|
397
430
|
}
|
|
398
|
-
while (!this.#draining) {
|
|
431
|
+
while (!this.#draining && !this.#handoffHeld) {
|
|
399
432
|
const scan = await this.#store.claimNextDue(this.#now());
|
|
400
433
|
for (const expired of scan.expired)
|
|
401
434
|
this.#publish('updated', expired.id);
|
|
@@ -616,7 +649,7 @@ export class HostScheduledTaskCoordinator {
|
|
|
616
649
|
async #refreshSchedule() {
|
|
617
650
|
this.#stopTimer();
|
|
618
651
|
await this.#refreshResidency();
|
|
619
|
-
if (!this.#started || this.#draining)
|
|
652
|
+
if (!this.#started || this.#draining || this.#handoffHeld)
|
|
620
653
|
return;
|
|
621
654
|
const [tasks, claims] = await Promise.all([this.#store.list(), this.#store.listPendingFires()]);
|
|
622
655
|
const next = tasks
|
|
@@ -626,6 +659,8 @@ export class HostScheduledTaskCoordinator {
|
|
|
626
659
|
return earliest === null || deadline < earliest ? deadline : earliest;
|
|
627
660
|
}, null);
|
|
628
661
|
const waitingForProvider = claims.some((claim) => claim.nativeState === 'waiting_for_provider');
|
|
662
|
+
if (this.#draining || this.#handoffHeld)
|
|
663
|
+
return;
|
|
629
664
|
if (next === null && !waitingForProvider)
|
|
630
665
|
return;
|
|
631
666
|
const nextTaskDelay = next === null
|