stitchkit 0.86.0 → 0.87.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -15,6 +15,89 @@ additive**; the first breaking change landed in 0.10.0. Grep the file for
15
15
 
16
16
  ## [Unreleased]
17
17
 
18
+ ## [0.87.0] — 2026-09-08
19
+
20
+ ### ⚠️ Breaking changes
21
+
22
+ **Who must act:** applications that implement `AgentConversationReader`
23
+ themselves. Applications that only call the reader returned by the SQLite store
24
+ need no change.
25
+
26
+ - **A message page says which of its messages compaction removed.**
27
+ `AgentConversationMessagePage` gains a required `compacted: string[]`, so a
28
+ reader that builds the page itself now names the ids in `items` that are no
29
+ longer part of the model's history — an empty array when it does not page
30
+ them at all.
31
+
32
+ ```ts
33
+ // before
34
+ return { items, ...(nextCursor && { nextCursor }) }
35
+
36
+ // after
37
+ return { items, compacted: [], ...(nextCursor && { nextCursor }) }
38
+ ```
39
+
40
+ ### Added
41
+
42
+ - **The history compaction removed can be read.** `conversations.messages`
43
+ takes `includeCompacted`, off by default. With it the page carries the whole
44
+ conversation in the order it happened, and `compacted` names the ids the
45
+ model no longer sees. Those rows were always in the store and no public read
46
+ reached them — every path filtered the active history — so an application
47
+ that shows a person their own long conversation had to keep a second copy of
48
+ it beside the store, which is the copy the event ledger exists to make
49
+ unnecessary. Paging is exact across a compaction boundary: a summary is
50
+ written at the position of the first message it replaced, so position alone
51
+ stopped identifying a row, and the page cursor now carries the row identity
52
+ beside it. A cursor issued by an earlier version still means what it meant.
53
+
54
+ ### Fixed
55
+
56
+ - **The v1 → v2 baseline carries the compacted history.** The migration built
57
+ each conversation's `runtime/baseline` from the active messages only, so for
58
+ exactly the conversations long enough to have been compacted the ledger could
59
+ not reconstruct what the person had said, while the normalized table still
60
+ held it. The baseline now records the whole sequence with `compacted` naming
61
+ what had been folded away. A file already migrated by 0.86.0 keeps the
62
+ baseline it got — one event cannot be rewritten after the fact — and its
63
+ compacted messages remain readable through `includeCompacted`.
64
+ - **A conversation that has a turn in it can be imported again.**
65
+ `importConversation` refused every archive whose snapshot carried a run —
66
+ that is, every conversation anyone actually had — with `Conversation archive
67
+ snapshot target is not empty`, into a target it had just reported empty. The
68
+ SQLite head compare-and-swap read its outcome from the driver's `changes`
69
+ count, and `bun:sqlite` counts what the event table's `AFTER INSERT` trigger
70
+ and FTS5's deferred index flush wrote during the same statement: an import
71
+ appends the whole archived ledger before it writes the head, so a swap that
72
+ moved one row reported five and was read as a conflict. The swap now reads
73
+ the version, compares it, and writes unconditionally inside the store's
74
+ `BEGIN IMMEDIATE` transaction. Export was never affected, and the memory
75
+ store never had the defect. Reported from a real consumer against published
76
+ 0.86.0.
77
+ - **A first head write is a compare-and-swap.** The same conditional upsert
78
+ guarded only its update branch, so a swap against a conversation with no head
79
+ row applied whatever `expectedVersion` it was given instead of conflicting.
80
+ No path in the runtime reaches it — a version above zero implies the row —
81
+ but the shipped Prisma example adapter carried the same shape and is fixed
82
+ with it.
83
+
84
+ ### Changed
85
+
86
+ - **`SqliteStatement.run`'s `changes` is documented as advisory.** The boundary
87
+ is satisfied structurally by a raw driver handle, so the number is that
88
+ driver's, and it may count trigger and virtual-table writes. Nothing in the
89
+ runtime decides correctness by it any more: the schedule claim, the firing
90
+ finalize and the cancellation each read their row and then write, the way the
91
+ head swap does. No input reached those three — each wrote before it appended
92
+ its event — and their observable behaviour is unchanged.
93
+ - **`runAgentStoreConformance` imports a conversation that has a run.** The kit
94
+ now announces an eighth conversation identity and carries a reference archive
95
+ with one accepted turn into the adapter under test, then compares the
96
+ restored snapshot and ledger against it. Adapters that provision from
97
+ `context.conversationIds`, as the kit has always required, need no change; an
98
+ adapter that cannot import a run-bearing archive now fails the kit instead of
99
+ passing it.
100
+
18
101
  ## [0.86.0] — 2026-09-08
19
102
 
20
103
  ### ⚠️ Breaking changes
@@ -107,6 +107,7 @@ export declare const AgentConversationMessagePageSchema: z.ZodObject<{
107
107
  createdAt: z.ZodISODateTime;
108
108
  updatedAt: z.ZodISODateTime;
109
109
  }, z.core.$strip>>;
110
+ compacted: z.ZodArray<z.ZodString>;
110
111
  nextCursor: z.ZodOptional<z.ZodString>;
111
112
  }, z.core.$strict>;
112
113
  export type AgentConversationSummary = z.infer<typeof AgentConversationSummarySchema>;
@@ -123,6 +124,16 @@ export interface AgentConversationReader {
123
124
  cursor?: string;
124
125
  limit: number;
125
126
  direction: 'before' | 'after';
127
+ /**
128
+ * Include the messages compaction removed from the model's history.
129
+ *
130
+ * Off by default: the model's view is what most callers page. A person
131
+ * reading back their own conversation needs the other one — the store
132
+ * keeps those rows, and without this they were reachable by no public
133
+ * read, which left an application keeping a second copy of its own
134
+ * history beside the store.
135
+ */
136
+ includeCompacted?: boolean;
126
137
  }): Promise<AgentConversationMessagePage>;
127
138
  }
128
139
  //# sourceMappingURL=conversations.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"conversations.d.ts","sourceRoot":"","sources":["../../src/agent-runtime/conversations.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAGxB,eAAO,MAAM,8BAA8B;;;;;;kBAQhC,CAAC;AAEZ,eAAO,MAAM,2BAA2B;;;;;;;;;kBAK7B,CAAC;AAEZ,eAAO,MAAM,kCAAkC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;kBAKpC,CAAC;AAEZ,MAAM,MAAM,wBAAwB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,8BAA8B,CAAC,CAAC;AACtF,MAAM,MAAM,qBAAqB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,2BAA2B,CAAC,CAAC;AAChF,MAAM,MAAM,4BAA4B,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,kCAAkC,CAAC,CAAC;AAE9F,MAAM,WAAW,uBAAuB;IACtC,IAAI,CAAC,KAAK,EAAE;QACV,MAAM,CAAC,EAAE,MAAM,CAAC;QAChB,KAAK,EAAE,MAAM,CAAC;QACd,MAAM,CAAC,EAAE,MAAM,CAAC;KACjB,GAAG,OAAO,CAAC,qBAAqB,CAAC,CAAC;IACnC,QAAQ,CAAC,KAAK,EAAE;QACd,cAAc,EAAE,MAAM,CAAC;QACvB,MAAM,CAAC,EAAE,MAAM,CAAC;QAChB,KAAK,EAAE,MAAM,CAAC;QACd,SAAS,EAAE,QAAQ,GAAG,OAAO,CAAC;KAC/B,GAAG,OAAO,CAAC,4BAA4B,CAAC,CAAC;CAC3C"}
1
+ {"version":3,"file":"conversations.d.ts","sourceRoot":"","sources":["../../src/agent-runtime/conversations.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAGxB,eAAO,MAAM,8BAA8B;;;;;;kBAQhC,CAAC;AAEZ,eAAO,MAAM,2BAA2B;;;;;;;;;kBAK7B,CAAC;AAEZ,eAAO,MAAM,kCAAkC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;kBAepC,CAAC;AAEZ,MAAM,MAAM,wBAAwB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,8BAA8B,CAAC,CAAC;AACtF,MAAM,MAAM,qBAAqB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,2BAA2B,CAAC,CAAC;AAChF,MAAM,MAAM,4BAA4B,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,kCAAkC,CAAC,CAAC;AAE9F,MAAM,WAAW,uBAAuB;IACtC,IAAI,CAAC,KAAK,EAAE;QACV,MAAM,CAAC,EAAE,MAAM,CAAC;QAChB,KAAK,EAAE,MAAM,CAAC;QACd,MAAM,CAAC,EAAE,MAAM,CAAC;KACjB,GAAG,OAAO,CAAC,qBAAqB,CAAC,CAAC;IACnC,QAAQ,CAAC,KAAK,EAAE;QACd,cAAc,EAAE,MAAM,CAAC;QACvB,MAAM,CAAC,EAAE,MAAM,CAAC;QAChB,KAAK,EAAE,MAAM,CAAC;QACd,SAAS,EAAE,QAAQ,GAAG,OAAO,CAAC;QAC9B;;;;;;;;WAQG;QACH,gBAAgB,CAAC,EAAE,OAAO,CAAC;KAC5B,GAAG,OAAO,CAAC,4BAA4B,CAAC,CAAC;CAC3C"}
@@ -1 +1 @@
1
- {"version":3,"file":"schedules.d.ts","sourceRoot":"","sources":["../../src/agent-runtime/schedules.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AACxB,OAAO,KAAK,EAAE,uBAAuB,EAAE,MAAM,UAAU,CAAC;AAExD,eAAO,MAAM,mBAAmB;;;;;;;;;;;;;;;;;;;;kBAcrB,CAAC;AACZ,MAAM,MAAM,aAAa,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,mBAAmB,CAAC,CAAC;AAEhE,MAAM,WAAW,oBAAoB;IACnC,aAAa,CAAC,OAAO,EAAE;QACrB,cAAc,EAAE,MAAM,CAAC;QACvB,KAAK,EAAE,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,IAAI,CAAC,CAAC;QAC9B,EAAE,CAAC,EAAE,MAAM,CAAC;QACZ,OAAO,CAAC,EAAE,MAAM,CAAC;QACjB,OAAO,CAAC,EAAE,MAAM,CAAC;QACjB,QAAQ,CAAC,EAAE,MAAM,CAAC;KACnB,GAAG,OAAO,CAAC,aAAa,CAAC,CAAC;IAC3B,cAAc,CAAC,cAAc,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;IACrE,aAAa,CAAC,cAAc,EAAE,MAAM,GAAG,SAAS,aAAa,EAAE,CAAC;IAChE,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;IACtB,KAAK,IAAI,IAAI,CAAC;IACd,KAAK,IAAI,IAAI,CAAC;CACf;AAiCD,wBAAgB,0BAA0B,CAAC,KAAK,EAAE;IAChD,MAAM,EAAE,uBAAuB,CAAC;IAChC,QAAQ,CAAC,OAAO,EAAE;QAChB,cAAc,EAAE,MAAM,CAAC;QACvB,cAAc,EAAE,MAAM,CAAC;QACvB,KAAK,EAAE,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,IAAI,CAAC,CAAC;QAC9B,QAAQ,EAAE;YAAE,EAAE,EAAE,MAAM,CAAC;YAAC,UAAU,EAAE,MAAM,CAAC;YAAC,QAAQ,EAAE,MAAM,CAAA;SAAE,CAAC;KAChE,GAAG,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACzB,GAAG,CAAC,EAAE,MAAM,IAAI,CAAC;IACjB,QAAQ,CAAC,EAAE,CAAC,QAAQ,EAAE,MAAM,IAAI,EAAE,OAAO,EAAE,MAAM,KAAK,UAAU,CAAC,OAAO,UAAU,CAAC,CAAC;IACpF,UAAU,CAAC,EAAE,CAAC,KAAK,EAAE,UAAU,CAAC,OAAO,UAAU,CAAC,KAAK,IAAI,CAAC;IAC5D;;;;;OAKG;IACH,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,OAAO,KAAK,IAAI,CAAC;CACpC,GAAG,oBAAoB,CAgTvB"}
1
+ {"version":3,"file":"schedules.d.ts","sourceRoot":"","sources":["../../src/agent-runtime/schedules.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AACxB,OAAO,KAAK,EAAE,uBAAuB,EAAE,MAAM,UAAU,CAAC;AAExD,eAAO,MAAM,mBAAmB;;;;;;;;;;;;;;;;;;;;kBAcrB,CAAC;AACZ,MAAM,MAAM,aAAa,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,mBAAmB,CAAC,CAAC;AAEhE,MAAM,WAAW,oBAAoB;IACnC,aAAa,CAAC,OAAO,EAAE;QACrB,cAAc,EAAE,MAAM,CAAC;QACvB,KAAK,EAAE,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,IAAI,CAAC,CAAC;QAC9B,EAAE,CAAC,EAAE,MAAM,CAAC;QACZ,OAAO,CAAC,EAAE,MAAM,CAAC;QACjB,OAAO,CAAC,EAAE,MAAM,CAAC;QACjB,QAAQ,CAAC,EAAE,MAAM,CAAC;KACnB,GAAG,OAAO,CAAC,aAAa,CAAC,CAAC;IAC3B,cAAc,CAAC,cAAc,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;IACrE,aAAa,CAAC,cAAc,EAAE,MAAM,GAAG,SAAS,aAAa,EAAE,CAAC;IAChE,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;IACtB,KAAK,IAAI,IAAI,CAAC;IACd,KAAK,IAAI,IAAI,CAAC;CACf;AA+DD,wBAAgB,0BAA0B,CAAC,KAAK,EAAE;IAChD,MAAM,EAAE,uBAAuB,CAAC;IAChC,QAAQ,CAAC,OAAO,EAAE;QAChB,cAAc,EAAE,MAAM,CAAC;QACvB,cAAc,EAAE,MAAM,CAAC;QACvB,KAAK,EAAE,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,IAAI,CAAC,CAAC;QAC9B,QAAQ,EAAE;YAAE,EAAE,EAAE,MAAM,CAAC;YAAC,UAAU,EAAE,MAAM,CAAC;YAAC,QAAQ,EAAE,MAAM,CAAA;SAAE,CAAC;KAChE,GAAG,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACzB,GAAG,CAAC,EAAE,MAAM,IAAI,CAAC;IACjB,QAAQ,CAAC,EAAE,CAAC,QAAQ,EAAE,MAAM,IAAI,EAAE,OAAO,EAAE,MAAM,KAAK,UAAU,CAAC,OAAO,UAAU,CAAC,CAAC;IACpF,UAAU,CAAC,EAAE,CAAC,KAAK,EAAE,UAAU,CAAC,OAAO,UAAU,CAAC,KAAK,IAAI,CAAC;IAC5D;;;;;OAKG;IACH,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,OAAO,KAAK,IAAI,CAAC;CACpC,GAAG,oBAAoB,CA4UvB"}
@@ -1 +1 @@
1
- {"version":3,"file":"sqlite.d.ts","sourceRoot":"","sources":["../../src/agent-runtime/sqlite.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,cAAc,EAAe,MAAM,oBAAoB,CAAC;AACtE,OAAO,EAGL,KAAK,uBAAuB,EAC7B,MAAM,iBAAiB,CAAC;AAKzB,OAAO,EAML,uBAAuB,EACxB,MAAM,gBAAgB,CAAC;AACxB,OAAO,EAEL,KAAK,uBAAuB,EAG5B,KAAK,qBAAqB,EAG3B,MAAM,gBAAgB,CAAC;AAMxB,YAAY,EAAE,cAAc,EAAE,eAAe,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAC;AAEvF,MAAM,WAAW,6BAA6B;IAC5C,QAAQ,EAAE,cAAc,CAAC;IACzB,sEAAsE;IACtE,UAAU,CAAC,EAAE,OAAO,CAAC;CACtB;AAED,MAAM,WAAW,uBAAuB;IACtC,KAAK,EAAE,UAAU,CAAC,OAAO,uBAAuB,CAAC,cAAc,CAAC,CAAC,CAAC;IAClE,aAAa,EAAE,uBAAuB,CAAC;IACvC;;;;;;;OAOG;IACH,QAAQ,EAAE,cAAc,CAAC;IACzB;;;;;;;;;OASG;IACH,WAAW,CAAC,MAAM,EAChB,IAAI,EAAE,CAAC,KAAK,EAAE,sBAAsB,KAAK,OAAO,CAAC,MAAM,CAAC,GACvD,OAAO,CAAC,MAAM,CAAC,CAAC;IACnB,oFAAoF;IACpF,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;CACxB;AAED,4DAA4D;AAC5D,MAAM,WAAW,sBAAsB;IACrC,QAAQ,EAAE,cAAc,CAAC;IACzB,WAAW,CAAC,KAAK,EAAE,qBAAqB,GAAG,OAAO,CAAC,uBAAuB,CAAC,CAAC;CAC7E;AAqJD;;;GAGG;AACH,wBAAgB,4BAA4B,CAAC,QAAQ,EAAE,cAAc,GAAG,IAAI,CAqG3E;AAED,wBAAgB,6BAA6B,CAC3C,MAAM,EAAE,6BAA6B,GACpC,uBAAuB,CAioBzB"}
1
+ {"version":3,"file":"sqlite.d.ts","sourceRoot":"","sources":["../../src/agent-runtime/sqlite.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,cAAc,EAAe,MAAM,oBAAoB,CAAC;AACtE,OAAO,EAGL,KAAK,uBAAuB,EAC7B,MAAM,iBAAiB,CAAC;AAKzB,OAAO,EAML,uBAAuB,EACxB,MAAM,gBAAgB,CAAC;AACxB,OAAO,EAEL,KAAK,uBAAuB,EAG5B,KAAK,qBAAqB,EAG3B,MAAM,gBAAgB,CAAC;AAMxB,YAAY,EAAE,cAAc,EAAE,eAAe,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAC;AAEvF,MAAM,WAAW,6BAA6B;IAC5C,QAAQ,EAAE,cAAc,CAAC;IACzB,sEAAsE;IACtE,UAAU,CAAC,EAAE,OAAO,CAAC;CACtB;AAED,MAAM,WAAW,uBAAuB;IACtC,KAAK,EAAE,UAAU,CAAC,OAAO,uBAAuB,CAAC,cAAc,CAAC,CAAC,CAAC;IAClE,aAAa,EAAE,uBAAuB,CAAC;IACvC;;;;;;;OAOG;IACH,QAAQ,EAAE,cAAc,CAAC;IACzB;;;;;;;;;OASG;IACH,WAAW,CAAC,MAAM,EAChB,IAAI,EAAE,CAAC,KAAK,EAAE,sBAAsB,KAAK,OAAO,CAAC,MAAM,CAAC,GACvD,OAAO,CAAC,MAAM,CAAC,CAAC;IACnB,oFAAoF;IACpF,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;CACxB;AAED,4DAA4D;AAC5D,MAAM,WAAW,sBAAsB;IACrC,QAAQ,EAAE,cAAc,CAAC;IACzB,WAAW,CAAC,KAAK,EAAE,qBAAqB,GAAG,OAAO,CAAC,uBAAuB,CAAC,CAAC;CAC7E;AAwKD;;;GAGG;AACH,wBAAgB,4BAA4B,CAAC,QAAQ,EAAE,cAAc,GAAG,IAAI,CAqG3E;AAED,wBAAgB,6BAA6B,CAC3C,MAAM,EAAE,6BAA6B,GACpC,uBAAuB,CAwqBzB"}
@@ -1 +1 @@
1
- {"version":3,"file":"v1-to-v2.d.ts","sourceRoot":"","sources":["../../../src/agent-runtime/store-migrations/v1-to-v2.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAC;AAyM5D,8EAA8E;AAC9E,wBAAgB,+BAA+B,CAC7C,QAAQ,EAAE,cAAc,EACxB,UAAU,GAAE,MAAiC,GAC5C,IAAI,CAQN;AAED,8DAA8D;AAC9D,wBAAgB,gCAAgC,CAAC,QAAQ,EAAE,cAAc,GAAG,IAAI,CAE/E"}
1
+ {"version":3,"file":"v1-to-v2.d.ts","sourceRoot":"","sources":["../../../src/agent-runtime/store-migrations/v1-to-v2.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAC;AA8N5D,8EAA8E;AAC9E,wBAAgB,+BAA+B,CAC7C,QAAQ,EAAE,cAAc,EACxB,UAAU,GAAE,MAAiC,GAC5C,IAAI,CAQN;AAED,8DAA8D;AAC9D,wBAAgB,gCAAgC,CAAC,QAAQ,EAAE,cAAc,GAAG,IAAI,CAE/E"}
@@ -6,14 +6,13 @@ import {
6
6
  composeAgentPrompt,
7
7
  createAgentRuntime,
8
8
  createAgentRuntimeEventSink
9
- } from "./index-33xckd7h.js";
9
+ } from "./index-35aefxby.js";
10
10
  import"./index-p13mwz16.js";
11
11
  import"./index-hkm6wysp.js";
12
12
  import {
13
13
  AgentModelDescriptorSchema
14
14
  } from "./index-tg3m2ec5.js";
15
- import"./index-y2s6h5bf.js";
16
- import"./index-vfgb58nf.js";
15
+ import"./index-z1m86vc8.js";
17
16
  import"./index-p1b1y93x.js";
18
17
  import"./index-wnfk50r0.js";
19
18
  import {
@@ -1,10 +1,10 @@
1
1
  import {
2
2
  createSqliteAgentRuntimeStore,
3
3
  initializeAgentRuntimeSqlite
4
- } from "./index-b7qwhbzq.js";
5
- import"./index-hber83mk.js";
6
- import"./index-y2s6h5bf.js";
7
- import"./index-vfgb58nf.js";
4
+ } from "./index-3yrqvza8.js";
5
+ import"./index-9553432s.js";
6
+ import"./index-5wezxcxx.js";
7
+ import"./index-z1m86vc8.js";
8
8
  import"./index-p1b1y93x.js";
9
9
 
10
10
  // src/agent-runtime-sqlite-bun.ts
@@ -1,10 +1,10 @@
1
1
  import {
2
2
  createSqliteAgentRuntimeStore,
3
3
  initializeAgentRuntimeSqlite
4
- } from "./index-b7qwhbzq.js";
5
- import"./index-hber83mk.js";
6
- import"./index-y2s6h5bf.js";
7
- import"./index-vfgb58nf.js";
4
+ } from "./index-3yrqvza8.js";
5
+ import"./index-9553432s.js";
6
+ import"./index-5wezxcxx.js";
7
+ import"./index-z1m86vc8.js";
8
8
  import"./index-p1b1y93x.js";
9
9
 
10
10
  // src/agent-runtime-sqlite-node.ts
@@ -1,19 +1,21 @@
1
1
  import {
2
- ACTIVE_AGENT_RUN_STATES,
3
- AgentAdmissionReceiptSchema,
4
2
  AgentConversationMessagePageSchema,
5
3
  AgentConversationPageSchema,
4
+ AgentConversationSummarySchema
5
+ } from "./index-9553432s.js";
6
+ import {
7
+ ACTIVE_AGENT_RUN_STATES,
8
+ AgentAdmissionReceiptSchema,
6
9
  AgentConversationPurgeInputSchema,
7
10
  AgentConversationPurgeResultSchema,
8
11
  AgentConversationPurgedError,
9
- AgentConversationSummarySchema,
10
12
  AgentHistoryMutationSchema,
11
13
  AgentRuntimeHeadSchema,
12
14
  AgentStoredRunSchema,
13
15
  createAgentRuntimeStore,
14
16
  createMemoryAgentRuntimeStore,
15
17
  purgeAgentConversation
16
- } from "./index-hber83mk.js";
18
+ } from "./index-5wezxcxx.js";
17
19
  import {
18
20
  AgentContextOverflowError,
19
21
  AgentProviderStreamCutError,
@@ -40,7 +42,7 @@ import {
40
42
  renderAgentStateSlots,
41
43
  repairedSearchCall,
42
44
  selectAgentHistory
43
- } from "./index-33xckd7h.js";
45
+ } from "./index-35aefxby.js";
44
46
  import"./index-p13mwz16.js";
45
47
  import {
46
48
  AgentAdmissionEventSchema,
@@ -76,10 +78,6 @@ import {
76
78
  searchAgentModelCatalog,
77
79
  validateAgentModelSnapshot
78
80
  } from "./index-tg3m2ec5.js";
79
- import {
80
- isAssistantHistoryEvidence,
81
- isCompleteAgentHistoryTurn
82
- } from "./index-y2s6h5bf.js";
83
81
  import {
84
82
  AcceptInputAndAssignRunSchema,
85
83
  AcquireAgentRunSchema,
@@ -107,8 +105,10 @@ import {
107
105
  canonicalAgentJson,
108
106
  decodeAgentConversationArchive,
109
107
  decodeAgentStoreEvent,
110
- encodeAgentConversationArchive
111
- } from "./index-vfgb58nf.js";
108
+ encodeAgentConversationArchive,
109
+ isAssistantHistoryEvidence,
110
+ isCompleteAgentHistoryTurn
111
+ } from "./index-z1m86vc8.js";
112
112
  import {
113
113
  AgentAssistantPlaceholderSchema,
114
114
  AgentControlPartSchema,
@@ -1771,6 +1771,17 @@ var ScheduleRowSchema = z9.object({
1771
1771
  created_at: z9.string(),
1772
1772
  updated_at: z9.string()
1773
1773
  });
1774
+ var ClaimRowSchema = z9.object({
1775
+ state: z9.enum(["scheduled", "cancelled", "completed"]),
1776
+ claim_until: z9.string().nullable()
1777
+ });
1778
+ var FinalizeRowSchema = z9.object({
1779
+ state: z9.enum(["scheduled", "cancelled", "completed"]),
1780
+ claim_owner: z9.string().nullable()
1781
+ });
1782
+ var StateRowSchema = z9.object({
1783
+ state: z9.enum(["scheduled", "cancelled", "completed"])
1784
+ });
1774
1785
  function parseSchedule(raw) {
1775
1786
  const row = ScheduleRowSchema.parse(raw);
1776
1787
  return AgentScheduleSchema.parse({
@@ -1828,11 +1839,24 @@ function createAgentScheduleService(input) {
1828
1839
  const owner = randomUUID2();
1829
1840
  const CLAIM_MS = 60000;
1830
1841
  let ticking;
1831
- const claim = (id, until, at) => input.sqlite.transaction(async (scope) => scope.database.prepare(`
1832
- UPDATE stitchkit_agent_runtime_schedules
1833
- SET claim_owner = ?, claim_until = ?, updated_at = ?
1834
- WHERE id = ? AND state = 'scheduled' AND (claim_until IS NULL OR claim_until < ?)
1835
- `).run(owner, until, at, id, at).changes === 1);
1842
+ const claim = (id, until, at) => input.sqlite.transaction(async (scope) => {
1843
+ const raw = scope.database.prepare(`
1844
+ SELECT state, claim_until FROM stitchkit_agent_runtime_schedules WHERE id = ?
1845
+ `).get(id);
1846
+ if (raw === null || raw === undefined)
1847
+ return false;
1848
+ const row = ClaimRowSchema.parse(raw);
1849
+ if (row.state !== "scheduled")
1850
+ return false;
1851
+ if (row.claim_until !== null && row.claim_until >= at)
1852
+ return false;
1853
+ scope.database.prepare(`
1854
+ UPDATE stitchkit_agent_runtime_schedules
1855
+ SET claim_owner = ?, claim_until = ?, updated_at = ?
1856
+ WHERE id = ?
1857
+ `).run(owner, until, at, id);
1858
+ return true;
1859
+ });
1836
1860
  const runTick = async () => {
1837
1861
  if (closed)
1838
1862
  return;
@@ -1886,12 +1910,21 @@ function createAgentScheduleService(input) {
1886
1910
  }
1887
1911
  const state = schedule.kind === "every" ? "scheduled" : "completed";
1888
1912
  await input.sqlite.transaction(async (scope) => {
1889
- const settled = scope.database.prepare(`
1913
+ const held = scope.database.prepare(`
1914
+ SELECT state, claim_owner FROM stitchkit_agent_runtime_schedules WHERE id = ?
1915
+ `).get(schedule.id);
1916
+ const settled = held !== null && held !== undefined && (() => {
1917
+ const row = FinalizeRowSchema.parse(held);
1918
+ return row.state === "scheduled" && row.claim_owner === owner;
1919
+ })();
1920
+ if (settled) {
1921
+ scope.database.prepare(`
1890
1922
  UPDATE stitchkit_agent_runtime_schedules
1891
1923
  SET state = ?, occurrence = ?, next_at = ?, updated_at = ?,
1892
1924
  claim_owner = NULL, claim_until = NULL
1893
- WHERE id = ? AND state = 'scheduled' AND claim_owner = ?
1894
- `).run(state, occurrence, nextAt, at, schedule.id, owner).changes === 1;
1925
+ WHERE id = ?
1926
+ `).run(state, occurrence, nextAt, at, schedule.id);
1927
+ }
1895
1928
  if (lateByMs > 0) {
1896
1929
  await scope.appendEvent({
1897
1930
  conversationId: schedule.conversationId,
@@ -1983,23 +2016,29 @@ function createAgentScheduleService(input) {
1983
2016
  const cancelSchedule = async (conversationId, id) => {
1984
2017
  const observedAt = now().toISOString();
1985
2018
  const changed = await input.sqlite.transaction(async (scope) => {
1986
- const count = scope.database.prepare(`
2019
+ const raw = scope.database.prepare(`
2020
+ SELECT state FROM stitchkit_agent_runtime_schedules
2021
+ WHERE id = ? AND conversation_id = ?
2022
+ `).get(id, conversationId);
2023
+ if (raw === null || raw === undefined)
2024
+ return false;
2025
+ if (StateRowSchema.parse(raw).state !== "scheduled")
2026
+ return false;
2027
+ scope.database.prepare(`
1987
2028
  UPDATE stitchkit_agent_runtime_schedules SET state = 'cancelled', updated_at = ?
1988
- WHERE id = ? AND conversation_id = ? AND state = 'scheduled'
1989
- `).run(observedAt, id, conversationId).changes;
1990
- if (count > 0) {
1991
- await scope.appendEvent({
1992
- conversationId,
1993
- kind: "schedule/cancelled",
1994
- occurredAt: observedAt,
1995
- payload: { id }
1996
- });
1997
- }
1998
- return count;
2029
+ WHERE id = ? AND conversation_id = ?
2030
+ `).run(observedAt, id, conversationId);
2031
+ await scope.appendEvent({
2032
+ conversationId,
2033
+ kind: "schedule/cancelled",
2034
+ occurredAt: observedAt,
2035
+ payload: { id }
2036
+ });
2037
+ return true;
1999
2038
  });
2000
- if (changed > 0)
2039
+ if (changed)
2001
2040
  arm();
2002
- return changed > 0;
2041
+ return changed;
2003
2042
  };
2004
2043
  return {
2005
2044
  scheduleInput,
@@ -9,13 +9,11 @@ import {
9
9
  advanceToolChronology,
10
10
  assistantStatus,
11
11
  canProjectToolChronology,
12
+ canonicalAgentJson,
12
13
  createToolChronology,
13
14
  isAssistantHistoryEvidence,
14
15
  isCompleteAgentHistoryTurn
15
- } from "./index-y2s6h5bf.js";
16
- import {
17
- canonicalAgentJson
18
- } from "./index-vfgb58nf.js";
16
+ } from "./index-z1m86vc8.js";
19
17
  import {
20
18
  AgentAssistantPlaceholderSchema,
21
19
  AgentJsonObjectSchema,
@@ -1,13 +1,15 @@
1
1
  import {
2
- AgentAdmissionReceiptSchema,
3
2
  AgentConversationMessagePageSchema,
4
- AgentConversationPageSchema,
3
+ AgentConversationPageSchema
4
+ } from "./index-9553432s.js";
5
+ import {
6
+ AgentAdmissionReceiptSchema,
5
7
  AgentConversationPurgedError,
6
8
  AgentHistoryMutationSchema,
7
9
  AgentRuntimeHeadSchema,
8
10
  AgentStoredRunSchema,
9
11
  createAgentRuntimeStore
10
- } from "./index-hber83mk.js";
12
+ } from "./index-5wezxcxx.js";
11
13
  import {
12
14
  AgentRecoverableDescriptorSchema,
13
15
  AgentRecoverablePageSchema,
@@ -16,7 +18,7 @@ import {
16
18
  AppendAgentStoreEventSchema,
17
19
  agentStoreEventDraft,
18
20
  canonicalAgentJson
19
- } from "./index-vfgb58nf.js";
21
+ } from "./index-z1m86vc8.js";
20
22
  import {
21
23
  AgentMessageSchema,
22
24
  AgentRunSchema
@@ -87,6 +89,10 @@ var PayloadRowSchema = z.object({
87
89
  payload: z.string(),
88
90
  terminal_assistant_payload: z.string().nullable().optional()
89
91
  });
92
+ var HistoryRowSchema = z.object({
93
+ payload: z.string(),
94
+ active: z.union([z.literal(0), z.literal(1)])
95
+ });
90
96
  var AdmissionRowSchema = z.object({
91
97
  conversation_id: z.string(),
92
98
  idempotency_key: z.string(),
@@ -202,7 +208,15 @@ function writeBaselines(database, migratedAt) {
202
208
  ) VALUES (?, 1, ?, 1, 'runtime/baseline', ?, 0, ?)
203
209
  `);
204
210
  for (const conversation of conversations) {
205
- const messages = database.prepare("SELECT payload FROM stitchkit_agent_runtime_messages WHERE conversation_id = ? AND active = 1 ORDER BY position").all(conversation.conversation_id).map((row) => AgentMessageSchema.parse(parseJson(PayloadRowSchema.parse(row).payload)));
211
+ const rows = database.prepare("SELECT payload, active FROM stitchkit_agent_runtime_messages WHERE conversation_id = ? ORDER BY position, rowid").all(conversation.conversation_id).map((row) => {
212
+ const parsed = HistoryRowSchema.parse(row);
213
+ return {
214
+ message: AgentMessageSchema.parse(parseJson(parsed.payload)),
215
+ active: parsed.active === 1
216
+ };
217
+ });
218
+ const messages = rows.map((row) => row.message);
219
+ const compacted = rows.filter((row) => !row.active).map((row) => row.message.id);
206
220
  const runs = database.prepare("SELECT payload, terminal_assistant_payload FROM stitchkit_agent_runtime_runs WHERE conversation_id = ? ORDER BY created_at, run_id").all(conversation.conversation_id).map((value) => {
207
221
  const row = PayloadRowSchema.parse(value);
208
222
  return {
@@ -234,6 +248,7 @@ function writeBaselines(database, migratedAt) {
234
248
  version: conversation.version
235
249
  },
236
250
  messages,
251
+ ...compacted.length > 0 && { compacted },
237
252
  runs,
238
253
  admissions
239
254
  }));
@@ -276,7 +291,9 @@ var ConversationHeadRowSchema = z2.object({
276
291
  });
277
292
  var CountRowSchema = z2.object({ count: z2.number().int().nonnegative() });
278
293
  var MessagePageRowSchema = z2.object({
294
+ row_id: z2.number().int().positive(),
279
295
  position: z2.number().int().nonnegative(),
296
+ active: z2.union([z2.literal(0), z2.literal(1)]),
280
297
  payload: z2.string()
281
298
  });
282
299
  var EventRowSchema = z2.object({
@@ -367,11 +384,16 @@ function conversationCursor(conversationId) {
367
384
  function parseConversationCursor(cursor) {
368
385
  return z2.tuple([z2.string().min(1)]).parse(parseJson2(cursor))[0];
369
386
  }
370
- function messageCursor(position) {
371
- return encodeJson([position]);
387
+ function messageCursor(position, rowId) {
388
+ return encodeJson([position, rowId]);
372
389
  }
373
390
  function parseMessageCursor(cursor) {
374
- return z2.tuple([z2.int().nonnegative()]).parse(parseJson2(cursor))[0];
391
+ const parsed = z2.union([
392
+ z2.tuple([z2.int().nonnegative(), z2.int().positive()]),
393
+ z2.tuple([z2.int().nonnegative()])
394
+ ]).parse(parseJson2(cursor));
395
+ const [position, rowId] = parsed;
396
+ return { position, ...rowId !== undefined && { rowId } };
375
397
  }
376
398
  function messagePreview(message) {
377
399
  const text = message.parts.find((part) => part.type === "text");
@@ -516,19 +538,16 @@ function createSqliteAgentRuntimeStore(config) {
516
538
  });
517
539
  },
518
540
  async compareAndSwap(transaction, input) {
519
- const result = transaction.prepare(`
541
+ const current = transaction.prepare("SELECT version FROM stitchkit_agent_runtime_heads WHERE conversation_id = ?").get(input.conversationId);
542
+ const actualVersion = missing(current) ? 0 : HeadRowSchema.parse(current).version;
543
+ if (actualVersion !== input.expectedVersion)
544
+ return { outcome: "conflict", actualVersion };
545
+ transaction.prepare(`
520
546
  INSERT INTO stitchkit_agent_runtime_heads (conversation_id, version)
521
547
  VALUES (?, ?)
522
548
  ON CONFLICT (conversation_id) DO UPDATE SET version = excluded.version
523
- WHERE stitchkit_agent_runtime_heads.version = ?
524
- `).run(input.conversationId, input.next.version, input.expectedVersion);
525
- if (result.changes === 1)
526
- return { outcome: "applied" };
527
- const current = transaction.prepare("SELECT version FROM stitchkit_agent_runtime_heads WHERE conversation_id = ?").get(input.conversationId);
528
- return {
529
- outcome: "conflict",
530
- actualVersion: missing(current) ? 0 : HeadRowSchema.parse(current).version
531
- };
549
+ `).run(input.conversationId, input.next.version);
550
+ return { outcome: "applied" };
532
551
  }
533
552
  },
534
553
  runs: {
@@ -873,14 +892,14 @@ function createSqliteAgentRuntimeStore(config) {
873
892
  const pageRows = rows.slice(0, input.limit);
874
893
  const items = pageRows.map((row) => {
875
894
  const latestRaw = database.prepare(`
876
- SELECT position, payload FROM stitchkit_agent_runtime_messages
895
+ SELECT payload FROM stitchkit_agent_runtime_messages
877
896
  WHERE conversation_id = ? AND active = 1
878
897
  ORDER BY position DESC LIMIT 1
879
898
  `).get(row.conversation_id);
880
899
  if (missing(latestRaw)) {
881
900
  throw new Error("Agent conversation head has no active history");
882
901
  }
883
- const latest = AgentMessageSchema.parse(parseJson2(MessagePageRowSchema.parse(latestRaw).payload));
902
+ const latest = AgentMessageSchema.parse(parseJson2(MessageRowSchema.parse(latestRaw).payload));
884
903
  const active = CountRowSchema.parse(database.prepare(`
885
904
  SELECT count(*) AS count FROM stitchkit_agent_runtime_runs
886
905
  WHERE conversation_id = ?
@@ -906,19 +925,28 @@ function createSqliteAgentRuntimeStore(config) {
906
925
  }
907
926
  const cursor = input.cursor ? parseMessageCursor(input.cursor) : undefined;
908
927
  const before = input.direction === "before";
928
+ const direction = before ? "DESC" : "ASC";
929
+ const comparison = cursor === undefined ? "" : cursor.rowId === undefined ? `AND position ${before ? "<" : ">"} ?` : `AND (position, rowid) ${before ? "<" : ">"} (?, ?)`;
909
930
  const rows = database.prepare(`
910
- SELECT position, payload FROM stitchkit_agent_runtime_messages
911
- WHERE conversation_id = ? AND active = 1
912
- ${cursor === undefined ? "" : before ? "AND position < ?" : "AND position > ?"}
913
- ORDER BY position ${before ? "DESC" : "ASC"} LIMIT ?
914
- `).all(input.conversationId, ...cursor === undefined ? [] : [cursor], input.limit + 1).map((row) => MessagePageRowSchema.parse(row));
931
+ SELECT rowid AS row_id, position, active, payload
932
+ FROM stitchkit_agent_runtime_messages
933
+ WHERE conversation_id = ?
934
+ ${input.includeCompacted === true ? "" : "AND active = 1"}
935
+ ${comparison}
936
+ ORDER BY position ${direction}, rowid ${direction} LIMIT ?
937
+ `).all(input.conversationId, ...cursor === undefined ? [] : cursor.rowId === undefined ? [cursor.position] : [cursor.position, cursor.rowId], input.limit + 1).map((row) => MessagePageRowSchema.parse(row));
915
938
  const hasMore = rows.length > input.limit;
916
939
  const pageRows = rows.slice(0, input.limit);
917
940
  const ordered = before ? [...pageRows].reverse() : pageRows;
918
941
  const boundary = pageRows.at(-1);
942
+ const items = ordered.map((row) => ({
943
+ message: AgentMessageSchema.parse(parseJson2(row.payload)),
944
+ active: row.active === 1
945
+ }));
919
946
  return AgentConversationMessagePageSchema.parse({
920
- items: ordered.map((row) => AgentMessageSchema.parse(parseJson2(row.payload))),
921
- ...hasMore && boundary ? { nextCursor: messageCursor(boundary.position) } : {}
947
+ items: items.map((entry) => entry.message),
948
+ compacted: items.filter((entry) => !entry.active).map((entry) => entry.message.id),
949
+ ...hasMore && boundary ? { nextCursor: messageCursor(boundary.position, boundary.row_id) } : {}
922
950
  });
923
951
  })
924
952
  },
@@ -1,6 +1,3 @@
1
- import {
2
- assistantStatus
3
- } from "./index-y2s6h5bf.js";
4
1
  import {
5
2
  AcceptInputAndAssignRunSchema,
6
3
  AcquireAgentRunSchema,
@@ -18,10 +15,11 @@ import {
18
15
  ReplaceCompactedRangeSchema,
19
16
  RequestRunInterruptSchema,
20
17
  agentStoreEventDraft,
18
+ assistantStatus,
21
19
  canonicalAgentJson,
22
20
  decodeAgentConversationArchive,
23
21
  encodeAgentConversationArchive
24
- } from "./index-vfgb58nf.js";
22
+ } from "./index-z1m86vc8.js";
25
23
  import {
26
24
  AgentMessageSchema,
27
25
  AgentRecordIdSchema,
@@ -61,27 +59,9 @@ async function purgeAgentConversation(store, input) {
61
59
  return AgentConversationPurgeResultSchema.parse(await store.purgeConversation(parsed));
62
60
  }
63
61
 
64
- // src/agent-runtime/conversations.ts
65
- import { z as z2 } from "zod";
66
- var AgentConversationSummarySchema = z2.object({
67
- conversationId: AgentRecordIdSchema,
68
- version: AgentRecordVersionSchema,
69
- updatedAt: z2.iso.datetime({ offset: true }),
70
- preview: z2.string(),
71
- activeRuns: z2.int().nonnegative()
72
- }).strict();
73
- var AgentConversationPageSchema = z2.object({
74
- items: z2.array(AgentConversationSummarySchema),
75
- nextCursor: z2.string().min(1).optional()
76
- }).strict();
77
- var AgentConversationMessagePageSchema = z2.object({
78
- items: z2.array(AgentMessageSchema),
79
- nextCursor: z2.string().min(1).optional()
80
- }).strict();
81
-
82
62
  // src/agent-runtime/store-driver.ts
83
63
  import { createHash } from "node:crypto";
84
- import { z as z3 } from "zod";
64
+ import { z as z2 } from "zod";
85
65
 
86
66
  // src/agent-runtime/store-purge.ts
87
67
  function createStoreConversationPurge(driver, conversations) {
@@ -107,39 +87,39 @@ function createStoreConversationPurge(driver, conversations) {
107
87
  }
108
88
 
109
89
  // src/agent-runtime/store-driver.ts
110
- var AgentRuntimeHeadSchema = z3.object({
111
- schemaVersion: z3.literal(1),
90
+ var AgentRuntimeHeadSchema = z2.object({
91
+ schemaVersion: z2.literal(1),
112
92
  conversationId: AgentRecordIdSchema,
113
93
  version: AgentRecordVersionSchema
114
94
  });
115
- var AgentStoredRunSchema = z3.object({
116
- schemaVersion: z3.literal(1),
95
+ var AgentStoredRunSchema = z2.object({
96
+ schemaVersion: z2.literal(1),
117
97
  run: AgentRunSchema,
118
98
  terminalAssistant: AgentMessageSchema.optional()
119
99
  });
120
- var AgentAdmissionReceiptSchema = z3.object({
121
- schemaVersion: z3.literal(1),
100
+ var AgentAdmissionReceiptSchema = z2.object({
101
+ schemaVersion: z2.literal(1),
122
102
  conversationId: AgentRecordIdSchema,
123
- idempotencyKey: z3.string().min(1),
103
+ idempotencyKey: z2.string().min(1),
124
104
  input: AgentMessageSchema,
125
105
  runId: AgentRecordIdSchema,
126
106
  assistantMessageId: AgentRecordIdSchema
127
107
  });
128
- var AgentHistoryMutationSchema = z3.discriminatedUnion("type", [
129
- z3.object({ type: z3.literal("admit"), input: AgentMessageSchema }),
130
- z3.object({
131
- type: z3.literal("upsert-assistant"),
108
+ var AgentHistoryMutationSchema = z2.discriminatedUnion("type", [
109
+ z2.object({ type: z2.literal("admit"), input: AgentMessageSchema }),
110
+ z2.object({
111
+ type: z2.literal("upsert-assistant"),
132
112
  message: AgentMessageSchema
133
113
  }),
134
- z3.object({
135
- type: z3.literal("replace-compacted-range"),
136
- replacedMessageIds: z3.array(AgentRecordIdSchema).min(1),
114
+ z2.object({
115
+ type: z2.literal("replace-compacted-range"),
116
+ replacedMessageIds: z2.array(AgentRecordIdSchema).min(1),
137
117
  summary: AgentMessageSchema
138
118
  })
139
119
  ]);
140
- var AgentRecoverableScanInputSchema = z3.object({
141
- cursor: z3.string().min(1).optional(),
142
- limit: z3.number().int().min(1).max(1000)
120
+ var AgentRecoverableScanInputSchema = z2.object({
121
+ cursor: z2.string().min(1).optional(),
122
+ limit: z2.number().int().min(1).max(1000)
143
123
  });
144
124
  function transitionRecord(operation) {
145
125
  if (operation.type !== "checkpoint")
@@ -262,7 +242,7 @@ function validateSnapshot(head, messages, records) {
262
242
  }
263
243
  }
264
244
  }
265
- var RecoverableCursorSchema = z3.tuple([AgentRecordIdSchema, AgentRecordIdSchema]);
245
+ var RecoverableCursorSchema = z2.tuple([AgentRecordIdSchema, AgentRecordIdSchema]);
266
246
  function recoverableCursor(input) {
267
247
  return JSON.stringify([input.conversationId, input.run.id]);
268
248
  }
@@ -738,7 +718,7 @@ function createAgentRuntimeStore(driver) {
738
718
  await driver.events.append(transaction, agentStoreEventDraft({
739
719
  conversationId,
740
720
  kind: "runtime/transition",
741
- payload: z3.json().parse(transitionRecord(operation))
721
+ payload: z2.json().parse(transitionRecord(operation))
742
722
  }));
743
723
  return { outcome: "applied", snapshot: reduced.snapshot };
744
724
  });
@@ -778,7 +758,7 @@ function createAgentRuntimeStore(driver) {
778
758
  conversationId,
779
759
  events,
780
760
  projections: [
781
- { archiveType: "runtime-snapshot", snapshot: z3.json().parse(snapshot) },
761
+ { archiveType: "runtime-snapshot", snapshot: z2.json().parse(snapshot) },
782
762
  ...durable.projections
783
763
  ],
784
764
  spills: durable.spills
@@ -1111,4 +1091,4 @@ function createMemoryAgentRuntimeStore() {
1111
1091
  return createAgentRuntimeStore(driver);
1112
1092
  }
1113
1093
 
1114
- export { AgentConversationPurgeInputSchema, AgentConversationPurgeResultSchema, AgentConversationPurgedError, purgeAgentConversation, AgentConversationSummarySchema, AgentConversationPageSchema, AgentConversationMessagePageSchema, AgentRuntimeHeadSchema, AgentStoredRunSchema, AgentAdmissionReceiptSchema, AgentHistoryMutationSchema, ACTIVE_AGENT_RUN_STATES, createAgentRuntimeStore, createMemoryAgentRuntimeStore };
1094
+ export { AgentConversationPurgeInputSchema, AgentConversationPurgeResultSchema, AgentConversationPurgedError, purgeAgentConversation, AgentRuntimeHeadSchema, AgentStoredRunSchema, AgentAdmissionReceiptSchema, AgentHistoryMutationSchema, ACTIVE_AGENT_RUN_STATES, createAgentRuntimeStore, createMemoryAgentRuntimeStore };
@@ -0,0 +1,26 @@
1
+ import {
2
+ AgentMessageSchema,
3
+ AgentRecordIdSchema,
4
+ AgentRecordVersionSchema
5
+ } from "./index-p1b1y93x.js";
6
+
7
+ // src/agent-runtime/conversations.ts
8
+ import { z } from "zod";
9
+ var AgentConversationSummarySchema = z.object({
10
+ conversationId: AgentRecordIdSchema,
11
+ version: AgentRecordVersionSchema,
12
+ updatedAt: z.iso.datetime({ offset: true }),
13
+ preview: z.string(),
14
+ activeRuns: z.int().nonnegative()
15
+ }).strict();
16
+ var AgentConversationPageSchema = z.object({
17
+ items: z.array(AgentConversationSummarySchema),
18
+ nextCursor: z.string().min(1).optional()
19
+ }).strict();
20
+ var AgentConversationMessagePageSchema = z.object({
21
+ items: z.array(AgentMessageSchema),
22
+ compacted: z.array(z.string().min(1)),
23
+ nextCursor: z.string().min(1).optional()
24
+ }).strict();
25
+
26
+ export { AgentConversationSummarySchema, AgentConversationPageSchema, AgentConversationMessagePageSchema };
@@ -9,6 +9,88 @@ import {
9
9
  AgentUsageSchema
10
10
  } from "./index-p1b1y93x.js";
11
11
 
12
+ // src/agent-runtime/history-chronology.ts
13
+ function createToolChronology() {
14
+ return { calls: new Map, approvals: new Map, pending: 0, resultsStarted: false };
15
+ }
16
+ function advanceToolChronology(previous, parts) {
17
+ const calls = new Map(previous.calls);
18
+ const approvals = new Map(previous.approvals);
19
+ let { pending, resultsStarted } = previous;
20
+ for (const part of parts) {
21
+ if (part.type === "tool-call") {
22
+ if (calls.has(part.callId) || resultsStarted && pending > 0)
23
+ return;
24
+ if (pending === 0)
25
+ resultsStarted = false;
26
+ calls.set(part.callId, { toolName: part.toolName, phase: "called" });
27
+ pending += 1;
28
+ } else if (part.type === "tool-approval-request") {
29
+ const call = calls.get(part.callId);
30
+ if (call?.phase !== "called" || approvals.has(part.approvalId))
31
+ return;
32
+ approvals.set(part.approvalId, part.callId);
33
+ calls.set(part.callId, { ...call, phase: "requested" });
34
+ } else if (part.type === "tool-approval-response") {
35
+ const callId = approvals.get(part.approvalId);
36
+ const call = callId === undefined ? undefined : calls.get(callId);
37
+ if (callId === undefined || !call || call.phase !== "requested")
38
+ return;
39
+ calls.set(callId, { ...call, phase: part.approved ? "approved" : "denied" });
40
+ } else if (part.type === "tool-result") {
41
+ const call = calls.get(part.callId);
42
+ if (!call || call.toolName !== part.toolName || call.phase === "result" || call.phase === "requested" || call.phase === "denied" && part.outcome === "success")
43
+ return;
44
+ calls.set(part.callId, { ...call, phase: "result" });
45
+ pending -= 1;
46
+ resultsStarted = true;
47
+ }
48
+ }
49
+ return { calls, approvals, pending, resultsStarted };
50
+ }
51
+ function canProjectToolChronology(state) {
52
+ return [...state.calls.values()].every(({ phase }) => phase !== "called");
53
+ }
54
+
55
+ // src/agent-runtime/terminal-status.ts
56
+ function isSpeakableAssistantStatus(status) {
57
+ return status === "completed" || status === "interrupted" || status === "committed";
58
+ }
59
+ function isAssistantHistoryEvidence(status, policy) {
60
+ return isSpeakableAssistantStatus(status) || status === "failed" && policy?.failedAssistant === "assistant-marked";
61
+ }
62
+ function isCompleteAgentHistoryTurn(messages, policy) {
63
+ if (messages[0]?.role !== "user")
64
+ return false;
65
+ let chronology = createToolChronology();
66
+ let assistantCount = 0;
67
+ for (const message of messages) {
68
+ if (message.role === "assistant") {
69
+ if (!isAssistantHistoryEvidence(message.status, policy))
70
+ return false;
71
+ assistantCount += 1;
72
+ }
73
+ const next = advanceToolChronology(chronology, message.parts);
74
+ if (!next)
75
+ return false;
76
+ chronology = next;
77
+ }
78
+ return assistantCount > 0 && chronology.pending === 0;
79
+ }
80
+ function assistantStatus(reason) {
81
+ if (reason === "success" || reason === "policy_stop" || reason === "provider_stop") {
82
+ return "completed";
83
+ }
84
+ if (reason === "superseded")
85
+ return "superseded";
86
+ if (reason === "absorbed")
87
+ return "superseded";
88
+ if (reason === "interrupted" || reason === "cancelled" || reason === "shutdown") {
89
+ return "interrupted";
90
+ }
91
+ return "failed";
92
+ }
93
+
12
94
  // src/agent-runtime/store.ts
13
95
  import { z } from "zod";
14
96
  var AgentStoreConflictSchema = z.object({
@@ -249,4 +331,4 @@ function decodeAgentConversationArchive(bytes) {
249
331
  return archive;
250
332
  }
251
333
 
252
- export { AgentStoreConflictSchema, AgentStoreNotFoundSchema, AgentStoreAppliedSchema, AgentStoreDuplicateSchema, AgentStoreMutationResultSchema, AgentRunViewSchema, AcceptInputAndAssignRunSchema, AcquireAgentRunSchema, CheckpointRunAssistantSchema, RecordRunOperationSchema, CommitRunTerminalSchema, RequestRunInterruptSchema, RecoverAgentRunSchema, ReplaceCompactedRangeSchema, AgentRecoverableDescriptorSchema, AgentRecoverablePageSchema, AgentStoreEventKindSchema, AgentStoreTransitionSchema, AgentStoreEventEnvelopeSchema, AppendAgentStoreEventSchema, ReadAgentStoreEventsSchema, AgentStoreEventPageSchema, decodeAgentStoreEvent, agentStoreEventDraft, canonicalAgentJson, AgentConversationArchiveSchema, encodeAgentConversationArchive, decodeAgentConversationArchive };
334
+ export { createToolChronology, advanceToolChronology, canProjectToolChronology, isAssistantHistoryEvidence, isCompleteAgentHistoryTurn, assistantStatus, AgentStoreConflictSchema, AgentStoreNotFoundSchema, AgentStoreAppliedSchema, AgentStoreDuplicateSchema, AgentStoreMutationResultSchema, AgentRunViewSchema, AcceptInputAndAssignRunSchema, AcquireAgentRunSchema, CheckpointRunAssistantSchema, RecordRunOperationSchema, CommitRunTerminalSchema, RequestRunInterruptSchema, RecoverAgentRunSchema, ReplaceCompactedRangeSchema, AgentRecoverableDescriptorSchema, AgentRecoverablePageSchema, AgentStoreEventKindSchema, AgentStoreTransitionSchema, AgentStoreEventEnvelopeSchema, AppendAgentStoreEventSchema, ReadAgentStoreEventsSchema, AgentStoreEventPageSchema, decodeAgentStoreEvent, agentStoreEventDraft, canonicalAgentJson, AgentConversationArchiveSchema, encodeAgentConversationArchive, decodeAgentConversationArchive };
@@ -15,6 +15,19 @@ export type SqliteValue = string | number | bigint | null | Uint8Array;
15
15
  export interface SqliteStatement {
16
16
  get(...parameters: SqliteValue[]): unknown;
17
17
  all(...parameters: SqliteValue[]): readonly unknown[];
18
+ /**
19
+ * `changes` is the driver's own count and is advisory: never decide
20
+ * correctness by it.
21
+ *
22
+ * Because this boundary is satisfied structurally by a raw handle, the
23
+ * number is whatever that driver reports. `bun:sqlite` includes rows written
24
+ * by an AFTER INSERT trigger and by FTS5's deferred index flush during the
25
+ * same statement, so a statement that moved one row can report five, and one
26
+ * that moved none can report more than zero. A guarded `UPDATE`/upsert whose
27
+ * outcome is read back from `changes` is therefore not a compare-and-swap.
28
+ * Read the row, compare, then write unconditionally — inside the store's
29
+ * `BEGIN IMMEDIATE` transaction that is atomic.
30
+ */
18
31
  run(...parameters: SqliteValue[]): {
19
32
  changes: number;
20
33
  };
@@ -1 +1 @@
1
- {"version":3,"file":"sqlite.d.ts","sourceRoot":"","sources":["../../src/internal/sqlite.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AACH,MAAM,MAAM,WAAW,GAAG,MAAM,GAAG,MAAM,GAAG,MAAM,GAAG,IAAI,GAAG,UAAU,CAAC;AAEvE,MAAM,WAAW,eAAe;IAC9B,GAAG,CAAC,GAAG,UAAU,EAAE,WAAW,EAAE,GAAG,OAAO,CAAC;IAC3C,GAAG,CAAC,GAAG,UAAU,EAAE,WAAW,EAAE,GAAG,SAAS,OAAO,EAAE,CAAC;IACtD,GAAG,CAAC,GAAG,UAAU,EAAE,WAAW,EAAE,GAAG;QAAE,OAAO,EAAE,MAAM,CAAA;KAAE,CAAC;CACxD;AAED,MAAM,WAAW,cAAc;IAC7B,IAAI,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,OAAO,CAAC,GAAG,EAAE,MAAM,GAAG,eAAe,CAAC;IACtC,KAAK,IAAI,IAAI,CAAC;CACf"}
1
+ {"version":3,"file":"sqlite.d.ts","sourceRoot":"","sources":["../../src/internal/sqlite.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AACH,MAAM,MAAM,WAAW,GAAG,MAAM,GAAG,MAAM,GAAG,MAAM,GAAG,IAAI,GAAG,UAAU,CAAC;AAEvE,MAAM,WAAW,eAAe;IAC9B,GAAG,CAAC,GAAG,UAAU,EAAE,WAAW,EAAE,GAAG,OAAO,CAAC;IAC3C,GAAG,CAAC,GAAG,UAAU,EAAE,WAAW,EAAE,GAAG,SAAS,OAAO,EAAE,CAAC;IACtD;;;;;;;;;;;;OAYG;IACH,GAAG,CAAC,GAAG,UAAU,EAAE,WAAW,EAAE,GAAG;QAAE,OAAO,EAAE,MAAM,CAAA;KAAE,CAAC;CACxD;AAED,MAAM,WAAW,cAAc;IAC7B,IAAI,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,OAAO,CAAC,GAAG,EAAE,MAAM,GAAG,eAAe,CAAC;IACtC,KAAK,IAAI,IAAI,CAAC;CACf"}
@@ -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;AAGhE;;;;;;;;;GASG;AACH,MAAM,WAAW,4BAA4B;IAC3C;;;;OAIG;IACH,QAAQ,CAAC,eAAe,EAAE,SAAS,MAAM,EAAE,CAAC;CAC7C;AAED,MAAM,WAAW,2BAA2B;IAC1C;;;;OAIG;IACH,WAAW,CACT,OAAO,EAAE,4BAA4B,GACpC,iBAAiB,GAAG,OAAO,CAAC,iBAAiB,CAAC,CAAC;IAClD;;;;;;;;OAQG;IACH,OAAO,CAAC,CAAC,OAAO,EAAE,4BAA4B,GAAG,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CACvE;AAwCD,gFAAgF;AAChF,wBAAsB,wBAAwB,CAC5C,MAAM,EAAE,2BAA2B,GAClC,OAAO,CAAC,IAAI,CAAC,CAmCf"}
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;AAIhE;;;;;;;;;GASG;AACH,MAAM,WAAW,4BAA4B;IAC3C;;;;OAIG;IACH,QAAQ,CAAC,eAAe,EAAE,SAAS,MAAM,EAAE,CAAC;CAC7C;AAED,MAAM,WAAW,2BAA2B;IAC1C;;;;OAIG;IACH,WAAW,CACT,OAAO,EAAE,4BAA4B,GACpC,iBAAiB,GAAG,OAAO,CAAC,iBAAiB,CAAC,CAAC;IAClD;;;;;;;;OAQG;IACH,OAAO,CAAC,CAAC,OAAO,EAAE,4BAA4B,GAAG,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CACvE;AAwCD,gFAAgF;AAChF,wBAAsB,wBAAwB,CAC5C,MAAM,EAAE,2BAA2B,GAClC,OAAO,CAAC,IAAI,CAAC,CAoCf"}
package/dist/testing.js CHANGED
@@ -4,6 +4,9 @@ import {
4
4
  import"./index-2k4yrqkc.js";
5
5
  import"./index-3gye9wzb.js";
6
6
  import"./index-3z73fh2c.js";
7
+ import {
8
+ createMemoryAgentRuntimeStore
9
+ } from "./index-5wezxcxx.js";
7
10
  import {
8
11
  createAgentRaceBarrier,
9
12
  createAgentRaceDriver,
@@ -11,7 +14,7 @@ import {
11
14
  } from "./index-cnyk6te3.js";
12
15
  import {
13
16
  decodeAgentConversationArchive
14
- } from "./index-vfgb58nf.js";
17
+ } from "./index-z1m86vc8.js";
15
18
  import {
16
19
  AgentMessageSchema,
17
20
  AgentRunSchema
@@ -93,7 +96,8 @@ async function runAgentStoreConformance(config) {
93
96
  `${run}-causal-history`,
94
97
  `${run}-causal-active`,
95
98
  `${run}-interrupt-priority`,
96
- `${run}-ledger`
99
+ `${run}-ledger`,
100
+ `${run}-archive`
97
101
  ]
98
102
  };
99
103
  const store = await config.createStore(context);
@@ -121,10 +125,13 @@ async function conformanceScenario(store, conversationIds) {
121
125
  causalHistoryConversationId,
122
126
  causalActiveConversationId,
123
127
  interruptPriorityConversationId,
124
- ledgerConversationId
128
+ ledgerConversationId,
129
+ archiveConversationId
125
130
  ] = conversationIds;
126
131
  if (ledgerConversationId)
127
132
  await ledgerScenario(store, ledgerConversationId);
133
+ if (archiveConversationId)
134
+ await archiveScenario(store, archiveConversationId);
128
135
  if (!conversationId || !recoveryConversationId || !absorbConversationId || !causalHistoryConversationId || !causalActiveConversationId || !interruptPriorityConversationId) {
129
136
  throw new Error("Agent store conformance requires six conversation identities");
130
137
  }
@@ -819,6 +826,34 @@ async function ledgerScenario(store, conversationId) {
819
826
  throw new Error(`Agent store conformance expected 22 archived events, received ${decoded.events.length}`);
820
827
  }
821
828
  }
829
+ async function archiveScenario(store, conversationId) {
830
+ const origin = createMemoryAgentRuntimeStore();
831
+ const inputMessage = userMessage(conversationId, "archived-input");
832
+ const run = queuedRun(conversationId, inputMessage.id, "archived-run");
833
+ requireOutcome(await origin.acceptInputAndAssignRun({
834
+ idempotencyKey: "archived-request",
835
+ input: inputMessage,
836
+ run
837
+ }), "applied");
838
+ await origin.appendEvent({
839
+ conversationId,
840
+ kind: "state/set",
841
+ payload: { name: "topic", value: "archive" }
842
+ });
843
+ const imported = await store.importConversation(await origin.exportConversation(conversationId));
844
+ if (imported.conversationId !== conversationId || imported.events !== 2) {
845
+ throw new Error(`Agent store conformance expected 2 imported events for ${conversationId}, received ${imported.events} for ${imported.conversationId}`);
846
+ }
847
+ const restored = await store.loadSnapshot(conversationId);
848
+ const expected = await origin.loadSnapshot(conversationId);
849
+ if (restored.version !== expected.version || restored.messages.map((message) => message.id).join(",") !== expected.messages.map((message) => message.id).join(",") || restored.runs.map((entry) => `${entry.id}:${entry.state}`).join(",") !== expected.runs.map((entry) => `${entry.id}:${entry.state}`).join(",")) {
850
+ throw new Error(`Agent store conformance expected the imported snapshot to match its archive, received version ${restored.version} with ${restored.messages.length} messages and ${restored.runs.length} runs`);
851
+ }
852
+ const events = await store.readEvents({ conversationId, limit: 10 });
853
+ if (events.items.map((event) => event.kind).join(",") !== "runtime/transition,state/set") {
854
+ throw new Error(`Agent store conformance expected the imported ledger to keep its kinds, received ${events.items.map((event) => event.kind).join(",")}`);
855
+ }
856
+ }
822
857
  // src/testing/managed-resource-conformance-contract.ts
823
858
  import { z } from "zod";
824
859
  var ManagedResourceConformanceScenarioIdSchema = z.enum([
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 31 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 32 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 17 of the 31 minors since 0.56.2, most recently 0.86.0_ | optional durable conversation/run loop, history, models, prompts, fencing and events |
70
+ | `stitchkit/agent-runtime` | server | evolving<br>_redefined in 18 of the 32 minors since 0.56.2, most recently 0.87.0_ | optional durable conversation/run loop, history, models, prompts, fencing and events |
71
71
  | `stitchkit/agent-runtime/testing` | tests on Bun or Node | evolving | credential-free replay, scripted provider faults and deterministic race controls |
72
72
  | `stitchkit/agent-runtime/harness` | server | evolving | resource-aware process-local facade over the canonical Agent runtime; supervision stays outside |
73
73
  | `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 |
@@ -76,7 +76,7 @@ own, recorded as an ADR.
76
76
  | `stitchkit/agent-runtime/sqlite/bun` | server (Bun) | evolving | durable built-in SQLite store for the agent runtime |
77
77
  | `stitchkit/agent-runtime/sqlite/node` | server (Node ≥ 22.5) | evolving | durable built-in SQLite store for the agent runtime |
78
78
  | `stitchkit-tui` | terminal (Bun) | evolving | optional official OpenTUI host over a caller-composed headless runtime |
79
- | `stitchkit/application` | browser + server | evolving<br>_redefined in 7 of the 31 minors since 0.56.2, most recently 0.83.0_ | managed resource graph, readiness, admission, schedules, subtree restart and bounded shutdown |
79
+ | `stitchkit/application` | browser + server | evolving<br>_redefined in 7 of the 32 minors since 0.56.2, most recently 0.83.0_ | managed resource graph, readiness, admission, schedules, subtree restart and bounded shutdown |
80
80
  | `stitchkit/application/grammy` | server | evolving | isolated grammY polling and webhook lifecycle adapters |
81
81
  | `stitchkit/application/opentelemetry` | server | evolving | maps application snapshots onto an injected OpenTelemetry `Meter` |
82
82
  | `stitchkit/application/schemas` | browser + server | evolving | the application's snapshot, health and shutdown schemas alone, without the kernel |
@@ -4855,8 +4855,14 @@ and supplies atomicity; Stitchkit owns transition validation and revision
4855
4855
  arithmetic. The executable reference is
4856
4856
  [`examples/agent-store-prisma/adapter.ts`](../../examples/agent-store-prisma/adapter.ts).
4857
4857
  `compareAndSwap` returns either `{ outcome: 'applied' }` or
4858
- `{ outcome: 'conflict', actualVersion }`. The head contains only schema version,
4859
- conversation identity and monotonic version. Runs and admission receipts are normalized records;
4858
+ `{ outcome: 'conflict', actualVersion }`. Decide that outcome by reading the
4859
+ version, comparing it and then writing unconditionally inside the transaction
4860
+ the adapter supplies, that is atomic. A conditional upsert is the shorter thing
4861
+ to write and it is wrong twice: `ON CONFLICT ... WHERE` guards the update branch
4862
+ only, so the first write to a conversation applies whatever `expectedVersion` it
4863
+ was given, and the affected-row count that would report the outcome is the
4864
+ driver's, which need not be the rows the statement moved. The head contains only
4865
+ schema version, conversation identity and monotonic version. Runs and admission receipts are normalized records;
4860
4866
  recovery queries active run states directly instead of maintaining a second projection.
4861
4867
  An admission receipt retains its canonical input, and a terminal run retains its canonical
4862
4868
  assistant, so physical product-history compaction cannot break idempotent retries.
@@ -4929,6 +4935,31 @@ companions — `createSqliteAgentProjectionStore`, `createSqliteAgentSpillStore`
4929
4935
  and write their rows and events through it, so a row and its event land
4930
4936
  together or not at all.
4931
4937
 
4938
+ `conversations.messages` pages the model's history: what compaction removed is
4939
+ not in it, which is the point of compaction. A person reading back their own
4940
+ conversation needs the other view, and asks for it explicitly:
4941
+
4942
+ ```ts
4943
+ const page = await conversations.messages({
4944
+ conversationId,
4945
+ limit: 50,
4946
+ direction: 'after',
4947
+ includeCompacted: true,
4948
+ })
4949
+ const removed = new Set(page.compacted)
4950
+ ```
4951
+
4952
+ `items` stays one sequence in the order it happened, and `compacted` names the
4953
+ ids inside it that the model no longer sees — the boundary is a mark on the
4954
+ conversation, not a second list. A compaction summary sits at the position of
4955
+ the first message it replaced, so it appears at the head of the block it
4956
+ stands for. Without the flag the page is exactly what it was, `compacted`
4957
+ empty. The v1 → v2 migration baseline records the same thing: the whole
4958
+ sequence, with `compacted` naming what had been folded away. A file migrated
4959
+ by 0.86.0 has a baseline built from the active history only — the messages
4960
+ themselves are still in the store and this read reaches them, but that one
4961
+ event cannot be rewritten after the fact.
4962
+
4932
4963
  ### Retried provider streams
4933
4964
 
4934
4965
  With `loop.retry` set, a provider stream that fails before any tool call in the
@@ -11196,6 +11227,24 @@ of the range if you want a different one.
11196
11227
  So upgrading is: read the `### ⚠️ Breaking changes` of every version *above* your
11197
11228
  current one *up to* your target, and apply each snippet.
11198
11229
 
11230
+ ## Released migration: 0.87.0
11231
+
11232
+ Only if your project implements `AgentConversationReader` itself. A message page
11233
+ now names which of its messages compaction removed:
11234
+
11235
+ ```ts
11236
+ // before
11237
+ return { items, ...(nextCursor && { nextCursor }) }
11238
+
11239
+ // after
11240
+ return { items, compacted: [], ...(nextCursor && { nextCursor }) }
11241
+ ```
11242
+
11243
+ An empty array is the honest answer for a reader that pages only the active
11244
+ history; it is what the SQLite reader returns unless the caller asks for
11245
+ `includeCompacted`. Calling the reader needs no change, and no data migration is
11246
+ involved.
11247
+
11199
11248
  ## Released migration: 0.86.0
11200
11249
 
11201
11250
  Only if you branch on `AgentTerminalReason` or show it to a person. Three
@@ -15437,7 +15486,7 @@ Server-only optional application runtime. See the
15437
15486
  | `searchAgentModelCatalog` | function | deterministic bounded search over a loaded canonical catalog |
15438
15487
  | `AgentModelSelectionSchema` / `AgentModelSelection` / `AgentModelSelectionStore` | schema / _type_ | durable per-conversation model choice; runtime resolvers receive run and snapshot to recover the model pinned to input metadata |
15439
15488
  | `createMemoryAgentModelSelectionStore` | function | process-local selection reference adapter |
15440
- | `AgentConversationReader` | _type_ | optional bounded conversation-summary and message-history reader; not part of the required runtime store contract |
15489
+ | `AgentConversationReader` | _type_ | optional bounded conversation-summary and message-history reader; `messages` takes `includeCompacted` and every page names its `compacted` ids; not part of the required runtime store contract |
15441
15490
  | `AgentConversationSummarySchema` / `AgentConversationSummary` | schema / _type_ | bounded durable conversation list item with version, activity and preview |
15442
15491
  | `AgentConversationPageSchema` / `AgentConversationPage` | schema / _type_ | cursor-paged conversation summaries |
15443
15492
  | `AgentConversationMessagePageSchema` / `AgentConversationMessagePage` | schema / _type_ | cursor-paged durable message history |
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "stitchkit",
3
- "version": "0.86.0",
3
+ "version": "0.87.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",
@@ -1,83 +0,0 @@
1
- // src/agent-runtime/history-chronology.ts
2
- function createToolChronology() {
3
- return { calls: new Map, approvals: new Map, pending: 0, resultsStarted: false };
4
- }
5
- function advanceToolChronology(previous, parts) {
6
- const calls = new Map(previous.calls);
7
- const approvals = new Map(previous.approvals);
8
- let { pending, resultsStarted } = previous;
9
- for (const part of parts) {
10
- if (part.type === "tool-call") {
11
- if (calls.has(part.callId) || resultsStarted && pending > 0)
12
- return;
13
- if (pending === 0)
14
- resultsStarted = false;
15
- calls.set(part.callId, { toolName: part.toolName, phase: "called" });
16
- pending += 1;
17
- } else if (part.type === "tool-approval-request") {
18
- const call = calls.get(part.callId);
19
- if (call?.phase !== "called" || approvals.has(part.approvalId))
20
- return;
21
- approvals.set(part.approvalId, part.callId);
22
- calls.set(part.callId, { ...call, phase: "requested" });
23
- } else if (part.type === "tool-approval-response") {
24
- const callId = approvals.get(part.approvalId);
25
- const call = callId === undefined ? undefined : calls.get(callId);
26
- if (callId === undefined || !call || call.phase !== "requested")
27
- return;
28
- calls.set(callId, { ...call, phase: part.approved ? "approved" : "denied" });
29
- } else if (part.type === "tool-result") {
30
- const call = calls.get(part.callId);
31
- if (!call || call.toolName !== part.toolName || call.phase === "result" || call.phase === "requested" || call.phase === "denied" && part.outcome === "success")
32
- return;
33
- calls.set(part.callId, { ...call, phase: "result" });
34
- pending -= 1;
35
- resultsStarted = true;
36
- }
37
- }
38
- return { calls, approvals, pending, resultsStarted };
39
- }
40
- function canProjectToolChronology(state) {
41
- return [...state.calls.values()].every(({ phase }) => phase !== "called");
42
- }
43
-
44
- // src/agent-runtime/terminal-status.ts
45
- function isSpeakableAssistantStatus(status) {
46
- return status === "completed" || status === "interrupted" || status === "committed";
47
- }
48
- function isAssistantHistoryEvidence(status, policy) {
49
- return isSpeakableAssistantStatus(status) || status === "failed" && policy?.failedAssistant === "assistant-marked";
50
- }
51
- function isCompleteAgentHistoryTurn(messages, policy) {
52
- if (messages[0]?.role !== "user")
53
- return false;
54
- let chronology = createToolChronology();
55
- let assistantCount = 0;
56
- for (const message of messages) {
57
- if (message.role === "assistant") {
58
- if (!isAssistantHistoryEvidence(message.status, policy))
59
- return false;
60
- assistantCount += 1;
61
- }
62
- const next = advanceToolChronology(chronology, message.parts);
63
- if (!next)
64
- return false;
65
- chronology = next;
66
- }
67
- return assistantCount > 0 && chronology.pending === 0;
68
- }
69
- function assistantStatus(reason) {
70
- if (reason === "success" || reason === "policy_stop" || reason === "provider_stop") {
71
- return "completed";
72
- }
73
- if (reason === "superseded")
74
- return "superseded";
75
- if (reason === "absorbed")
76
- return "superseded";
77
- if (reason === "interrupted" || reason === "cancelled" || reason === "shutdown") {
78
- return "interrupted";
79
- }
80
- return "failed";
81
- }
82
-
83
- export { createToolChronology, advanceToolChronology, canProjectToolChronology, isAssistantHistoryEvidence, isCompleteAgentHistoryTurn, assistantStatus };