@guuey/threads 0.2.2 → 0.3.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 +7 -1
- package/dist/fold-rows.d.ts.map +1 -1
- package/dist/fold-rows.js +73 -40
- package/package.json +3 -2
package/dist/fold-rows.d.ts
CHANGED
|
@@ -13,7 +13,13 @@ export interface RowCtx {
|
|
|
13
13
|
at: string;
|
|
14
14
|
clientMessageId: string;
|
|
15
15
|
}
|
|
16
|
-
/**
|
|
16
|
+
/**
|
|
17
|
+
* Plain-text projection of an AgMessage. Distinct text blocks are distinct
|
|
18
|
+
* paragraphs, so they join with a paragraph break — the same block-boundary
|
|
19
|
+
* contract as `@guuey/agent-client`'s live flat-text fold (guuey#98); raw
|
|
20
|
+
* concatenation jammed "…instead.Here's your packing…". Empty blocks
|
|
21
|
+
* contribute nothing (no stacked separators).
|
|
22
|
+
*/
|
|
17
23
|
export declare function messageText(msg: AgMessage): string;
|
|
18
24
|
export declare function agMessageToRow(msg: AgMessage, ctx: RowCtx & {
|
|
19
25
|
turnRecord?: AgTurnRecord;
|
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;
|
|
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;CACzB;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,CAyBlB;AAkDD,wBAAgB,2BAA2B,CAAC,QAAQ,EAAE,SAAS,EAAE,GAAG,UAAU,EAAE,CA2B/E;AAED,wBAAgB,mBAAmB,CAAC,GAAG,EAAE,UAAU,EAAE,GAAG,EAAE,MAAM,GAAG,gBAAgB,CAqBlF;AAOD;;;;;GAKG;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,oBAAoB,CAClC,UAAU,EAAE,SAAS,GAAG,SAAS,EACjC,iBAAiB,EAAE,cAAc,EAAE,GAClC,OAAO,EAAE,CAmBX"}
|
package/dist/fold-rows.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { AgMessage as AgMessageSchema } from "@silverprotocol/core";
|
|
2
|
+
import { asUiResource, scanProviderRawForUiResource, uiLocator } from "@guuey/mcp-apps-host/narrowing";
|
|
2
3
|
/** AgRole → the row's coarse authorRole projection (secondary; role of record lives in content). */
|
|
3
4
|
function roleToAuthor(role) {
|
|
4
5
|
if (role === "user")
|
|
@@ -7,14 +8,20 @@ function roleToAuthor(role) {
|
|
|
7
8
|
return "system";
|
|
8
9
|
return "agent"; // assistant | tool
|
|
9
10
|
}
|
|
10
|
-
/**
|
|
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).
|
|
17
|
+
*/
|
|
11
18
|
export function messageText(msg) {
|
|
12
|
-
|
|
19
|
+
const parts = [];
|
|
13
20
|
for (const block of msg.content) {
|
|
14
|
-
if (block.type === "text")
|
|
15
|
-
|
|
21
|
+
if (block.type === "text" && block.text)
|
|
22
|
+
parts.push(block.text);
|
|
16
23
|
}
|
|
17
|
-
return
|
|
24
|
+
return parts.join("\n\n");
|
|
18
25
|
}
|
|
19
26
|
export function agMessageToRow(msg, ctx) {
|
|
20
27
|
const text = messageText(msg);
|
|
@@ -28,56 +35,66 @@ export function agMessageToRow(msg, ctx) {
|
|
|
28
35
|
kind,
|
|
29
36
|
authorRole: roleToAuthor(msg.role),
|
|
30
37
|
...(text ? { text } : {}),
|
|
31
|
-
|
|
38
|
+
// guuey#122: the ONE deliberate byte-identity exception — tool-result
|
|
39
|
+
// `_meta` (live-turn mount material, incl. short-TTL credentials) is
|
|
40
|
+
// stripped before persistence, on the message row exactly as on the
|
|
41
|
+
// card projection.
|
|
42
|
+
content: messageWithoutToolResultMeta(msg),
|
|
43
|
+
// SANCTIONED EXCEPTION to the #122 no-short-TTL rule (explicit decision,
|
|
44
|
+
// 2026-08-08): the turnRecord persists verbatim, INCLUDING paused-outcome
|
|
45
|
+
// asks[].token/expiresAt — reassembleFold round-trips turns into the
|
|
46
|
+
// reducer seed, so stripping could orphan a paused turn's resume after a
|
|
47
|
+
// reload, and the token authorizes the thread owner's OWN resume (not a
|
|
48
|
+
// cross-trust credential like a render wsToken). Revisit on guuey#122.
|
|
32
49
|
...(ctx.turnRecord ? { aiContext: ctx.turnRecord } : {}),
|
|
33
50
|
};
|
|
34
51
|
}
|
|
52
|
+
/** Strip every tool-result block's `_meta` (guuey#122); identity when none carried one. */
|
|
53
|
+
function messageWithoutToolResultMeta(msg) {
|
|
54
|
+
let changed = false;
|
|
55
|
+
const content = msg.content.map((block) => {
|
|
56
|
+
if (block.type !== "tool-result")
|
|
57
|
+
return block;
|
|
58
|
+
const stripped = withoutToolResultMeta(block);
|
|
59
|
+
if (stripped !== block)
|
|
60
|
+
changed = true;
|
|
61
|
+
return stripped;
|
|
62
|
+
});
|
|
63
|
+
return changed ? { ...msg, content } : msg;
|
|
64
|
+
}
|
|
35
65
|
/**
|
|
36
66
|
* MCP-App cards ride `tool-result` blocks' UI channels inside AgMessages —
|
|
37
67
|
* the Claude facet emits NO `artifact.*` events, so `fold.artifacts` alone
|
|
38
68
|
* misses every generative-UI card and nothing ever wrote a `kind:'card'`
|
|
39
69
|
* row for them (guuey#86: cards never rehydrated after a reload). Project
|
|
40
70
|
* UI-carrying tool-result blocks into synthetic AgArtifacts so the
|
|
41
|
-
* EXISTING card-row lane persists them; the client's `
|
|
71
|
+
* EXISTING card-row lane persists them; the client's `snapshotViewMount`
|
|
42
72
|
* mounts `{ parts: [block] }` through its inline arm unchanged. Facets
|
|
43
73
|
* that DO emit artifact events don't stamp `uiData` on tool-results, so
|
|
44
74
|
* the two sources don't double-write for one card.
|
|
45
75
|
*
|
|
46
|
-
* The narrowing
|
|
47
|
-
*
|
|
76
|
+
* The narrowing is IMPORTED from `@guuey/mcp-apps-host` (the SEP-1865 Host
|
|
77
|
+
* package) — one implementation, no mirror to keep in sync:
|
|
48
78
|
* 1. `uiData` carrying an MCP resource payload (`{uri, text|blob}`,
|
|
49
79
|
* inlined directly or wrapped as `{ resource: {...} }`) — the explicit
|
|
50
80
|
* surface channel, no `ui://` gate on purpose;
|
|
51
81
|
* 2. a `ui://` resource degraded into a `provider-raw` content part —
|
|
52
82
|
* gated on the scheme, because provider-raw is a lossy catch-all.
|
|
53
83
|
*/
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
return
|
|
68
|
-
}
|
|
69
|
-
function uiDataCarriesResource(uiData) {
|
|
70
|
-
if (!isJsonObject(uiData))
|
|
71
|
-
return false;
|
|
72
|
-
return isResourcePayload(uiData) || isResourcePayload(uiData.resource);
|
|
73
|
-
}
|
|
74
|
-
function providerRawCarriesUiResource(raw) {
|
|
75
|
-
if (!isJsonObject(raw))
|
|
76
|
-
return false;
|
|
77
|
-
const candidate = raw.resource !== undefined ? raw.resource : raw;
|
|
78
|
-
if (!isResourcePayload(candidate))
|
|
79
|
-
return false;
|
|
80
|
-
return resourceUri(candidate)?.startsWith("ui://") === true;
|
|
84
|
+
/**
|
|
85
|
+
* Persistence boundary rule (guuey#122): a tool-result block's `_meta` is
|
|
86
|
+
* live-turn MOUNT/TRANSPORT material — vendor slices there routinely carry
|
|
87
|
+
* short-TTL credentials (ggui's render bootstrap `wsToken` is the concrete
|
|
88
|
+
* case) and are dead on arrival when replayed, so `_meta` NEVER persists.
|
|
89
|
+
* Durable identity belongs in `uiData`/`content`, which survive untouched.
|
|
90
|
+
* Deliberately vendor-agnostic: no `_meta` key list to maintain, and the
|
|
91
|
+
* next vendor's expiring credential is unpersistable by construction.
|
|
92
|
+
*/
|
|
93
|
+
function withoutToolResultMeta(block) {
|
|
94
|
+
if (block._meta === undefined)
|
|
95
|
+
return block;
|
|
96
|
+
const { _meta: _dropped, ...rest } = block;
|
|
97
|
+
return rest;
|
|
81
98
|
}
|
|
82
99
|
export function uiCardArtifactsFromMessages(messages) {
|
|
83
100
|
const artifacts = [];
|
|
@@ -86,21 +103,33 @@ export function uiCardArtifactsFromMessages(messages) {
|
|
|
86
103
|
const block = msg.content[i];
|
|
87
104
|
if (block.type !== "tool-result")
|
|
88
105
|
continue;
|
|
89
|
-
|
|
90
|
-
|
|
106
|
+
// guuey#122: a ui:// locator (uiData.resourceUri — ggui renders are
|
|
107
|
+
// one producer) persists as a PLACEHOLDER row: the locator without
|
|
108
|
+
// mount material (mount = re-fetch of the resourceUri). Before this,
|
|
109
|
+
// locator-only results persisted nothing and vanished from history.
|
|
110
|
+
const carriesUi = asUiResource(block.uiData) !== undefined ||
|
|
111
|
+
uiLocator(block.uiData) !== undefined ||
|
|
112
|
+
block.content.some((part) => part.type === "provider-raw" && scanProviderRawForUiResource(part.raw) !== undefined);
|
|
91
113
|
if (!carriesUi)
|
|
92
114
|
continue;
|
|
93
115
|
artifacts.push({
|
|
94
116
|
artifactId: `${msg.id}#ui#${i}`,
|
|
95
117
|
turnId: msg.turnId ?? "",
|
|
96
118
|
threadId: msg.threadId ?? "",
|
|
97
|
-
parts: [block],
|
|
119
|
+
parts: [withoutToolResultMeta(block)],
|
|
98
120
|
});
|
|
99
121
|
}
|
|
100
122
|
}
|
|
101
123
|
return artifacts;
|
|
102
124
|
}
|
|
103
125
|
export function agArtifactToCardRow(art, ctx) {
|
|
126
|
+
// guuey#122: the projection lane strips tool-result _meta upstream, but
|
|
127
|
+
// REAL artifact.* events land here verbatim — apply the same persistence
|
|
128
|
+
// boundary to any tool-result-typed part (and the artifact-level _meta),
|
|
129
|
+
// so no lane can persist a short-TTL credential.
|
|
130
|
+
const parts = art.parts.map((part) => isJsonObjectLike(part) && part.type === "tool-result" ? withoutToolResultMeta(part) : part);
|
|
131
|
+
const { _meta: _artifactMeta, ...artRest } = art;
|
|
132
|
+
const snapshot = { ...artRest, parts };
|
|
104
133
|
return {
|
|
105
134
|
threadId: ctx.threadId,
|
|
106
135
|
seq: ctx.seq,
|
|
@@ -110,9 +139,13 @@ export function agArtifactToCardRow(art, ctx) {
|
|
|
110
139
|
kind: "card",
|
|
111
140
|
authorRole: "agent",
|
|
112
141
|
content: { producedInTurnId: art.turnId },
|
|
113
|
-
cardSnapshot:
|
|
142
|
+
cardSnapshot: snapshot,
|
|
114
143
|
};
|
|
115
144
|
}
|
|
145
|
+
/** Local structural check (narrow enough for the strip dispatch above). */
|
|
146
|
+
function isJsonObjectLike(v) {
|
|
147
|
+
return typeof v === "object" && v !== null && !Array.isArray(v);
|
|
148
|
+
}
|
|
116
149
|
/**
|
|
117
150
|
* Reconstruct an AgMessage from a row. Agent-fold rows store the verbatim
|
|
118
151
|
* AgMessage in `content`; user/system rows (persisted up-front) store plain
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@guuey/threads",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.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",
|
|
@@ -22,7 +22,8 @@
|
|
|
22
22
|
}
|
|
23
23
|
},
|
|
24
24
|
"dependencies": {
|
|
25
|
-
"@silverprotocol/core": "0.4.1"
|
|
25
|
+
"@silverprotocol/core": "0.4.1",
|
|
26
|
+
"@guuey/mcp-apps-host": "0.3.0"
|
|
26
27
|
},
|
|
27
28
|
"devDependencies": {
|
|
28
29
|
"@types/node": "^24.0.0",
|