@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.
- package/dist/fold-rows.d.ts +66 -5
- package/dist/fold-rows.d.ts.map +1 -1
- package/dist/fold-rows.js +142 -7
- package/dist/index.d.ts +3 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -1
- package/dist/rows.d.ts +20 -0
- package/dist/rows.d.ts.map +1 -1
- package/dist/store.d.ts +15 -2
- package/dist/store.d.ts.map +1 -1
- package/dist/store.js +31 -5
- package/dist/stored-row.d.ts +57 -0
- package/dist/stored-row.d.ts.map +1 -0
- package/dist/stored-row.js +79 -0
- package/package.json +6 -3
package/dist/fold-rows.d.ts
CHANGED
|
@@ -20,13 +20,37 @@ export interface RowCtx {
|
|
|
20
20
|
untrustedOrigin?: boolean;
|
|
21
21
|
}
|
|
22
22
|
/**
|
|
23
|
-
*
|
|
24
|
-
* paragraphs, so they join with
|
|
25
|
-
* contract as `@guuey/agent-client`'s
|
|
26
|
-
* concatenation jammed "…instead.Here's
|
|
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
|
*/
|
package/dist/fold-rows.d.ts.map
CHANGED
|
@@ -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;
|
|
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
|
-
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
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
|
-
|
|
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
|
package/dist/index.d.ts.map
CHANGED
|
@@ -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,
|
|
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 {
|
package/dist/rows.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"rows.d.ts","sourceRoot":"","sources":["../src/rows.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AACH,OAAO,KAAK,EAAE,cAAc,EAAE,SAAS,EAAE,MAAM,sBAAsB,CAAC;AAEtE,MAAM,MAAM,iBAAiB,GAAG,MAAM,GAAG,OAAO,GAAG,QAAQ,CAAC;AAC5D,MAAM,MAAM,iBAAiB,GAAG,MAAM,GAAG,MAAM,GAAG,OAAO,CAAC;
|
|
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[];
|
package/dist/store.d.ts.map
CHANGED
|
@@ -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;
|
|
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
|
-
|
|
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
|
-
|
|
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.
|
|
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.
|
|
26
|
-
"@guuey/mcp-apps-host": "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",
|