@stratusagent/gateway 0.14.7 → 0.14.9

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 (53) hide show
  1. package/dist/agent-source.d.ts +10 -0
  2. package/dist/agent-source.d.ts.map +1 -0
  3. package/dist/agent-source.js +7 -0
  4. package/dist/agent-source.js.map +1 -0
  5. package/dist/approvals.d.ts +38 -0
  6. package/dist/approvals.d.ts.map +1 -0
  7. package/dist/approvals.js +330 -0
  8. package/dist/approvals.js.map +1 -0
  9. package/dist/credential-requests.d.ts +24 -0
  10. package/dist/credential-requests.d.ts.map +1 -0
  11. package/dist/credential-requests.js +452 -0
  12. package/dist/credential-requests.js.map +1 -0
  13. package/dist/dispatch.d.ts +34 -0
  14. package/dist/dispatch.d.ts.map +1 -0
  15. package/dist/dispatch.js +461 -0
  16. package/dist/dispatch.js.map +1 -0
  17. package/dist/index.d.ts +4 -1290
  18. package/dist/index.d.ts.map +1 -1
  19. package/dist/index.js +202 -2546
  20. package/dist/index.js.map +1 -1
  21. package/dist/lifecycle.d.ts +130 -0
  22. package/dist/lifecycle.d.ts.map +1 -0
  23. package/dist/lifecycle.js +147 -0
  24. package/dist/lifecycle.js.map +1 -0
  25. package/dist/merge-batch.d.ts +60 -0
  26. package/dist/merge-batch.d.ts.map +1 -0
  27. package/dist/merge-batch.js +59 -0
  28. package/dist/merge-batch.js.map +1 -0
  29. package/dist/recovery.d.ts +49 -0
  30. package/dist/recovery.d.ts.map +1 -0
  31. package/dist/recovery.js +487 -0
  32. package/dist/recovery.js.map +1 -0
  33. package/dist/session-keys.d.ts +101 -0
  34. package/dist/session-keys.d.ts.map +1 -0
  35. package/dist/session-keys.js +148 -0
  36. package/dist/session-keys.js.map +1 -0
  37. package/dist/steering.d.ts +24 -0
  38. package/dist/steering.d.ts.map +1 -0
  39. package/dist/steering.js +108 -0
  40. package/dist/steering.js.map +1 -0
  41. package/dist/sub-agents.d.ts +20 -0
  42. package/dist/sub-agents.d.ts.map +1 -0
  43. package/dist/sub-agents.js +263 -0
  44. package/dist/sub-agents.js.map +1 -0
  45. package/dist/turn-dispatch.d.ts +34 -0
  46. package/dist/turn-dispatch.d.ts.map +1 -0
  47. package/dist/turn-dispatch.js +179 -0
  48. package/dist/turn-dispatch.js.map +1 -0
  49. package/dist/types.d.ts +1103 -0
  50. package/dist/types.d.ts.map +1 -0
  51. package/dist/types.js +2 -0
  52. package/dist/types.js.map +1 -0
  53. package/package.json +7 -7
@@ -0,0 +1,487 @@
1
+ /*
2
+ * Restart recovery: the turns the last process left parked on a person or
3
+ * still running, the sub-sessions it orphaned, and how a repeated
4
+ * idempotency key settles against an unfinished item or a rollover's
5
+ * archived transcript. Moved out of `createGateway` in index.ts as is: the
6
+ * statements are unchanged, inside a factory that takes what they closed
7
+ * over there, and `createGateway` calls it where they were. The edits:
8
+ * `stopping`, `abortingWith` and `orphanedAtStart` are `let`s reassigned
9
+ * later, so they are read through `isStopping()`, `getAbortingWith()` and
10
+ * `getOrphanedAtStart()`, and `onSessionChain`, declared further down, is
11
+ * passed late-bound.
12
+ */
13
+ import { completeAnsweredTurn, lastResponseIsAnswer, PENDING_APPROVAL_METADATA_KEY, readPendingApproval, recordTurnFailure, turnKeyOf, turnMessageOf, workItemState, } from '@stratusagent/core';
14
+ import { delegatingSessionIdOf, isDelegatedSession, outstandingDelegationFor, SCHEDULE_ID_METADATA_KEY, } from '@stratusagent/agents';
15
+ import { ABANDONED_TURN_ERROR, crossIdentityError, isSubAgentSession, ORPHANED_DELEGATION_ERROR, ROLLED_OVER_FROM_METADATA_KEY, ROLLOVER_REPEAT_WINDOW_MS, } from "./session-keys.js";
16
+ import { FALLBACK_ACTIVE_METADATA_KEY } from '@stratusagent/state';
17
+ export const createRecovery = ({ log, warn, bus, store, scheduler, effectiveApprovalTimeoutMs, refreshAgent, runtimeContextFor, runnerFor, withWatchdog, isStopping, trackTurn, untrackTurn, getAbortingWith, shutdownAbort, activeTurns, onTurnRuntime, getOrphanedAtStart, runtimeForAgent, watchdogStreamsFor, reachesHarness, onSessionChain, }) => {
18
+ /**
19
+ * Finishes the turns that were parked on a human when this daemon last
20
+ * went down.
21
+ *
22
+ * Run after the channels are up, deliberately: recovery re-asks, and a
23
+ * request emitted into a gateway with no live adapter would be declined
24
+ * for want of anywhere to put it — turning "your approval survived the
25
+ * restart" into "your approval was denied by the restart".
26
+ *
27
+ * Each session is recovered independently and failures are per-session:
28
+ * one unrecoverable turn must not stop the daemon from serving, or from
29
+ * recovering the others.
30
+ */
31
+ const recoverParkedTurns = async (orphaned) => {
32
+ if (!store.listIdsByStatus) {
33
+ return;
34
+ }
35
+ const parked = await store.listIdsByStatus('pending_approval');
36
+ if (parked.length === 0) {
37
+ return;
38
+ }
39
+ log(`recovering ${parked.length} turn(s) parked on approval`);
40
+ // Started together, not one after another. Each recovery re-asks and
41
+ // then waits on a human, so awaiting them in sequence would mean the
42
+ // second parked turn is not even *announced* until the first is
43
+ // answered — fifteen minutes later by default, and never at all with a
44
+ // zero timeout. They are independent turns; only same-session work is
45
+ // ordered, and onSessionChain already guarantees that.
46
+ await Promise.allSettled(parked.map((sessionId) => recoverOne(sessionId, orphaned.has(sessionId))));
47
+ };
48
+ /**
49
+ * The parked sub-sessions whose delegating turn the last process was
50
+ * still inside — read BEFORE the channels start, for the same reason the
51
+ * abandoned list is.
52
+ *
53
+ * The proof is the parent's transcript: only the runner writes an
54
+ * assistant `agent.delegate` call, and only a delegation in flight leaves
55
+ * one with no result behind it. That evidence does not survive contact
56
+ * with a channel, though — a message for the parent arriving once the
57
+ * adapters are up resumes it, and the resume closes the dangling call as
58
+ * interrupted. Read live from the sweep, the parent would then look as if
59
+ * it awaited nothing and the child would be re-asked after all, which is
60
+ * the bug this exists to stop. So it is read while nothing can dispatch,
61
+ * and the sweep judges from the snapshot; a child whose parent is missing
62
+ * or not inside such a call is left to ordinary recovery.
63
+ *
64
+ * Matched call by call, not parent by parent: the call that awaits a
65
+ * child names it (see `outstandingDelegationFor`), and a call can await
66
+ * only one. Two parked children claiming the same call — one of them a
67
+ * sub-session continued from outside under a version that kept its
68
+ * markers, parked again on something of its own — is an ambiguity this
69
+ * cannot settle, and both are left to ordinary recovery rather than one
70
+ * of them being failed for a delegation that was never its.
71
+ */
72
+ const listOrphanedDelegations = async () => {
73
+ const claimants = new Map();
74
+ if (!store.listIdsByStatus) {
75
+ return new Set();
76
+ }
77
+ for (const sessionId of await store.listIdsByStatus('pending_approval')) {
78
+ const parentId = delegatingSessionIdOf(sessionId);
79
+ if (parentId === undefined) {
80
+ continue;
81
+ }
82
+ const session = await store.get(sessionId);
83
+ if (!session || !isDelegatedSession(session)) {
84
+ continue;
85
+ }
86
+ const parent = await store.get(parentId);
87
+ const callId = parent ? outstandingDelegationFor(parent, session) : undefined;
88
+ if (callId !== undefined) {
89
+ const key = `${parentId}\u0000${callId}`;
90
+ claimants.set(key, [...(claimants.get(key) ?? []), sessionId]);
91
+ }
92
+ }
93
+ return new Set([...claimants.values()].filter((ids) => ids.length === 1).map(([id]) => id));
94
+ };
95
+ const recoverOne = (sessionId, orphaned) => onSessionChain(sessionId, () => recoverParked(sessionId, orphaned));
96
+ /**
97
+ * `recoverOne`'s work, for a caller already on the session's chain: a
98
+ * repeated idempotency key that reached the chain before the parked sweep
99
+ * did, and would otherwise wait on a turn nothing is running yet.
100
+ */
101
+ const recoverParked = async (sessionId, orphaned) => {
102
+ if (isStopping()) {
103
+ return;
104
+ }
105
+ try {
106
+ const session = await store.get(sessionId);
107
+ if (!session) {
108
+ return;
109
+ }
110
+ const record = readPendingApproval(session);
111
+ // A delegated sub-session is only ever awaited by its parent's
112
+ // `agent.delegate` call, and that parent was running when the last
113
+ // process died — it is in the abandoned sweep, not in this one.
114
+ // Nothing will read the reply, so the parked call is not re-asked:
115
+ // a person would be approving a command that runs for no one.
116
+ //
117
+ // Judged on the re-read, on the chain: the channels are up before
118
+ // this sweep starts, so a message for this id can have taken the
119
+ // chain first and moved the session on — and a turn that finished
120
+ // normally must not be rewritten as failed. The delegation metadata
121
+ // is durable, so it proves what the session is, not what state it
122
+ // is in; the status and the checkpoint say that.
123
+ //
124
+ // Whether it IS an orphan was decided before the channels started
125
+ // (`listOrphanedDelegations`), from the parent's transcript, which
126
+ // a message arriving since may already have rewritten.
127
+ // A sub-agent never parks (nobody is asked for it), and if one ever
128
+ // did, its parent's turn died with the last process: nothing would
129
+ // read its answer, and recovering it would run it as the full agent.
130
+ if (isSubAgentSession(session) && session.status === 'pending_approval') {
131
+ log(`${sessionId}: a sub-agent parked when the last stratusd stopped; failing it`);
132
+ await failParked(session, ABANDONED_TURN_ERROR);
133
+ return;
134
+ }
135
+ if (orphaned && isDelegatedSession(session) && session.status === 'pending_approval' && record) {
136
+ log(`${sessionId}: parked on ${record.call.toolName} for a delegating turn the last stratusd was still running; failing it`);
137
+ await failOrphanedDelegation(session);
138
+ return;
139
+ }
140
+ // A wait that outlived its window while the daemon was down is
141
+ // honoured, not restarted: the request really did go unanswered for
142
+ // the whole time it was configured to wait, and downtime is not a
143
+ // reason to extend a security decision. Measured from when the turn
144
+ // parked against this daemon's timeout — the transport's original
145
+ // deadline was chosen after the checkpoint was written and is gone
146
+ // with the process that chose it. Denying goes through the same
147
+ // recovery path, so the queue behind it still drains.
148
+ const parkedAt = record ? Date.parse(record.parkedAt) : Number.NaN;
149
+ const expired = effectiveApprovalTimeoutMs > 0
150
+ && Number.isFinite(parkedAt)
151
+ && Date.now() - parkedAt >= effectiveApprovalTimeoutMs;
152
+ if (expired) {
153
+ log(`${sessionId}: the approval for ${record?.call.toolName} outlived its window while the daemon was down; denying it`);
154
+ }
155
+ // Resolved the way a dispatch for this agent would resolve it, so a
156
+ // recovered turn finishes on the same provider, model, and
157
+ // credentials it was parked on.
158
+ const source = await refreshAgent(session.agent.id);
159
+ // And on the CURRENT definition, saved before recovery reads the
160
+ // session back. An allowlist is a permission boundary: a soul that
161
+ // dropped a tool while the daemon was down must not have that tool
162
+ // executed by the turn that outlived the change, and
163
+ // `allowedToolsFor` reads the definition frozen into the session
164
+ // ahead of the registry. Dispatch refreshes it the same way before
165
+ // resuming.
166
+ session.agent = source.definition;
167
+ await store.save(session);
168
+ const recoveredConfig = await runtimeForAgent(source);
169
+ const runner = runnerFor(recoveredConfig, source.definition.id);
170
+ // Tracked like a dispatched turn's controller: a recovered turn that
171
+ // runs on past its approval is a turn like any other, so a restart's
172
+ // window has to be able to cut it short with the same reason (or it
173
+ // runs unseen to the second window and the restart reports
174
+ // undrained), and a stop for the session has to find it. Only once
175
+ // the checkpoint has been read on this chain, right before the runner
176
+ // takes it: a stop counted then always ends in the turn's own
177
+ // outcome. A stop sent during the reads above finds nothing running
178
+ // and reaches the session as an ordinary message, queued behind it.
179
+ const controller = new AbortController();
180
+ if (record) {
181
+ trackTurn(sessionId, controller);
182
+ if (getAbortingWith() !== undefined) {
183
+ controller.abort(shutdownAbort());
184
+ }
185
+ }
186
+ try {
187
+ await onTurnRuntime(sessionId, { source, config: recoveredConfig }, () => runner.recoverPendingApproval(sessionId, {
188
+ denyPending: expired,
189
+ runtime: runtimeContextFor(source, recoveredConfig, session.metadata?.[FALLBACK_ACTIVE_METADATA_KEY] === true, session.metadata),
190
+ signal: controller.signal,
191
+ }));
192
+ }
193
+ finally {
194
+ untrackTurn(sessionId, controller);
195
+ // A recovered firing's row outlived the process that would have
196
+ // retired it, and the firing is over either way — a recovery that
197
+ // failed has durably failed its session, which is as finished as
198
+ // completing. The scheduler decides whether there is a row to
199
+ // retire, and only this firing's own.
200
+ const scheduleId = session.metadata?.[SCHEDULE_ID_METADATA_KEY];
201
+ if (typeof scheduleId === 'string') {
202
+ scheduler.retireSpentOneShot(scheduleId, sessionId);
203
+ }
204
+ }
205
+ }
206
+ catch (error) {
207
+ warn(`could not recover parked session ${sessionId}: ${error instanceof Error ? error.message : String(error)}`);
208
+ // A keyed item is a promise to its caller, and one whose recovery
209
+ // cannot even start (its agent gone, its provider unresolvable) would
210
+ // otherwise stay parked for every later start and redelivery to try
211
+ // again. An unkeyed one keeps waiting for a sweep that can, as before.
212
+ const after = await store.get(sessionId).catch(() => undefined);
213
+ const key = after === undefined ? undefined : turnKeyOf(turnMessageOf(after));
214
+ if (after !== undefined && key !== undefined && after.status === 'pending_approval' && workItemState(after, key) === 'unfinished') {
215
+ await failParked(after, ABANDONED_TURN_ERROR).catch(() => { });
216
+ }
217
+ }
218
+ };
219
+ /**
220
+ * Closes an orphaned sub-session the way the abandoned sweep closes its
221
+ * parent: durably failed, with the checkpoint retired so no later sweep
222
+ * can find a call to re-enter, and announced where surfaces can hear it.
223
+ */
224
+ const failOrphanedDelegation = (session) => failParked(session, ORPHANED_DELEGATION_ERROR);
225
+ const failParked = async (session, error) => {
226
+ const metadata = { ...(session.metadata ?? {}) };
227
+ delete metadata[PENDING_APPROVAL_METADATA_KEY];
228
+ session.metadata = metadata;
229
+ recordTurnFailure(session, error);
230
+ await store.save(session);
231
+ await bus.emit({ type: 'session.updated', sessionId: session.id, status: 'failed' });
232
+ await bus.emit({ type: 'session.failed', sessionId: session.id, error });
233
+ };
234
+ /**
235
+ * Turns the last process was still running when it stopped being able to
236
+ * finish them.
237
+ *
238
+ * `pending_approval` is the one window a turn can be picked up from, and
239
+ * it has its own sweep above. Everything else a turn does is saved as
240
+ * `running`: the provider call, the executor, and — on a provider that
241
+ * hosts its own loop — the approval wait too, since a hosted call is
242
+ * deliberately not checkpointed (`executeHostedToolCall` passes
243
+ * `recoverable: false`, because the SDK's inner loop is not something a
244
+ * restart can rebuild). None of those can be re-entered where they
245
+ * stopped, and nothing else sweeps them, so the record would go on
246
+ * claiming the turn is running for as long as the session exists. A turn
247
+ * whose caller gave it an idempotency key is continued from its
248
+ * transcript instead, with its dangling calls closed as interrupted
249
+ * (`settleAbandonedTurn`); the rest are failed.
250
+ *
251
+ * Note what this is *not* for. A graceful stop denies everything parked
252
+ * and drains the turns those denials release, so an operator restarting
253
+ * the daemon normally has nothing here. These are the turns a crash, a
254
+ * SIGKILL, or a lost machine left behind.
255
+ */
256
+ const listAbandonedTurns = async () => {
257
+ if (!store.listIdsByStatus) {
258
+ return [];
259
+ }
260
+ return store.listIdsByStatus('running');
261
+ };
262
+ /**
263
+ * Settles each of them — continued if keyed, otherwise marked failed with
264
+ * a reason that says what happened.
265
+ *
266
+ * Read BEFORE the channels start and applied after, which is the only
267
+ * ordering that is both honest and safe. Reading first means the list is
268
+ * exactly what the last process left, with no chance of a turn this
269
+ * process started being in it. Applying after means the failure is
270
+ * emitted into a gateway whose surfaces are listening, rather than into
271
+ * one where nothing is.
272
+ *
273
+ * The gap between the two is real, though: an inbound message for one of
274
+ * these sessions can arrive as soon as the channels are up. What makes
275
+ * that safe is single-flight — every writer of a session goes through
276
+ * `onSessionChain`, so this runs either wholly before that turn (it
277
+ * fails, and `resume()` then clears `lastError` and starts fresh) or
278
+ * wholly after it (the status has moved off `running`, and the re-read
279
+ * below leaves it alone). There is no interleaving in which this
280
+ * overwrites a live turn.
281
+ *
282
+ * What that invariant does NOT cover is a second daemon on the same
283
+ * database, whose turns are running with no chain of ours in front of
284
+ * them. Two processes sharing a session store is already unsupported —
285
+ * the parked sweep above would re-ask that daemon's approvals for the
286
+ * same reason — and no check here would make it supported.
287
+ */
288
+ const failAbandonedTurns = async (abandoned) => {
289
+ if (abandoned.length === 0) {
290
+ return;
291
+ }
292
+ log(`settling ${abandoned.length} turn(s) left running by the last stratusd`);
293
+ await Promise.allSettled(abandoned.map((id) => failAbandonedTurn(id)));
294
+ };
295
+ const failAbandonedTurn = (id) => onSessionChain(id, async () => {
296
+ if (isStopping()) {
297
+ return;
298
+ }
299
+ try {
300
+ const session = await store.get(id);
301
+ // Re-read on the chain, because the snapshot is from before the
302
+ // channels came up: a message that arrived since has already taken
303
+ // this session somewhere else, and that turn owns it now.
304
+ if (!session || session.status !== 'running') {
305
+ return;
306
+ }
307
+ await settleAbandonedTurn(session);
308
+ }
309
+ catch (error) {
310
+ warn(`could not settle abandoned turn ${id}: ${error instanceof Error ? error.message : String(error)}`);
311
+ }
312
+ });
313
+ /**
314
+ * A turn left `running` with nothing running it, read on its chain.
315
+ *
316
+ * One whose message carries an idempotency key is a work item its caller
317
+ * was promised would finish, so it is continued from the transcript
318
+ * (`AgentRunner.continueTurn`) — once. A turn that already was, and is
319
+ * here again, took the last process down with it, and continuing it again
320
+ * would do the same on every start; it is failed like an unkeyed one,
321
+ * whose caller was never promised anything and is told to send again.
322
+ */
323
+ const settleAbandonedTurn = async (session) => {
324
+ const message = turnMessageOf(session);
325
+ if (message === undefined || turnKeyOf(message) === undefined) {
326
+ return failAbandoned(session);
327
+ }
328
+ // An answer already in the transcript is completed whatever came before
329
+ // — a second crash, a harness, an agent or provider gone since —
330
+ // because completing it needs none of them and runs nothing again;
331
+ // failing it would throw away a finished reply.
332
+ if (lastResponseIsAnswer(session)) {
333
+ log(`${session.id}: a keyed turn the last stratusd was running had saved its answer; completing it`);
334
+ return completeAnsweredTurn(session, store, bus);
335
+ }
336
+ if (message.continuedAfterCrash === true) {
337
+ return failAbandoned(session);
338
+ }
339
+ if (message.hostedLoop === true) {
340
+ log(`${session.id}: a keyed turn the last stratusd was running could reach a harness, which cannot be continued; failing it`);
341
+ return failAbandoned(session);
342
+ }
343
+ return continueAbandonedTurn(session);
344
+ };
345
+ const failAbandoned = async (session) => {
346
+ recordTurnFailure(session, ABANDONED_TURN_ERROR);
347
+ await store.save(session);
348
+ await bus.emit({ type: 'session.updated', sessionId: session.id, status: 'failed' });
349
+ await bus.emit({ type: 'session.failed', sessionId: session.id, error: ABANDONED_TURN_ERROR });
350
+ return session;
351
+ };
352
+ /**
353
+ * Continues an abandoned turn on the configuration a dispatch for its
354
+ * agent would resolve now, and under the same watchdog — it is a provider
355
+ * call like any turn's. The definition is refreshed and saved first, as
356
+ * `recoverOne` does and for the same reason: an allowlist is a permission
357
+ * boundary, and a tool dropped while the daemon was down must not run.
358
+ *
359
+ * Not on a harness (`runsOnHarness`) — neither one the turn could have
360
+ * run on (`Message.hostedLoop`, recorded when it was accepted) nor one it
361
+ * would continue on. A harness runs its own tool loop inside a single
362
+ * provider call and keeps its own conversation, so the prompt the dead
363
+ * turn sent may already have run tools there that the transcript never
364
+ * saw, and continuing would send it again. That turn is failed as an
365
+ * unkeyed one is — the same answer as before keys existed. (A turn whose
366
+ * answer is already saved never gets here: `settleAbandonedTurn`
367
+ * completes it first.)
368
+ */
369
+ const continueAbandonedTurn = async (session) => {
370
+ // Its parent's turn died with the last process, and continuing it here
371
+ // would run it as the full agent. (Sub-agent turns carry no key today,
372
+ // so this is a guard, not a path.)
373
+ if (isSubAgentSession(session)) {
374
+ return failAbandoned(session);
375
+ }
376
+ // Settled either way: a turn whose agent left the roster, or whose
377
+ // provider no longer resolves, must not stay `running` for every later
378
+ // start and every redelivery to find unfinished and trip over again.
379
+ let source;
380
+ let config;
381
+ try {
382
+ source = await refreshAgent(session.agent.id);
383
+ config = await runtimeForAgent(source);
384
+ }
385
+ catch (error) {
386
+ log(`${session.id}: a keyed turn the last stratusd was running cannot be continued (${error instanceof Error ? error.message : String(error)}); failing it`);
387
+ return failAbandoned(session);
388
+ }
389
+ const { switchedToFallback, effectiveStreams, fallbackStreams } = watchdogStreamsFor(session, config);
390
+ // The message's own record covers the runtimes the turn could have run
391
+ // on; this covers the ones it would continue on — a fallback included —
392
+ // which a changed config may make a harness.
393
+ if (reachesHarness(config, switchedToFallback)) {
394
+ log(`${session.id}: a keyed turn the last stratusd was running would continue on a harness; failing it`);
395
+ return failAbandoned(session);
396
+ }
397
+ log(`${session.id}: continuing a keyed turn the last stratusd was still running`);
398
+ session.agent = source.definition;
399
+ await store.save(session);
400
+ const runner = runnerFor(config, source.definition.id);
401
+ return onTurnRuntime(session.id, { source, config }, () => withWatchdog(session.id, undefined, effectiveStreams, fallbackStreams, async (signal) => await runner.continueTurn(session.id, {
402
+ runtime: runtimeContextFor(source, config, switchedToFallback, session.metadata),
403
+ signal,
404
+ }) ?? session));
405
+ };
406
+ /**
407
+ * A repeated idempotency key that reached the session's chain, so nothing
408
+ * else is running on the session. `undefined` means the key is new here
409
+ * and the dispatch runs as any other; otherwise this is the session to
410
+ * resolve with, and no new turn starts.
411
+ *
412
+ * An unfinished item here has no live turn — the live map would have
413
+ * answered first — so it is one the last process left, reached before
414
+ * the start-up sweep that would have settled it: settled now, the way
415
+ * that sweep would, which then finds it no longer needs anything.
416
+ */
417
+ const settleRepeat = async (sessionId, idempotencyKey, agentId, turnId, onRepeat) => {
418
+ const session = await store.get(sessionId);
419
+ if (session === undefined) {
420
+ return undefined;
421
+ }
422
+ // Before anything is returned: the turn a dispatch would run is refused
423
+ // for naming another agent, and the one it would repeat is no less that
424
+ // agent's — a repeat is not a way to read its transcript.
425
+ if (agentId !== undefined && session.agent.id !== agentId) {
426
+ throw crossIdentityError(sessionId, session.agent.id, agentId);
427
+ }
428
+ const state = workItemState(session, idempotencyKey);
429
+ if (state === undefined) {
430
+ const archived = await archivedRepeat(session, idempotencyKey);
431
+ if (archived !== undefined) {
432
+ onRepeat?.('finished');
433
+ }
434
+ return archived;
435
+ }
436
+ if (state === 'finished') {
437
+ onRepeat?.('finished');
438
+ return session;
439
+ }
440
+ // Run now, on this caller's behalf, so its events are this caller's
441
+ // turn — the adapter waiting on them has no other way to claim them.
442
+ if (turnId !== undefined) {
443
+ activeTurns.set(sessionId, turnId);
444
+ }
445
+ try {
446
+ return await settleUnfinished(session);
447
+ }
448
+ finally {
449
+ if (turnId !== undefined) {
450
+ activeTurns.delete(sessionId);
451
+ }
452
+ }
453
+ };
454
+ /**
455
+ * The archived transcript holding a key the live row does not, when the
456
+ * row is a rollover's fresh one made inside `ROLLOVER_REPEAT_WINDOW_MS`.
457
+ * Only the one rollover back: a second inside the window is a redelivery
458
+ * outliving two deliberate resets, and the chain is not walked for it.
459
+ * Always finished — a rollover refuses a session with a turn in flight.
460
+ */
461
+ const archivedRepeat = async (session, idempotencyKey) => {
462
+ const archivedAs = session.metadata?.[ROLLED_OVER_FROM_METADATA_KEY];
463
+ if (typeof archivedAs !== 'string' || Date.now() - Date.parse(session.createdAt) > ROLLOVER_REPEAT_WINDOW_MS) {
464
+ return undefined;
465
+ }
466
+ const archived = await store.get(archivedAs);
467
+ return archived !== undefined && workItemState(archived, idempotencyKey) !== undefined ? archived : undefined;
468
+ };
469
+ /** An unfinished item with no live turn, settled the way the start-up sweeps would. */
470
+ const settleUnfinished = async (session) => {
471
+ if (session.status === 'pending_approval') {
472
+ await recoverParked(session.id, getOrphanedAtStart().has(session.id));
473
+ return (await store.get(session.id)) ?? session;
474
+ }
475
+ return settleAbandonedTurn(session);
476
+ };
477
+ return {
478
+ recoverParkedTurns,
479
+ listOrphanedDelegations,
480
+ listAbandonedTurns,
481
+ failAbandonedTurns,
482
+ settleRepeat,
483
+ archivedRepeat,
484
+ settleUnfinished,
485
+ };
486
+ };
487
+ //# sourceMappingURL=recovery.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"recovery.js","sourceRoot":"","sources":["../src/recovery.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,OAAO,EAGL,oBAAoB,EAGpB,oBAAoB,EACpB,6BAA6B,EAC7B,mBAAmB,EACnB,iBAAiB,EAGjB,SAAS,EACT,aAAa,EACb,aAAa,GACd,MAAM,oBAAoB,CAAC;AAC5B,OAAO,EACL,qBAAqB,EACrB,kBAAkB,EAClB,wBAAwB,EACxB,wBAAwB,GACzB,MAAM,sBAAsB,CAAC;AAK9B,OAAO,EACL,oBAAoB,EACpB,kBAAkB,EAClB,iBAAiB,EACjB,yBAAyB,EACzB,6BAA6B,EAC7B,yBAAyB,GAC1B,MAAM,mBAAmB,CAAC;AAC3B,OAAO,EAAE,4BAA4B,EAAsB,MAAM,qBAAqB,CAAC;AA+CvF,MAAM,CAAC,MAAM,cAAc,GAAG,CAAC,EAC7B,GAAG,EACH,IAAI,EACJ,GAAG,EACH,KAAK,EACL,SAAS,EACT,0BAA0B,EAC1B,YAAY,EACZ,iBAAiB,EACjB,SAAS,EACT,YAAY,EACZ,UAAU,EACV,SAAS,EACT,WAAW,EACX,eAAe,EACf,aAAa,EACb,WAAW,EACX,aAAa,EACb,kBAAkB,EAClB,eAAe,EACf,kBAAkB,EAClB,cAAc,EACd,cAAc,GACD,EAAE,EAAE;IACjB;;;;;;;;;;;;OAYG;IACH,MAAM,kBAAkB,GAAG,KAAK,EAAE,QAA6B,EAAiB,EAAE;QAChF,IAAI,CAAC,KAAK,CAAC,eAAe,EAAE,CAAC;YAC3B,OAAO;QACT,CAAC;QACD,MAAM,MAAM,GAAG,MAAM,KAAK,CAAC,eAAe,CAAC,kBAAkB,CAAC,CAAC;QAC/D,IAAI,MAAM,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;YACxB,OAAO;QACT,CAAC;QACD,GAAG,CAAC,cAAc,MAAM,CAAC,MAAM,6BAA6B,CAAC,CAAC;QAE9D,qEAAqE;QACrE,qEAAqE;QACrE,gEAAgE;QAChE,uEAAuE;QACvE,sEAAsE;QACtE,uDAAuD;QACvD,MAAM,OAAO,CAAC,UAAU,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,SAAS,EAAE,EAAE,CAAC,UAAU,CAAC,SAAS,EAAE,QAAQ,CAAC,GAAG,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,CAAC;IACtG,CAAC,CAAC;IAEF;;;;;;;;;;;;;;;;;;;;;;;OAuBG;IACH,MAAM,uBAAuB,GAAG,KAAK,IAA0B,EAAE;QAC/D,MAAM,SAAS,GAAG,IAAI,GAAG,EAAoB,CAAC;QAC9C,IAAI,CAAC,KAAK,CAAC,eAAe,EAAE,CAAC;YAC3B,OAAO,IAAI,GAAG,EAAE,CAAC;QACnB,CAAC;QACD,KAAK,MAAM,SAAS,IAAI,MAAM,KAAK,CAAC,eAAe,CAAC,kBAAkB,CAAC,EAAE,CAAC;YACxE,MAAM,QAAQ,GAAG,qBAAqB,CAAC,SAAS,CAAC,CAAC;YAClD,IAAI,QAAQ,KAAK,SAAS,EAAE,CAAC;gBAC3B,SAAS;YACX,CAAC;YACD,MAAM,OAAO,GAAG,MAAM,KAAK,CAAC,GAAG,CAAC,SAAS,CAAC,CAAC;YAC3C,IAAI,CAAC,OAAO,IAAI,CAAC,kBAAkB,CAAC,OAAO,CAAC,EAAE,CAAC;gBAC7C,SAAS;YACX,CAAC;YACD,MAAM,MAAM,GAAG,MAAM,KAAK,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC;YACzC,MAAM,MAAM,GAAG,MAAM,CAAC,CAAC,CAAC,wBAAwB,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC;YAC9E,IAAI,MAAM,KAAK,SAAS,EAAE,CAAC;gBACzB,MAAM,GAAG,GAAG,GAAG,QAAQ,SAAS,MAAM,EAAE,CAAC;gBACzC,SAAS,CAAC,GAAG,CAAC,GAAG,EAAE,CAAC,GAAG,CAAC,SAAS,CAAC,GAAG,CAAC,GAAG,CAAC,IAAI,EAAE,CAAC,EAAE,SAAS,CAAC,CAAC,CAAC;YACjE,CAAC;QACH,CAAC;QACD,OAAO,IAAI,GAAG,CAAC,CAAC,GAAG,SAAS,CAAC,MAAM,EAAE,CAAC,CAAC,MAAM,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,GAAG,CAAC,MAAM,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,EAAG,CAAC,CAAC,CAAC;IAC/F,CAAC,CAAC;IAEF,MAAM,UAAU,GAAG,CAAC,SAAiB,EAAE,QAAiB,EAAiB,EAAE,CACzE,cAAc,CAAC,SAAS,EAAE,GAAG,EAAE,CAAC,aAAa,CAAC,SAAS,EAAE,QAAQ,CAAC,CAAC,CAAC;IAEtE;;;;OAIG;IACH,MAAM,aAAa,GAAG,KAAK,EAAE,SAAiB,EAAE,QAAiB,EAAiB,EAAE;QAClF,IAAI,UAAU,EAAE,EAAE,CAAC;YACjB,OAAO;QACT,CAAC;QACD,IAAI,CAAC;YACH,MAAM,OAAO,GAAG,MAAM,KAAK,CAAC,GAAG,CAAC,SAAS,CAAC,CAAC;YAC3C,IAAI,CAAC,OAAO,EAAE,CAAC;gBACb,OAAO;YACT,CAAC;YACD,MAAM,MAAM,GAAG,mBAAmB,CAAC,OAAO,CAAC,CAAC;YAC5C,+DAA+D;YAC/D,mEAAmE;YACnE,gEAAgE;YAChE,mEAAmE;YACnE,8DAA8D;YAC9D,EAAE;YACF,kEAAkE;YAClE,iEAAiE;YACjE,kEAAkE;YAClE,oEAAoE;YACpE,kEAAkE;YAClE,iDAAiD;YACjD,EAAE;YACF,kEAAkE;YAClE,mEAAmE;YACnE,uDAAuD;YACvD,oEAAoE;YACpE,mEAAmE;YACnE,qEAAqE;YACrE,IAAI,iBAAiB,CAAC,OAAO,CAAC,IAAI,OAAO,CAAC,MAAM,KAAK,kBAAkB,EAAE,CAAC;gBACxE,GAAG,CAAC,GAAG,SAAS,iEAAiE,CAAC,CAAC;gBACnF,MAAM,UAAU,CAAC,OAAO,EAAE,oBAAoB,CAAC,CAAC;gBAChD,OAAO;YACT,CAAC;YACD,IAAI,QAAQ,IAAI,kBAAkB,CAAC,OAAO,CAAC,IAAI,OAAO,CAAC,MAAM,KAAK,kBAAkB,IAAI,MAAM,EAAE,CAAC;gBAC/F,GAAG,CAAC,GAAG,SAAS,eAAe,MAAM,CAAC,IAAI,CAAC,QAAQ,wEAAwE,CAAC,CAAC;gBAC7H,MAAM,sBAAsB,CAAC,OAAO,CAAC,CAAC;gBACtC,OAAO;YACT,CAAC;YACD,+DAA+D;YAC/D,oEAAoE;YACpE,kEAAkE;YAClE,oEAAoE;YACpE,kEAAkE;YAClE,mEAAmE;YACnE,gEAAgE;YAChE,sDAAsD;YACtD,MAAM,QAAQ,GAAG,MAAM,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC;YACnE,MAAM,OAAO,GAAG,0BAA0B,GAAG,CAAC;mBACzC,MAAM,CAAC,QAAQ,CAAC,QAAQ,CAAC;mBACzB,IAAI,CAAC,GAAG,EAAE,GAAG,QAAQ,IAAI,0BAA0B,CAAC;YACzD,IAAI,OAAO,EAAE,CAAC;gBACZ,GAAG,CAAC,GAAG,SAAS,sBAAsB,MAAM,EAAE,IAAI,CAAC,QAAQ,4DAA4D,CAAC,CAAC;YAC3H,CAAC;YAED,oEAAoE;YACpE,2DAA2D;YAC3D,gCAAgC;YAChC,MAAM,MAAM,GAAG,MAAM,YAAY,CAAC,OAAO,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC;YAEpD,iEAAiE;YACjE,mEAAmE;YACnE,mEAAmE;YACnE,qDAAqD;YACrD,iEAAiE;YACjE,mEAAmE;YACnE,YAAY;YACZ,OAAO,CAAC,KAAK,GAAG,MAAM,CAAC,UAAU,CAAC;YAClC,MAAM,KAAK,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;YAE1B,MAAM,eAAe,GAAG,MAAM,eAAe,CAAC,MAAM,CAAC,CAAC;YACtD,MAAM,MAAM,GAAG,SAAS,CAAC,eAAe,EAAE,MAAM,CAAC,UAAU,CAAC,EAAE,CAAC,CAAC;YAChE,qEAAqE;YACrE,qEAAqE;YACrE,oEAAoE;YACpE,2DAA2D;YAC3D,mEAAmE;YACnE,sEAAsE;YACtE,8DAA8D;YAC9D,oEAAoE;YACpE,oEAAoE;YACpE,MAAM,UAAU,GAAG,IAAI,eAAe,EAAE,CAAC;YACzC,IAAI,MAAM,EAAE,CAAC;gBACX,SAAS,CAAC,SAAS,EAAE,UAAU,CAAC,CAAC;gBACjC,IAAI,eAAe,EAAE,KAAK,SAAS,EAAE,CAAC;oBACpC,UAAU,CAAC,KAAK,CAAC,aAAa,EAAE,CAAC,CAAC;gBACpC,CAAC;YACH,CAAC;YACD,IAAI,CAAC;gBACH,MAAM,aAAa,CAAC,SAAS,EAAE,EAAE,MAAM,EAAE,MAAM,EAAE,eAAe,EAAE,EAAE,GAAG,EAAE,CAAC,MAAM,CAAC,sBAAsB,CAAC,SAAS,EAAE;oBACjH,WAAW,EAAE,OAAO;oBACpB,OAAO,EAAE,iBAAiB,CAAC,MAAM,EAAE,eAAe,EAAE,OAAO,CAAC,QAAQ,EAAE,CAAC,4BAA4B,CAAC,KAAK,IAAI,EAAE,OAAO,CAAC,QAAQ,CAAC;oBAChI,MAAM,EAAE,UAAU,CAAC,MAAM;iBAC1B,CAAC,CAAC,CAAC;YACN,CAAC;oBAAS,CAAC;gBACT,WAAW,CAAC,SAAS,EAAE,UAAU,CAAC,CAAC;gBACnC,gEAAgE;gBAChE,kEAAkE;gBAClE,iEAAiE;gBACjE,8DAA8D;gBAC9D,sCAAsC;gBACtC,MAAM,UAAU,GAAG,OAAO,CAAC,QAAQ,EAAE,CAAC,wBAAwB,CAAC,CAAC;gBAChE,IAAI,OAAO,UAAU,KAAK,QAAQ,EAAE,CAAC;oBACnC,SAAS,CAAC,kBAAkB,CAAC,UAAU,EAAE,SAAS,CAAC,CAAC;gBACtD,CAAC;YACH,CAAC;QACH,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,IAAI,CAAC,oCAAoC,SAAS,KAAK,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC;YACjH,kEAAkE;YAClE,sEAAsE;YACtE,oEAAoE;YACpE,uEAAuE;YACvE,MAAM,KAAK,GAAG,MAAM,KAAK,CAAC,GAAG,CAAC,SAAS,CAAC,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,SAAS,CAAC,CAAC;YAChE,MAAM,GAAG,GAAG,KAAK,KAAK,SAAS,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,SAAS,CAAC,aAAa,CAAC,KAAK,CAAC,CAAC,CAAC;YAC9E,IAAI,KAAK,KAAK,SAAS,IAAI,GAAG,KAAK,SAAS,IAAI,KAAK,CAAC,MAAM,KAAK,kBAAkB,IAAI,aAAa,CAAC,KAAK,EAAE,GAAG,CAAC,KAAK,YAAY,EAAE,CAAC;gBAClI,MAAM,UAAU,CAAC,KAAK,EAAE,oBAAoB,CAAC,CAAC,KAAK,CAAC,GAAG,EAAE,GAAE,CAAC,CAAC,CAAC;YAChE,CAAC;QACH,CAAC;IACH,CAAC,CAAC;IAEF;;;;OAIG;IACH,MAAM,sBAAsB,GAAG,CAAC,OAAgB,EAAiB,EAAE,CAAC,UAAU,CAAC,OAAO,EAAE,yBAAyB,CAAC,CAAC;IAEnH,MAAM,UAAU,GAAG,KAAK,EAAE,OAAgB,EAAE,KAAa,EAAiB,EAAE;QAC1E,MAAM,QAAQ,GAAG,EAAE,GAAG,CAAC,OAAO,CAAC,QAAQ,IAAI,EAAE,CAAC,EAAE,CAAC;QACjD,OAAO,QAAQ,CAAC,6BAA6B,CAAC,CAAC;QAC/C,OAAO,CAAC,QAAQ,GAAG,QAAQ,CAAC;QAC5B,iBAAiB,CAAC,OAAO,EAAE,KAAK,CAAC,CAAC;QAClC,MAAM,KAAK,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;QAC1B,MAAM,GAAG,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,iBAAiB,EAAE,SAAS,EAAE,OAAO,CAAC,EAAE,EAAE,MAAM,EAAE,QAAQ,EAAE,CAAC,CAAC;QACrF,MAAM,GAAG,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,gBAAgB,EAAE,SAAS,EAAE,OAAO,CAAC,EAAE,EAAE,KAAK,EAAE,CAAC,CAAC;IAC3E,CAAC,CAAC;IAEF;;;;;;;;;;;;;;;;;;;;;OAqBG;IACH,MAAM,kBAAkB,GAAG,KAAK,IAAuB,EAAE;QACvD,IAAI,CAAC,KAAK,CAAC,eAAe,EAAE,CAAC;YAC3B,OAAO,EAAE,CAAC;QACZ,CAAC;QACD,OAAO,KAAK,CAAC,eAAe,CAAC,SAAS,CAAC,CAAC;IAC1C,CAAC,CAAC;IAEF;;;;;;;;;;;;;;;;;;;;;;;;;OAyBG;IACH,MAAM,kBAAkB,GAAG,KAAK,EAAE,SAAmB,EAAiB,EAAE;QACtE,IAAI,SAAS,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;YAC3B,OAAO;QACT,CAAC;QACD,GAAG,CAAC,YAAY,SAAS,CAAC,MAAM,4CAA4C,CAAC,CAAC;QAC9E,MAAM,OAAO,CAAC,UAAU,CAAC,SAAS,CAAC,GAAG,CAAC,CAAC,EAAE,EAAE,EAAE,CAAC,iBAAiB,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC;IACzE,CAAC,CAAC;IAEF,MAAM,iBAAiB,GAAG,CAAC,EAAU,EAAiB,EAAE,CACtD,cAAc,CAAC,EAAE,EAAE,KAAK,IAAI,EAAE;QAC5B,IAAI,UAAU,EAAE,EAAE,CAAC;YACjB,OAAO;QACT,CAAC;QACD,IAAI,CAAC;YACH,MAAM,OAAO,GAAG,MAAM,KAAK,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;YACpC,gEAAgE;YAChE,mEAAmE;YACnE,0DAA0D;YAC1D,IAAI,CAAC,OAAO,IAAI,OAAO,CAAC,MAAM,KAAK,SAAS,EAAE,CAAC;gBAC7C,OAAO;YACT,CAAC;YACD,MAAM,mBAAmB,CAAC,OAAO,CAAC,CAAC;QACrC,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,IAAI,CAAC,mCAAmC,EAAE,KAAK,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC;QAC3G,CAAC;IACH,CAAC,CAAC,CAAC;IAEL;;;;;;;;;OASG;IACH,MAAM,mBAAmB,GAAG,KAAK,EAAE,OAAgB,EAAoB,EAAE;QACvE,MAAM,OAAO,GAAG,aAAa,CAAC,OAAO,CAAC,CAAC;QACvC,IAAI,OAAO,KAAK,SAAS,IAAI,SAAS,CAAC,OAAO,CAAC,KAAK,SAAS,EAAE,CAAC;YAC9D,OAAO,aAAa,CAAC,OAAO,CAAC,CAAC;QAChC,CAAC;QACD,wEAAwE;QACxE,iEAAiE;QACjE,mEAAmE;QACnE,gDAAgD;QAChD,IAAI,oBAAoB,CAAC,OAAO,CAAC,EAAE,CAAC;YAClC,GAAG,CAAC,GAAG,OAAO,CAAC,EAAE,kFAAkF,CAAC,CAAC;YACrG,OAAO,oBAAoB,CAAC,OAAO,EAAE,KAAK,EAAE,GAAG,CAAC,CAAC;QACnD,CAAC;QACD,IAAI,OAAO,CAAC,mBAAmB,KAAK,IAAI,EAAE,CAAC;YACzC,OAAO,aAAa,CAAC,OAAO,CAAC,CAAC;QAChC,CAAC;QACD,IAAI,OAAO,CAAC,UAAU,KAAK,IAAI,EAAE,CAAC;YAChC,GAAG,CAAC,GAAG,OAAO,CAAC,EAAE,2GAA2G,CAAC,CAAC;YAC9H,OAAO,aAAa,CAAC,OAAO,CAAC,CAAC;QAChC,CAAC;QACD,OAAO,qBAAqB,CAAC,OAAO,CAAC,CAAC;IACxC,CAAC,CAAC;IAEF,MAAM,aAAa,GAAG,KAAK,EAAE,OAAgB,EAAoB,EAAE;QACjE,iBAAiB,CAAC,OAAO,EAAE,oBAAoB,CAAC,CAAC;QACjD,MAAM,KAAK,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;QAC1B,MAAM,GAAG,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,iBAAiB,EAAE,SAAS,EAAE,OAAO,CAAC,EAAE,EAAE,MAAM,EAAE,QAAQ,EAAE,CAAC,CAAC;QACrF,MAAM,GAAG,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,gBAAgB,EAAE,SAAS,EAAE,OAAO,CAAC,EAAE,EAAE,KAAK,EAAE,oBAAoB,EAAE,CAAC,CAAC;QAC/F,OAAO,OAAO,CAAC;IACjB,CAAC,CAAC;IAEF;;;;;;;;;;;;;;;;OAgBG;IACH,MAAM,qBAAqB,GAAG,KAAK,EAAE,OAAgB,EAAoB,EAAE;QACzE,uEAAuE;QACvE,uEAAuE;QACvE,mCAAmC;QACnC,IAAI,iBAAiB,CAAC,OAAO,CAAC,EAAE,CAAC;YAC/B,OAAO,aAAa,CAAC,OAAO,CAAC,CAAC;QAChC,CAAC;QACD,mEAAmE;QACnE,uEAAuE;QACvE,qEAAqE;QACrE,IAAI,MAAmB,CAAC;QACxB,IAAI,MAAqB,CAAC;QAC1B,IAAI,CAAC;YACH,MAAM,GAAG,MAAM,YAAY,CAAC,OAAO,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC;YAC9C,MAAM,GAAG,MAAM,eAAe,CAAC,MAAM,CAAC,CAAC;QACzC,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,GAAG,CAAC,GAAG,OAAO,CAAC,EAAE,qEAAqE,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,eAAe,CAAC,CAAC;YAC7J,OAAO,aAAa,CAAC,OAAO,CAAC,CAAC;QAChC,CAAC;QACD,MAAM,EAAE,kBAAkB,EAAE,gBAAgB,EAAE,eAAe,EAAE,GAAG,kBAAkB,CAAC,OAAO,EAAE,MAAM,CAAC,CAAC;QACtG,uEAAuE;QACvE,wEAAwE;QACxE,6CAA6C;QAC7C,IAAI,cAAc,CAAC,MAAM,EAAE,kBAAkB,CAAC,EAAE,CAAC;YAC/C,GAAG,CAAC,GAAG,OAAO,CAAC,EAAE,sFAAsF,CAAC,CAAC;YACzG,OAAO,aAAa,CAAC,OAAO,CAAC,CAAC;QAChC,CAAC;QACD,GAAG,CAAC,GAAG,OAAO,CAAC,EAAE,+DAA+D,CAAC,CAAC;QAClF,OAAO,CAAC,KAAK,GAAG,MAAM,CAAC,UAAU,CAAC;QAClC,MAAM,KAAK,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;QAC1B,MAAM,MAAM,GAAG,SAAS,CAAC,MAAM,EAAE,MAAM,CAAC,UAAU,CAAC,EAAE,CAAC,CAAC;QACvD,OAAO,aAAa,CAAC,OAAO,CAAC,EAAE,EAAE,EAAE,MAAM,EAAE,MAAM,EAAE,EAAE,GAAG,EAAE,CAAC,YAAY,CAAC,OAAO,CAAC,EAAE,EAAE,SAAS,EAAE,gBAAgB,EAAE,eAAe,EAAE,KAAK,EAAE,MAAM,EAAE,EAAE,CACjJ,MAAM,MAAM,CAAC,YAAY,CAAC,OAAO,CAAC,EAAE,EAAE;YACpC,OAAO,EAAE,iBAAiB,CAAC,MAAM,EAAE,MAAM,EAAE,kBAAkB,EAAE,OAAO,CAAC,QAAQ,CAAC;YAChF,MAAM;SACP,CAAC,IAAI,OAAO,CAAC,CAAC,CAAC;IACpB,CAAC,CAAC;IAEF;;;;;;;;;;OAUG;IACH,MAAM,YAAY,GAAG,KAAK,EACxB,SAAiB,EACjB,cAAsB,EACtB,OAA2B,EAC3B,MAA0B,EAC1B,QAAmC,EACL,EAAE;QAChC,MAAM,OAAO,GAAG,MAAM,KAAK,CAAC,GAAG,CAAC,SAAS,CAAC,CAAC;QAC3C,IAAI,OAAO,KAAK,SAAS,EAAE,CAAC;YAC1B,OAAO,SAAS,CAAC;QACnB,CAAC;QACD,wEAAwE;QACxE,wEAAwE;QACxE,0DAA0D;QAC1D,IAAI,OAAO,KAAK,SAAS,IAAI,OAAO,CAAC,KAAK,CAAC,EAAE,KAAK,OAAO,EAAE,CAAC;YAC1D,MAAM,kBAAkB,CAAC,SAAS,EAAE,OAAO,CAAC,KAAK,CAAC,EAAE,EAAE,OAAO,CAAC,CAAC;QACjE,CAAC;QACD,MAAM,KAAK,GAAG,aAAa,CAAC,OAAO,EAAE,cAAc,CAAC,CAAC;QACrD,IAAI,KAAK,KAAK,SAAS,EAAE,CAAC;YACxB,MAAM,QAAQ,GAAG,MAAM,cAAc,CAAC,OAAO,EAAE,cAAc,CAAC,CAAC;YAC/D,IAAI,QAAQ,KAAK,SAAS,EAAE,CAAC;gBAC3B,QAAQ,EAAE,CAAC,UAAU,CAAC,CAAC;YACzB,CAAC;YACD,OAAO,QAAQ,CAAC;QAClB,CAAC;QACD,IAAI,KAAK,KAAK,UAAU,EAAE,CAAC;YACzB,QAAQ,EAAE,CAAC,UAAU,CAAC,CAAC;YACvB,OAAO,OAAO,CAAC;QACjB,CAAC;QACD,oEAAoE;QACpE,qEAAqE;QACrE,IAAI,MAAM,KAAK,SAAS,EAAE,CAAC;YACzB,WAAW,CAAC,GAAG,CAAC,SAAS,EAAE,MAAM,CAAC,CAAC;QACrC,CAAC;QACD,IAAI,CAAC;YACH,OAAO,MAAM,gBAAgB,CAAC,OAAO,CAAC,CAAC;QACzC,CAAC;gBAAS,CAAC;YACT,IAAI,MAAM,KAAK,SAAS,EAAE,CAAC;gBACzB,WAAW,CAAC,MAAM,CAAC,SAAS,CAAC,CAAC;YAChC,CAAC;QACH,CAAC;IACH,CAAC,CAAC;IAEF;;;;;;OAMG;IACH,MAAM,cAAc,GAAG,KAAK,EAAE,OAAgB,EAAE,cAAsB,EAAgC,EAAE;QACtG,MAAM,UAAU,GAAG,OAAO,CAAC,QAAQ,EAAE,CAAC,6BAA6B,CAAC,CAAC;QACrE,IAAI,OAAO,UAAU,KAAK,QAAQ,IAAI,IAAI,CAAC,GAAG,EAAE,GAAG,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,SAAS,CAAC,GAAG,yBAAyB,EAAE,CAAC;YAC7G,OAAO,SAAS,CAAC;QACnB,CAAC;QACD,MAAM,QAAQ,GAAG,MAAM,KAAK,CAAC,GAAG,CAAC,UAAU,CAAC,CAAC;QAC7C,OAAO,QAAQ,KAAK,SAAS,IAAI,aAAa,CAAC,QAAQ,EAAE,cAAc,CAAC,KAAK,SAAS,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,SAAS,CAAC;IAChH,CAAC,CAAC;IAEF,uFAAuF;IACvF,MAAM,gBAAgB,GAAG,KAAK,EAAE,OAAgB,EAAoB,EAAE;QACpE,IAAI,OAAO,CAAC,MAAM,KAAK,kBAAkB,EAAE,CAAC;YAC1C,MAAM,aAAa,CAAC,OAAO,CAAC,EAAE,EAAE,kBAAkB,EAAE,CAAC,GAAG,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC,CAAC;YACtE,OAAO,CAAC,MAAM,KAAK,CAAC,GAAG,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC,IAAI,OAAO,CAAC;QAClD,CAAC;QACD,OAAO,mBAAmB,CAAC,OAAO,CAAC,CAAC;IACtC,CAAC,CAAC;IAEF,OAAO;QACL,kBAAkB;QAClB,uBAAuB;QACvB,kBAAkB;QAClB,kBAAkB;QAClB,YAAY;QACZ,cAAc;QACd,gBAAgB;KACjB,CAAC;AACJ,CAAC,CAAC"}
@@ -0,0 +1,101 @@
1
+ import { type JsonObject, type Session } from '@stratusagent/core';
2
+ /**
3
+ * Recorded on a turn the previous process was still running when it died.
4
+ *
5
+ * Exported because "distinguishable" is the whole point: a surface, a
6
+ * test, or an operator reading `lastError` has to be able to tell a turn
7
+ * nobody finished apart from one a provider or a tool actually failed, and
8
+ * matching on prose that lives in one place beats each caller inventing
9
+ * its own guess at the wording.
10
+ */
11
+ export declare const ABANDONED_TURN_ERROR = "stratusd stopped while this turn was still running; it was not resumed. Send the message again.";
12
+ /**
13
+ * The longest `DispatchInput.idempotencyKey` accepted. A platform message id
14
+ * is far shorter; the bound is so a key cannot be used to grow every row it
15
+ * is stored on.
16
+ */
17
+ /**
18
+ * Recorded on a delegated sub-session that was parked on a human when the
19
+ * previous process died.
20
+ *
21
+ * Its parent — the turn whose `agent.delegate` call was awaiting the reply
22
+ * — was running, so the restart settles it as abandoned (above). Recovering
23
+ * the child anyway would re-ask a person to approve a call whose result no
24
+ * turn will ever read, and then run it: a command executed, tokens spent,
25
+ * and a reply delivered to nobody. Distinguishable from the parent's error
26
+ * because the remedy differs — there is no message to send again here;
27
+ * the parent's is the one to repeat.
28
+ */
29
+ /**
30
+ * Session metadata keys the daemon writes for itself, which no external
31
+ * dispatch may supply.
32
+ *
33
+ * Metadata is one bag: the keys a channel attaches to say where a turn is
34
+ * happening sit beside the ones the kernel, the scheduler, the fallback
35
+ * wrapper, and `agent.delegate` write to remember what a session *is* —
36
+ * and the control API forwards a caller's `metadata` into a new session as
37
+ * given. A caller who can write `delegatedBy` can have the restart sweep
38
+ * fail its own parked turn as an orphan; one who can write `fallbackActive`
39
+ * bills its conversation to the fallback model from the first turn. The
40
+ * scheduler already refused to trust its own keys for exactly this reason
41
+ * (see `activeFirings`); the durable ones cannot be re-derived in memory,
42
+ * so they are refused at the door instead.
43
+ */
44
+ /**
45
+ * A rolled-over session: the fresh row records where its predecessor's
46
+ * transcript went, and the archived row records which live id continues
47
+ * the conversation. See `Gateway.rolloverSession`.
48
+ */
49
+ export declare const ROLLED_OVER_FROM_METADATA_KEY = "rolledOverFrom";
50
+ export declare const ROLLED_OVER_TO_METADATA_KEY = "rolledOverTo";
51
+ /** The segment a rollover mints into the archived transcript's id. */
52
+ export declare const ROLLED_OVER_SESSION_ID_MARKER = ":rolledover:";
53
+ /**
54
+ * How long after a rollover a repeated key is still looked for in the
55
+ * archived transcript. A rollover moves every keyed message out of the
56
+ * live row, so a redelivery arriving after one would otherwise read as a
57
+ * new message and run again. Platforms redeliver within minutes — Slack
58
+ * gives up after about five — and past the window the archive is not
59
+ * loaded at all, since every new message to the fresh row would pay for a
60
+ * read that can no longer find anything.
61
+ */
62
+ export declare const ROLLOVER_REPEAT_WINDOW_MS: number;
63
+ /**
64
+ * The one refusal for a dispatch naming an agent other than its session's.
65
+ * Its own type so a repeat attached to a claimant can tell the claimant was
66
+ * refused for who it named — the item then still has no owner — from the
67
+ * claimant's turn failing. Internal: callers read the message.
68
+ */
69
+ export declare class CrossIdentityError extends Error {
70
+ constructor(sessionId: string, owner: string, claimed: string);
71
+ }
72
+ export declare const crossIdentityError: (sessionId: string, owner: string, claimed: string) => Error;
73
+ /** Where the session's tool calls ran: `local-command`, or a contributed executor's registered name. */
74
+ export declare const EXECUTOR_METADATA_KEY = "executor";
75
+ /**
76
+ * Marks a sub-agent's session (roadmap 24). Reserved: it is what makes the
77
+ * daemon deny the session's gated calls without asking anyone, so a caller
78
+ * that could set it could also clear it.
79
+ */
80
+ export declare const SUB_AGENT_METADATA_KEY = "subAgent";
81
+ export declare const SUB_AGENT_SESSION_ID_MARKER = ":subagent:";
82
+ /** How many sub-agents one parent turn runs at once unless the host says otherwise. */
83
+ export declare const DEFAULT_SUB_AGENTS_PER_TURN = 4;
84
+ export declare const isSubAgentSession: (session: Pick<Session, "id" | "metadata">) => boolean;
85
+ /**
86
+ * Tools a sub-agent never gets, whatever its parent holds. `agent.spawn`
87
+ * would fan out inside a fan-out; `agent.delegate` runs a roster agent
88
+ * under that agent's own allowlist, which is the way out of a narrowed
89
+ * slice; `memory.*` would let it persist what nobody reviewed; and
90
+ * `credential.request` asks a person, which a sub-agent never does.
91
+ */
92
+ export declare const subAgentNeverGets: (name: string) => boolean;
93
+ export declare const RESERVED_SESSION_METADATA_KEYS: readonly string[];
94
+ /**
95
+ * The first reserved key `metadata` carries, or undefined when it carries
96
+ * none. Exported so the control API can refuse the request where the
97
+ * caller can see it, with the same rule the gateway applies at dispatch.
98
+ */
99
+ export declare const reservedSessionMetadataKey: (metadata: JsonObject | undefined) => string | undefined;
100
+ export declare const ORPHANED_DELEGATION_ERROR = "stratusd stopped while the turn that delegated this one was still running; the delegation was not resumed. Send the original message again.";
101
+ //# sourceMappingURL=session-keys.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"session-keys.d.ts","sourceRoot":"","sources":["../src/session-keys.ts"],"names":[],"mappings":"AAOA,OAAO,EAGL,KAAK,UAAU,EAEf,KAAK,OAAO,EAGb,MAAM,oBAAoB,CAAC;AAY5B;;;;;;;;GAQG;AACH,eAAO,MAAM,oBAAoB,oGACkE,CAAC;AAEpG;;;;GAIG;AAEH;;;;;;;;;;;GAWG;AACH;;;;;;;;;;;;;;GAcG;AACH;;;;GAIG;AACH,eAAO,MAAM,6BAA6B,mBAAmB,CAAC;AAC9D,eAAO,MAAM,2BAA2B,iBAAiB,CAAC;AAC1D,sEAAsE;AACtE,eAAO,MAAM,6BAA6B,iBAAiB,CAAC;AAE5D;;;;;;;;GAQG;AACH,eAAO,MAAM,yBAAyB,QAAiB,CAAC;AAExD;;;;;GAKG;AACH,qBAAa,kBAAmB,SAAQ,KAAK;gBAC/B,SAAS,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM;CAI9D;AAED,eAAO,MAAM,kBAAkB,GAAI,WAAW,MAAM,EAAE,OAAO,MAAM,EAAE,SAAS,MAAM,KAAG,KACpC,CAAC;AAEpD,wGAAwG;AACxG,eAAO,MAAM,qBAAqB,aAAa,CAAC;AAEhD;;;;GAIG;AACH,eAAO,MAAM,sBAAsB,aAAa,CAAC;AACjD,eAAO,MAAM,2BAA2B,eAAe,CAAC;AACxD,uFAAuF;AACvF,eAAO,MAAM,2BAA2B,IAAI,CAAC;AAE7C,eAAO,MAAM,iBAAiB,GAAI,SAAS,IAAI,CAAC,OAAO,EAAE,IAAI,GAAG,UAAU,CAAC,KAAG,OAC2B,CAAC;AAE1G;;;;;;GAMG;AACH,eAAO,MAAM,iBAAiB,GAAI,MAAM,MAAM,KAAG,OAIP,CAAC;AAE3C,eAAO,MAAM,8BAA8B,EAAE,SAAS,MAAM,EA8B3D,CAAC;AAEF;;;;GAIG;AACH,eAAO,MAAM,0BAA0B,GAAI,UAAU,UAAU,GAAG,SAAS,KAAG,MAAM,GAAG,SAGgB,CAAC;AAExG,eAAO,MAAM,yBAAyB,gJACyG,CAAC"}