@jini-ai/daemon 0.3.8 → 0.5.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (159) hide show
  1. package/CHANGELOG.md +124 -0
  2. package/README.md +50 -8
  3. package/dist/agent-executor/contracts.d.ts +736 -0
  4. package/dist/agent-executor/contracts.js +28 -0
  5. package/dist/agent-executor/event-translation.d.ts +87 -0
  6. package/dist/agent-executor/event-translation.js +230 -0
  7. package/dist/agent-executor/events.d.ts +190 -0
  8. package/dist/agent-executor/events.js +848 -0
  9. package/dist/agent-executor/handoff.d.ts +18 -0
  10. package/dist/agent-executor/handoff.js +16 -0
  11. package/dist/agent-executor/index.d.ts +98 -0
  12. package/dist/agent-executor/index.js +98 -0
  13. package/dist/agent-executor/launch.d.ts +375 -0
  14. package/dist/agent-executor/launch.js +1001 -0
  15. package/dist/agent-executor/mcp-bridge.d.ts +338 -0
  16. package/dist/agent-executor/mcp-bridge.js +835 -0
  17. package/dist/agent-executor/values.d.ts +5 -0
  18. package/dist/agent-executor/values.js +13 -0
  19. package/dist/agent-executor.d.ts +375 -176
  20. package/dist/agent-executor.js +388 -335
  21. package/dist/close-status.d.ts +11 -8
  22. package/dist/close-status.js +31 -19
  23. package/dist/continuation/continuation-transport.d.ts +3 -1
  24. package/dist/continuation/continuation-transport.js +1 -1
  25. package/dist/continuation/journal.d.ts +12 -5
  26. package/dist/continuation/journal.js +4 -4
  27. package/dist/continuation/run-scoped-context-store.d.ts +13 -4
  28. package/dist/continuation/run-scoped-context-store.js +6 -6
  29. package/dist/continuation/run-start-handler.d.ts +1 -1
  30. package/dist/continuation/run-start-handler.js +3 -16
  31. package/dist/delegated-tool-bridge.d.ts +7 -3
  32. package/dist/delegated-tool-bridge.js +32 -31
  33. package/dist/event-log.d.ts +7 -5
  34. package/dist/event-log.js +12 -11
  35. package/dist/frontend-capability-tools.d.ts +1 -1
  36. package/dist/frontend-capability-tools.js +3 -2
  37. package/dist/frontend-session-registry.d.ts +32 -9
  38. package/dist/frontend-session-registry.js +12 -11
  39. package/dist/http/active-context.d.ts +105 -0
  40. package/dist/http/active-context.js +67 -0
  41. package/dist/http/agents.d.ts +68 -0
  42. package/dist/http/agents.js +12 -0
  43. package/dist/http/attachments.d.ts +618 -0
  44. package/dist/http/attachments.js +1034 -0
  45. package/dist/http/cancel-owned-runs.d.ts +49 -0
  46. package/dist/http/cancel-owned-runs.js +35 -0
  47. package/dist/http/component-catalog.d.ts +84 -0
  48. package/dist/http/component-catalog.js +64 -0
  49. package/dist/http/connectors.d.ts +209 -0
  50. package/dist/http/connectors.js +409 -0
  51. package/dist/http/daemon-status.d.ts +76 -0
  52. package/dist/http/daemon-status.js +55 -0
  53. package/dist/http/db-ops.d.ts +123 -0
  54. package/dist/http/db-ops.js +189 -0
  55. package/dist/http/delegated-tools.d.ts +113 -0
  56. package/dist/http/delegated-tools.js +230 -0
  57. package/dist/http/frontend-control.d.ts +136 -0
  58. package/dist/http/frontend-control.js +79 -0
  59. package/dist/http/frontend-sessions.d.ts +110 -0
  60. package/dist/http/frontend-sessions.js +180 -0
  61. package/dist/http/health.d.ts +95 -0
  62. package/dist/http/health.js +40 -0
  63. package/dist/http/host-tools.d.ts +214 -0
  64. package/dist/http/host-tools.js +329 -0
  65. package/dist/http/index.d.ts +52 -0
  66. package/dist/http/index.js +27 -0
  67. package/dist/http/media.d.ts +146 -0
  68. package/dist/http/media.js +346 -0
  69. package/dist/http/memory.d.ts +265 -0
  70. package/dist/http/memory.js +328 -0
  71. package/dist/http/model-proxy.d.ts +49 -0
  72. package/dist/http/model-proxy.js +642 -0
  73. package/dist/http/remote-run-events.d.ts +70 -0
  74. package/dist/http/remote-run-events.js +224 -0
  75. package/dist/http/research.d.ts +48 -0
  76. package/dist/http/research.js +315 -0
  77. package/dist/http/route-manifest.d.ts +35 -0
  78. package/dist/http/route-manifest.js +103 -0
  79. package/dist/http/routines.d.ts +120 -0
  80. package/dist/http/routines.js +298 -0
  81. package/dist/http/run-credentials.d.ts +73 -0
  82. package/dist/http/run-credentials.js +71 -0
  83. package/dist/http/runs.d.ts +91 -0
  84. package/dist/http/runs.js +221 -0
  85. package/dist/http/terminals.d.ts +138 -0
  86. package/dist/http/terminals.js +325 -0
  87. package/dist/http/tool-catalog.d.ts +78 -0
  88. package/dist/http/tool-catalog.js +66 -0
  89. package/dist/http/workspace-root.d.ts +77 -0
  90. package/dist/http/workspace-root.js +63 -0
  91. package/dist/http/xai.d.ts +90 -0
  92. package/dist/http/xai.js +655 -0
  93. package/dist/image-prompt-delivery.d.ts +13 -3
  94. package/dist/image-prompt-delivery.js +5 -5
  95. package/dist/index.d.ts +556 -4
  96. package/dist/index.js +556 -4
  97. package/dist/legacy-data-migration.d.ts +59 -8
  98. package/dist/legacy-data-migration.js +38 -33
  99. package/dist/read-only-tools.d.ts +130 -0
  100. package/dist/read-only-tools.js +115 -0
  101. package/dist/remote-tool-bridge.d.ts +11 -5
  102. package/dist/remote-tool-bridge.js +15 -15
  103. package/dist/routines/index.d.ts +1 -1
  104. package/dist/routines/index.js +1 -1
  105. package/dist/routines/routine-store.d.ts +34 -12
  106. package/dist/routines/routine-store.js +21 -20
  107. package/dist/routines/schedule.d.ts +48 -10
  108. package/dist/routines/schedule.js +21 -21
  109. package/dist/routines/scheduler.d.ts +32 -11
  110. package/dist/routines/scheduler.js +61 -57
  111. package/dist/routines/types.d.ts +10 -3
  112. package/dist/run/core/result.d.ts +6 -2
  113. package/dist/run/core/result.js +3 -3
  114. package/dist/run/core/retry.d.ts +17 -4
  115. package/dist/run/core/retry.js +7 -11
  116. package/dist/run/diagnostics/diagnostics.d.ts +14 -4
  117. package/dist/run/diagnostics/diagnostics.js +7 -7
  118. package/dist/run/index.d.ts +1 -1
  119. package/dist/run/index.js +1 -1
  120. package/dist/run-credentials.d.ts +138 -0
  121. package/dist/run-credentials.js +152 -0
  122. package/dist/run-lifecycle.d.ts +92 -33
  123. package/dist/run-lifecycle.js +81 -74
  124. package/dist/scheduler.d.ts +8 -0
  125. package/dist/scheduler.js +2 -0
  126. package/dist/session-coordination.d.ts +115 -0
  127. package/dist/session-coordination.js +111 -0
  128. package/dist/store/agent-sessions/errors.d.ts +8 -0
  129. package/dist/store/agent-sessions/errors.js +10 -0
  130. package/dist/store/agent-sessions/index.d.ts +12 -0
  131. package/dist/store/agent-sessions/index.js +19 -0
  132. package/dist/store/agent-sessions/pglite.d.ts +12 -0
  133. package/dist/store/agent-sessions/pglite.js +7 -0
  134. package/dist/store/agent-sessions/ports.d.ts +24 -0
  135. package/dist/store/agent-sessions/ports.js +2 -0
  136. package/dist/store/agent-sessions/postgres.d.ts +12 -0
  137. package/dist/store/agent-sessions/postgres.js +7 -0
  138. package/dist/store/agent-sessions/schema.d.ts +3 -0
  139. package/dist/store/agent-sessions/schema.js +23 -0
  140. package/dist/store/agent-sessions/sql.d.ts +35 -0
  141. package/dist/store/agent-sessions/sql.js +59 -0
  142. package/dist/store/agent-sessions/sqlite.d.ts +117 -0
  143. package/dist/store/agent-sessions/sqlite.js +128 -0
  144. package/dist/store/event-log/sqlite.d.ts +75 -0
  145. package/dist/store/event-log/sqlite.js +230 -0
  146. package/dist/surface-exchanges.d.ts +97 -0
  147. package/dist/surface-exchanges.js +153 -0
  148. package/dist/terminal-session.d.ts +43 -11
  149. package/dist/terminal-session.js +22 -20
  150. package/dist/tokens.js +4 -4
  151. package/dist/tool-audit.d.ts +114 -0
  152. package/dist/tool-audit.js +95 -0
  153. package/dist/tool-executor.d.ts +22 -7
  154. package/dist/tool-executor.js +16 -16
  155. package/dist/tool-result-media.d.ts +3 -1
  156. package/dist/tool-result-media.js +1 -1
  157. package/dist/tool-result-surfaces.d.ts +3 -1
  158. package/dist/tool-result-surfaces.js +1 -1
  159. package/package.json +127 -9
@@ -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 `source-map.md` for exact line citations).
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(input) {
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
- event: 'agent',
527
- data: {
528
- type: 'slow_running',
529
- detail: 'Still working — this turn is taking longer than usual.',
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(startInput) {
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
- void handleInactivityTimeout(runId);
617
- });
618
- armSlowRunWatchdogIfConfigured(record, slowRunThresholdMs, () => {
619
- void handleSlowRunNotice(runId);
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,29 +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, driverInput) {
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 "${driverInput.event}" on terminal run "${runId}" — drivers must stop emitting once finish() has been called`);
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();
678
+ record.watchdog?.noteActivity({});
677
679
  // The crash watchdog above counts ANY output (the process is alive). The slow-run notice asks
678
680
  // a different question — "has the person seen anything new?" — so only `agent` events count.
679
681
  // A CLI's raw stdout/stderr (e.g. Claude Code's 30 s `tool_progress` heartbeats during a long
680
682
  // tool call) is not rendered, and letting it reset this window meant the notice could never
681
683
  // fire during exactly the long, silent tool calls it exists for.
682
- if (driverInput.event === 'agent')
683
- record.slowRunWatchdog?.noteActivity();
684
- return appendEvent(runId, record, driverInput.event, driverInput.data);
684
+ if (input.event === 'agent')
685
+ record.slowRunWatchdog?.noteActivity({});
686
+ return appendEvent(runId, record, input.event, input.data);
685
687
  },
686
- async finish(finishInput) {
688
+ async finish(requiredArgs, optionalArgs = {}) {
689
+ const finishInput = { ...requiredArgs, ...optionalArgs };
687
690
  const record = requireRun(finishInput.runId);
688
691
  await record.startPromise;
689
692
  if (record.finishPromise) {
690
693
  return record.finishPromise;
691
694
  }
692
- if (isTerminalRunState(record.status.state)) {
695
+ if (isTerminalRunState({ state: record.status.state })) {
693
696
  return toPublicStatus(record);
694
697
  }
695
698
  const finishing = (async () => {
@@ -703,8 +706,8 @@ export function createRunLifecycle(input) {
703
706
  const endEntry = await eventLog.append({ runId: finishInput.runId, event: 'end', data: endPayload });
704
707
  // Commit the in-memory terminal transition only after its durable end entry exists. Until
705
708
  // then `finishPromise` reserves the transition and blocks emits/concurrent finishes.
706
- record.watchdog?.cancel();
707
- record.slowRunWatchdog?.cancel();
709
+ record.watchdog?.cancel({});
710
+ record.slowRunWatchdog?.cancel({});
708
711
  record.status.state = finishInput.status;
709
712
  record.status.updatedAt = endEntry.recordedAt;
710
713
  record.status.endedAt = endEntry.recordedAt;
@@ -726,9 +729,9 @@ export function createRunLifecycle(input) {
726
729
  }
727
730
  }
728
731
  },
729
- async resume(runId) {
732
+ async resume({ runId }) {
730
733
  const record = requireRun(runId);
731
- const eligible = isTerminalRunState(record.status.state) && record.resumable;
734
+ const eligible = isTerminalRunState({ state: record.status.state }) && record.resumable;
732
735
  if (!eligible) {
733
736
  return { run: toPublicStatus(record), resumed: false };
734
737
  }
@@ -746,28 +749,32 @@ export function createRunLifecycle(input) {
746
749
  // never fires again on its own — the underlying timer is one-shot) left the notice permanently
747
750
  // dark for the rest of a resumed run's life, silently disabling a feature the run is otherwise
748
751
  // still eligible for.
749
- armSlowRunWatchdogIfConfigured(record, slowRunThresholdMs, () => {
750
- void handleSlowRunNotice(runId);
751
- });
752
+ armSlowRunWatchdogIfConfigured({ record: record, timeoutMs: slowRunThresholdMs, onTimeout: () => {
753
+ void handleSlowRunNotice(runId);
754
+ } });
752
755
  // No protocol event is emitted here: none of RunProtocolEvent's six
753
756
  // kinds represents "resumed" (extraction-plan scope decision — see
754
- // source-map.md). The event log's cursor sequence continues
757
+ // archived provenance ledger). The event log's cursor sequence continues
755
758
  // unbroken; only RunStatus.state changes.
756
759
  return { run: toPublicStatus(record), resumed: true };
757
760
  },
758
- suspendSlowRunNotice(runId) {
761
+ suspendSlowRunNotice({ runId }) {
759
762
  const record = runs.get(runId);
760
- if (!record || isTerminalRunState(record.status.state))
763
+ if (!record || isTerminalRunState({ state: record.status.state }))
761
764
  return;
762
- record.slowRunWatchdog?.cancel();
765
+ record.slowRunWatchdog?.cancel({});
763
766
  },
764
- resumeSlowRunNotice(runId) {
767
+ resumeSlowRunNotice({ runId }) {
765
768
  const record = runs.get(runId);
766
- if (!record || isTerminalRunState(record.status.state))
769
+ if (!record || isTerminalRunState({ state: record.status.state }))
767
770
  return;
768
- record.slowRunWatchdog?.noteActivity();
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
+ } });
769
776
  },
770
- async waitForTerminal(runId) {
777
+ async waitForTerminal({ runId }) {
771
778
  const record = requireRun(runId);
772
779
  // Awaited (and *propagated*, unlike in `get`/`list`) before any waiter is registered. A run
773
780
  // whose durable start append rejects is unwound, and nothing ever calls `finish()` for it — so
@@ -775,14 +782,14 @@ export function createRunLifecycle(input) {
775
782
  // the same error that failed the start is the only honest terminal answer, and awaiting first
776
783
  // means `terminalWaiters` can only ever hold waiters for a run that really does exist.
777
784
  await record.startPromise;
778
- if (isTerminalRunState(record.status.state)) {
785
+ if (isTerminalRunState({ state: record.status.state })) {
779
786
  return toPublicStatus(record);
780
787
  }
781
788
  return new Promise((resolve) => {
782
789
  record.terminalWaiters.push(resolve);
783
790
  });
784
791
  },
785
- async stream(runId, onEvent, options = {}) {
792
+ async stream({ runId, onEvent }, options = {}) {
786
793
  const record = runs.get(runId);
787
794
  if (!record) {
788
795
  return { kind: 'unknown-run' };
@@ -801,19 +808,19 @@ export function createRunLifecycle(input) {
801
808
  };
802
809
  record.subscribers.add(subscriber);
803
810
  try {
804
- const replay = await eventLog.replay(runId, options.afterCursor ?? null);
811
+ const replay = await eventLog.replay({ runId: runId, afterCursor: options.afterCursor ?? null });
805
812
  if (replay.kind !== 'ok') {
806
813
  record.subscribers.delete(subscriber);
807
814
  return replay;
808
815
  }
809
- const deliveredEventIds = deliverReplayedEvents(runId, replay.entries, onEvent);
810
- deliverUndeliveredEvents(bufferedLiveEvents, deliveredEventIds, onEvent);
816
+ const deliveredEventIds = deliverReplayedEvents({ runId: runId, entries: replay.entries, onEvent: onEvent });
817
+ deliverUndeliveredEvents({ events: bufferedLiveEvents, deliveredEventIds: deliveredEventIds, onEvent: onEvent });
811
818
  replaying = false;
812
- const terminal = isTerminalRunState(record.status.state);
819
+ const terminal = isTerminalRunState({ state: record.status.state });
813
820
  if (terminal && record.terminalEndEntry) {
814
- deliverUndeliveredEvents([toRunEvent(runId, record.terminalEndEntry)], deliveredEventIds, onEvent);
821
+ deliverUndeliveredEvents({ events: [toRunEvent(runId, record.terminalEndEntry)], deliveredEventIds: deliveredEventIds, onEvent: onEvent });
815
822
  }
816
- return finishStreamSubscription(record, subscriber, terminal);
823
+ return finishStreamSubscription({ record: record, subscriber: subscriber, terminal: terminal });
817
824
  }
818
825
  catch (error) {
819
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,2 @@
1
+ export {};
2
+ //# sourceMappingURL=scheduler.js.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