stitchkit 0.84.1 → 0.85.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (32) hide show
  1. package/CHANGELOG.md +37 -0
  2. package/dist/agent-runtime/control-schema.d.ts +61 -0
  3. package/dist/agent-runtime/control-schema.d.ts.map +1 -1
  4. package/dist/agent-runtime/event-schema.d.ts +88 -1
  5. package/dist/agent-runtime/event-schema.d.ts.map +1 -1
  6. package/dist/agent-runtime/run-execution.d.ts.map +1 -1
  7. package/dist/agent-runtime/run-operation-lifecycle.d.ts +21 -0
  8. package/dist/agent-runtime/run-operation-lifecycle.d.ts.map +1 -0
  9. package/dist/agent-runtime/schemas.d.ts +67 -0
  10. package/dist/agent-runtime/schemas.d.ts.map +1 -1
  11. package/dist/agent-runtime/store-driver.d.ts +18 -0
  12. package/dist/agent-runtime/store-driver.d.ts.map +1 -1
  13. package/dist/agent-runtime/store.d.ts +208 -0
  14. package/dist/agent-runtime/store.d.ts.map +1 -1
  15. package/dist/agent-runtime/terminal-commit.d.ts +9 -0
  16. package/dist/agent-runtime/terminal-commit.d.ts.map +1 -1
  17. package/dist/agent-runtime-browser.js +11 -3
  18. package/dist/agent-runtime-harness.js +4 -4
  19. package/dist/agent-runtime-sqlite-bun.js +3 -3
  20. package/dist/agent-runtime-sqlite-node.js +3 -3
  21. package/dist/agent-runtime.d.ts +2 -2
  22. package/dist/agent-runtime.d.ts.map +1 -1
  23. package/dist/agent-runtime.js +14 -4
  24. package/dist/{index-85vfqd7m.js → index-4hk633vz.js} +11 -3
  25. package/dist/{index-devfkwfm.js → index-66nxrgy0.js} +2 -2
  26. package/dist/{index-st8v8739.js → index-9merfjxf.js} +141 -14
  27. package/dist/{index-5s3zajp8.js → index-ff0kcqvp.js} +9 -3
  28. package/dist/{index-11vzj2sk.js → index-fq492hg0.js} +28 -2
  29. package/dist/{index-x1th9s8c.js → index-x15ss2dx.js} +50 -1
  30. package/dist/testing.js +26 -2
  31. package/llms-full.txt +56 -5
  32. package/package.json +1 -1
@@ -12,17 +12,18 @@ import {
12
12
  import {
13
13
  AgentRuntimeEventSchema,
14
14
  agentDurableEventId
15
- } from "./index-85vfqd7m.js";
15
+ } from "./index-4hk633vz.js";
16
16
  import {
17
17
  AgentAssistantPlaceholderSchema,
18
18
  AgentJsonObjectSchema,
19
19
  AgentMessagePartSchema,
20
20
  AgentMessageSchema,
21
21
  AgentProvenanceSchema,
22
+ AgentRunOperationSchema,
22
23
  AgentRunSchema,
23
24
  AgentSnapshotSchema,
24
25
  runStateForTerminalReason
25
- } from "./index-x1th9s8c.js";
26
+ } from "./index-x15ss2dx.js";
26
27
  import {
27
28
  isAgentToolError
28
29
  } from "./index-3xnq72rz.js";
@@ -1074,6 +1075,73 @@ function createIdleDeadline(parent, timeoutMs) {
1074
1075
  };
1075
1076
  }
1076
1077
 
1078
+ // src/agent-runtime/run-operation-lifecycle.ts
1079
+ function createAgentRunOperationLifecycle(config) {
1080
+ let active;
1081
+ const record = async (operation) => {
1082
+ const current = config.currentRun();
1083
+ const snapshot = appliedSnapshot(await config.store.recordRunOperation({
1084
+ conversationId: current.conversationId,
1085
+ runId: current.id,
1086
+ expectedRevision: current.revision,
1087
+ ownerId: config.runtimeEpoch,
1088
+ ...current.fencingToken !== undefined && { fencingToken: current.fencingToken },
1089
+ operation
1090
+ }), "run operation");
1091
+ config.acceptSnapshot(snapshot);
1092
+ const run = findRun(snapshot.runs, current.id);
1093
+ active = operation;
1094
+ await config.publish({
1095
+ type: "run-operation",
1096
+ eventId: agentDurableEventId("run-operation", run.id, snapshot.version),
1097
+ conversationId: run.conversationId,
1098
+ runId: run.id,
1099
+ snapshotVersion: snapshot.version,
1100
+ operation,
1101
+ emittedAt: config.now().toISOString()
1102
+ });
1103
+ };
1104
+ return {
1105
+ startCompaction(operationId) {
1106
+ return record(AgentRunOperationSchema.parse({
1107
+ operationId,
1108
+ kind: "compaction",
1109
+ phase: "started",
1110
+ startedAt: config.now().toISOString()
1111
+ }));
1112
+ },
1113
+ startModelRequest(providerCallId, step) {
1114
+ return record(AgentRunOperationSchema.parse({
1115
+ operationId: `${providerCallId}:${step}`,
1116
+ kind: "model-request",
1117
+ phase: "started",
1118
+ step,
1119
+ startedAt: config.now().toISOString()
1120
+ }));
1121
+ },
1122
+ firstOutput() {
1123
+ if (active?.kind !== "model-request" || active.phase !== "started") {
1124
+ return Promise.resolve();
1125
+ }
1126
+ return record(AgentRunOperationSchema.parse({
1127
+ ...active,
1128
+ phase: "first-output",
1129
+ firstOutputAt: config.now().toISOString()
1130
+ }));
1131
+ },
1132
+ finish(phase) {
1133
+ if (!active || ["completed", "failed", "cancelled"].includes(active.phase)) {
1134
+ return Promise.resolve();
1135
+ }
1136
+ return record(AgentRunOperationSchema.parse({
1137
+ ...active,
1138
+ phase,
1139
+ finishedAt: config.now().toISOString()
1140
+ }));
1141
+ }
1142
+ };
1143
+ }
1144
+
1077
1145
  // src/agent-runtime/run-execution.ts
1078
1146
  function snapshotForRunPrompt(snapshot, runId) {
1079
1147
  const runIndex = snapshot.runs.findIndex((candidate) => candidate.id === runId);
@@ -1184,7 +1252,7 @@ function createRunExecutor(dependencies) {
1184
1252
  let observedVersion = snapshot.version;
1185
1253
  const parts = [];
1186
1254
  const absorbed = new Map;
1187
- let eventCount = 0;
1255
+ let eventsSinceCheckpoint = 0;
1188
1256
  let sequence = 0;
1189
1257
  let terminalReason = "success";
1190
1258
  let nonModelUsage = input.acceptedRun.usage;
@@ -1199,6 +1267,18 @@ function createRunExecutor(dependencies) {
1199
1267
  let terminalPolicyName;
1200
1268
  const idleDeadline = createIdleDeadline(input.signal, idleTimeoutMs);
1201
1269
  const executionSignal = idleDeadline.signal;
1270
+ const operationLifecycle = createAgentRunOperationLifecycle({
1271
+ store: config.store,
1272
+ runtimeEpoch,
1273
+ currentRun: () => run,
1274
+ acceptSnapshot: (next) => {
1275
+ snapshot = next;
1276
+ observedVersion = next.version;
1277
+ run = findRun(next.runs, run.id);
1278
+ },
1279
+ publish,
1280
+ now
1281
+ });
1202
1282
  const updateReasoning = (text, metadata) => {
1203
1283
  const provider = providerEnvelope(metadata);
1204
1284
  if (reasoningPartIndex === undefined) {
@@ -1256,14 +1336,30 @@ function createRunExecutor(dependencies) {
1256
1336
  };
1257
1337
  try {
1258
1338
  if (config.history?.compact) {
1259
- const compacted = await config.history.compact({
1260
- conversationId: run.conversationId,
1261
- store: config.store,
1262
- signal: executionSignal
1263
- });
1264
- snapshot = compacted.snapshot;
1265
- observedVersion = snapshot.version;
1266
- run = findRun(snapshot.runs, run.id);
1339
+ await operationLifecycle.startCompaction(generateId());
1340
+ let compacted;
1341
+ try {
1342
+ compacted = await config.history.compact({
1343
+ conversationId: run.conversationId,
1344
+ store: config.store,
1345
+ signal: executionSignal
1346
+ });
1347
+ snapshot = compacted.snapshot;
1348
+ observedVersion = snapshot.version;
1349
+ run = findRun(snapshot.runs, run.id);
1350
+ await operationLifecycle.finish("completed");
1351
+ } catch (error) {
1352
+ const latest = await config.store.loadRun({
1353
+ conversationId: run.conversationId,
1354
+ runId: run.id
1355
+ });
1356
+ if (latest?.run.ownerId === runtimeEpoch) {
1357
+ observedVersion = latest.snapshotVersion;
1358
+ run = latest.run;
1359
+ }
1360
+ await operationLifecycle.finish(executionSignal.aborted ? "cancelled" : "failed");
1361
+ throw error;
1362
+ }
1267
1363
  if (compacted.usage) {
1268
1364
  nonModelUsage = addUsage(nonModelUsage, compacted.usage);
1269
1365
  usage = modelUsage ? addUsage(nonModelUsage, modelUsage) : nonModelUsage;
@@ -1418,6 +1514,9 @@ function createRunExecutor(dependencies) {
1418
1514
  abortSignal: executionSignal,
1419
1515
  maxRetries: 0,
1420
1516
  stopWhen: stopConditions,
1517
+ onLanguageModelCallStart: async (event) => {
1518
+ await operationLifecycle.startModelRequest(event.callId, step);
1519
+ },
1421
1520
  repairToolCall: deferredToolRepair(config.loop?.prepareStep),
1422
1521
  ...config.loop?.toolApproval && {
1423
1522
  toolApproval: config.loop.toolApproval,
@@ -1428,6 +1527,13 @@ function createRunExecutor(dependencies) {
1428
1527
  },
1429
1528
  ...(config.loop?.prepareStep || injection) && {
1430
1529
  prepareStep: async (options) => {
1530
+ const previousStep = options.steps.at(-1);
1531
+ if (previousStep) {
1532
+ lastPromptTokens = selectedModel?.normalizeUsage?.({
1533
+ usage: previousStep.usage,
1534
+ providerMetadata: previousStep.providerMetadata
1535
+ })?.inputTokens ?? normalizeSdkUsage(previousStep.usage).inputTokens;
1536
+ }
1431
1537
  const prepared = await config.loop?.prepareStep?.({
1432
1538
  ...options,
1433
1539
  ...runtimeContext
@@ -1442,11 +1548,14 @@ function createRunExecutor(dependencies) {
1442
1548
  });
1443
1549
  for await (const part of result.stream) {
1444
1550
  idleDeadline.touch();
1445
- eventCount += 1;
1551
+ eventsSinceCheckpoint += 1;
1446
1552
  sequence += 1;
1447
- if (firstOutputAt === undefined && ["text-delta", "reasoning-delta", "tool-call", "file", "source"].includes(part.type)) {
1553
+ if (firstOutputAt === undefined && (part.type === "text-delta" && part.text.length > 0 || part.type === "reasoning-delta" && part.text.length > 0 || part.type === "tool-input-delta" && part.delta.length > 0 || part.type === "tool-call")) {
1448
1554
  firstOutputAt = performance.now();
1449
1555
  }
1556
+ if (part.type === "text-delta" && part.text.length > 0 || part.type === "reasoning-delta" && part.text.length > 0 || part.type === "tool-input-delta" && part.delta.length > 0 || part.type === "tool-call") {
1557
+ await operationLifecycle.firstOutput();
1558
+ }
1450
1559
  if (part.type === "text-delta") {
1451
1560
  appendText(parts, part.text);
1452
1561
  await publish({
@@ -1552,6 +1661,7 @@ function createRunExecutor(dependencies) {
1552
1661
  status: "interrupted",
1553
1662
  emittedAt: now().toISOString()
1554
1663
  });
1664
+ await checkpoint();
1555
1665
  throw part.error;
1556
1666
  }
1557
1667
  const output = isAgentToolError(part.error) ? jsonValue(part.error.output) : { message: "Tool execution failed" };
@@ -1661,6 +1771,7 @@ function createRunExecutor(dependencies) {
1661
1771
  terminalReason = part.error instanceof AgentContextOverflowError ? "context_overflow" : "provider_failure";
1662
1772
  internalCause = part.error;
1663
1773
  } else if (part.type === "finish-step") {
1774
+ await operationLifecycle.finish(part.finishReason === "error" ? "failed" : "completed");
1664
1775
  const stepTrace = trace ? config.observe?.rootTrace(trace) : undefined;
1665
1776
  const stepUsage = selectedModel.normalizeUsage?.({
1666
1777
  usage: part.usage,
@@ -1696,9 +1807,21 @@ function createRunExecutor(dependencies) {
1696
1807
  modelUsage = mergeModelTotals(normalizeSdkUsage(part.totalUsage), modelUsage);
1697
1808
  usage = nonModelUsage ? addUsage(nonModelUsage, modelUsage) : modelUsage;
1698
1809
  }
1699
- if (eventCount % checkpointEveryEvents === 0)
1810
+ const structuralBoundary = [
1811
+ "tool-call",
1812
+ "tool-result",
1813
+ "tool-error",
1814
+ "tool-output-denied",
1815
+ "tool-approval-request",
1816
+ "tool-approval-response",
1817
+ "finish-step"
1818
+ ].includes(part.type);
1819
+ if (structuralBoundary || eventsSinceCheckpoint >= checkpointEveryEvents) {
1700
1820
  await checkpoint();
1821
+ eventsSinceCheckpoint = 0;
1822
+ }
1701
1823
  }
1824
+ await operationLifecycle.finish(executionSignal.aborted ? "cancelled" : terminalReason === "provider_failure" ? "failed" : "completed");
1702
1825
  if (terminalPolicyName !== undefined)
1703
1826
  terminalReason = "policy_stop";
1704
1827
  if (executionSignal.aborted)
@@ -1732,6 +1855,10 @@ function createRunExecutor(dependencies) {
1732
1855
  observedVersion = latest.snapshotVersion;
1733
1856
  run = latestRun;
1734
1857
  }
1858
+ const staleOwner = isToolExecutionControlError(error) && error.reason === "stale_run";
1859
+ if (!staleOwner) {
1860
+ await operationLifecycle.finish(executionSignal.aborted || durableInterrupt ? "cancelled" : "failed");
1861
+ }
1735
1862
  if (isToolExecutionControlError(error) || executionSignal.aborted || durableInterrupt) {
1736
1863
  terminalReason = executionSignal.aborted ? abortTerminalReason(executionSignal) : "interrupted";
1737
1864
  parts.push(AgentMessagePartSchema.parse({
@@ -1,10 +1,10 @@
1
1
  import {
2
2
  AgentRuntimeEventSchema
3
- } from "./index-85vfqd7m.js";
3
+ } from "./index-4hk633vz.js";
4
4
  import {
5
5
  AgentMessagePartSchema,
6
6
  AgentSnapshotSchema
7
- } from "./index-x1th9s8c.js";
7
+ } from "./index-x15ss2dx.js";
8
8
 
9
9
  // src/agent-runtime/control-schema.ts
10
10
  import { z } from "zod";
@@ -82,7 +82,13 @@ var AgentMultiSessionCursorSchema = z.object({
82
82
  }).strict())
83
83
  }).strict();
84
84
  function durable(event) {
85
- return ["admission", "assistant-checkpoint", "run-state", "terminal"].includes(event.type);
85
+ return [
86
+ "admission",
87
+ "assistant-checkpoint",
88
+ "run-state",
89
+ "run-operation",
90
+ "terminal"
91
+ ].includes(event.type);
86
92
  }
87
93
  function advanceAgentMultiSessionCursor(raw, event) {
88
94
  const cursor = AgentMultiSessionCursorSchema.parse(raw);
@@ -5,12 +5,13 @@ import {
5
5
  AgentMessageSchema,
6
6
  AgentRecordIdSchema,
7
7
  AgentRecordVersionSchema,
8
+ AgentRunOperationSchema,
8
9
  AgentRunSchema,
9
10
  AgentSnapshotSchema,
10
11
  AgentTerminalReasonSchema,
11
12
  AgentUsageSchema,
12
13
  runStateForTerminalReason
13
- } from "./index-x1th9s8c.js";
14
+ } from "./index-x15ss2dx.js";
14
15
 
15
16
  // src/agent-runtime/conversations.ts
16
17
  import { z } from "zod";
@@ -114,6 +115,14 @@ var CheckpointRunAssistantSchema = z3.object({
114
115
  assistant: AgentMessageSchema,
115
116
  usage: AgentUsageSchema.optional()
116
117
  });
118
+ var RecordRunOperationSchema = z3.object({
119
+ conversationId: AgentRecordIdSchema,
120
+ runId: AgentRecordIdSchema,
121
+ expectedRevision: AgentRecordVersionSchema,
122
+ ownerId: z3.string().min(1),
123
+ fencingToken: AgentRecordVersionSchema.optional(),
124
+ operation: AgentRunOperationSchema
125
+ });
117
126
  var CommitRunTerminalSchema = z3.object({
118
127
  conversationId: AgentRecordIdSchema,
119
128
  runId: AgentRecordIdSchema,
@@ -437,6 +446,19 @@ function reduceStore(current, operation) {
437
446
  historyMutations: [{ type: "upsert-assistant", message: input.assistant }]
438
447
  });
439
448
  }
449
+ if (operation.type === "operation" && run) {
450
+ const input = operation.input;
451
+ if (run.revision !== input.expectedRevision || run.state !== "running" && run.state !== "interrupt_requested" || run.ownerId !== input.ownerId || input.fencingToken !== undefined && run.fencingToken !== input.fencingToken) {
452
+ return conflict(run.revision);
453
+ }
454
+ const next = AgentRunSchema.parse({
455
+ ...run,
456
+ lastOperation: input.operation,
457
+ revision: run.revision + 1,
458
+ updatedAt: new Date().toISOString()
459
+ });
460
+ return applied(current, { runs: replaceRun(current.runs, next) }, { runRecords: [AgentStoredRunSchema.parse({ schemaVersion: 1, run: next })] });
461
+ }
440
462
  if (operation.type === "interrupt" && run) {
441
463
  if (run.revision !== operation.input.expectedRevision || run.state !== "running") {
442
464
  return conflict(run.revision);
@@ -794,6 +816,10 @@ function createAgentRuntimeStore(driver) {
794
816
  type: "checkpoint",
795
817
  input: CheckpointRunAssistantSchema.parse(input)
796
818
  }),
819
+ recordRunOperation: (input) => mutate({
820
+ type: "operation",
821
+ input: RecordRunOperationSchema.parse(input)
822
+ }),
797
823
  requestRunInterrupt: (input) => mutate({
798
824
  type: "interrupt",
799
825
  input: RequestRunInterruptSchema.parse(input)
@@ -988,4 +1014,4 @@ function createMemoryAgentRuntimeStore() {
988
1014
  return createAgentRuntimeStore(driver);
989
1015
  }
990
1016
 
991
- export { AgentConversationSummarySchema, AgentConversationPageSchema, AgentConversationMessagePageSchema, AgentConversationPurgeInputSchema, AgentConversationPurgeResultSchema, AgentConversationPurgedError, purgeAgentConversation, AgentStoreConflictSchema, AgentStoreNotFoundSchema, AgentStoreAppliedSchema, AgentStoreDuplicateSchema, AgentStoreMutationResultSchema, AgentRunViewSchema, AcceptInputAndAssignRunSchema, AcquireAgentRunSchema, CheckpointRunAssistantSchema, CommitRunTerminalSchema, RequestRunInterruptSchema, RecoverAgentRunSchema, ReplaceCompactedRangeSchema, AgentRecoverableDescriptorSchema, AgentRecoverablePageSchema, AgentRuntimeHeadSchema, AgentStoredRunSchema, AgentAdmissionReceiptSchema, AgentHistoryMutationSchema, ACTIVE_AGENT_RUN_STATES, createAgentRuntimeStore, createMemoryAgentRuntimeStore };
1017
+ export { AgentConversationSummarySchema, AgentConversationPageSchema, AgentConversationMessagePageSchema, AgentConversationPurgeInputSchema, AgentConversationPurgeResultSchema, AgentConversationPurgedError, purgeAgentConversation, AgentStoreConflictSchema, AgentStoreNotFoundSchema, AgentStoreAppliedSchema, AgentStoreDuplicateSchema, AgentStoreMutationResultSchema, AgentRunViewSchema, AcceptInputAndAssignRunSchema, AcquireAgentRunSchema, CheckpointRunAssistantSchema, RecordRunOperationSchema, CommitRunTerminalSchema, RequestRunInterruptSchema, RecoverAgentRunSchema, ReplaceCompactedRangeSchema, AgentRecoverableDescriptorSchema, AgentRecoverablePageSchema, AgentRuntimeHeadSchema, AgentStoredRunSchema, AgentAdmissionReceiptSchema, AgentHistoryMutationSchema, ACTIVE_AGENT_RUN_STATES, createAgentRuntimeStore, createMemoryAgentRuntimeStore };
@@ -125,6 +125,54 @@ var AgentRunStateSchema = z.enum([
125
125
  "abandoned"
126
126
  ]);
127
127
  var AgentRunQueuePrioritySchema = z.enum(["interrupt-next"]);
128
+ var AgentRunOperationKindSchema = z.enum(["model-request", "compaction"]);
129
+ var AgentRunOperationPhaseSchema = z.enum([
130
+ "started",
131
+ "first-output",
132
+ "completed",
133
+ "failed",
134
+ "cancelled"
135
+ ]);
136
+ var AgentRunOperationFieldsSchema = z.object({
137
+ operationId: AgentRecordIdSchema,
138
+ kind: AgentRunOperationKindSchema,
139
+ phase: AgentRunOperationPhaseSchema,
140
+ step: z.int().nonnegative().optional(),
141
+ startedAt: AgentTimestampSchema,
142
+ firstOutputAt: AgentTimestampSchema.optional(),
143
+ finishedAt: AgentTimestampSchema.optional()
144
+ });
145
+ var AgentRunOperationSchema = AgentRunOperationFieldsSchema.superRefine((operation, ctx) => {
146
+ if (operation.kind === "model-request" !== (operation.step !== undefined)) {
147
+ ctx.addIssue({
148
+ code: "custom",
149
+ path: ["step"],
150
+ message: "Only a model request has a step identity"
151
+ });
152
+ }
153
+ if (operation.kind === "compaction" && operation.phase === "first-output") {
154
+ ctx.addIssue({
155
+ code: "custom",
156
+ path: ["phase"],
157
+ message: "Compaction has no first-output phase"
158
+ });
159
+ }
160
+ if (operation.phase === "first-output" && operation.firstOutputAt === undefined || operation.phase === "started" && operation.firstOutputAt !== undefined || operation.kind === "compaction" && operation.firstOutputAt !== undefined) {
161
+ ctx.addIssue({
162
+ code: "custom",
163
+ path: ["firstOutputAt"],
164
+ message: "Only model output phases carry the observed first-output timestamp"
165
+ });
166
+ }
167
+ const terminal = ["completed", "failed", "cancelled"].includes(operation.phase);
168
+ if (terminal !== (operation.finishedAt !== undefined)) {
169
+ ctx.addIssue({
170
+ code: "custom",
171
+ path: ["finishedAt"],
172
+ message: "A terminal operation phase carries its observed finish timestamp"
173
+ });
174
+ }
175
+ });
128
176
  var AgentTerminalReasonSchema = z.enum([
129
177
  "success",
130
178
  "policy_stop",
@@ -204,6 +252,7 @@ var AgentRunFieldsSchema = z.object({
204
252
  terminalPolicyName: z.string().min(1).optional(),
205
253
  absorbedIntoRunId: AgentRecordIdSchema.optional(),
206
254
  usage: AgentUsageSchema.optional(),
255
+ lastOperation: AgentRunOperationSchema.optional(),
207
256
  createdAt: AgentTimestampSchema,
208
257
  updatedAt: AgentTimestampSchema
209
258
  });
@@ -277,4 +326,4 @@ var AgentRunMetricsSchema = z.object({
277
326
  ttftMs: z.number().nonnegative().optional()
278
327
  });
279
328
 
280
- export { AgentRecordIdSchema, AgentRecordVersionSchema, AgentTimestampSchema, AgentJsonObjectSchema, AgentProviderEnvelopeSchema, AgentTextPartSchema, AgentReasoningPartSchema, AgentFilePartSchema, AgentSourcePartSchema, AgentToolCallPartSchema, AgentToolResultPartSchema, AgentToolApprovalRequestPartSchema, AgentToolApprovalResponsePartSchema, AgentOpaquePartSchema, AgentControlPartSchema, AgentMessagePartSchema, AgentMessageRoleSchema, AgentMessageStatusSchema, AgentMessageSchema, AgentAssistantPlaceholderSchema, AgentRunStateSchema, AgentRunQueuePrioritySchema, AgentTerminalReasonSchema, runStateForTerminalReason, AgentProvenanceSchema, AgentUsageValueSchema, AgentCostValueSchema, AgentUsageSchema, AgentRunSchema, AgentSnapshotSchema, AgentRunMetricsSchema };
329
+ export { AgentRecordIdSchema, AgentRecordVersionSchema, AgentTimestampSchema, AgentJsonObjectSchema, AgentProviderEnvelopeSchema, AgentTextPartSchema, AgentReasoningPartSchema, AgentFilePartSchema, AgentSourcePartSchema, AgentToolCallPartSchema, AgentToolResultPartSchema, AgentToolApprovalRequestPartSchema, AgentToolApprovalResponsePartSchema, AgentOpaquePartSchema, AgentControlPartSchema, AgentMessagePartSchema, AgentMessageRoleSchema, AgentMessageStatusSchema, AgentMessageSchema, AgentAssistantPlaceholderSchema, AgentRunStateSchema, AgentRunQueuePrioritySchema, AgentRunOperationKindSchema, AgentRunOperationPhaseSchema, AgentRunOperationSchema, AgentTerminalReasonSchema, runStateForTerminalReason, AgentProvenanceSchema, AgentUsageValueSchema, AgentCostValueSchema, AgentUsageSchema, AgentRunSchema, AgentSnapshotSchema, AgentRunMetricsSchema };
package/dist/testing.js CHANGED
@@ -7,7 +7,7 @@ import"./index-3z73fh2c.js";
7
7
  import {
8
8
  AgentMessageSchema,
9
9
  AgentRunSchema
10
- } from "./index-x1th9s8c.js";
10
+ } from "./index-x15ss2dx.js";
11
11
  import {
12
12
  RealtimeRejectDirectionSchema,
13
13
  RealtimeRejectFaultSchema,
@@ -450,10 +450,34 @@ async function conformanceScenario(store, conversationIds) {
450
450
  if (!recoverable.items.some((item) => item.run.id === running.id)) {
451
451
  throw new Error("A running run must appear in a recoverable scan");
452
452
  }
453
+ const operation = await store.recordRunOperation({
454
+ conversationId,
455
+ runId: running.id,
456
+ expectedRevision: checkpointedRun.revision,
457
+ ownerId: "conformance-owner",
458
+ fencingToken: checkpointedRun.fencingToken,
459
+ operation: {
460
+ operationId: "model-call-1:0",
461
+ kind: "model-request",
462
+ phase: "first-output",
463
+ step: 0,
464
+ startedAt: "2026-08-22T00:00:01.800Z",
465
+ firstOutputAt: "2026-08-22T00:00:01.900Z"
466
+ }
467
+ });
468
+ requireOutcome(operation, "applied");
469
+ const operatedRun = operation.snapshot.runs.find((run) => run.id === running.id);
470
+ if (operatedRun?.lastOperation?.operationId !== "model-call-1:0") {
471
+ throw new Error("Run operation identity did not survive its durable mutation");
472
+ }
473
+ const operatedView = await store.loadRun({ conversationId, runId: running.id });
474
+ if (operatedView?.run.lastOperation?.startedAt !== "2026-08-22T00:00:01.800Z") {
475
+ throw new Error("loadRun did not retain the original operation timestamp");
476
+ }
453
477
  const interrupted = await store.requestRunInterrupt({
454
478
  conversationId,
455
479
  runId: running.id,
456
- expectedRevision: checkpointedRun.revision
480
+ expectedRevision: operatedRun.revision
457
481
  });
458
482
  requireOutcome(interrupted, "applied");
459
483
  const interruptedRun = interrupted.snapshot.runs.find((run) => run.id === running.id);
package/llms-full.txt CHANGED
@@ -63,11 +63,11 @@ own, recorded as an ADR.
63
63
  | `stitchkit/tracking/server` | server (Bun or Node) | evolving | the decisions a tracking backend makes — dispositions, visit lease over an application-owned store, active intervals, presence; no database |
64
64
  | `stitchkit/release` | browser **and** server | evolving | a page follows the release it was built for — `createReleaseMarker` on the server, `createReleaseWatcher` in the browser, the `X-Build-Id` header and a socket event between them |
65
65
  | `stitchkit/geo` | server (Bun or Node) | evolving | managed GeoIP reader generations, last-known-good reload and the optional MaxMind adapter |
66
- | `stitchkit/observability` | server | stable<br>_redefined in 1 of the 29 minors since 0.56.2, most recently 0.83.0_ | request/tool event projections — `createObservability`, trace context, sanitisation |
66
+ | `stitchkit/observability` | server | stable<br>_redefined in 1 of the 30 minors since 0.56.2, most recently 0.83.0_ | request/tool event projections — `createObservability`, trace context, sanitisation |
67
67
  | `stitchkit/testing` | tests on Bun or Node | stable | in-process generated clients over a real Fetch handler, plus the store and managed-resource conformance kits |
68
68
  | `stitchkit/declaration` | browser + build and deployment tooling (Bun or Node) | evolving | `ProjectDeclarationSchema` — the one machine-readable statement a repository makes about itself |
69
69
  | `stitchkit/react` | browser + server rendering | stable | `createCursorQuery`, `createCacheBridge`, QueryClient and `ApiError` retry policy |
70
- | `stitchkit/agent-runtime` | server | evolving<br>_redefined in 15 of the 29 minors since 0.56.2, most recently 0.84.0_ | optional durable conversation/run loop, history, models, prompts, fencing and events |
70
+ | `stitchkit/agent-runtime` | server | evolving<br>_redefined in 16 of the 30 minors since 0.56.2, most recently 0.85.0_ | optional durable conversation/run loop, history, models, prompts, fencing and events |
71
71
  | `stitchkit/agent-runtime/harness` | server | evolving | resource-aware process-local facade over the canonical Agent runtime; supervision stays outside |
72
72
  | `stitchkit/agent-runtime/coding-tools` | server (Bun or Node) | evolving | bounded host-authorized direct file and shell tools; a root boundary, not an OS sandbox |
73
73
  | `stitchkit/agent-runtime/openrouter` | server | evolving | isolated OpenRouter language-model adapter |
@@ -75,7 +75,7 @@ own, recorded as an ADR.
75
75
  | `stitchkit/agent-runtime/sqlite/bun` | server (Bun) | evolving | durable built-in SQLite store for the agent runtime |
76
76
  | `stitchkit/agent-runtime/sqlite/node` | server (Node ≥ 22.5) | evolving | durable built-in SQLite store for the agent runtime |
77
77
  | `stitchkit-tui` | terminal (Bun) | evolving | optional official OpenTUI host over a caller-composed headless runtime |
78
- | `stitchkit/application` | browser + server | evolving<br>_redefined in 7 of the 29 minors since 0.56.2, most recently 0.83.0_ | managed resource graph, readiness, admission, schedules, subtree restart and bounded shutdown |
78
+ | `stitchkit/application` | browser + server | evolving<br>_redefined in 7 of the 30 minors since 0.56.2, most recently 0.83.0_ | managed resource graph, readiness, admission, schedules, subtree restart and bounded shutdown |
79
79
  | `stitchkit/application/grammy` | server | evolving | isolated grammY polling and webhook lifecycle adapters |
80
80
  | `stitchkit/application/opentelemetry` | server | evolving | maps application snapshots onto an injected OpenTelemetry `Meter` |
81
81
  | `stitchkit/application/schemas` | browser + server | evolving | the application's snapshot, health and shutdown schemas alone, without the kernel |
@@ -5273,12 +5273,33 @@ transaction. Drivers without this guarantee must leave the capability absent. Se
5273
5273
  while provider metadata stays inside a validated canonical envelope;
5274
5274
  - `assistant-checkpoint` follows a successful checkpoint CAS;
5275
5275
  - `run-state` follows durable queue/acquire/interrupt transitions;
5276
+ - `run-operation` follows a successful operation CAS. Its `operation` is the
5277
+ durable `AgentRun.lastOperation`: `model-request` or `compaction`, with
5278
+ `started`, `first-output`, `completed`, `failed` or `cancelled` phase and the
5279
+ original timestamps. Model request start means the provider call is about to
5280
+ enter `doStream`, not that HTTP transmission is measured. First output is a
5281
+ non-empty text/reasoning/tool-argument delta or complete tool call; metadata,
5282
+ usage, files and sources do not count;
5276
5283
  - `tool-status` is transient lifecycle presentation with JSON-safe input on
5277
5284
  start and output on completion. A mounted typed failure carries the same safe
5278
5285
  `{ error, details?, _hint? }` envelope as the durable result; an unknown
5279
5286
  internal cause remains generic and stays in local observability only;
5280
5287
  - `terminal` follows the winning terminal CAS.
5281
5288
 
5289
+ `loop.checkpointEveryEvents` batches ordinary stream parts (default `20`) and
5290
+ does not batch live event delivery. Tool call/result/error/denial, approval
5291
+ request/response and step-finish boundaries force an assistant checkpoint and
5292
+ reset that batch. The checkpoint is awaited after the managed loop observes the
5293
+ normalized boundary. It does **not** promise persistence before a tool side
5294
+ effect: tool fencing supplies the real ownership checks around execution, while
5295
+ cross-crash effect idempotency stays application-owned. → ADR 0174.
5296
+
5297
+ `compaction` names invocation of the configured callback, including a
5298
+ `not_needed` context-budget check; it does not by itself say that history was
5299
+ summarized. Operation timestamps are retained wall-clock observations and may
5300
+ move backwards after a clock correction. Do not subtract them for duration;
5301
+ phase order comes from durable snapshot versions/event order.
5302
+
5282
5303
  These are post-commit notifications, not a transactional outbox: a process can
5283
5304
  crash between the database commit and `publish`. Reconnect should load canonical
5284
5305
  state. Exactly-once external delivery remains an application-owned outbox.
@@ -11022,6 +11043,33 @@ of the range if you want a different one.
11022
11043
  So upgrading is: read the `### ⚠️ Breaking changes` of every version *above* your
11023
11044
  current one *up to* your target, and apply each snippet.
11024
11045
 
11046
+ ## Released migration: 0.85.0
11047
+
11048
+ Only if you implement `AgentRuntimeStore` directly. Add the new durable
11049
+ operation mutation beside the existing run mutations:
11050
+
11051
+ ```ts
11052
+ // before
11053
+ const store: AgentRuntimeStore = {
11054
+ checkpointRunAssistant,
11055
+ commitRunTerminal,
11056
+ // ...
11057
+ }
11058
+
11059
+ // after
11060
+ const store: AgentRuntimeStore = {
11061
+ checkpointRunAssistant,
11062
+ recordRunOperation,
11063
+ commitRunTerminal,
11064
+ // ...
11065
+ }
11066
+ ```
11067
+
11068
+ It must atomically replace `AgentRun.lastOperation`, increment the run revision
11069
+ and conversation snapshot version, and enforce the supplied owner, fencing
11070
+ token and expected revision. Adapters built with `createAgentRuntimeStore`
11071
+ already receive the reference implementation; no driver method is added.
11072
+
11025
11073
  ## What your range does, and does not, do for you
11026
11074
 
11027
11075
  A caret range (`"stitchkit": "^0.71.0"`) is a real gate: it resolves `< 0.72.0`,
@@ -15138,7 +15186,9 @@ Server-only optional application runtime. See the
15138
15186
  | `AgentSessionCloseResult` | _type_ | what `close()` achieved: `settled`, or `timedOut` with `remaining` runs still in flight. Only omitting `forceTimeoutMs` guarantees nothing is in flight on return |
15139
15187
  | `AgentHistoryProjectionOptions` | _type_ | storage-neutral file resolver, explicit unresolved-file behavior, and how an interrupted turn reaches the model (`interruptedAssistant`) |
15140
15188
  | `createAgentToolFenceLifecycle` | function | pre-effect and post-effect run ownership fence for `mountAgent`; compose beside application idempotency for [durable operations](../guide/mcp-and-agents.md#durable-application-owned-execution) |
15141
- | `AgentRuntimeEventSchema` | schema | transient stream lifecycle plus post-commit admission/checkpoint/run-state/terminal projections |
15189
+ | `AgentRuntimeEventSchema` | schema | transient stream lifecycle plus post-commit admission/checkpoint/run-state/run-operation/terminal projections |
15190
+ | `AgentRunOperationKindSchema` / `AgentRunOperationPhaseSchema` / `AgentRunOperationSchema` / `AgentRunOperation` | schemas / _type_ | latest durable model-request or compaction phase with operation/step identity and original timestamps |
15191
+ | `RecordRunOperationSchema` / `RecordRunOperation` | schema / _type_ | owner/fencing/revision-checked mutation of `AgentRun.lastOperation` |
15142
15192
  | `createAgentObservability` | function | separate agent-run sink over the shared bounded observability lifecycle |
15143
15193
 
15144
15194
  ### Complete runtime inventory
@@ -15268,7 +15318,8 @@ provider and required capabilities without constructing the model; runtime `mode
15268
15318
  runs before durable admission.
15269
15319
 
15270
15320
  Delivery exports are `AgentAdmissionEventSchema`, `AgentCheckpointEventSchema`,
15271
- `AgentRunStateEventSchema`, `AgentTerminalEventSchema`, `AgentTransientDeltaEventSchema`,
15321
+ `AgentRunStateEventSchema`, `AgentRunOperationEventSchema`, `AgentTerminalEventSchema`,
15322
+ `AgentTransientDeltaEventSchema`,
15272
15323
  `AgentReasoningStartEventSchema`, `AgentReasoningDeltaEventSchema`,
15273
15324
  `AgentReasoningEndEventSchema`, `AgentToolStatusEventSchema`, `AgentRuntimeEvent`,
15274
15325
  `AgentRuntimeEventCursor`, `AgentRuntimeEventCursorSchema`, `AgentRuntimeCursorAdvance`,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "stitchkit",
3
- "version": "0.84.1",
3
+ "version": "0.85.0",
4
4
  "description": "Contract-first backend framework — one defineContract() into an HTTP API, MCP tools, AI-agent tools and a typed client. Bun and Node.",
5
5
  "keywords": [
6
6
  "bun",