@guuey/threads 0.29.0 → 0.31.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.
@@ -20,13 +20,37 @@ export interface RowCtx {
20
20
  untrustedOrigin?: boolean;
21
21
  }
22
22
  /**
23
- * Plain-text projection of an AgMessage. Distinct text blocks are distinct
24
- * paragraphs, so they join with a paragraph break — the same block-boundary
25
- * contract as `@guuey/agent-client`'s live flat-text fold (guuey#98); raw
26
- * concatenation jammed "…instead.Here's your packing…". Empty blocks
27
- * contribute nothing (no stacked separators).
23
+ * The ANSWER as plain text: the message's text blocks WITHOUT interim
24
+ * narration. Distinct text blocks are distinct paragraphs, so they join with
25
+ * a paragraph break, the same block-boundary contract as `@guuey/agent-client`'s
26
+ * live flat-text fold (guuey#98); raw concatenation jammed "…instead.Here's
27
+ * your packing…". Empty blocks contribute nothing (no stacked separators).
28
+ * This is the row's `text`: what a reloaded thread, a preview and a hook read.
28
29
  */
29
30
  export declare function messageText(msg: AgMessage): string;
31
+ /** The message's interim narration, one entry per non-empty narration block, in order. */
32
+ export declare function messageNarration(msg: AgMessage): string[];
33
+ /**
34
+ * EVERY text block, narration included, joined as the answer text was joined
35
+ * before narration split out of it: the MODEL's view of a stored message.
36
+ * `loadHistory` replays this, so the model's conversation history is the same
37
+ * bytes whether a row was written before the split or after it.
38
+ */
39
+ export declare function messageFullText(msg: AgMessage): string;
40
+ /** A stored message's text, split: the answer, the narration, and everything (the model's view). */
41
+ export interface StoredTextParts {
42
+ answer: string;
43
+ narration: string[];
44
+ all: string;
45
+ }
46
+ /**
47
+ * Split a stored row's `content` into answer, narration and the model's full
48
+ * text, when `content` is a stored `AgMessage` (an object, or the JSON string
49
+ * an AppSync writer stores). Anything else is null, and a reader falls back to
50
+ * the row's `text`. It is read through core's lenient stored-record reader, so
51
+ * an unknown block is omitted rather than failing the row.
52
+ */
53
+ export declare function storedTextParts(content: unknown): StoredTextParts | null;
30
54
  export declare function agMessageToRow(msg: AgMessage, ctx: RowCtx & {
31
55
  turnRecord?: AgTurnRecord;
32
56
  }): ThreadMessageRow;
@@ -102,6 +126,39 @@ export declare function splitStoredThreadMemory(stored: readonly JsonValue[]): P
102
126
  * turn, so write order stays time order.
103
127
  */
104
128
  export declare function joinStoredThreadMemory(row: Pick<ThreadSnapshotRow, "threadMemory" | "carriedThreadMemory">): JsonValue[];
129
+ /**
130
+ * The thread records a turn persists: each record of the fold, except that a
131
+ * record the turn did NOT change is persisted as the PRIOR stored record
132
+ * instead of the fold's rebuild of it (guuey#1669).
133
+ *
134
+ * The prior view comes from the stored-record reader, whose elements are raw
135
+ * copies that keep members this runtime does not know. The fold cannot
136
+ * carry those: {@link seedEventsForReducer} rebuilds each record as a
137
+ * `memory.write` from its known fields, and the reducer keeps only those. So
138
+ * persisting the fold alone would drop a record's unknown members on the first
139
+ * turn that re-persists the thread, even though the turn never touched it. A
140
+ * record keeps what it was stored with until a turn changes it.
141
+ *
142
+ * - **Identity** is the reducer's: scope plus `key ?? ""`. A fold record meets
143
+ * the LAST prior record of that identity, the one the seed left standing,
144
+ * because the last write wins. Several key-less records of one scope were
145
+ * already collapsed to that one by the reducer before this existed; that
146
+ * limit predates the overlay and is not papered over here.
147
+ * - **Unchanged** means the known fields a turn can write are deep-equal:
148
+ * `scope`, `key`, `value`, `reason`, `durable`, `turnId` and `_meta`
149
+ * (draft.8: a `memory.write` sets it, and a patch can replace it without
150
+ * touching the value or the turnId, so a `_meta`-only change must count as
151
+ * a change or the prior `_meta` would be persisted over it). A live write
152
+ * stamps the current turn's `turnId`, so a turn that rewrites the SAME value
153
+ * still changes the record, and the fold's record wins (the unknown members
154
+ * described the previous write). **`threadId` is deliberately left out:** a
155
+ * `memory.write` has no `threadId` on its event arm, so no turn can set it and
156
+ * the seed cannot carry it. Comparing it would mark every stored record that
157
+ * has one as changed, which is exactly the loss this prevents.
158
+ * - Records the turn removed are absent from the fold and stay absent; a
159
+ * record the turn added has no prior and is the fold's own.
160
+ */
161
+ export declare function keepUnchangedThreadMemory(priorView: readonly AgMemoryRecord[], folded: readonly AgMemoryRecord[]): AgMemoryRecord[];
105
162
  /**
106
163
  * Synthetic events that seed a reducer with prior latest-replace state before
107
164
  * folding a turn (design §8.1). Seeds state + thread-memory ONLY — never
@@ -114,6 +171,10 @@ export declare function joinStoredThreadMemory(row: Pick<ThreadSnapshotRow, "thr
114
171
  * Each synthetic `memory.write` carries `turnId` so the reducer's SET handler
115
172
  * lands it back onto the AgMemoryRecord — without it, a re-seeded thread-memory
116
173
  * record loses its turnId and memory byte-identity breaks from turn 2 on.
174
+ * It carries the record's `_meta` the same way: AgJSON draft.8 declares
175
+ * `_meta` on both the record and `memory.write`, and the reducer folds it
176
+ * onto the record (set on create, replaced on update), so a record's host
177
+ * side-channel (e.g. `_meta.provenance`) survives every re-seeded turn.
117
178
  * (`threadId` is NOT carried: `memory.write` has no threadId on its event arm,
118
179
  * so it cannot round-trip through a live write — that is expected.)
119
180
  */
@@ -1 +1 @@
1
- {"version":3,"file":"fold-rows.d.ts","sourceRoot":"","sources":["../src/fold-rows.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AACH,OAAO,KAAK,EACV,SAAS,EACT,UAAU,EACV,YAAY,EAEZ,cAAc,EACd,OAAO,EACP,cAAc,EACd,SAAS,EACV,MAAM,sBAAsB,CAAC;AAE9B,OAAO,KAAK,EAAE,gBAAgB,EAAwC,iBAAiB,EAAE,MAAM,WAAW,CAAC;AAG3G,MAAM,WAAW,MAAM;IACrB,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,MAAM,CAAC;IACf,GAAG,EAAE,MAAM,CAAC;IACZ,EAAE,EAAE,MAAM,CAAC;IACX,eAAe,EAAE,MAAM,CAAC;IACxB;;;;OAIG;IACH,eAAe,CAAC,EAAE,OAAO,CAAC;CAC3B;AASD;;;;;;GAMG;AACH,wBAAgB,WAAW,CAAC,GAAG,EAAE,SAAS,GAAG,MAAM,CAMlD;AAED,wBAAgB,cAAc,CAC5B,GAAG,EAAE,SAAS,EACd,GAAG,EAAE,MAAM,GAAG;IAAE,UAAU,CAAC,EAAE,YAAY,CAAA;CAAE,GAC1C,gBAAgB,CA2BlB;AA+DD,wBAAgB,2BAA2B,CAAC,QAAQ,EAAE,SAAS,EAAE,GAAG,UAAU,EAAE,CAmC/E;AAED;;;;;;GAMG;AACH,wBAAgB,iBAAiB,CAAC,QAAQ,EAAE,SAAS,EAAE,GAAG,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAU5E;AAED;;;;;;GAMG;AACH,wBAAgB,iBAAiB,CAC/B,GAAG,EAAE,UAAU,EACf,SAAS,EAAE,WAAW,CAAC,MAAM,EAAE,MAAM,CAAC,GACrC,MAAM,GAAG,SAAS,CAQpB;AAED,wBAAgB,mBAAmB,CAAC,GAAG,EAAE,UAAU,EAAE,GAAG,EAAE,MAAM,EAAE,QAAQ,CAAC,EAAE,MAAM,GAAG,gBAAgB,CA0BrG;AAOD;;;;;;;;;;;;GAYG;AACH,wBAAgB,cAAc,CAAC,GAAG,EAAE,gBAAgB,GAAG,SAAS,CAY/D;AAED,gFAAgF;AAChF,wBAAgB,mBAAmB,CAAC,GAAG,EAAE,gBAAgB,GAAG,UAAU,GAAG,SAAS,CAMjF;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,cAAc,CAC5B,IAAI,EAAE,gBAAgB,EAAE,EACxB,QAAQ,EAAE,iBAAiB,GAAG,SAAS,GACtC,cAAc,CA2BhB;AAED;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,uBAAuB,CAAC,MAAM,EAAE,SAAS,SAAS,EAAE,GAAG,IAAI,CAAC,iBAAiB,EAAE,cAAc,CAAC,GAAG;IAC/G,mBAAmB,EAAE,SAAS,EAAE,CAAC;CAClC,CAoBA;AAED;;;;;;GAMG;AACH,wBAAgB,sBAAsB,CAAC,GAAG,EAAE,IAAI,CAAC,iBAAiB,EAAE,cAAc,GAAG,qBAAqB,CAAC,GAAG,SAAS,EAAE,CAExH;AAED;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,oBAAoB,CAClC,UAAU,EAAE,SAAS,GAAG,SAAS,EACjC,iBAAiB,EAAE,cAAc,EAAE,GAClC,OAAO,EAAE,CAmBX"}
1
+ {"version":3,"file":"fold-rows.d.ts","sourceRoot":"","sources":["../src/fold-rows.ts"],"names":[],"mappings":"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"}
package/dist/fold-rows.js CHANGED
@@ -9,20 +9,78 @@ function roleToAuthor(role) {
9
9
  return "agent"; // assistant | tool
10
10
  }
11
11
  /**
12
- * Plain-text projection of an AgMessage. Distinct text blocks are distinct
13
- * paragraphs, so they join with a paragraph break — the same block-boundary
14
- * contract as `@guuey/agent-client`'s live flat-text fold (guuey#98); raw
15
- * concatenation jammed "…instead.Here's your packing…". Empty blocks
16
- * contribute nothing (no stacked separators).
12
+ * A text block the producer marked as INTERIM narration (AgJSON's open-string
13
+ * `phase`; an OpenAI `commentary` message arrives this way), not the answer.
14
+ * Only the one value this package knows splits; any other phase is answer text.
17
15
  */
18
- export function messageText(msg) {
16
+ function isNarration(block) {
17
+ return block.type === "text" && block.phase === "interim";
18
+ }
19
+ /** The non-empty text blocks, in order, that `keep` admits, joined as paragraphs. */
20
+ function joinText(msg, keep) {
19
21
  const parts = [];
20
22
  for (const block of msg.content) {
21
- if (block.type === "text" && block.text)
23
+ if (block.type === "text" && block.text && keep(block))
22
24
  parts.push(block.text);
23
25
  }
24
26
  return parts.join("\n\n");
25
27
  }
28
+ /**
29
+ * The ANSWER as plain text: the message's text blocks WITHOUT interim
30
+ * narration. Distinct text blocks are distinct paragraphs, so they join with
31
+ * a paragraph break, the same block-boundary contract as `@guuey/agent-client`'s
32
+ * live flat-text fold (guuey#98); raw concatenation jammed "…instead.Here's
33
+ * your packing…". Empty blocks contribute nothing (no stacked separators).
34
+ * This is the row's `text`: what a reloaded thread, a preview and a hook read.
35
+ */
36
+ export function messageText(msg) {
37
+ return joinText(msg, (block) => !isNarration(block));
38
+ }
39
+ /** The message's interim narration, one entry per non-empty narration block, in order. */
40
+ export function messageNarration(msg) {
41
+ const out = [];
42
+ for (const block of msg.content) {
43
+ if (isNarration(block) && block.type === "text" && block.text)
44
+ out.push(block.text);
45
+ }
46
+ return out;
47
+ }
48
+ /**
49
+ * EVERY text block, narration included, joined as the answer text was joined
50
+ * before narration split out of it: the MODEL's view of a stored message.
51
+ * `loadHistory` replays this, so the model's conversation history is the same
52
+ * bytes whether a row was written before the split or after it.
53
+ */
54
+ export function messageFullText(msg) {
55
+ return joinText(msg, () => true);
56
+ }
57
+ /**
58
+ * Split a stored row's `content` into answer, narration and the model's full
59
+ * text, when `content` is a stored `AgMessage` (an object, or the JSON string
60
+ * an AppSync writer stores). Anything else is null, and a reader falls back to
61
+ * the row's `text`. It is read through core's lenient stored-record reader, so
62
+ * an unknown block is omitted rather than failing the row.
63
+ */
64
+ export function storedTextParts(content) {
65
+ let raw = content;
66
+ if (typeof content === "string") {
67
+ try {
68
+ raw = JSON.parse(content);
69
+ }
70
+ catch (err) {
71
+ if (err instanceof SyntaxError)
72
+ return null; // not JSON: not a stored AgMessage
73
+ throw err;
74
+ }
75
+ }
76
+ if (raw === null || typeof raw !== "object")
77
+ return null;
78
+ const read = readStoredAgMessage(raw);
79
+ if (read.value === undefined)
80
+ return null;
81
+ const msg = read.value;
82
+ return { answer: messageText(msg), narration: messageNarration(msg), all: messageFullText(msg) };
83
+ }
26
84
  export function agMessageToRow(msg, ctx) {
27
85
  const text = messageText(msg);
28
86
  const kind = "text";
@@ -330,6 +388,78 @@ export function splitStoredThreadMemory(stored) {
330
388
  export function joinStoredThreadMemory(row) {
331
389
  return [...(row.carriedThreadMemory ?? []), ...row.threadMemory];
332
390
  }
391
+ /** Structural equality over JSON values (object key order does not matter). */
392
+ function jsonEqual(a, b) {
393
+ if (a === b)
394
+ return true;
395
+ if (a === undefined || b === undefined || a === null || b === null)
396
+ return false;
397
+ if (Array.isArray(a) || Array.isArray(b)) {
398
+ if (!Array.isArray(a) || !Array.isArray(b) || a.length !== b.length)
399
+ return false;
400
+ return a.every((v, i) => jsonEqual(v, b[i]));
401
+ }
402
+ if (typeof a !== "object" || typeof b !== "object")
403
+ return false;
404
+ const keys = Object.keys(a);
405
+ if (keys.length !== Object.keys(b).length)
406
+ return false;
407
+ return keys.every((k) => Object.prototype.hasOwnProperty.call(b, k) && jsonEqual(a[k], b[k]));
408
+ }
409
+ /** The reducer's own memory identity (`${scope}${key ?? ""}`): a key-less record is its scope's singleton. */
410
+ function memoryIdentity(rec) {
411
+ return `${rec.scope}${rec.key ?? ""}`;
412
+ }
413
+ /**
414
+ * The thread records a turn persists: each record of the fold, except that a
415
+ * record the turn did NOT change is persisted as the PRIOR stored record
416
+ * instead of the fold's rebuild of it (guuey#1669).
417
+ *
418
+ * The prior view comes from the stored-record reader, whose elements are raw
419
+ * copies that keep members this runtime does not know. The fold cannot
420
+ * carry those: {@link seedEventsForReducer} rebuilds each record as a
421
+ * `memory.write` from its known fields, and the reducer keeps only those. So
422
+ * persisting the fold alone would drop a record's unknown members on the first
423
+ * turn that re-persists the thread, even though the turn never touched it. A
424
+ * record keeps what it was stored with until a turn changes it.
425
+ *
426
+ * - **Identity** is the reducer's: scope plus `key ?? ""`. A fold record meets
427
+ * the LAST prior record of that identity, the one the seed left standing,
428
+ * because the last write wins. Several key-less records of one scope were
429
+ * already collapsed to that one by the reducer before this existed; that
430
+ * limit predates the overlay and is not papered over here.
431
+ * - **Unchanged** means the known fields a turn can write are deep-equal:
432
+ * `scope`, `key`, `value`, `reason`, `durable`, `turnId` and `_meta`
433
+ * (draft.8: a `memory.write` sets it, and a patch can replace it without
434
+ * touching the value or the turnId, so a `_meta`-only change must count as
435
+ * a change or the prior `_meta` would be persisted over it). A live write
436
+ * stamps the current turn's `turnId`, so a turn that rewrites the SAME value
437
+ * still changes the record, and the fold's record wins (the unknown members
438
+ * described the previous write). **`threadId` is deliberately left out:** a
439
+ * `memory.write` has no `threadId` on its event arm, so no turn can set it and
440
+ * the seed cannot carry it. Comparing it would mark every stored record that
441
+ * has one as changed, which is exactly the loss this prevents.
442
+ * - Records the turn removed are absent from the fold and stay absent; a
443
+ * record the turn added has no prior and is the fold's own.
444
+ */
445
+ export function keepUnchangedThreadMemory(priorView, folded) {
446
+ const prior = new Map();
447
+ for (const rec of priorView)
448
+ prior.set(memoryIdentity(rec), rec);
449
+ return folded.map((rec) => {
450
+ const before = prior.get(memoryIdentity(rec));
451
+ if (before === undefined)
452
+ return rec;
453
+ const unchanged = before.scope === rec.scope &&
454
+ before.key === rec.key &&
455
+ jsonEqual(before.value, rec.value) &&
456
+ before.reason === rec.reason &&
457
+ before.durable === rec.durable &&
458
+ before.turnId === rec.turnId &&
459
+ jsonEqual(before._meta, rec._meta);
460
+ return unchanged ? before : rec;
461
+ });
462
+ }
333
463
  /**
334
464
  * Synthetic events that seed a reducer with prior latest-replace state before
335
465
  * folding a turn (design §8.1). Seeds state + thread-memory ONLY — never
@@ -342,6 +472,10 @@ export function joinStoredThreadMemory(row) {
342
472
  * Each synthetic `memory.write` carries `turnId` so the reducer's SET handler
343
473
  * lands it back onto the AgMemoryRecord — without it, a re-seeded thread-memory
344
474
  * record loses its turnId and memory byte-identity breaks from turn 2 on.
475
+ * It carries the record's `_meta` the same way: AgJSON draft.8 declares
476
+ * `_meta` on both the record and `memory.write`, and the reducer folds it
477
+ * onto the record (set on create, replaced on update), so a record's host
478
+ * side-channel (e.g. `_meta.provenance`) survives every re-seeded turn.
345
479
  * (`threadId` is NOT carried: `memory.write` has no threadId on its event arm,
346
480
  * so it cannot round-trip through a live write — that is expected.)
347
481
  */
@@ -361,6 +495,7 @@ export function seedEventsForReducer(priorState, priorThreadMemory) {
361
495
  ...(rec.reason !== undefined ? { reason: rec.reason } : {}),
362
496
  ...(rec.durable !== undefined ? { durable: rec.durable } : {}),
363
497
  ...(rec.turnId !== undefined ? { turnId: rec.turnId } : {}),
498
+ ...(rec._meta !== undefined ? { _meta: rec._meta } : {}),
364
499
  });
365
500
  }
366
501
  return events;
package/dist/index.d.ts CHANGED
@@ -17,9 +17,10 @@
17
17
  * - `@guuey/threads/testing` — the port contract suite: run it against
18
18
  * your binding for the same guarantees the hosted one carries.
19
19
  */
20
- export type { HandoffEvent, ReportEvent, StoredHistoryMessage, ThreadMessageEvent, ThreadMessageKind, ThreadMessageRole, ThreadMessageRow, ThreadPersistencePort, ThreadRow, ThreadSnapshotRow, } from "./rows.js";
20
+ export type { HandoffEvent, ReportEvent, StoredHistoryMessage, ThreadMessageEvent, ThreadMessageKind, ThreadMessageRole, ThreadMessageRow, ThreadPersistencePort, ThreadRow, ThreadSnapshotRow, TurnOrigin, ClientClass, } from "./rows.js";
21
21
  export { ThreadStore, type AppendFoldInput, type AppendFoldResult, type AppendMessageInput, type AppendMessageResult, type EnsureThreadInput, } from "./store.js";
22
22
  export { InMemoryThreadPersistence } from "./in-memory.js";
23
+ export { AGENT_ERROR_ROW_SUFFIX, classifyStoredRow, isBlankBlock, isInformationless, type StoredRowClass, } from "./stored-row.js";
23
24
  export { HttpThreadPersistence, HttpThreadStoreError, type HttpThreadPersistenceOptions, type ThreadScope, } from "./http.js";
24
- export { agArtifactToCardRow, agMessageToRow, cardRowToAgArtifact, messageText, producingToolName, joinStoredThreadMemory, reassembleFold, rowToAgMessage, seedEventsForReducer, splitStoredThreadMemory, toolNamesByCallId, uiCardArtifactsFromMessages, type RowCtx, } from "./fold-rows.js";
25
+ export { agArtifactToCardRow, agMessageToRow, cardRowToAgArtifact, messageFullText, messageNarration, messageText, producingToolName, storedTextParts, type StoredTextParts, joinStoredThreadMemory, reassembleFold, rowToAgMessage, seedEventsForReducer, splitStoredThreadMemory, toolNamesByCallId, uiCardArtifactsFromMessages, type RowCtx, } from "./fold-rows.js";
25
26
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AACH,YAAY,EACV,YAAY,EACZ,WAAW,EACX,oBAAoB,EACpB,kBAAkB,EAClB,iBAAiB,EACjB,iBAAiB,EACjB,gBAAgB,EAChB,qBAAqB,EACrB,SAAS,EACT,iBAAiB,GAClB,MAAM,WAAW,CAAC;AACnB,OAAO,EACL,WAAW,EACX,KAAK,eAAe,EACpB,KAAK,gBAAgB,EACrB,KAAK,kBAAkB,EACvB,KAAK,mBAAmB,EACxB,KAAK,iBAAiB,GACvB,MAAM,YAAY,CAAC;AACpB,OAAO,EAAE,yBAAyB,EAAE,MAAM,gBAAgB,CAAC;AAC3D,OAAO,EACL,qBAAqB,EACrB,oBAAoB,EACpB,KAAK,4BAA4B,EACjC,KAAK,WAAW,GACjB,MAAM,WAAW,CAAC;AACnB,OAAO,EACL,mBAAmB,EACnB,cAAc,EACd,mBAAmB,EACnB,WAAW,EACX,iBAAiB,EACjB,sBAAsB,EACtB,cAAc,EACd,cAAc,EACd,oBAAoB,EACpB,uBAAuB,EACvB,iBAAiB,EACjB,2BAA2B,EAC3B,KAAK,MAAM,GACZ,MAAM,gBAAgB,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AACH,YAAY,EACV,YAAY,EACZ,WAAW,EACX,oBAAoB,EACpB,kBAAkB,EAClB,iBAAiB,EACjB,iBAAiB,EACjB,gBAAgB,EAChB,qBAAqB,EACrB,SAAS,EACT,iBAAiB,EACjB,UAAU,EACV,WAAW,GACZ,MAAM,WAAW,CAAC;AACnB,OAAO,EACL,WAAW,EACX,KAAK,eAAe,EACpB,KAAK,gBAAgB,EACrB,KAAK,kBAAkB,EACvB,KAAK,mBAAmB,EACxB,KAAK,iBAAiB,GACvB,MAAM,YAAY,CAAC;AACpB,OAAO,EAAE,yBAAyB,EAAE,MAAM,gBAAgB,CAAC;AAC3D,OAAO,EACL,sBAAsB,EACtB,iBAAiB,EACjB,YAAY,EACZ,iBAAiB,EACjB,KAAK,cAAc,GACpB,MAAM,iBAAiB,CAAC;AACzB,OAAO,EACL,qBAAqB,EACrB,oBAAoB,EACpB,KAAK,4BAA4B,EACjC,KAAK,WAAW,GACjB,MAAM,WAAW,CAAC;AACnB,OAAO,EACL,mBAAmB,EACnB,cAAc,EACd,mBAAmB,EACnB,eAAe,EACf,gBAAgB,EAChB,WAAW,EACX,iBAAiB,EACjB,eAAe,EACf,KAAK,eAAe,EACpB,sBAAsB,EACtB,cAAc,EACd,cAAc,EACd,oBAAoB,EACpB,uBAAuB,EACvB,iBAAiB,EACjB,2BAA2B,EAC3B,KAAK,MAAM,GACZ,MAAM,gBAAgB,CAAC"}
package/dist/index.js CHANGED
@@ -1,4 +1,5 @@
1
1
  export { ThreadStore, } from "./store.js";
2
2
  export { InMemoryThreadPersistence } from "./in-memory.js";
3
+ export { AGENT_ERROR_ROW_SUFFIX, classifyStoredRow, isBlankBlock, isInformationless, } from "./stored-row.js";
3
4
  export { HttpThreadPersistence, HttpThreadStoreError, } from "./http.js";
4
- export { agArtifactToCardRow, agMessageToRow, cardRowToAgArtifact, messageText, producingToolName, joinStoredThreadMemory, reassembleFold, rowToAgMessage, seedEventsForReducer, splitStoredThreadMemory, toolNamesByCallId, uiCardArtifactsFromMessages, } from "./fold-rows.js";
5
+ export { agArtifactToCardRow, agMessageToRow, cardRowToAgArtifact, messageFullText, messageNarration, messageText, producingToolName, storedTextParts, joinStoredThreadMemory, reassembleFold, rowToAgMessage, seedEventsForReducer, splitStoredThreadMemory, toolNamesByCallId, uiCardArtifactsFromMessages, } from "./fold-rows.js";
package/dist/rows.d.ts CHANGED
@@ -12,6 +12,20 @@
12
12
  import type { AgMemoryRecord, JsonValue } from "@silverprotocol/core";
13
13
  export type ThreadMessageRole = "user" | "agent" | "system";
14
14
  export type ThreadMessageKind = "text" | "card" | "event";
15
+ /**
16
+ * How a user turn was started: a gesture on a card (`card_action`), a
17
+ * suggested-prompt chip (`chip`), or typed input (`typed`). Stamped by the
18
+ * runtime on user rows; absent on rows written before the stamp existed.
19
+ */
20
+ export type TurnOrigin = "card_action" | "chip" | "typed";
21
+ /**
22
+ * The kind of client that sent a user turn, from its User-Agent at request
23
+ * time (the agent string is never stored): the hosting operator's own
24
+ * automated clients, such as health checks and smoke tests (`operator`); a
25
+ * third-party crawler or automated client (`bot`); or anything else
26
+ * (`browser`).
27
+ */
28
+ export type ClientClass = "operator" | "bot" | "browser";
15
29
  export interface ThreadRow {
16
30
  id: string;
17
31
  userId: string;
@@ -116,6 +130,12 @@ export interface ThreadMessageRow {
116
130
  * the row types never carried it). Absent on every non-event row.
117
131
  */
118
132
  event?: ThreadMessageEvent;
133
+ /** On a user row: how the turn was started. Absent on older rows. */
134
+ turnOrigin?: TurnOrigin;
135
+ /** On a `card_action` user row: the render session id of the card the gesture answered. */
136
+ answeredCardSessionId?: string;
137
+ /** On a user row: the sending client's class. Absent on older rows. */
138
+ clientClass?: ClientClass;
119
139
  }
120
140
  /** Latest-replace fold snapshot for a thread. */
121
141
  export interface ThreadSnapshotRow {
@@ -1 +1 @@
1
- {"version":3,"file":"rows.d.ts","sourceRoot":"","sources":["../src/rows.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"rows.d.ts","sourceRoot":"","sources":["../src/rows.ts"],"names":[],"mappings":"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"}
package/dist/store.d.ts CHANGED
@@ -1,5 +1,5 @@
1
- import type { AgReduceResult, JsonValue } from "@silverprotocol/core";
2
- import type { ThreadMessageEvent, StoredHistoryMessage, ThreadMessageKind, ThreadMessageRole, ThreadPersistencePort, ThreadSnapshotRow } from "./rows.js";
1
+ import type { AgMemoryRecord, AgReduceResult, JsonValue } from "@silverprotocol/core";
2
+ import type { ThreadMessageEvent, StoredHistoryMessage, ThreadMessageKind, ThreadMessageRole, ThreadPersistencePort, ThreadSnapshotRow, TurnOrigin, ClientClass } from "./rows.js";
3
3
  export interface EnsureThreadInput {
4
4
  /** Client-supplied thread id (localStorage). Absent on first contact. */
5
5
  threadId?: string;
@@ -33,6 +33,12 @@ export interface AppendMessageInput {
33
33
  * store does not infer one from the other.
34
34
  */
35
35
  event?: ThreadMessageEvent;
36
+ /** A user row's origin (see {@link ThreadMessageRow.turnOrigin}); carried verbatim. */
37
+ turnOrigin?: TurnOrigin;
38
+ /** A `card_action` user row's answered card session id; carried verbatim. */
39
+ answeredCardSessionId?: string;
40
+ /** A user row's client class (see {@link ThreadMessageRow.clientClass}); carried verbatim. */
41
+ clientClass?: ClientClass;
36
42
  }
37
43
  export interface AppendMessageResult {
38
44
  seq: number;
@@ -64,6 +70,13 @@ export interface AppendFoldInput {
64
70
  * own thread records, so they are never dropped at rest. Absent = none.
65
71
  */
66
72
  carriedThreadMemory?: readonly JsonValue[];
73
+ /**
74
+ * The prior snapshot's `threadMemory`: the view the reducer was seeded
75
+ * from. A record this turn did not change is persisted as this prior record,
76
+ * with the members it was stored with (`keepUnchangedThreadMemory`), never as
77
+ * the fold's rebuild of it. Absent = the fold's records as they are.
78
+ */
79
+ priorThreadMemory?: readonly AgMemoryRecord[];
67
80
  }
68
81
  export interface AppendFoldResult {
69
82
  messageSeqs: number[];
@@ -1 +1 @@
1
- {"version":3,"file":"store.d.ts","sourceRoot":"","sources":["../src/store.ts"],"names":[],"mappings":"AAUA,OAAO,KAAK,EAAE,cAAc,EAAE,SAAS,EAAE,MAAM,sBAAsB,CAAC;AAQtE,OAAO,KAAK,EACV,kBAAkB,EAClB,oBAAoB,EACpB,iBAAiB,EACjB,iBAAiB,EAEjB,qBAAqB,EAErB,iBAAiB,EAClB,MAAM,WAAW,CAAC;AAOnB,MAAM,WAAW,iBAAiB;IAChC,yEAAyE;IACzE,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,yCAAyC;IACzC,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,EAAE,MAAM,CAAC;IACd,wEAAwE;IACxE,MAAM,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,WAAW,kBAAkB;IACjC,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,iBAAiB,CAAC;IACxB,yEAAyE;IACzE,OAAO,EAAE,OAAO,CAAC;IACjB,iEAAiE;IACjE,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,+EAA+E;IAC/E,eAAe,EAAE,MAAM,CAAC;IACxB,IAAI,CAAC,EAAE,iBAAiB,CAAC;IACzB;;;;;OAKG;IACH,eAAe,CAAC,EAAE,OAAO,CAAC;IAC1B;;;;OAIG;IACH,KAAK,CAAC,EAAE,kBAAkB,CAAC;CAC5B;AAED,MAAM,WAAW,mBAAmB;IAClC,GAAG,EAAE,MAAM,CAAC;IACZ,0EAA0E;IAC1E,OAAO,EAAE,OAAO,CAAC;CAClB;AAED,MAAM,WAAW,eAAe;IAC9B,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,cAAc,CAAC;IACrB,2EAA2E;IAC3E,mBAAmB,EAAE,MAAM,CAAC;IAC5B;;;;;OAKG;IACH,YAAY,CAAC,EAAE,OAAO,CAAC;IACvB;;;;OAIG;IACH,eAAe,CAAC,EAAE,OAAO,CAAC;IAC1B;;;;OAIG;IACH,mBAAmB,CAAC,EAAE,SAAS,SAAS,EAAE,CAAC;CAC5C;AAED,MAAM,WAAW,gBAAgB;IAC/B,WAAW,EAAE,MAAM,EAAE,CAAC;IACtB,YAAY,EAAE,MAAM,EAAE,CAAC;IACvB,kEAAkE;IAClE,oBAAoB,EAAE,MAAM,CAAC;IAC7B;;;OAGG;IACH,OAAO,EAAE,OAAO,CAAC;CAClB;AAMD,qBAAa,WAAW;IACV,OAAO,CAAC,QAAQ,CAAC,EAAE;gBAAF,EAAE,EAAE,qBAAqB;IAEtD;;;;;;;;;;;;;OAaG;IACG,YAAY,CAAC,KAAK,EAAE,iBAAiB,GAAG,OAAO,CAAC,MAAM,CAAC;IA4B7D;;;;;;;OAOG;IACG,UAAU,CAAC,QAAQ,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;IAKpE,4EAA4E;IACtE,WAAW,CACf,QAAQ,EAAE,MAAM,EAChB,KAAK,GAAE,MAA8B,GACpC,OAAO,CAAC,oBAAoB,EAAE,CAAC;IAqBlC;;;;OAIG;IACG,aAAa,CAAC,KAAK,EAAE,kBAAkB,GAAG,OAAO,CAAC,mBAAmB,CAAC;IA8B5E,+EAA+E;IACzE,WAAW,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,iBAAiB,GAAG,SAAS,CAAC;IAI3E;;;;;;;;;;;;;;;;;OAiBG;IACG,UAAU,CAAC,KAAK,EAAE,eAAe,GAAG,OAAO,CAAC,gBAAgB,CAAC;CAoGpE"}
1
+ {"version":3,"file":"store.d.ts","sourceRoot":"","sources":["../src/store.ts"],"names":[],"mappings":"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"}
package/dist/store.js CHANGED
@@ -8,7 +8,8 @@
8
8
  * DynamoDB).
9
9
  */
10
10
  import { randomUUID } from "node:crypto";
11
- import { agMessageToRow, agArtifactToCardRow, producingToolName, toolNamesByCallId, uiCardArtifactsFromMessages, } from "./fold-rows.js";
11
+ import { agMessageToRow, agArtifactToCardRow, keepUnchangedThreadMemory, producingToolName, toolNamesByCallId, uiCardArtifactsFromMessages, storedTextParts, } from "./fold-rows.js";
12
+ import { classifyStoredRow } from "./stored-row.js";
12
13
  /** Max history messages fed back as context — bounds latency on long threads. */
13
14
  const DEFAULT_HISTORY_LIMIT = 40;
14
15
  const PREVIEW_MAX_LEN = 240;
@@ -85,14 +86,32 @@ export class ThreadStore {
85
86
  // Known trade-off: cards still consume the DynamoDB Limit before this
86
87
  // filter, so card-heavy threads under-fill the window (bounded, most-
87
88
  // recent-first; revisit with an over-fetch if it bites).
88
- return rows.filter((r) => r.kind !== 'card').map((r) => ({
89
+ // The model's text is EVERY text block from the stored message, narration
90
+ // included (`storedTextParts(...).all`): the row's `text` is the answer only
91
+ // since narration split out of it, so reading `text` here would change what
92
+ // the model is replayed. For rows written before the split, `all` is the same
93
+ // bytes their `text` holds. A row whose content is not a stored AgMessage
94
+ // (an event, a managed-adapter row) keeps its `text`.
95
+ //
96
+ // Only CONVERSATION reaches this lane: a failed turn's reader line, a
97
+ // framework notice and a blank agent message are stored rows, never words
98
+ // the agent said, so replaying them made the model (and a hook's
99
+ // transcript) read them as its own ({@link classifyStoredRow}).
100
+ // Two user turns can now sit side by side (the agent row between them
101
+ // dropped). That is fine: every runner renders this history as TEXT lines
102
+ // into its system prompt (`@guuey/host` `withContextPreamble`), never as a
103
+ // role-alternating message list a provider could refuse.
104
+ const conversation = rows
105
+ .filter((r) => r.kind !== 'card' && classifyStoredRow(r).kind === 'conversation')
106
+ .map((r) => ({
89
107
  seq: r.seq,
90
108
  authorRole: r.authorRole,
91
109
  kind: r.kind,
92
- text: r.text ?? null,
110
+ text: storedTextParts(r.content)?.all || (r.text ?? null),
93
111
  content: r.content ?? null,
94
112
  at: r.at,
95
113
  }));
114
+ return conversation;
96
115
  }
97
116
  /**
98
117
  * Append one message with an atomic, gap-free seq. Idempotent on
@@ -119,6 +138,9 @@ export class ThreadStore {
119
138
  content: input.content,
120
139
  ...(input.untrustedOrigin === true ? { untrustedOrigin: true } : {}),
121
140
  ...(input.event !== undefined ? { event: input.event } : {}),
141
+ ...(input.turnOrigin !== undefined ? { turnOrigin: input.turnOrigin } : {}),
142
+ ...(input.answeredCardSessionId !== undefined ? { answeredCardSessionId: input.answeredCardSessionId } : {}),
143
+ ...(input.clientClass !== undefined ? { clientClass: input.clientClass } : {}),
122
144
  };
123
145
  await this.db.putMessage(row);
124
146
  return { seq, deduped: false };
@@ -161,7 +183,11 @@ export class ThreadStore {
161
183
  const msg = fold.messages[i];
162
184
  const clientMessageId = sentinelClaimed ? `${clientMessageIdBase}#agent#${i}` : sentinelKey;
163
185
  sentinelClaimed = true;
164
- const text = msg.content.reduce((acc, b) => (b.type === 'text' ? acc + b.text : acc), '');
186
+ // The preview is the ANSWER: interim narration (`phase: "interim"`) never becomes a thread's preview,
187
+ // and neither does a framework notice (a non-conversational row).
188
+ const text = msg.role === 'notice'
189
+ ? ''
190
+ : msg.content.reduce((acc, b) => (b.type === 'text' && b.phase !== 'interim' ? acc + b.text : acc), '');
165
191
  const now = new Date().toISOString();
166
192
  // Text-less agent messages (tool-call + tool-result only — the canonical
167
193
  // card-producing turn) must not blank the preview: '' takes the SET
@@ -210,7 +236,7 @@ export class ThreadStore {
210
236
  }, producingToolName(art, toolNames)));
211
237
  artifactSeqs.push(seq);
212
238
  }
213
- const threadMemory = fold.memory.filter((m) => m.scope === 'thread');
239
+ const threadMemory = keepUnchangedThreadMemory(input.priorThreadMemory ?? [], fold.memory.filter((m) => m.scope === 'thread'));
214
240
  const droppedDurableMemory = fold.memory.length - threadMemory.length;
215
241
  if (skipSnapshot !== true) {
216
242
  const lastTurnId = fold.turns.length ? fold.turns[fold.turns.length - 1].turnId : undefined;
@@ -0,0 +1,57 @@
1
+ /**
2
+ * What a stored thread row IS, for every reader that turns rows back into
3
+ * conversation. Some stored rows are not "the agent said this", and reading
4
+ * them as such replays them to the model as the agent's own words and draws
5
+ * them as answer bubbles:
6
+ *
7
+ * - a FAILURE row: the runtime's reader line for a failed turn, keyed
8
+ * `…#agentError`. It carries `content.error.code` from the runtime release
9
+ * that added codes; rows written before it carry only `{kind:'text', text}`,
10
+ * so the key suffix alone must classify them too;
11
+ * - a NOTICE row: a stored message with `role: "notice"` (a framework notice
12
+ * the agent facet emits), a non-conversational, user-facing row;
13
+ * - a BLANK row: an agent message whose every block shows the reader nothing
14
+ * (an empty text part, or a provider carry with nothing in it).
15
+ *
16
+ * The reader is lenient: it reads the stored `content` through the core's
17
+ * stored-record reader, so a row written before any of this is classified the
18
+ * same way, with no migration.
19
+ */
20
+ import { type AgBlock, type JsonValue } from "@silverprotocol/core";
21
+ import type { ThreadMessageRow } from "./rows.js";
22
+ /** The clientMessageId suffix the runtime gives a failed turn's reader-line row. */
23
+ export declare const AGENT_ERROR_ROW_SUFFIX = "#agentError";
24
+ /**
25
+ * True when a JSON value carries no information a reader could act on:
26
+ * `null`/`undefined`, the empty string, an empty array or object, or an
27
+ * array/object whose every member is itself informationless. Numbers and
28
+ * booleans are information. The same rule `@guuey/chat` applies to a provider
29
+ * carry it hides from the transcript.
30
+ */
31
+ export declare function isInformationless(value: JsonValue | undefined): boolean;
32
+ /**
33
+ * A block the reader's transcript shows nothing for: an empty text part, or a
34
+ * `provider-raw` carry whose payload is informationless. Every other block,
35
+ * reasoning included, is something a reader can see. ONE predicate for the
36
+ * runtime's turn watch, its stored fold, and this classifier.
37
+ */
38
+ export declare function isBlankBlock(block: AgBlock): boolean;
39
+ /** The class of one stored row. */
40
+ export type StoredRowClass = {
41
+ kind: "conversation";
42
+ }
43
+ /** A failed turn's reader line. `code` is null on a row stored before codes were persisted. */
44
+ | {
45
+ kind: "failure";
46
+ code: string | null;
47
+ }
48
+ /** A framework notice. `source` is the injecting layer when the row names one. */
49
+ | {
50
+ kind: "notice";
51
+ source: string | null;
52
+ } | {
53
+ kind: "blank";
54
+ };
55
+ /** Classify one stored row (see the module doc). */
56
+ export declare function classifyStoredRow(row: Pick<ThreadMessageRow, "clientMessageId" | "authorRole" | "content">): StoredRowClass;
57
+ //# sourceMappingURL=stored-row.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"stored-row.d.ts","sourceRoot":"","sources":["../src/stored-row.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AACH,OAAO,EAAuB,KAAK,OAAO,EAAE,KAAK,SAAS,EAAE,MAAM,sBAAsB,CAAC;AACzF,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,WAAW,CAAC;AAElD,oFAAoF;AACpF,eAAO,MAAM,sBAAsB,gBAAgB,CAAC;AAEpD;;;;;;GAMG;AACH,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,SAAS,GAAG,SAAS,GAAG,OAAO,CAMvE;AAED;;;;;GAKG;AACH,wBAAgB,YAAY,CAAC,KAAK,EAAE,OAAO,GAAG,OAAO,CAIpD;AAED,mCAAmC;AACnC,MAAM,MAAM,cAAc,GACtB;IAAE,IAAI,EAAE,cAAc,CAAA;CAAE;AAC1B,+FAA+F;GAC7F;IAAE,IAAI,EAAE,SAAS,CAAC;IAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAAA;CAAE;AAC1C,kFAAkF;GAChF;IAAE,IAAI,EAAE,QAAQ,CAAC;IAAC,MAAM,EAAE,MAAM,GAAG,IAAI,CAAA;CAAE,GACzC;IAAE,IAAI,EAAE,OAAO,CAAA;CAAE,CAAC;AAUtB,oDAAoD;AACpD,wBAAgB,iBAAiB,CAC/B,GAAG,EAAE,IAAI,CAAC,gBAAgB,EAAE,iBAAiB,GAAG,YAAY,GAAG,SAAS,CAAC,GACxE,cAAc,CAWhB"}
@@ -0,0 +1,79 @@
1
+ /**
2
+ * What a stored thread row IS, for every reader that turns rows back into
3
+ * conversation. Some stored rows are not "the agent said this", and reading
4
+ * them as such replays them to the model as the agent's own words and draws
5
+ * them as answer bubbles:
6
+ *
7
+ * - a FAILURE row: the runtime's reader line for a failed turn, keyed
8
+ * `…#agentError`. It carries `content.error.code` from the runtime release
9
+ * that added codes; rows written before it carry only `{kind:'text', text}`,
10
+ * so the key suffix alone must classify them too;
11
+ * - a NOTICE row: a stored message with `role: "notice"` (a framework notice
12
+ * the agent facet emits), a non-conversational, user-facing row;
13
+ * - a BLANK row: an agent message whose every block shows the reader nothing
14
+ * (an empty text part, or a provider carry with nothing in it).
15
+ *
16
+ * The reader is lenient: it reads the stored `content` through the core's
17
+ * stored-record reader, so a row written before any of this is classified the
18
+ * same way, with no migration.
19
+ */
20
+ import { readStoredAgMessage } from "@silverprotocol/core";
21
+ /** The clientMessageId suffix the runtime gives a failed turn's reader-line row. */
22
+ export const AGENT_ERROR_ROW_SUFFIX = "#agentError";
23
+ /**
24
+ * True when a JSON value carries no information a reader could act on:
25
+ * `null`/`undefined`, the empty string, an empty array or object, or an
26
+ * array/object whose every member is itself informationless. Numbers and
27
+ * booleans are information. The same rule `@guuey/chat` applies to a provider
28
+ * carry it hides from the transcript.
29
+ */
30
+ export function isInformationless(value) {
31
+ if (value === undefined || value === null)
32
+ return true;
33
+ if (typeof value === "string")
34
+ return value === "";
35
+ if (typeof value === "number" || typeof value === "boolean")
36
+ return false;
37
+ if (Array.isArray(value))
38
+ return value.every((v) => isInformationless(v));
39
+ return Object.values(value).every((v) => isInformationless(v));
40
+ }
41
+ /**
42
+ * A block the reader's transcript shows nothing for: an empty text part, or a
43
+ * `provider-raw` carry whose payload is informationless. Every other block,
44
+ * reasoning included, is something a reader can see. ONE predicate for the
45
+ * runtime's turn watch, its stored fold, and this classifier.
46
+ */
47
+ export function isBlankBlock(block) {
48
+ if (block.type === "text")
49
+ return block.text === "";
50
+ if (block.type === "provider-raw")
51
+ return isInformationless(block.raw);
52
+ return false;
53
+ }
54
+ /** The failure code a row's content carries (`content.error.code`), when it carries one. */
55
+ function failureCodeOf(content) {
56
+ if (typeof content !== "object" || content === null || !("error" in content))
57
+ return undefined;
58
+ const error = content.error;
59
+ if (typeof error !== "object" || error === null || !("code" in error))
60
+ return undefined;
61
+ return typeof error.code === "string" && error.code !== "" ? error.code : undefined;
62
+ }
63
+ /** Classify one stored row (see the module doc). */
64
+ export function classifyStoredRow(row) {
65
+ const code = failureCodeOf(row.content);
66
+ if (row.clientMessageId.endsWith(AGENT_ERROR_ROW_SUFFIX) || code !== undefined) {
67
+ return { kind: "failure", code: code ?? null };
68
+ }
69
+ if (row.authorRole === "user")
70
+ return { kind: "conversation" };
71
+ const stored = readStoredAgMessage(row.content).value;
72
+ if (stored === undefined)
73
+ return { kind: "conversation" };
74
+ if (stored.role === "notice")
75
+ return { kind: "notice", source: stored.noticeSource ?? null };
76
+ if (stored.content.every(isBlankBlock))
77
+ return { kind: "blank" };
78
+ return { kind: "conversation" };
79
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@guuey/threads",
3
- "version": "0.29.0",
3
+ "version": "0.31.0",
4
4
  "description": "Universal session/thread persistence for AgJSON agents — the ThreadStore contract (append-fold, history, snapshots), the fold↔row mapping, and an in-memory binding. Guuey's hosted runtime is one binding; implement the port against your own store and run the exported contract suite for the same guarantees.",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -10,6 +10,9 @@
10
10
  "dist",
11
11
  "README.md"
12
12
  ],
13
+ "engines": {
14
+ "node": ">=22.12.0"
15
+ },
13
16
  "exports": {
14
17
  ".": {
15
18
  "types": "./dist/index.d.ts",
@@ -22,8 +25,8 @@
22
25
  }
23
26
  },
24
27
  "dependencies": {
25
- "@silverprotocol/core": "0.9.0",
26
- "@guuey/mcp-apps-host": "0.29.0"
28
+ "@silverprotocol/core": "0.12.1",
29
+ "@guuey/mcp-apps-host": "0.31.0"
27
30
  },
28
31
  "devDependencies": {
29
32
  "@types/node": "^24.0.0",