stitchkit 0.64.0 → 0.65.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.
@@ -28,8 +28,9 @@ import {
28
28
  AgentToolCallPartSchema,
29
29
  AgentToolResultPartSchema,
30
30
  AgentUsageSchema,
31
- AgentUsageValueSchema
32
- } from "./index-b1k33127.js";
31
+ AgentUsageValueSchema,
32
+ runStateForTerminalReason
33
+ } from "./index-sbkyvacf.js";
33
34
  import"./index-6djpbnda.js";
34
35
  import"./index-cby4ar3v.js";
35
36
  import {
@@ -395,7 +396,7 @@ function advanceAgentRuntimeEventCursor(rawCursor, event) {
395
396
  return { status: "duplicate", cursor };
396
397
  }
397
398
  return {
398
- status: previous !== undefined && event.snapshotVersion > previous + 1 ? "gap" : "accepted",
399
+ status: "accepted",
399
400
  cursor: {
400
401
  ...cursor,
401
402
  snapshotVersion: event.snapshotVersion,
@@ -561,13 +562,7 @@ function interruptedSystemNote(message) {
561
562
  return { rendered };
562
563
  rendered.add("text");
563
564
  rendered.add("control");
564
- return {
565
- message: modelMessageSchema.parse({
566
- role: "system",
567
- content: `[interrupted] partial response: ${text}`
568
- }),
569
- rendered
570
- };
565
+ return { text: `[interrupted] partial response: ${text}`, rendered };
571
566
  }
572
567
  function completeToolChronology(message) {
573
568
  const calls = new Set(message.parts.filter((part) => part.type === "tool-call").map((part) => part.callId));
@@ -576,12 +571,13 @@ function completeToolChronology(message) {
576
571
  }
577
572
  async function projectAgentHistoryDetailed(messages, options = {}) {
578
573
  const projected = [];
574
+ const system = [];
579
575
  const decisions = [];
580
576
  const interruptedRule = options.interruptedAssistant ?? "assistant-marked";
581
577
  let observedUser = false;
582
578
  for (const message of messages) {
583
- if (message.status === "superseded") {
584
- decisions.push(decide(message, "omitted", "superseded"));
579
+ if (message.role === "assistant" && !isSpeakableAssistantStatus(message.status)) {
580
+ decisions.push(decide(message, "omitted", message.status === "superseded" ? "superseded" : "draft-or-failed"));
585
581
  continue;
586
582
  }
587
583
  if (message.status === "streaming" || message.status === "failed") {
@@ -602,7 +598,7 @@ async function projectAgentHistoryDetailed(messages, options = {}) {
602
598
  if (message.role === "system" || message.role === "summary") {
603
599
  const content = textContent(message.parts);
604
600
  if (content) {
605
- projected.push(modelMessageSchema.parse({ role: "system", content }));
601
+ system.push(content);
606
602
  decisions.push(decide(message, "projected", "projected", new Set(["text"])));
607
603
  } else {
608
604
  decisions.push(decide(message, "omitted", "empty"));
@@ -623,8 +619,8 @@ async function projectAgentHistoryDetailed(messages, options = {}) {
623
619
  }
624
620
  if (interrupted && interruptedRule === "system-note") {
625
621
  const note = interruptedSystemNote(message);
626
- if (note.message) {
627
- projected.push(note.message);
622
+ if (note.text) {
623
+ system.push(note.text);
628
624
  decisions.push(decide(message, "projected", "projected", note.rendered));
629
625
  } else {
630
626
  decisions.push(decide(message, "omitted", "empty"));
@@ -642,7 +638,7 @@ async function projectAgentHistoryDetailed(messages, options = {}) {
642
638
  projected.push(...assistant.messages);
643
639
  decisions.push(assistant.messages.length > 0 ? decide(message, "projected", "projected", assistant.rendered) : decide(message, "omitted", "empty"));
644
640
  }
645
- return { messages: projected, decisions };
641
+ return { messages: projected, decisions, system };
646
642
  }
647
643
  async function projectAgentHistory(messages, options = {}) {
648
644
  return [...(await projectAgentHistoryDetailed(messages, options)).messages];
@@ -882,7 +878,7 @@ async function selectAgentHistory(options) {
882
878
  if (!Number.isSafeInteger(keepRecentTurns) || keepRecentTurns < 0) {
883
879
  throw new TypeError("keepRecentTurns must be a non-negative safe integer");
884
880
  }
885
- const spoken = options.messages.filter((message) => message.status !== "superseded");
881
+ const spoken = options.messages.filter((message) => message.role !== "assistant" || isSpeakableAssistantStatus(message.status));
886
882
  const counts = new Map;
887
883
  let total = 0;
888
884
  let estimated = false;
@@ -895,8 +891,8 @@ async function selectAgentHistory(options) {
895
891
  messages: [...spoken],
896
892
  decisions: options.messages.map((candidate) => ({
897
893
  messageId: candidate.id,
898
- action: candidate.status === "superseded" ? "removed" : "kept",
899
- reason: candidate.status === "superseded" ? "superseded" : "token-count-unavailable",
894
+ action: candidate.role === "assistant" && !isSpeakableAssistantStatus(candidate.status) ? "removed" : "kept",
895
+ reason: candidate.role === "assistant" && !isSpeakableAssistantStatus(candidate.status) ? "unspeakable" : "token-count-unavailable",
900
896
  tokens: counts.get(candidate.id) ?? { provenance: "unavailable" }
901
897
  })),
902
898
  totalTokens: { provenance: "unavailable" },
@@ -922,11 +918,11 @@ async function selectAgentHistory(options) {
922
918
  }
923
919
  const messages = spoken.filter((message) => !removed.has(message.id));
924
920
  const decisions = options.messages.map((message) => {
925
- if (message.status === "superseded") {
921
+ if (message.role === "assistant" && !isSpeakableAssistantStatus(message.status)) {
926
922
  return {
927
923
  messageId: message.id,
928
924
  action: "removed",
929
- reason: "superseded",
925
+ reason: "unspeakable",
930
926
  tokens: { provenance: "unavailable" }
931
927
  };
932
928
  }
@@ -1126,6 +1122,7 @@ function interruptedCandidate(candidate, run, now) {
1126
1122
  AgentMessagePartSchema.parse({ type: "control", reason: "run-interrupted" })
1127
1123
  ];
1128
1124
  return {
1125
+ ...candidate.usage && { usage: candidate.usage },
1129
1126
  run,
1130
1127
  assistant: AgentMessageSchema.parse({
1131
1128
  ...candidate.assistant,
@@ -1394,6 +1391,7 @@ function createRunExecutor(dependencies) {
1394
1391
  let terminalReason = "success";
1395
1392
  let usage = input.acceptedRun.usage;
1396
1393
  let sawProviderFinish = false;
1394
+ let contextRefusal = false;
1397
1395
  let step = 0;
1398
1396
  let selectedModel;
1399
1397
  let internalCause;
@@ -1510,56 +1508,38 @@ function createRunExecutor(dependencies) {
1510
1508
  config.tools(runtimeContext)
1511
1509
  ]);
1512
1510
  if (prompt.contextDecision === "oversized") {
1511
+ contextRefusal = true;
1513
1512
  throw new Error("Agent context exceeds the configured model budget");
1514
1513
  }
1515
1514
  if (prompt.contextDecision === "requires-compaction") {
1515
+ contextRefusal = true;
1516
1516
  throw new Error("Agent context still exceeds the model budget after compaction");
1517
1517
  }
1518
- const projectHistory = (source) => config.history?.project ? config.history.project(source.messages) : projectAgentHistory(source.messages, {
1519
- ...config.history?.resolveFile && { resolveFile: config.history.resolveFile },
1520
- ...config.history?.unresolvedFile && {
1521
- unresolvedFile: config.history.unresolvedFile
1522
- },
1523
- ...config.history?.interruptedAssistant && {
1524
- interruptedAssistant: config.history.interruptedAssistant
1525
- }
1526
- });
1527
- const history = await projectHistory(snapshot);
1528
- const absorbPending = async () => {
1529
- if (!input.absorbable?.size || executionSignal.aborted)
1530
- return;
1531
- const latest = await config.store.loadSnapshot(run.conversationId);
1532
- const current = latest.runs.find((candidate) => candidate.id === run.id);
1533
- if (current?.state !== "running" || current.ownerId !== runtimeEpoch) {
1534
- return;
1535
- }
1536
- const pending = latest.runs.find((candidate) => candidate.state === "queued" && input.absorbable?.has(candidate.id));
1537
- if (!pending)
1538
- return;
1539
- const absorbed = await config.store.absorbQueuedRun({
1540
- conversationId: run.conversationId,
1541
- runningRunId: current.id,
1542
- runningExpectedRevision: current.revision,
1543
- ownerId: runtimeEpoch,
1544
- ...current.fencingToken !== undefined && { fencingToken: current.fencingToken },
1545
- queuedRunId: pending.id,
1546
- queuedExpectedRevision: pending.revision
1547
- });
1548
- if (absorbed.outcome !== "applied")
1549
- return;
1550
- input.onAbsorbed?.(pending.id);
1551
- snapshot = absorbed.snapshot;
1552
- run = findRun(snapshot.runs, run.id);
1553
- await publish({
1554
- type: "run-state",
1555
- eventId: agentDurableEventId("run-state", run.id, snapshot.version),
1556
- conversationId: run.conversationId,
1557
- runId: run.id,
1558
- snapshotVersion: snapshot.version,
1559
- state: run.state,
1560
- emittedAt: now().toISOString()
1518
+ let carriedSystem = [];
1519
+ const projectHistory = async (source) => {
1520
+ if (config.history?.project)
1521
+ return config.history.project(source.messages);
1522
+ const detailed = await projectAgentHistoryDetailed(source.messages, {
1523
+ ...config.history?.resolveFile && { resolveFile: config.history.resolveFile },
1524
+ ...config.history?.unresolvedFile && {
1525
+ unresolvedFile: config.history.unresolvedFile
1526
+ },
1527
+ ...config.history?.interruptedAssistant && {
1528
+ interruptedAssistant: config.history.interruptedAssistant
1529
+ }
1561
1530
  });
1562
- return [...await projectHistory(snapshot)];
1531
+ carriedSystem = detailed.system;
1532
+ return [...detailed.messages];
1533
+ };
1534
+ const history = await projectHistory(snapshot);
1535
+ const withCarriedSystem = (instructions) => {
1536
+ if (carriedSystem.length === 0)
1537
+ return instructions;
1538
+ const composed = typeof instructions === "string" ? [{ role: "system", content: instructions }] : Array.isArray(instructions) ? instructions : [instructions];
1539
+ return [
1540
+ ...composed,
1541
+ ...carriedSystem.map((content) => ({ role: "system", content }))
1542
+ ];
1563
1543
  };
1564
1544
  const maxStepCondition = stepCountIs(maxSteps);
1565
1545
  const stopConditions = [
@@ -1581,15 +1561,13 @@ function createRunExecutor(dependencies) {
1581
1561
  const result = streamText({
1582
1562
  model: selectedModel.model,
1583
1563
  tools,
1584
- instructions: prompt.instructions,
1564
+ instructions: withCarriedSystem(prompt.instructions),
1585
1565
  messages: history,
1586
1566
  abortSignal: executionSignal,
1587
1567
  maxRetries: 0,
1588
1568
  stopWhen: stopConditions,
1589
- prepareStep: async (options) => {
1590
- const absorbed = await absorbPending();
1591
- const prepared = await config.loop?.prepareStep?.({ ...options, ...runtimeContext }) ?? {};
1592
- return absorbed && !prepared.messages ? { ...prepared, messages: absorbed } : prepared;
1569
+ ...config.loop?.prepareStep && {
1570
+ prepareStep: (options) => config.loop?.prepareStep?.({ ...options, ...runtimeContext })
1593
1571
  }
1594
1572
  });
1595
1573
  for await (const part of result.stream) {
@@ -1854,7 +1832,7 @@ function createRunExecutor(dependencies) {
1854
1832
  reason: isToolExecutionControlError(error) && error.reason === "stale_run" ? "stale-run" : "run-interrupted"
1855
1833
  }));
1856
1834
  } else {
1857
- terminalReason = "provider_failure";
1835
+ terminalReason = contextRefusal ? "context_overflow" : "provider_failure";
1858
1836
  }
1859
1837
  } finally {
1860
1838
  idleDeadline.dispose();
@@ -1963,7 +1941,8 @@ function createAgentRuntime(config) {
1963
1941
  const waitForAdmissionAcceptances = admissionLanes.waitForAcceptances;
1964
1942
  const checkpointEveryEvents = config.loop?.checkpointEveryEvents ?? 20;
1965
1943
  const maxSteps = config.loop?.maxSteps ?? 50;
1966
- const idleTimeoutMs = config.loop?.idleTimeoutMs;
1944
+ const declaredIdleTimeoutMs = config.loop?.idleTimeoutMs;
1945
+ const idleTimeoutMs = declaredIdleTimeoutMs === null ? undefined : declaredIdleTimeoutMs ?? 60000;
1967
1946
  if (!Number.isSafeInteger(checkpointEveryEvents) || checkpointEveryEvents < 1) {
1968
1947
  throw new TypeError("checkpointEveryEvents must be a positive safe integer");
1969
1948
  }
@@ -1973,9 +1952,6 @@ function createAgentRuntime(config) {
1973
1952
  if (idleTimeoutMs !== undefined && (!Number.isSafeInteger(idleTimeoutMs) || idleTimeoutMs < 1)) {
1974
1953
  throw new TypeError("idleTimeoutMs must be a positive safe integer");
1975
1954
  }
1976
- const absorbable = new Set;
1977
- const absorbedInto = new Map;
1978
- const runResults = new Map;
1979
1955
  const policyNames = new Set(["max-steps"]);
1980
1956
  for (const policy of config.loop?.stopPolicies ?? []) {
1981
1957
  if (!policy.name || policyNames.has(policy.name)) {
@@ -2108,8 +2084,7 @@ function createAgentRuntime(config) {
2108
2084
  if (existingTicket)
2109
2085
  return existingTicket;
2110
2086
  const key = config.runs?.key?.(input) ?? input.conversationId;
2111
- const declaredPolicy = typeof config.runs?.inputPolicy === "function" ? config.runs.inputPolicy(input) : config.runs?.inputPolicy ?? "queue";
2112
- const policy = declaredPolicy === "inject" ? "queue" : declaredPolicy;
2087
+ const policy = typeof config.runs?.inputPolicy === "function" ? config.runs.inputPolicy(input) : config.runs?.inputPolicy ?? "queue";
2113
2088
  const nowIso = now().toISOString();
2114
2089
  const inputMessageId = rawInput.recordIds?.inputMessageId ?? generateId();
2115
2090
  const runId = rawInput.recordIds?.runId ?? generateId();
@@ -2235,8 +2210,6 @@ function createAgentRuntime(config) {
2235
2210
  state: acceptedRun.state,
2236
2211
  emittedAt: now().toISOString()
2237
2212
  });
2238
- if (declaredPolicy === "inject")
2239
- absorbable.add(acceptedRun.id);
2240
2213
  outerAccepted.resolve();
2241
2214
  if (acceptance.outcome === "duplicate") {
2242
2215
  if (!acceptedRun.terminalReason) {
@@ -2285,25 +2258,7 @@ function createAgentRuntime(config) {
2285
2258
  await waitForAdmissionAcceptances(reservation.lane);
2286
2259
  return {
2287
2260
  runId: acceptedRun.id,
2288
- execute: () => {
2289
- const answeredBy = absorbedInto.get(acceptedRun.id);
2290
- const answer = answeredBy ? runResults.get(answeredBy) : undefined;
2291
- if (answer)
2292
- return answer;
2293
- const running = executeRun({
2294
- acceptedRun,
2295
- context,
2296
- signal,
2297
- absorbable,
2298
- onAbsorbed: (absorbedRunId) => {
2299
- absorbedInto.set(absorbedRunId, acceptedRun.id);
2300
- }
2301
- });
2302
- runResults.set(acceptedRun.id, running);
2303
- return running.finally(() => {
2304
- absorbable.delete(acceptedRun.id);
2305
- });
2306
- }
2261
+ execute: () => executeRun({ acceptedRun, context, signal })
2307
2262
  };
2308
2263
  }
2309
2264
  });
@@ -2550,15 +2505,6 @@ var CommitRunTerminalSchema = z6.object({
2550
2505
  policyName: z6.string().min(1).optional(),
2551
2506
  usage: AgentUsageSchema.optional()
2552
2507
  });
2553
- var AbsorbQueuedRunSchema = z6.object({
2554
- conversationId: AgentRecordIdSchema,
2555
- runningRunId: AgentRecordIdSchema,
2556
- runningExpectedRevision: AgentRecordVersionSchema,
2557
- ownerId: z6.string().min(1),
2558
- fencingToken: AgentRecordVersionSchema.optional(),
2559
- queuedRunId: AgentRecordIdSchema,
2560
- queuedExpectedRevision: AgentRecordVersionSchema
2561
- });
2562
2508
  var RequestRunInterruptSchema = z6.object({
2563
2509
  conversationId: AgentRecordIdSchema,
2564
2510
  runId: AgentRecordIdSchema,
@@ -2699,24 +2645,17 @@ function replaceRun(runs, next) {
2699
2645
  function replaceMessage(messages, next) {
2700
2646
  return messages.some((message) => message.id === next.id) ? messages.map((message) => message.id === next.id ? next : message) : [...messages, next];
2701
2647
  }
2648
+ var ACTIVE_AGENT_RUN_STATES = [
2649
+ "queued",
2650
+ "running",
2651
+ "interrupt_requested"
2652
+ ];
2653
+ function isActiveRunState(state) {
2654
+ return ACTIVE_AGENT_RUN_STATES.includes(state);
2655
+ }
2702
2656
  function conflict(actualVersion) {
2703
2657
  return { outcome: "conflict", actualVersion };
2704
2658
  }
2705
- function terminalState(reason) {
2706
- if (reason === "success" || reason === "policy_stop" || reason === "provider_stop") {
2707
- return "completed";
2708
- }
2709
- if (reason === "interrupted")
2710
- return "interrupted";
2711
- if (reason === "superseded")
2712
- return "superseded";
2713
- if (reason === "cancelled" || reason === "shutdown" || reason === "timeout") {
2714
- return "cancelled";
2715
- }
2716
- if (reason === "abandoned")
2717
- return "abandoned";
2718
- return "failed";
2719
- }
2720
2659
  function applied(current, input, effects) {
2721
2660
  return {
2722
2661
  outcome: "applied",
@@ -2727,7 +2666,6 @@ function applied(current, input, effects) {
2727
2666
  messages: input.messages ?? current.messages
2728
2667
  }),
2729
2668
  ...effects?.runRecord && { runRecord: effects.runRecord },
2730
- ...effects?.secondaryRunRecord && { secondaryRunRecord: effects.secondaryRunRecord },
2731
2669
  ...effects?.admissionReceipt && { admissionReceipt: effects.admissionReceipt },
2732
2670
  ...effects?.historyMutation && { historyMutation: effects.historyMutation }
2733
2671
  };
@@ -2771,39 +2709,6 @@ function reduceStore(current, operation) {
2771
2709
  historyMutation: { type: "admit", input: input.input }
2772
2710
  });
2773
2711
  }
2774
- if (operation.type === "absorb") {
2775
- const input = operation.input;
2776
- if (current.conversationId !== input.conversationId)
2777
- return { outcome: "not_found" };
2778
- const running = current.runs.find((candidate) => candidate.id === input.runningRunId);
2779
- const queued = current.runs.find((candidate) => candidate.id === input.queuedRunId);
2780
- if (!running || !queued)
2781
- return { outcome: "not_found" };
2782
- if (running.revision !== input.runningExpectedRevision || running.state !== "running" || running.ownerId !== input.ownerId || input.fencingToken !== undefined && running.fencingToken !== input.fencingToken) {
2783
- return conflict(running.revision);
2784
- }
2785
- if (queued.revision !== input.queuedExpectedRevision || queued.state !== "queued" || queued.ownerId !== undefined || queued.terminalReason !== undefined || queued.id === running.id) {
2786
- return conflict(queued.revision);
2787
- }
2788
- const stamp = new Date().toISOString();
2789
- const grown = AgentRunSchema.parse({
2790
- ...running,
2791
- inputMessageIds: [...running.inputMessageIds, ...queued.inputMessageIds],
2792
- revision: running.revision + 1,
2793
- updatedAt: stamp
2794
- });
2795
- const emptied = AgentRunSchema.parse({
2796
- ...queued,
2797
- state: "absorbed",
2798
- absorbedIntoRunId: running.id,
2799
- revision: queued.revision + 1,
2800
- updatedAt: stamp
2801
- });
2802
- return applied(current, { runs: replaceRun(replaceRun(current.runs, grown), emptied) }, {
2803
- runRecord: AgentStoredRunSchema.parse({ schemaVersion: 1, run: grown }),
2804
- secondaryRunRecord: AgentStoredRunSchema.parse({ schemaVersion: 1, run: emptied })
2805
- });
2806
- }
2807
2712
  const conversationId = operation.input.conversationId;
2808
2713
  const run = operation.type === "compact" ? undefined : current.runs.find((candidate) => candidate.id === operation.input.runId);
2809
2714
  if (operation.type !== "compact" && !run)
@@ -2861,22 +2766,18 @@ function reduceStore(current, operation) {
2861
2766
  }
2862
2767
  if (operation.type === "recover" && run) {
2863
2768
  const input = operation.input;
2864
- if (run.revision !== input.expectedRevision || !["queued", "running", "interrupt_requested"].includes(run.state)) {
2769
+ if (run.revision !== input.expectedRevision || !isActiveRunState(run.state)) {
2865
2770
  return conflict(run.revision);
2866
2771
  }
2867
2772
  if (input.action === "requeue" && run.state !== "queued" && input.replaySafe !== true) {
2868
2773
  throw new TypeError("Recovering an acquired run requires explicit replaySafe evidence");
2869
2774
  }
2775
+ const { ownerId: _released, ...carried } = run;
2870
2776
  const next = AgentRunSchema.parse({
2871
- schemaVersion: 1,
2872
- id: run.id,
2873
- conversationId: run.conversationId,
2874
- inputMessageIds: run.inputMessageIds,
2875
- assistantMessageId: run.assistantMessageId,
2777
+ ...carried,
2876
2778
  state: input.action === "requeue" ? "queued" : "abandoned",
2877
2779
  revision: run.revision + 1,
2878
2780
  ...input.action === "abandon" && { terminalReason: "abandoned" },
2879
- createdAt: run.createdAt,
2880
2781
  updatedAt: new Date().toISOString()
2881
2782
  });
2882
2783
  if (input.action === "abandon") {
@@ -2917,7 +2818,7 @@ function reduceStore(current, operation) {
2917
2818
  }
2918
2819
  const next = AgentRunSchema.parse({
2919
2820
  ...run,
2920
- state: terminalState(input.reason),
2821
+ state: runStateForTerminalReason(input.reason),
2921
2822
  terminalReason: input.reason,
2922
2823
  ...input.policyName && { terminalPolicyName: input.policyName },
2923
2824
  ...input.usage && { usage: input.usage },
@@ -2946,6 +2847,10 @@ function reduceStore(current, operation) {
2946
2847
  if (!input.replacedMessageIds.every((id) => current.messages.some((m) => m.id === id))) {
2947
2848
  return { outcome: "not_found" };
2948
2849
  }
2850
+ const liveAssistant = current.runs.find((candidate) => isActiveRunState(candidate.state) && replaced.has(candidate.assistantMessageId));
2851
+ if (liveAssistant) {
2852
+ throw new TypeError(`Compaction may not replace the assistant message of run ${liveAssistant.id}, which has not finished`);
2853
+ }
2949
2854
  const positions = current.messages.map((message, index) => replaced.has(message.id) ? index : undefined).filter((index) => index !== undefined);
2950
2855
  const first = positions[0];
2951
2856
  if (first === undefined || positions.some((position, offset) => position !== first + offset) || input.summary.conversationId !== input.conversationId || input.summary.runId !== undefined || input.summary.role !== "summary" || input.summary.status !== "committed" || current.messages.some((message) => message.id === input.summary.id) || current.runs.some((candidate) => candidate.assistantMessageId === input.summary.id)) {
@@ -3009,7 +2914,7 @@ function createAgentRuntimeStore(driver) {
3009
2914
  });
3010
2915
  const mutate = (operation) => driver.transaction(async (transaction) => {
3011
2916
  const conversationId = operationConversationId(operation);
3012
- const operationRunId = operation.type === "accept" ? operation.input.coalesceIntoRunId : operation.type === "compact" ? undefined : operation.type === "absorb" ? operation.input.queuedRunId : operation.input.runId;
2917
+ const operationRunId = operation.type === "accept" ? operation.input.coalesceIntoRunId : operation.type === "compact" ? undefined : operation.input.runId;
3013
2918
  const [stored, messages, activeRecords, operationRecord, duplicateReceipt] = await Promise.all([
3014
2919
  driver.head.load(transaction, conversationId),
3015
2920
  driver.history.load(transaction, conversationId),
@@ -3090,9 +2995,6 @@ function createAgentRuntimeStore(driver) {
3090
2995
  return conflict(outcome.actualVersion);
3091
2996
  if (reduced.runRecord)
3092
2997
  await driver.runs.save(transaction, reduced.runRecord);
3093
- if (reduced.secondaryRunRecord) {
3094
- await driver.runs.save(transaction, reduced.secondaryRunRecord);
3095
- }
3096
2998
  if (reduced.admissionReceipt) {
3097
2999
  await driver.admissions.create(transaction, reduced.admissionReceipt);
3098
3000
  }
@@ -3116,7 +3018,6 @@ function createAgentRuntimeStore(driver) {
3116
3018
  type: "interrupt",
3117
3019
  input: RequestRunInterruptSchema.parse(input)
3118
3020
  }),
3119
- absorbQueuedRun: (input) => mutate({ type: "absorb", input: AbsorbQueuedRunSchema.parse(input) }),
3120
3021
  recoverRun: (input) => mutate({ type: "recover", input: RecoverAgentRunSchema.parse(input) }),
3121
3022
  commitRunTerminal: (input) => mutate({ type: "terminal", input: CommitRunTerminalSchema.parse(input) }),
3122
3023
  replaceCompactedRange: (input) => mutate({
@@ -3212,7 +3113,7 @@ function createMemoryAgentRuntimeStore() {
3212
3113
  });
3213
3114
  },
3214
3115
  async listActive(transaction, conversationId) {
3215
- return [...transaction.runs.get(conversationId)?.values() ?? []].filter((record) => ["queued", "running", "interrupt_requested"].includes(record.run.state)).map((record) => AgentStoredRunSchema.parse(structuredClone(record)));
3116
+ return [...transaction.runs.get(conversationId)?.values() ?? []].filter((record) => isActiveRunState(record.run.state)).map((record) => AgentStoredRunSchema.parse(structuredClone(record)));
3216
3117
  },
3217
3118
  async save(transaction, rawRecord) {
3218
3119
  const record = AgentStoredRunSchema.parse(structuredClone(rawRecord));
@@ -3274,9 +3175,12 @@ function createMemoryAgentRuntimeStore() {
3274
3175
  }
3275
3176
  },
3276
3177
  async scanRecoverable(input) {
3277
- const descriptors = [...runs].flatMap(([conversationId, conversationRuns]) => [...conversationRuns.values()].filter((record) => ["queued", "running", "interrupt_requested"].includes(record.run.state)).map((record) => ({ conversationId, run: record.run }))).sort((left, right) => left.conversationId.localeCompare(right.conversationId) || left.run.id.localeCompare(right.run.id));
3178
+ const descriptors = [...runs].flatMap(([conversationId, conversationRuns]) => [...conversationRuns.values()].filter((record) => isActiveRunState(record.run.state)).map((record) => ({ conversationId, run: record.run }))).sort((left, right) => left.conversationId.localeCompare(right.conversationId) || left.run.id.localeCompare(right.run.id));
3278
3179
  const cursorTuple = input.cursor ? parseRecoverableCursor(input.cursor) : undefined;
3279
- const start = cursorTuple ? descriptors.findIndex((item) => item.conversationId === cursorTuple[0] && item.run.id === cursorTuple[1]) + 1 : 0;
3180
+ const start = cursorTuple ? descriptors.findIndex((item) => item.conversationId.localeCompare(cursorTuple[0]) > 0 || item.conversationId === cursorTuple[0] && item.run.id.localeCompare(cursorTuple[1]) > 0) : 0;
3181
+ if (start === -1) {
3182
+ return AgentRecoverablePageSchema.parse({ items: [] });
3183
+ }
3280
3184
  const items = descriptors.slice(start, start + input.limit);
3281
3185
  const last = items.at(-1);
3282
3186
  const hasMore = start + items.length < descriptors.length;
@@ -3289,7 +3193,7 @@ function createMemoryAgentRuntimeStore() {
3289
3193
  return createAgentRuntimeStore(driver);
3290
3194
  }
3291
3195
  export {
3292
- AbsorbQueuedRunSchema,
3196
+ ACTIVE_AGENT_RUN_STATES,
3293
3197
  AcceptInputAndAssignRunSchema,
3294
3198
  AcquireAgentRunSchema,
3295
3199
  AgentAdmissionEventSchema,
@@ -3368,6 +3272,7 @@ export {
3368
3272
  defineModelRegistry,
3369
3273
  projectAgentHistory,
3370
3274
  projectAgentHistoryDetailed,
3275
+ runStateForTerminalReason,
3371
3276
  selectAgentHistory,
3372
3277
  structuredCompaction,
3373
3278
  validateAgentModelSnapshot
@@ -99,7 +99,6 @@ var AgentRunStateSchema = z.enum([
99
99
  "completed",
100
100
  "interrupted",
101
101
  "superseded",
102
- "absorbed",
103
102
  "failed",
104
103
  "cancelled",
105
104
  "abandoned"
@@ -114,9 +113,24 @@ var AgentTerminalReasonSchema = z.enum([
114
113
  "timeout",
115
114
  "shutdown",
116
115
  "provider_failure",
117
- "tool_failure",
116
+ "context_overflow",
118
117
  "abandoned"
119
118
  ]);
119
+ function runStateForTerminalReason(reason) {
120
+ if (reason === "success" || reason === "policy_stop" || reason === "provider_stop") {
121
+ return "completed";
122
+ }
123
+ if (reason === "interrupted")
124
+ return "interrupted";
125
+ if (reason === "superseded")
126
+ return "superseded";
127
+ if (reason === "cancelled" || reason === "shutdown" || reason === "timeout") {
128
+ return "cancelled";
129
+ }
130
+ if (reason === "abandoned")
131
+ return "abandoned";
132
+ return "failed";
133
+ }
120
134
  var AgentUsageValueSchema = z.object({
121
135
  value: z.number().nonnegative().optional(),
122
136
  provenance: z.enum(["provider-reported", "computed", "estimated", "unavailable"])
@@ -134,7 +148,7 @@ var AgentUsageSchema = z.object({
134
148
  cacheWriteTokens: AgentUsageValueSchema.optional(),
135
149
  cost: AgentCostValueSchema.optional()
136
150
  });
137
- var AgentRunSchema = z.object({
151
+ var AgentRunFieldsSchema = z.object({
138
152
  schemaVersion: z.literal(1),
139
153
  id: AgentRecordIdSchema,
140
154
  conversationId: AgentRecordIdSchema,
@@ -146,11 +160,45 @@ var AgentRunSchema = z.object({
146
160
  fencingToken: AgentRecordVersionSchema.optional(),
147
161
  terminalReason: AgentTerminalReasonSchema.optional(),
148
162
  terminalPolicyName: z.string().min(1).optional(),
149
- absorbedIntoRunId: AgentRecordIdSchema.optional(),
150
163
  usage: AgentUsageSchema.optional(),
151
164
  createdAt: AgentTimestampSchema,
152
165
  updatedAt: AgentTimestampSchema
153
166
  });
167
+ var AgentRunSchema = AgentRunFieldsSchema.superRefine((run, ctx) => {
168
+ if (run.terminalReason === undefined) {
169
+ if (TERMINAL_RUN_STATES.has(run.state)) {
170
+ ctx.addIssue({
171
+ code: "custom",
172
+ path: ["terminalReason"],
173
+ message: `A run in terminal state "${run.state}" must say why it ended`
174
+ });
175
+ }
176
+ return;
177
+ }
178
+ const expected = runStateForTerminalReason(run.terminalReason);
179
+ if (run.state !== expected) {
180
+ ctx.addIssue({
181
+ code: "custom",
182
+ path: ["state"],
183
+ message: `Terminal reason "${run.terminalReason}" ends a run in state "${expected}", not "${run.state}"`
184
+ });
185
+ }
186
+ if (run.terminalReason === "policy_stop" && run.terminalPolicyName === undefined) {
187
+ ctx.addIssue({
188
+ code: "custom",
189
+ path: ["terminalPolicyName"],
190
+ message: "A policy stop names the policy that stopped the run"
191
+ });
192
+ }
193
+ });
194
+ var TERMINAL_RUN_STATES = new Set([
195
+ "completed",
196
+ "interrupted",
197
+ "superseded",
198
+ "cancelled",
199
+ "abandoned",
200
+ "failed"
201
+ ]);
154
202
  var AgentSnapshotSchema = z.object({
155
203
  schemaVersion: z.literal(1),
156
204
  conversationId: AgentRecordIdSchema,
@@ -160,9 +208,9 @@ var AgentSnapshotSchema = z.object({
160
208
  });
161
209
  var AgentRunMetricsSchema = z.object({
162
210
  partial: z.boolean(),
163
- usage: AgentUsageSchema.optional(),
211
+ usage: AgentUsageSchema,
164
212
  durationMs: z.number().nonnegative().optional(),
165
213
  ttftMs: z.number().nonnegative().optional()
166
214
  });
167
215
 
168
- export { AgentRecordIdSchema, AgentRecordVersionSchema, AgentTimestampSchema, AgentJsonObjectSchema, AgentProviderEnvelopeSchema, AgentTextPartSchema, AgentReasoningPartSchema, AgentFilePartSchema, AgentSourcePartSchema, AgentToolCallPartSchema, AgentToolResultPartSchema, AgentOpaquePartSchema, AgentControlPartSchema, AgentMessagePartSchema, AgentMessageRoleSchema, AgentMessageStatusSchema, AgentMessageSchema, AgentAssistantPlaceholderSchema, AgentRunStateSchema, AgentTerminalReasonSchema, AgentUsageValueSchema, AgentCostValueSchema, AgentUsageSchema, AgentRunSchema, AgentSnapshotSchema, AgentRunMetricsSchema };
216
+ export { AgentRecordIdSchema, AgentRecordVersionSchema, AgentTimestampSchema, AgentJsonObjectSchema, AgentProviderEnvelopeSchema, AgentTextPartSchema, AgentReasoningPartSchema, AgentFilePartSchema, AgentSourcePartSchema, AgentToolCallPartSchema, AgentToolResultPartSchema, AgentOpaquePartSchema, AgentControlPartSchema, AgentMessagePartSchema, AgentMessageRoleSchema, AgentMessageStatusSchema, AgentMessageSchema, AgentAssistantPlaceholderSchema, AgentRunStateSchema, AgentTerminalReasonSchema, runStateForTerminalReason, AgentUsageValueSchema, AgentCostValueSchema, AgentUsageSchema, AgentRunSchema, AgentSnapshotSchema, AgentRunMetricsSchema };
@@ -1 +1 @@
1
- {"version":3,"file":"agent-store-conformance.d.ts","sourceRoot":"","sources":["../../src/testing/agent-store-conformance.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,wBAAwB,CAAC;AAEhE,MAAM,WAAW,2BAA2B;IAC1C,WAAW,IAAI,iBAAiB,GAAG,OAAO,CAAC,iBAAiB,CAAC,CAAC;CAC/D;AAwCD,gFAAgF;AAChF,wBAAsB,wBAAwB,CAC5C,MAAM,EAAE,2BAA2B,GAClC,OAAO,CAAC,IAAI,CAAC,CA4Sf"}
1
+ {"version":3,"file":"agent-store-conformance.d.ts","sourceRoot":"","sources":["../../src/testing/agent-store-conformance.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,wBAAwB,CAAC;AAEhE,MAAM,WAAW,2BAA2B;IAC1C,WAAW,IAAI,iBAAiB,GAAG,OAAO,CAAC,iBAAiB,CAAC,CAAC;CAC/D;AAwCD,gFAAgF;AAChF,wBAAsB,wBAAwB,CAC5C,MAAM,EAAE,2BAA2B,GAClC,OAAO,CAAC,IAAI,CAAC,CAiXf"}