@jini-ai/daemon 0.3.7 → 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/CHANGELOG.md +119 -0
- package/README.md +50 -8
- package/dist/agent-executor/contracts.d.ts +736 -0
- package/dist/agent-executor/contracts.js +28 -0
- package/dist/agent-executor/event-translation.d.ts +87 -0
- package/dist/agent-executor/event-translation.js +230 -0
- package/dist/agent-executor/events.d.ts +190 -0
- package/dist/agent-executor/events.js +848 -0
- package/dist/agent-executor/handoff.d.ts +18 -0
- package/dist/agent-executor/handoff.js +16 -0
- package/dist/agent-executor/index.d.ts +98 -0
- package/dist/agent-executor/index.js +98 -0
- package/dist/agent-executor/launch.d.ts +375 -0
- package/dist/agent-executor/launch.js +1001 -0
- package/dist/agent-executor/mcp-bridge.d.ts +338 -0
- package/dist/agent-executor/mcp-bridge.js +835 -0
- package/dist/agent-executor/values.d.ts +5 -0
- package/dist/agent-executor/values.js +13 -0
- package/dist/agent-executor.d.ts +382 -177
- package/dist/agent-executor.js +408 -344
- package/dist/close-status.d.ts +11 -8
- package/dist/close-status.js +31 -19
- package/dist/continuation/continuation-transport.d.ts +3 -1
- package/dist/continuation/continuation-transport.js +1 -1
- package/dist/continuation/journal.d.ts +12 -5
- package/dist/continuation/journal.js +4 -4
- package/dist/continuation/run-scoped-context-store.d.ts +13 -4
- package/dist/continuation/run-scoped-context-store.js +6 -6
- package/dist/continuation/run-start-handler.d.ts +1 -1
- package/dist/continuation/run-start-handler.js +3 -16
- package/dist/delegated-tool-bridge.d.ts +7 -3
- package/dist/delegated-tool-bridge.js +32 -31
- package/dist/event-log.d.ts +7 -5
- package/dist/event-log.js +12 -11
- package/dist/frontend-capability-tools.d.ts +1 -1
- package/dist/frontend-capability-tools.js +3 -2
- package/dist/frontend-session-registry.d.ts +32 -9
- package/dist/frontend-session-registry.js +12 -11
- package/dist/http/active-context.d.ts +105 -0
- package/dist/http/active-context.js +67 -0
- package/dist/http/agents.d.ts +68 -0
- package/dist/http/agents.js +12 -0
- package/dist/http/attachments.d.ts +618 -0
- package/dist/http/attachments.js +1034 -0
- package/dist/http/cancel-owned-runs.d.ts +49 -0
- package/dist/http/cancel-owned-runs.js +35 -0
- package/dist/http/component-catalog.d.ts +84 -0
- package/dist/http/component-catalog.js +64 -0
- package/dist/http/connectors.d.ts +209 -0
- package/dist/http/connectors.js +409 -0
- package/dist/http/daemon-status.d.ts +76 -0
- package/dist/http/daemon-status.js +55 -0
- package/dist/http/db-ops.d.ts +123 -0
- package/dist/http/db-ops.js +189 -0
- package/dist/http/delegated-tools.d.ts +113 -0
- package/dist/http/delegated-tools.js +230 -0
- package/dist/http/frontend-control.d.ts +136 -0
- package/dist/http/frontend-control.js +79 -0
- package/dist/http/frontend-sessions.d.ts +110 -0
- package/dist/http/frontend-sessions.js +180 -0
- package/dist/http/health.d.ts +95 -0
- package/dist/http/health.js +40 -0
- package/dist/http/host-tools.d.ts +214 -0
- package/dist/http/host-tools.js +329 -0
- package/dist/http/index.d.ts +52 -0
- package/dist/http/index.js +27 -0
- package/dist/http/media.d.ts +146 -0
- package/dist/http/media.js +346 -0
- package/dist/http/memory.d.ts +265 -0
- package/dist/http/memory.js +328 -0
- package/dist/http/model-proxy.d.ts +49 -0
- package/dist/http/model-proxy.js +642 -0
- package/dist/http/remote-run-events.d.ts +70 -0
- package/dist/http/remote-run-events.js +224 -0
- package/dist/http/research.d.ts +48 -0
- package/dist/http/research.js +315 -0
- package/dist/http/route-manifest.d.ts +35 -0
- package/dist/http/route-manifest.js +103 -0
- package/dist/http/routines.d.ts +120 -0
- package/dist/http/routines.js +298 -0
- package/dist/http/run-credentials.d.ts +73 -0
- package/dist/http/run-credentials.js +71 -0
- package/dist/http/runs.d.ts +91 -0
- package/dist/http/runs.js +221 -0
- package/dist/http/terminals.d.ts +138 -0
- package/dist/http/terminals.js +325 -0
- package/dist/http/tool-catalog.d.ts +78 -0
- package/dist/http/tool-catalog.js +66 -0
- package/dist/http/workspace-root.d.ts +77 -0
- package/dist/http/workspace-root.js +63 -0
- package/dist/http/xai.d.ts +90 -0
- package/dist/http/xai.js +655 -0
- package/dist/image-prompt-delivery.d.ts +13 -3
- package/dist/image-prompt-delivery.js +5 -5
- package/dist/index.d.ts +556 -4
- package/dist/index.js +556 -4
- package/dist/legacy-data-migration.d.ts +59 -8
- package/dist/legacy-data-migration.js +38 -33
- package/dist/read-only-tools.d.ts +130 -0
- package/dist/read-only-tools.js +115 -0
- package/dist/remote-tool-bridge.d.ts +11 -5
- package/dist/remote-tool-bridge.js +15 -15
- package/dist/routines/index.d.ts +1 -1
- package/dist/routines/index.js +1 -1
- package/dist/routines/routine-store.d.ts +34 -12
- package/dist/routines/routine-store.js +21 -20
- package/dist/routines/schedule.d.ts +48 -10
- package/dist/routines/schedule.js +21 -21
- package/dist/routines/scheduler.d.ts +32 -11
- package/dist/routines/scheduler.js +61 -57
- package/dist/routines/types.d.ts +10 -3
- package/dist/run/core/result.d.ts +6 -2
- package/dist/run/core/result.js +3 -3
- package/dist/run/core/retry.d.ts +17 -4
- package/dist/run/core/retry.js +7 -11
- package/dist/run/diagnostics/diagnostics.d.ts +14 -4
- package/dist/run/diagnostics/diagnostics.js +7 -7
- package/dist/run/index.d.ts +1 -1
- package/dist/run/index.js +1 -1
- package/dist/run-credentials.d.ts +138 -0
- package/dist/run-credentials.js +152 -0
- package/dist/run-lifecycle.d.ts +92 -33
- package/dist/run-lifecycle.js +86 -73
- package/dist/scheduler.d.ts +8 -0
- package/dist/scheduler.js +2 -0
- package/dist/session-coordination.d.ts +115 -0
- package/dist/session-coordination.js +111 -0
- package/dist/store/agent-sessions/errors.d.ts +8 -0
- package/dist/store/agent-sessions/errors.js +10 -0
- package/dist/store/agent-sessions/index.d.ts +12 -0
- package/dist/store/agent-sessions/index.js +19 -0
- package/dist/store/agent-sessions/pglite.d.ts +12 -0
- package/dist/store/agent-sessions/pglite.js +7 -0
- package/dist/store/agent-sessions/ports.d.ts +24 -0
- package/dist/store/agent-sessions/ports.js +2 -0
- package/dist/store/agent-sessions/postgres.d.ts +12 -0
- package/dist/store/agent-sessions/postgres.js +7 -0
- package/dist/store/agent-sessions/schema.d.ts +3 -0
- package/dist/store/agent-sessions/schema.js +23 -0
- package/dist/store/agent-sessions/sql.d.ts +35 -0
- package/dist/store/agent-sessions/sql.js +59 -0
- package/dist/store/agent-sessions/sqlite.d.ts +117 -0
- package/dist/store/agent-sessions/sqlite.js +128 -0
- package/dist/store/event-log/sqlite.d.ts +75 -0
- package/dist/store/event-log/sqlite.js +230 -0
- package/dist/surface-exchanges.d.ts +97 -0
- package/dist/surface-exchanges.js +153 -0
- package/dist/terminal-session.d.ts +43 -11
- package/dist/terminal-session.js +22 -20
- package/dist/tokens.js +4 -4
- package/dist/tool-audit.d.ts +114 -0
- package/dist/tool-audit.js +95 -0
- package/dist/tool-executor.d.ts +22 -7
- package/dist/tool-executor.js +16 -16
- package/dist/tool-result-media.d.ts +3 -1
- package/dist/tool-result-media.js +1 -1
- package/dist/tool-result-surfaces.d.ts +3 -1
- package/dist/tool-result-surfaces.js +1 -1
- package/package.json +127 -9
package/dist/run-lifecycle.js
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
* never a product-record identifier), generalized from OD's `design.runs` service
|
|
5
5
|
* (`apps/daemon/src/runtimes/runs.ts` + the terminal-decision half of
|
|
6
6
|
* `apps/daemon/src/runtimes/start-chat-run.ts`, `arch/server-startserver-endgame`
|
|
7
|
-
* branch — see `
|
|
7
|
+
* branch — see `archived provenance ledger` for exact line citations).
|
|
8
8
|
*
|
|
9
9
|
* Scope boundary: this module owns the run *state machine* and its event
|
|
10
10
|
* log — create, transition, emit, replay, cancellation-intent propagation,
|
|
@@ -132,13 +132,13 @@ function terminalStateFromEndEntry(entry) {
|
|
|
132
132
|
* or `undefined` when there is no key or no existing mapping (a fresh start proceeds normally).
|
|
133
133
|
* Pure.
|
|
134
134
|
*/
|
|
135
|
-
export function resolveIdempotentReplayRunId(idempotencyIndex, idempotencyKey) {
|
|
135
|
+
export function resolveIdempotentReplayRunId({ idempotencyIndex, idempotencyKey }) {
|
|
136
136
|
if (idempotencyKey === undefined)
|
|
137
137
|
return undefined;
|
|
138
138
|
return idempotencyIndex.get(idempotencyKey);
|
|
139
139
|
}
|
|
140
140
|
/** Registers `runId` under `idempotencyKey` in `idempotencyIndex` — a no-op when no key was supplied. */
|
|
141
|
-
export function registerIdempotencyKeyIfPresent(idempotencyIndex, idempotencyKey, runId) {
|
|
141
|
+
export function registerIdempotencyKeyIfPresent({ idempotencyIndex, idempotencyKey, runId }) {
|
|
142
142
|
if (idempotencyKey === undefined)
|
|
143
143
|
return;
|
|
144
144
|
idempotencyIndex.set(idempotencyKey, runId);
|
|
@@ -149,7 +149,7 @@ export function registerIdempotencyKeyIfPresent(idempotencyIndex, idempotencyKey
|
|
|
149
149
|
* run that may have since claimed the same key (defensive; `start()`'s own locking makes that
|
|
150
150
|
* race unreachable today, but the check costs nothing and documents the invariant).
|
|
151
151
|
*/
|
|
152
|
-
export function clearIdempotencyIndexEntryIfMatching(idempotencyIndex, idempotencyKey, runId) {
|
|
152
|
+
export function clearIdempotencyIndexEntryIfMatching({ idempotencyIndex, idempotencyKey, runId }) {
|
|
153
153
|
if (idempotencyKey !== undefined && idempotencyIndex.get(idempotencyKey) === runId) {
|
|
154
154
|
idempotencyIndex.delete(idempotencyKey);
|
|
155
155
|
}
|
|
@@ -175,7 +175,7 @@ export const DEFAULT_TERMINAL_RETENTION_MS = 24 * 60 * 60 * 1000;
|
|
|
175
175
|
export const DEFAULT_MAX_TERMINAL_RUNS = 1000;
|
|
176
176
|
/**
|
|
177
177
|
* Default wall-clock threshold for the slow-run notice (distinct from the crash/inactivity watchdog
|
|
178
|
-
* above): how long a run may go with no `emit()` before a `'slow_running'` agent event tells the UI
|
|
178
|
+
* above): how long a run may go with no agent-event `emit()` (excluding the notice itself) before a `'slow_running'` agent event tells the UI
|
|
179
179
|
* "this is taking longer than usual," without ever finishing the run. See {@link armSlowRunWatchdogIfConfigured}'s
|
|
180
180
|
* doc for the full "why a second, non-terminating watchdog" rationale. 45s was picked as long enough
|
|
181
181
|
* that an ordinary quiet stretch (the agent thinking between tool calls, a slow but healthy model
|
|
@@ -192,11 +192,11 @@ export const DEFAULT_SLOW_RUN_THRESHOLD_MS = 45_000;
|
|
|
192
192
|
* where a run may have gone terminal long before this process started) schedules an immediate
|
|
193
193
|
* eviction rather than a negative-delay timer. Pure.
|
|
194
194
|
*/
|
|
195
|
-
export function computeRetentionDelayMs(terminalAt, retentionMs, now) {
|
|
195
|
+
export function computeRetentionDelayMs({ terminalAt, retentionMs, now }) {
|
|
196
196
|
return Math.max(0, retentionMs - (now - terminalAt));
|
|
197
197
|
}
|
|
198
198
|
/** The durable `'start'` entry's payload — pure field mapping, split out of `start()` so its two optional-field spreads don't count toward that method's own complexity. */
|
|
199
|
-
export function buildStartPayload(runId, startInput) {
|
|
199
|
+
export function buildStartPayload({ runId, startInput }) {
|
|
200
200
|
return {
|
|
201
201
|
runId,
|
|
202
202
|
contextRef: startInput.contextRef,
|
|
@@ -209,7 +209,7 @@ export function buildStartPayload(runId, startInput) {
|
|
|
209
209
|
* plain `{watchdog}` shape (not `Pick<RunRecord, 'watchdog'>`) purely so this function can be
|
|
210
210
|
* exported without naming the private `RunRecord` type in a public signature.
|
|
211
211
|
*/
|
|
212
|
-
export function armWatchdogIfConfigured(record, timeoutMs, onTimeout) {
|
|
212
|
+
export function armWatchdogIfConfigured({ record, timeoutMs, onTimeout }) {
|
|
213
213
|
if (timeoutMs === undefined)
|
|
214
214
|
return;
|
|
215
215
|
record.watchdog = createInactivityWatchdog({ timeoutMs, onTimeout });
|
|
@@ -224,7 +224,7 @@ export function armWatchdogIfConfigured(record, timeoutMs, onTimeout) {
|
|
|
224
224
|
* three-line dedup. `record` is typed as a plain `{slowRunWatchdog}` shape for the same exportability
|
|
225
225
|
* reason {@link armWatchdogIfConfigured} already documents.
|
|
226
226
|
*/
|
|
227
|
-
export function armSlowRunWatchdogIfConfigured(record, timeoutMs, onTimeout) {
|
|
227
|
+
export function armSlowRunWatchdogIfConfigured({ record, timeoutMs, onTimeout }) {
|
|
228
228
|
if (timeoutMs === undefined)
|
|
229
229
|
return;
|
|
230
230
|
record.slowRunWatchdog = createInactivityWatchdog({ timeoutMs, onTimeout });
|
|
@@ -235,7 +235,7 @@ export function armSlowRunWatchdogIfConfigured(record, timeoutMs, onTimeout) {
|
|
|
235
235
|
* (buffered live events observed during the replay, a terminal catch-up event). Pure aside from
|
|
236
236
|
* calling the injected `onEvent`.
|
|
237
237
|
*/
|
|
238
|
-
export function deliverReplayedEvents(runId, entries, onEvent) {
|
|
238
|
+
export function deliverReplayedEvents({ runId, entries, onEvent }) {
|
|
239
239
|
const deliveredEventIds = new Set();
|
|
240
240
|
for (const entry of entries) {
|
|
241
241
|
const event = toRunEvent(runId, entry);
|
|
@@ -249,7 +249,7 @@ export function deliverReplayedEvents(runId, entries, onEvent) {
|
|
|
249
249
|
* delivered as it goes. Reused in `stream()` for both the buffered-live-event catch-up and the
|
|
250
250
|
* single terminal-event catch-up, so the "don't double-deliver" bookkeeping lives in one place.
|
|
251
251
|
*/
|
|
252
|
-
export function deliverUndeliveredEvents(events, deliveredEventIds, onEvent) {
|
|
252
|
+
export function deliverUndeliveredEvents({ events, deliveredEventIds, onEvent }) {
|
|
253
253
|
for (const event of events) {
|
|
254
254
|
if (!deliveredEventIds.has(event.eventId)) {
|
|
255
255
|
deliveredEventIds.add(event.eventId);
|
|
@@ -263,7 +263,7 @@ export function deliverUndeliveredEvents(events, deliveredEventIds, onEvent) {
|
|
|
263
263
|
* registered behind the returned `unsubscribe`. `record` is typed as a plain `{subscribers}` shape
|
|
264
264
|
* for the same exportability reason as {@link armWatchdogIfConfigured}.
|
|
265
265
|
*/
|
|
266
|
-
export function finishStreamSubscription(record, subscriber, terminal) {
|
|
266
|
+
export function finishStreamSubscription({ record, subscriber, terminal }) {
|
|
267
267
|
if (terminal) {
|
|
268
268
|
record.subscribers.delete(subscriber);
|
|
269
269
|
return { kind: 'ok', unsubscribe: () => { } };
|
|
@@ -305,7 +305,8 @@ function buildNewRunRecord(startInput, runId, now) {
|
|
|
305
305
|
* @complexity Per-call complexities documented on each method; the registry itself is a `Map` keyed by `runId` (O(1) lookup). Memory is bounded: non-terminal runs are O(n) in concurrently-live runs, and terminal runs are capped at `maxTerminalRuns` (each additionally bounded in retention time by `terminalRetentionMs`) rather than growing for the life of the process.
|
|
306
306
|
* @overallScore 100/100
|
|
307
307
|
*/
|
|
308
|
-
export function createRunLifecycle(
|
|
308
|
+
export function createRunLifecycle(requiredArgs, optionalArgs = {}) {
|
|
309
|
+
const input = { ...requiredArgs, ...optionalArgs };
|
|
309
310
|
const { eventLog } = input;
|
|
310
311
|
const terminalRetentionMs = input.terminalRetentionMs ?? DEFAULT_TERMINAL_RETENTION_MS;
|
|
311
312
|
const maxTerminalRuns = input.maxTerminalRuns ?? DEFAULT_MAX_TERMINAL_RUNS;
|
|
@@ -352,7 +353,7 @@ export function createRunLifecycle(input) {
|
|
|
352
353
|
}
|
|
353
354
|
/** `start()`'s phase 2: appends the durable `'start'` entry, rolling back the in-memory record and idempotency-index entry on failure. */
|
|
354
355
|
async function appendStartOrRollback(runId, record, startInput) {
|
|
355
|
-
const startPayload = buildStartPayload(runId, startInput);
|
|
356
|
+
const startPayload = buildStartPayload({ runId: runId, startInput: startInput });
|
|
356
357
|
const startPromise = appendEvent(runId, record, 'start', startPayload).then(() => undefined);
|
|
357
358
|
record.startPromise = startPromise;
|
|
358
359
|
try {
|
|
@@ -361,7 +362,7 @@ export function createRunLifecycle(input) {
|
|
|
361
362
|
}
|
|
362
363
|
catch (error) {
|
|
363
364
|
runs.delete(runId);
|
|
364
|
-
clearIdempotencyIndexEntryIfMatching(idempotencyIndex, startInput.idempotencyKey, runId);
|
|
365
|
+
clearIdempotencyIndexEntryIfMatching({ idempotencyIndex: idempotencyIndex, idempotencyKey: startInput.idempotencyKey, runId: runId });
|
|
365
366
|
throw error;
|
|
366
367
|
}
|
|
367
368
|
}
|
|
@@ -412,7 +413,7 @@ export function createRunLifecycle(input) {
|
|
|
412
413
|
return;
|
|
413
414
|
record.retentionTimer = undefined;
|
|
414
415
|
runs.delete(runId);
|
|
415
|
-
clearIdempotencyIndexEntryIfMatching(idempotencyIndex, record.idempotencyKey, runId);
|
|
416
|
+
clearIdempotencyIndexEntryIfMatching({ idempotencyIndex: idempotencyIndex, idempotencyKey: record.idempotencyKey, runId: runId });
|
|
416
417
|
}
|
|
417
418
|
/** Trims the oldest terminal records until `terminalRunOrder` is back at or under `maxTerminalRuns`. */
|
|
418
419
|
function enforceTerminalCap() {
|
|
@@ -430,7 +431,7 @@ export function createRunLifecycle(input) {
|
|
|
430
431
|
*/
|
|
431
432
|
function trackTerminalRun(runId, record, terminalAt) {
|
|
432
433
|
terminalRunOrder.push(runId);
|
|
433
|
-
const delay = computeRetentionDelayMs(terminalAt, terminalRetentionMs, Date.now());
|
|
434
|
+
const delay = computeRetentionDelayMs({ terminalAt: terminalAt, retentionMs: terminalRetentionMs, now: Date.now() });
|
|
434
435
|
const timer = setTimeout(() => evictTerminalRun(runId), delay);
|
|
435
436
|
if (typeof timer.unref === 'function')
|
|
436
437
|
timer.unref();
|
|
@@ -472,7 +473,7 @@ export function createRunLifecycle(input) {
|
|
|
472
473
|
*/
|
|
473
474
|
async function handleInactivityTimeout(runId) {
|
|
474
475
|
const record = runs.get(runId);
|
|
475
|
-
if (!record || isTerminalRunState(record.status.state)) {
|
|
476
|
+
if (!record || isTerminalRunState({ state: record.status.state })) {
|
|
476
477
|
return;
|
|
477
478
|
}
|
|
478
479
|
try {
|
|
@@ -518,17 +519,17 @@ export function createRunLifecycle(input) {
|
|
|
518
519
|
*/
|
|
519
520
|
async function handleSlowRunNotice(runId) {
|
|
520
521
|
const record = runs.get(runId);
|
|
521
|
-
if (!record || isTerminalRunState(record.status.state)) {
|
|
522
|
+
if (!record || isTerminalRunState({ state: record.status.state })) {
|
|
522
523
|
return;
|
|
523
524
|
}
|
|
524
525
|
try {
|
|
525
|
-
await lifecycle.emit(runId, {
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
526
|
+
await lifecycle.emit({ runId: runId, input: {
|
|
527
|
+
event: 'agent',
|
|
528
|
+
data: {
|
|
529
|
+
type: 'slow_running',
|
|
530
|
+
detail: 'Still working — this turn is taking longer than usual.',
|
|
531
|
+
},
|
|
532
|
+
} });
|
|
532
533
|
}
|
|
533
534
|
catch (error) {
|
|
534
535
|
const context = { source: 'slow-run-notice', runId, error };
|
|
@@ -556,7 +557,7 @@ export function createRunLifecycle(input) {
|
|
|
556
557
|
async function rehydrateOne(runId) {
|
|
557
558
|
if (runs.has(runId))
|
|
558
559
|
return 'skipped';
|
|
559
|
-
const replay = await eventLog.replay(runId, null);
|
|
560
|
+
const replay = await eventLog.replay({ runId: runId, afterCursor: null });
|
|
560
561
|
if (replay.kind !== 'ok' || replay.entries.length === 0)
|
|
561
562
|
return 'skipped';
|
|
562
563
|
const { record, idempotencyKey, isTerminal } = rehydratedRunRecord(runId, replay.entries);
|
|
@@ -572,12 +573,12 @@ export function createRunLifecycle(input) {
|
|
|
572
573
|
return isTerminal ? 'terminal' : 'non-terminal';
|
|
573
574
|
}
|
|
574
575
|
const lifecycle = {
|
|
575
|
-
async rehydrate() {
|
|
576
|
+
async rehydrate(_args) {
|
|
576
577
|
if (hydration)
|
|
577
578
|
return hydration;
|
|
578
579
|
hydration = (async () => {
|
|
579
580
|
const rehydratedNonTerminalRunIds = [];
|
|
580
|
-
for (const runId of await eventLog.listRunIds()) {
|
|
581
|
+
for (const runId of await eventLog.listRunIds({})) {
|
|
581
582
|
if (await rehydrateOne(runId) === 'non-terminal') {
|
|
582
583
|
rehydratedNonTerminalRunIds.push(runId);
|
|
583
584
|
}
|
|
@@ -590,7 +591,8 @@ export function createRunLifecycle(input) {
|
|
|
590
591
|
})();
|
|
591
592
|
return hydration;
|
|
592
593
|
},
|
|
593
|
-
async start(
|
|
594
|
+
async start(requiredArgs, optionalArgs = {}) {
|
|
595
|
+
const startInput = { ...requiredArgs, ...optionalArgs };
|
|
594
596
|
// Synchronous on purpose: `resolveIdempotentReplayRunId` does no I/O, and the common case
|
|
595
597
|
// (no idempotencyKey, or a fresh one) must reach `runs.set()` below in the same synchronous
|
|
596
598
|
// turn as this call — not after even one microtask tick — so a caller that races `finish()`
|
|
@@ -598,7 +600,7 @@ export function createRunLifecycle(input) {
|
|
|
598
600
|
// needs. Wrapping this check in its own `await`ed async function previously broke exactly
|
|
599
601
|
// that: an `async function` always defers its continuation by a microtask even when its body
|
|
600
602
|
// never itself awaits, which shifted `runs.set()` behind the racing `finish()` call.
|
|
601
|
-
const existingRunId = resolveIdempotentReplayRunId(idempotencyIndex, startInput.idempotencyKey);
|
|
603
|
+
const existingRunId = resolveIdempotentReplayRunId({ idempotencyIndex: idempotencyIndex, idempotencyKey: startInput.idempotencyKey });
|
|
602
604
|
if (existingRunId !== undefined) {
|
|
603
605
|
const existing = requireRun(existingRunId);
|
|
604
606
|
await existing.startPromise;
|
|
@@ -610,17 +612,17 @@ export function createRunLifecycle(input) {
|
|
|
610
612
|
}
|
|
611
613
|
const record = buildNewRunRecord(startInput, runId, Date.now());
|
|
612
614
|
runs.set(runId, record);
|
|
613
|
-
registerIdempotencyKeyIfPresent(idempotencyIndex, startInput.idempotencyKey, runId);
|
|
615
|
+
registerIdempotencyKeyIfPresent({ idempotencyIndex: idempotencyIndex, idempotencyKey: startInput.idempotencyKey, runId: runId });
|
|
614
616
|
await appendStartOrRollback(runId, record, startInput);
|
|
615
|
-
armWatchdogIfConfigured(record, startInput.inactivityTimeoutMs, () => {
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
armSlowRunWatchdogIfConfigured(record, slowRunThresholdMs, () => {
|
|
619
|
-
|
|
620
|
-
|
|
617
|
+
armWatchdogIfConfigured({ record: record, timeoutMs: startInput.inactivityTimeoutMs, onTimeout: () => {
|
|
618
|
+
void handleInactivityTimeout(runId);
|
|
619
|
+
} });
|
|
620
|
+
armSlowRunWatchdogIfConfigured({ record: record, timeoutMs: slowRunThresholdMs, onTimeout: () => {
|
|
621
|
+
void handleSlowRunNotice(runId);
|
|
622
|
+
} });
|
|
621
623
|
return { run: toPublicStatus(record), started: true };
|
|
622
624
|
},
|
|
623
|
-
async get(runId) {
|
|
625
|
+
async get({ runId }) {
|
|
624
626
|
await settlePendingStart(runs.get(runId));
|
|
625
627
|
// Re-read rather than reusing the record above: a failed start deletes it (see `start()`'s
|
|
626
628
|
// unwind), and reporting a run whose durable `'start'` entry does not exist would advertise a
|
|
@@ -628,7 +630,7 @@ export function createRunLifecycle(input) {
|
|
|
628
630
|
const record = runs.get(runId);
|
|
629
631
|
return record ? toPublicStatus(record) : undefined;
|
|
630
632
|
},
|
|
631
|
-
async list(contextRef) {
|
|
633
|
+
async list(_requiredArgs, { contextRef } = {}) {
|
|
632
634
|
await Promise.all(Array.from(runs.values(), settlePendingStart));
|
|
633
635
|
const all = Array.from(runs.values());
|
|
634
636
|
const filtered = contextRef === undefined ? all : all.filter((record) => record.contextRef === contextRef);
|
|
@@ -640,7 +642,7 @@ export function createRunLifecycle(input) {
|
|
|
640
642
|
if (record.finishPromise) {
|
|
641
643
|
return record.finishPromise;
|
|
642
644
|
}
|
|
643
|
-
if (isTerminalRunState(record.status.state)) {
|
|
645
|
+
if (isTerminalRunState({ state: record.status.state })) {
|
|
644
646
|
return toPublicStatus(record);
|
|
645
647
|
}
|
|
646
648
|
record.cancelRequested = true;
|
|
@@ -659,7 +661,7 @@ export function createRunLifecycle(input) {
|
|
|
659
661
|
* happened would silently never learn about it, since a plain
|
|
660
662
|
* subscribe-for-future-events registry has no memory of past firings.
|
|
661
663
|
*/
|
|
662
|
-
onCancelRequested(runId, listener) {
|
|
664
|
+
onCancelRequested({ runId, listener }) {
|
|
663
665
|
const record = requireRun(runId);
|
|
664
666
|
if (record.cancelRequested && record.lastCancelRequest) {
|
|
665
667
|
listener(record.lastCancelRequest);
|
|
@@ -667,23 +669,30 @@ export function createRunLifecycle(input) {
|
|
|
667
669
|
record.cancelListeners.add(listener);
|
|
668
670
|
return () => record.cancelListeners.delete(listener);
|
|
669
671
|
},
|
|
670
|
-
async emit(runId,
|
|
672
|
+
async emit({ runId, input }) {
|
|
671
673
|
const record = requireRun(runId);
|
|
672
674
|
await record.startPromise;
|
|
673
|
-
if (record.finishPromise || isTerminalRunState(record.status.state)) {
|
|
674
|
-
throw new Error(`RunLifecycle: cannot emit "${
|
|
675
|
+
if (record.finishPromise || isTerminalRunState({ state: record.status.state })) {
|
|
676
|
+
throw new Error(`RunLifecycle: cannot emit "${input.event}" on terminal run "${runId}" — drivers must stop emitting once finish() has been called`);
|
|
675
677
|
}
|
|
676
|
-
record.watchdog?.noteActivity();
|
|
677
|
-
|
|
678
|
-
|
|
678
|
+
record.watchdog?.noteActivity({});
|
|
679
|
+
// The crash watchdog above counts ANY output (the process is alive). The slow-run notice asks
|
|
680
|
+
// a different question — "has the person seen anything new?" — so only `agent` events count.
|
|
681
|
+
// A CLI's raw stdout/stderr (e.g. Claude Code's 30 s `tool_progress` heartbeats during a long
|
|
682
|
+
// tool call) is not rendered, and letting it reset this window meant the notice could never
|
|
683
|
+
// fire during exactly the long, silent tool calls it exists for.
|
|
684
|
+
if (input.event === 'agent')
|
|
685
|
+
record.slowRunWatchdog?.noteActivity({});
|
|
686
|
+
return appendEvent(runId, record, input.event, input.data);
|
|
679
687
|
},
|
|
680
|
-
async finish(
|
|
688
|
+
async finish(requiredArgs, optionalArgs = {}) {
|
|
689
|
+
const finishInput = { ...requiredArgs, ...optionalArgs };
|
|
681
690
|
const record = requireRun(finishInput.runId);
|
|
682
691
|
await record.startPromise;
|
|
683
692
|
if (record.finishPromise) {
|
|
684
693
|
return record.finishPromise;
|
|
685
694
|
}
|
|
686
|
-
if (isTerminalRunState(record.status.state)) {
|
|
695
|
+
if (isTerminalRunState({ state: record.status.state })) {
|
|
687
696
|
return toPublicStatus(record);
|
|
688
697
|
}
|
|
689
698
|
const finishing = (async () => {
|
|
@@ -697,8 +706,8 @@ export function createRunLifecycle(input) {
|
|
|
697
706
|
const endEntry = await eventLog.append({ runId: finishInput.runId, event: 'end', data: endPayload });
|
|
698
707
|
// Commit the in-memory terminal transition only after its durable end entry exists. Until
|
|
699
708
|
// then `finishPromise` reserves the transition and blocks emits/concurrent finishes.
|
|
700
|
-
record.watchdog?.cancel();
|
|
701
|
-
record.slowRunWatchdog?.cancel();
|
|
709
|
+
record.watchdog?.cancel({});
|
|
710
|
+
record.slowRunWatchdog?.cancel({});
|
|
702
711
|
record.status.state = finishInput.status;
|
|
703
712
|
record.status.updatedAt = endEntry.recordedAt;
|
|
704
713
|
record.status.endedAt = endEntry.recordedAt;
|
|
@@ -720,9 +729,9 @@ export function createRunLifecycle(input) {
|
|
|
720
729
|
}
|
|
721
730
|
}
|
|
722
731
|
},
|
|
723
|
-
async resume(runId) {
|
|
732
|
+
async resume({ runId }) {
|
|
724
733
|
const record = requireRun(runId);
|
|
725
|
-
const eligible = isTerminalRunState(record.status.state) && record.resumable;
|
|
734
|
+
const eligible = isTerminalRunState({ state: record.status.state }) && record.resumable;
|
|
726
735
|
if (!eligible) {
|
|
727
736
|
return { run: toPublicStatus(record), resumed: false };
|
|
728
737
|
}
|
|
@@ -740,28 +749,32 @@ export function createRunLifecycle(input) {
|
|
|
740
749
|
// never fires again on its own — the underlying timer is one-shot) left the notice permanently
|
|
741
750
|
// dark for the rest of a resumed run's life, silently disabling a feature the run is otherwise
|
|
742
751
|
// still eligible for.
|
|
743
|
-
armSlowRunWatchdogIfConfigured(record, slowRunThresholdMs, () => {
|
|
744
|
-
|
|
745
|
-
|
|
752
|
+
armSlowRunWatchdogIfConfigured({ record: record, timeoutMs: slowRunThresholdMs, onTimeout: () => {
|
|
753
|
+
void handleSlowRunNotice(runId);
|
|
754
|
+
} });
|
|
746
755
|
// No protocol event is emitted here: none of RunProtocolEvent's six
|
|
747
756
|
// kinds represents "resumed" (extraction-plan scope decision — see
|
|
748
|
-
//
|
|
757
|
+
// archived provenance ledger). The event log's cursor sequence continues
|
|
749
758
|
// unbroken; only RunStatus.state changes.
|
|
750
759
|
return { run: toPublicStatus(record), resumed: true };
|
|
751
760
|
},
|
|
752
|
-
suspendSlowRunNotice(runId) {
|
|
761
|
+
suspendSlowRunNotice({ runId }) {
|
|
753
762
|
const record = runs.get(runId);
|
|
754
|
-
if (!record || isTerminalRunState(record.status.state))
|
|
763
|
+
if (!record || isTerminalRunState({ state: record.status.state }))
|
|
755
764
|
return;
|
|
756
|
-
record.slowRunWatchdog?.cancel();
|
|
765
|
+
record.slowRunWatchdog?.cancel({});
|
|
757
766
|
},
|
|
758
|
-
resumeSlowRunNotice(runId) {
|
|
767
|
+
resumeSlowRunNotice({ runId }) {
|
|
759
768
|
const record = runs.get(runId);
|
|
760
|
-
if (!record || isTerminalRunState(record.status.state))
|
|
769
|
+
if (!record || isTerminalRunState({ state: record.status.state }))
|
|
761
770
|
return;
|
|
762
|
-
|
|
771
|
+
// Suspension cancels permanently. Resuming must create a new window, never revive that handle.
|
|
772
|
+
record.slowRunWatchdog?.cancel({});
|
|
773
|
+
armSlowRunWatchdogIfConfigured({ record, timeoutMs: slowRunThresholdMs, onTimeout: () => {
|
|
774
|
+
void handleSlowRunNotice(runId);
|
|
775
|
+
} });
|
|
763
776
|
},
|
|
764
|
-
async waitForTerminal(runId) {
|
|
777
|
+
async waitForTerminal({ runId }) {
|
|
765
778
|
const record = requireRun(runId);
|
|
766
779
|
// Awaited (and *propagated*, unlike in `get`/`list`) before any waiter is registered. A run
|
|
767
780
|
// whose durable start append rejects is unwound, and nothing ever calls `finish()` for it — so
|
|
@@ -769,14 +782,14 @@ export function createRunLifecycle(input) {
|
|
|
769
782
|
// the same error that failed the start is the only honest terminal answer, and awaiting first
|
|
770
783
|
// means `terminalWaiters` can only ever hold waiters for a run that really does exist.
|
|
771
784
|
await record.startPromise;
|
|
772
|
-
if (isTerminalRunState(record.status.state)) {
|
|
785
|
+
if (isTerminalRunState({ state: record.status.state })) {
|
|
773
786
|
return toPublicStatus(record);
|
|
774
787
|
}
|
|
775
788
|
return new Promise((resolve) => {
|
|
776
789
|
record.terminalWaiters.push(resolve);
|
|
777
790
|
});
|
|
778
791
|
},
|
|
779
|
-
async stream(runId, onEvent, options = {}) {
|
|
792
|
+
async stream({ runId, onEvent }, options = {}) {
|
|
780
793
|
const record = runs.get(runId);
|
|
781
794
|
if (!record) {
|
|
782
795
|
return { kind: 'unknown-run' };
|
|
@@ -795,19 +808,19 @@ export function createRunLifecycle(input) {
|
|
|
795
808
|
};
|
|
796
809
|
record.subscribers.add(subscriber);
|
|
797
810
|
try {
|
|
798
|
-
const replay = await eventLog.replay(runId, options.afterCursor ?? null);
|
|
811
|
+
const replay = await eventLog.replay({ runId: runId, afterCursor: options.afterCursor ?? null });
|
|
799
812
|
if (replay.kind !== 'ok') {
|
|
800
813
|
record.subscribers.delete(subscriber);
|
|
801
814
|
return replay;
|
|
802
815
|
}
|
|
803
|
-
const deliveredEventIds = deliverReplayedEvents(runId, replay.entries, onEvent);
|
|
804
|
-
deliverUndeliveredEvents(bufferedLiveEvents, deliveredEventIds, onEvent);
|
|
816
|
+
const deliveredEventIds = deliverReplayedEvents({ runId: runId, entries: replay.entries, onEvent: onEvent });
|
|
817
|
+
deliverUndeliveredEvents({ events: bufferedLiveEvents, deliveredEventIds: deliveredEventIds, onEvent: onEvent });
|
|
805
818
|
replaying = false;
|
|
806
|
-
const terminal = isTerminalRunState(record.status.state);
|
|
819
|
+
const terminal = isTerminalRunState({ state: record.status.state });
|
|
807
820
|
if (terminal && record.terminalEndEntry) {
|
|
808
|
-
deliverUndeliveredEvents([toRunEvent(runId, record.terminalEndEntry)], deliveredEventIds, onEvent);
|
|
821
|
+
deliverUndeliveredEvents({ events: [toRunEvent(runId, record.terminalEndEntry)], deliveredEventIds: deliveredEventIds, onEvent: onEvent });
|
|
809
822
|
}
|
|
810
|
-
return finishStreamSubscription(record, subscriber, terminal);
|
|
823
|
+
return finishStreamSubscription({ record: record, subscriber: subscriber, terminal: terminal });
|
|
811
824
|
}
|
|
812
825
|
catch (error) {
|
|
813
826
|
// Subscription is installed before durable replay to close the replay→live race. Any
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/** Host timers must invoke callbacks asynchronously; cancellation must be idempotent. */
|
|
2
|
+
export interface SchedulerPort {
|
|
3
|
+
schedule(args: {
|
|
4
|
+
readonly delayMs: number;
|
|
5
|
+
readonly callback: () => void;
|
|
6
|
+
}): () => void;
|
|
7
|
+
}
|
|
8
|
+
//# sourceMappingURL=scheduler.d.ts.map
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
import type { RunProtocolEvent } from '@jini-ai/protocol';
|
|
2
|
+
import type { SchedulerPort } from './scheduler.js';
|
|
3
|
+
export interface ConversationRun {
|
|
4
|
+
readonly conversationId: string;
|
|
5
|
+
readonly runId: string;
|
|
6
|
+
}
|
|
7
|
+
export interface LiveRunTracker {
|
|
8
|
+
register(args: ConversationRun): void;
|
|
9
|
+
unregister(args: ConversationRun): void;
|
|
10
|
+
hasConcurrentLiveRun(args: ConversationRun): boolean;
|
|
11
|
+
concurrentLiveRunIds(args: ConversationRun): string[];
|
|
12
|
+
conversationIdForRun(args: {
|
|
13
|
+
runId: string;
|
|
14
|
+
}): string | undefined;
|
|
15
|
+
}
|
|
16
|
+
export declare function createLiveRunTracker(_args: Record<string, never>): LiveRunTracker;
|
|
17
|
+
export interface ConversationStartLock {
|
|
18
|
+
run<T>(args: {
|
|
19
|
+
conversationId: string | undefined;
|
|
20
|
+
critical: () => Promise<T>;
|
|
21
|
+
}): Promise<T>;
|
|
22
|
+
trackedConversationCount(): number;
|
|
23
|
+
}
|
|
24
|
+
/** Serialize initialization only. Run execution belongs outside the critical section. */
|
|
25
|
+
export declare function createConversationStartLock(_args: Record<string, never>): ConversationStartLock;
|
|
26
|
+
/** Existing lifecycle port; no persistence is imported or implemented here. */
|
|
27
|
+
export interface StoppingRunLifecycle {
|
|
28
|
+
onCancelRequested(args: {
|
|
29
|
+
runId: string;
|
|
30
|
+
listener: () => void;
|
|
31
|
+
}): () => void;
|
|
32
|
+
waitForTerminal(args: {
|
|
33
|
+
runId: string;
|
|
34
|
+
}): Promise<unknown>;
|
|
35
|
+
}
|
|
36
|
+
export declare const STOPPING_RUN_WAIT_MS = 20000;
|
|
37
|
+
export declare function waitForStoppingRuns({ tracker, lifecycle, scheduler, conversationId, runId }: {
|
|
38
|
+
tracker: LiveRunTracker;
|
|
39
|
+
lifecycle: StoppingRunLifecycle;
|
|
40
|
+
scheduler: SchedulerPort;
|
|
41
|
+
conversationId: string;
|
|
42
|
+
runId: string;
|
|
43
|
+
}, { timeoutMs }?: {
|
|
44
|
+
timeoutMs?: number;
|
|
45
|
+
}): Promise<void>;
|
|
46
|
+
export interface FailingRunLifecycle {
|
|
47
|
+
emit(args: {
|
|
48
|
+
runId: string;
|
|
49
|
+
input: {
|
|
50
|
+
event: 'error';
|
|
51
|
+
data: {
|
|
52
|
+
message: string;
|
|
53
|
+
};
|
|
54
|
+
};
|
|
55
|
+
}): Promise<unknown>;
|
|
56
|
+
finish(input: {
|
|
57
|
+
runId: string;
|
|
58
|
+
status: 'failed';
|
|
59
|
+
code: null;
|
|
60
|
+
signal: null;
|
|
61
|
+
resumable: false;
|
|
62
|
+
}): Promise<unknown>;
|
|
63
|
+
}
|
|
64
|
+
export declare function failRunBeforeStart({ lifecycle, runId, message }: {
|
|
65
|
+
lifecycle: FailingRunLifecycle;
|
|
66
|
+
runId: string;
|
|
67
|
+
message: string;
|
|
68
|
+
}): Promise<void>;
|
|
69
|
+
export interface AgentCapabilities {
|
|
70
|
+
readonly resumesSessionViaCli?: boolean;
|
|
71
|
+
readonly resumesSessionViaAcpLoad?: boolean;
|
|
72
|
+
readonly capturesSessionIdFromStream?: boolean;
|
|
73
|
+
}
|
|
74
|
+
export interface AgentCapabilityResolver {
|
|
75
|
+
lookup(args: {
|
|
76
|
+
agentId: string;
|
|
77
|
+
}): AgentCapabilities | undefined;
|
|
78
|
+
}
|
|
79
|
+
export declare function agentAcceptsHostMintedSessionId({ agentId, agents }: {
|
|
80
|
+
agentId: string;
|
|
81
|
+
agents: AgentCapabilityResolver;
|
|
82
|
+
}): boolean;
|
|
83
|
+
export declare function agentCarriesOwnMemory({ agentId, agents }: {
|
|
84
|
+
agentId: string;
|
|
85
|
+
agents: AgentCapabilityResolver;
|
|
86
|
+
}): boolean;
|
|
87
|
+
export declare function resolveHostMintedSessionId(input: {
|
|
88
|
+
conversationId: string | undefined;
|
|
89
|
+
effectiveResumeSessionId: string | null;
|
|
90
|
+
acceptsHostMintedSessionId: boolean;
|
|
91
|
+
mint: () => string;
|
|
92
|
+
}): string | null;
|
|
93
|
+
export declare function resolveNewSessionField({ hostMintedSessionId }: {
|
|
94
|
+
hostMintedSessionId: string | null;
|
|
95
|
+
}): {
|
|
96
|
+
newSessionId?: string;
|
|
97
|
+
};
|
|
98
|
+
export declare function resolveResumeSessionField({ storedSessionId }: {
|
|
99
|
+
storedSessionId: string | null;
|
|
100
|
+
}): {
|
|
101
|
+
resumeSessionId?: string;
|
|
102
|
+
};
|
|
103
|
+
export declare function extractSessionRefFromEndEvent({ event }: {
|
|
104
|
+
event: RunProtocolEvent;
|
|
105
|
+
}): string | undefined;
|
|
106
|
+
export declare function shouldClearSessionOnFailedResume({ event, attemptedResumeSessionId }: {
|
|
107
|
+
event: RunProtocolEvent;
|
|
108
|
+
attemptedResumeSessionId: string | null;
|
|
109
|
+
}): boolean;
|
|
110
|
+
export declare function wouldForcedColdStartLoseConversationContext(input: {
|
|
111
|
+
storedSessionId: string | null;
|
|
112
|
+
hasConcurrentLiveRun: boolean;
|
|
113
|
+
carriesOwnMemory: boolean;
|
|
114
|
+
}): boolean;
|
|
115
|
+
//# sourceMappingURL=session-coordination.d.ts.map
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
export function createLiveRunTracker(_args) {
|
|
2
|
+
const runs = new Map();
|
|
3
|
+
const conversations = new Map();
|
|
4
|
+
function unregister({ conversationId, runId }) {
|
|
5
|
+
if (conversations.get(runId) === conversationId)
|
|
6
|
+
conversations.delete(runId);
|
|
7
|
+
const ids = runs.get(conversationId);
|
|
8
|
+
ids?.delete(runId);
|
|
9
|
+
if (ids?.size === 0)
|
|
10
|
+
runs.delete(conversationId);
|
|
11
|
+
}
|
|
12
|
+
return {
|
|
13
|
+
register({ conversationId, runId }) {
|
|
14
|
+
const previous = conversations.get(runId);
|
|
15
|
+
if (previous !== undefined && previous !== conversationId)
|
|
16
|
+
unregister({ conversationId: previous, runId });
|
|
17
|
+
conversations.set(runId, conversationId);
|
|
18
|
+
const ids = runs.get(conversationId) ?? new Set();
|
|
19
|
+
ids.add(runId);
|
|
20
|
+
runs.set(conversationId, ids);
|
|
21
|
+
},
|
|
22
|
+
unregister,
|
|
23
|
+
concurrentLiveRunIds: ({ conversationId, runId }) => [...(runs.get(conversationId) ?? [])].filter(id => id !== runId),
|
|
24
|
+
hasConcurrentLiveRun({ conversationId, runId }) {
|
|
25
|
+
for (const id of runs.get(conversationId) ?? [])
|
|
26
|
+
if (id !== runId)
|
|
27
|
+
return true;
|
|
28
|
+
return false;
|
|
29
|
+
},
|
|
30
|
+
conversationIdForRun: ({ runId }) => conversations.get(runId),
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
/** Serialize initialization only. Run execution belongs outside the critical section. */
|
|
34
|
+
export function createConversationStartLock(_args) {
|
|
35
|
+
const tails = new Map();
|
|
36
|
+
return {
|
|
37
|
+
run({ conversationId, critical }) {
|
|
38
|
+
if (conversationId === undefined)
|
|
39
|
+
return critical();
|
|
40
|
+
const result = (tails.get(conversationId) ?? Promise.resolve()).then(critical);
|
|
41
|
+
const release = () => { if (tails.get(conversationId) === tail)
|
|
42
|
+
tails.delete(conversationId); };
|
|
43
|
+
const tail = result.then(release, release);
|
|
44
|
+
tails.set(conversationId, tail);
|
|
45
|
+
return result;
|
|
46
|
+
},
|
|
47
|
+
trackedConversationCount: () => tails.size,
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
export const STOPPING_RUN_WAIT_MS = 20_000;
|
|
51
|
+
export async function waitForStoppingRuns({ tracker, lifecycle, scheduler, conversationId, runId }, { timeoutMs = STOPPING_RUN_WAIT_MS } = {}) {
|
|
52
|
+
const stopping = tracker.concurrentLiveRunIds({ conversationId, runId }).filter(id => {
|
|
53
|
+
let cancelled = false;
|
|
54
|
+
try {
|
|
55
|
+
lifecycle.onCancelRequested({ runId: id, listener: () => { cancelled = true; } })();
|
|
56
|
+
}
|
|
57
|
+
catch {
|
|
58
|
+
return false;
|
|
59
|
+
}
|
|
60
|
+
return cancelled;
|
|
61
|
+
});
|
|
62
|
+
if (stopping.length === 0)
|
|
63
|
+
return;
|
|
64
|
+
let cancelTimer;
|
|
65
|
+
const timedOut = new Promise(resolve => { cancelTimer = scheduler.schedule({ delayMs: timeoutMs, callback: resolve }); });
|
|
66
|
+
// Promise.resolve also captures a synchronously throwing host port, ensuring timer cleanup.
|
|
67
|
+
const ended = Promise.all(stopping.map(id => Promise.resolve().then(() => lifecycle.waitForTerminal({ runId: id })).then(() => tracker.unregister({ conversationId, runId: id }))));
|
|
68
|
+
try {
|
|
69
|
+
await Promise.race([ended, timedOut]);
|
|
70
|
+
}
|
|
71
|
+
finally {
|
|
72
|
+
cancelTimer?.();
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
export async function failRunBeforeStart({ lifecycle, runId, message }) {
|
|
76
|
+
try {
|
|
77
|
+
await lifecycle.emit({ runId: runId, input: { event: 'error', data: { message } } });
|
|
78
|
+
}
|
|
79
|
+
catch { /* Finishing remains mandatory. */ }
|
|
80
|
+
await lifecycle.finish({ runId, status: 'failed', code: null, signal: null, resumable: false });
|
|
81
|
+
}
|
|
82
|
+
export function agentAcceptsHostMintedSessionId({ agentId, agents }) {
|
|
83
|
+
const def = agents.lookup({ agentId });
|
|
84
|
+
return Boolean(def?.resumesSessionViaCli) && !def?.capturesSessionIdFromStream;
|
|
85
|
+
}
|
|
86
|
+
export function agentCarriesOwnMemory({ agentId, agents }) {
|
|
87
|
+
const def = agents.lookup({ agentId });
|
|
88
|
+
return Boolean(def?.resumesSessionViaCli) || Boolean(def?.resumesSessionViaAcpLoad);
|
|
89
|
+
}
|
|
90
|
+
export function resolveHostMintedSessionId(input) {
|
|
91
|
+
return input.conversationId !== undefined && input.effectiveResumeSessionId === null && input.acceptsHostMintedSessionId ? input.mint() : null;
|
|
92
|
+
}
|
|
93
|
+
export function resolveNewSessionField({ hostMintedSessionId }) {
|
|
94
|
+
return hostMintedSessionId !== null ? { newSessionId: hostMintedSessionId } : {};
|
|
95
|
+
}
|
|
96
|
+
export function resolveResumeSessionField({ storedSessionId }) {
|
|
97
|
+
return storedSessionId !== null ? { resumeSessionId: storedSessionId } : {};
|
|
98
|
+
}
|
|
99
|
+
export function extractSessionRefFromEndEvent({ event }) {
|
|
100
|
+
if (event.kind !== 'end')
|
|
101
|
+
return undefined;
|
|
102
|
+
const id = event.payload.sessionRef;
|
|
103
|
+
return typeof id === 'string' && id.length > 0 ? id : undefined;
|
|
104
|
+
}
|
|
105
|
+
export function shouldClearSessionOnFailedResume({ event, attemptedResumeSessionId }) {
|
|
106
|
+
return attemptedResumeSessionId !== null && event.kind === 'end' && extractSessionRefFromEndEvent({ event }) === undefined;
|
|
107
|
+
}
|
|
108
|
+
export function wouldForcedColdStartLoseConversationContext(input) {
|
|
109
|
+
return input.storedSessionId !== null && input.hasConcurrentLiveRun && input.carriesOwnMemory;
|
|
110
|
+
}
|
|
111
|
+
//# sourceMappingURL=session-coordination.js.map
|