@guuey/threads 0.31.0 → 0.32.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/in-memory.js CHANGED
@@ -1,3 +1,11 @@
1
+ /**
2
+ * InMemoryThreadPersistence — the dev/test binding of
3
+ * {@link ThreadPersistencePort}. Same contract as any real binding (run
4
+ * the suite in `@guuey/threads/testing` to prove yours), including the
5
+ * two conditional-write guards and the null-preview no-touch semantics.
6
+ */
7
+ import { previewDigest } from "./seal.js";
8
+ import { ThreadClosedError, threadClosure } from "./closed.js";
1
9
  export class InMemoryThreadPersistence {
2
10
  threads = new Map();
3
11
  messages = [];
@@ -15,15 +23,33 @@ export class InMemoryThreadPersistence {
15
23
  const t = this.threads.get(threadId);
16
24
  if (!t)
17
25
  throw new Error(`incrementSeq: thread ${threadId} does not exist`);
26
+ if (threadClosure(t) !== undefined)
27
+ throw new ThreadClosedError(threadId);
18
28
  t.lastSeq += 1;
19
29
  t.lastMessageAt = atIso;
20
30
  t.updatedAt = atIso;
21
31
  // null = allocate the seq WITHOUT touching the preview (card rows,
22
32
  // text-less agent turns) — mirror of the hosted binding's branch.
23
- if (preview !== null)
33
+ if (preview !== null) {
24
34
  t.lastMessagePreview = preview;
35
+ // The preview's source seq and digest ride the same update, so a reader
36
+ // can tell whether the message the preview came from is sealed (`seal.ts`).
37
+ t.lastMessagePreviewSeq = t.lastSeq;
38
+ t.lastMessagePreviewDigest = previewDigest(preview);
39
+ }
25
40
  return t.lastSeq;
26
41
  }
42
+ async closeThread(threadId, closure) {
43
+ const t = this.threads.get(threadId);
44
+ if (!t)
45
+ throw new Error(`closeThread: thread ${threadId} does not exist`);
46
+ if (threadClosure(t) !== undefined)
47
+ return "already_closed";
48
+ t.closedCode = closure.code;
49
+ t.closedMessage = closure.message;
50
+ t.closedAt = closure.at;
51
+ return "closed";
52
+ }
27
53
  async putMessage(row) {
28
54
  if (this.messages.some((m) => m.threadId === row.threadId && m.seq === row.seq)) {
29
55
  throw new Error(`putMessage: seq ${row.seq} already exists on ${row.threadId}`);
package/dist/index.d.ts CHANGED
@@ -17,10 +17,16 @@
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, TurnOrigin, ClientClass, } from "./rows.js";
21
- export { ThreadStore, type AppendFoldInput, type AppendFoldResult, type AppendMessageInput, type AppendMessageResult, type EnsureThreadInput, } from "./store.js";
20
+ export type { HandoffEvent, ReportEvent, SealInterval, StoredHistoryMessage, ThreadMessageEvent, ThreadMessageKind, ThreadMessageRole, RowBudget, ThreadClosure, ThreadMessageRow, ThreadPersistencePort, ThreadRow, ThreadSnapshotRow, TurnOrigin, ClientClass, ThreadInputKind, } from "./rows.js";
21
+ export { THREAD_INPUT_KINDS, readThreadInputKind } from "./rows.js";
22
+ export { DYNAMODB_ITEM_LIMIT_BYTES, DYNAMODB_ROW_BUDGET, DYNAMODB_ROW_BUDGET_BYTES, dynamoItemBytes, dynamoNumberFits, } from "./item-size.js";
23
+ export { ThreadStore, STORED_TURN_TOO_LARGE_CODE, STORED_TURN_UNSTORABLE_NUMBER_CODE, type AppendFoldInput, type AppendFoldUnstorable, type AppendFoldResult, type ElidedToolInput, type AppendMessageInput, type AppendMessageResult, type EnsureThreadInput, } from "./store.js";
22
24
  export { InMemoryThreadPersistence } from "./in-memory.js";
23
- export { AGENT_ERROR_ROW_SUFFIX, classifyStoredRow, isBlankBlock, isInformationless, type StoredRowClass, } from "./stored-row.js";
25
+ export { ThreadClosedError, threadClosure } from "./closed.js";
26
+ export { hasSealedPart, isSeqSealed, previewDigest, readableThreadPreview, readSealState, threadSealState, type StoredPreviewSlice, type ThreadSealState, } from "./seal.js";
27
+ export { AGENT_ERROR_ROW_SUFFIX, classifyStoredRow, type ClassifiedRowInput, isBlankBlock, isInformationless, type StoredRowClass, } from "./stored-row.js";
24
28
  export { HttpThreadPersistence, HttpThreadStoreError, type HttpThreadPersistenceOptions, type ThreadScope, } from "./http.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";
29
+ export { planSnapshot, type PlanSnapshotInput, type PlannedSnapshot, type SnapshotRefusal, type SnapshotRefusalReason, } from "./snapshot-plan.js";
30
+ export { agArtifactToCardRow, agMessageToBoundedRow, agMessageToRow, assertRowFits, cardUiDataByCallId, type CardUiData, type CardUiDataByCallId, ELIDED_VALUE_KEY, isStoredRowRefusal, StoredRowTooLargeError, StoredRowUnstorableNumberError, type ElidedField, type ElisionReason, type RefusedRowKind, type StoredElision, type StoredRowRefusal, cardRowToAgArtifact, messageFullText, messageNarration, messageText, producingToolName, storedTextParts, storedToolCallViews, storedToolResultCallIds, storedToolResults, toolResultCarriesUi, type StoredToolCallView, type StoredToolResultBlock, type StoredRowText, type StoredTextParts, joinStoredThreadMemory, reassembleFold, rowToAgMessage, seedEventsForReducer, splitStoredThreadMemory, toolNamesByCallId, uiCardArtifactsFromMessages, type RowCtx, } from "./fold-rows.js";
31
+ export { isViewDirectiveText, latestPaintProps, paintPartsOfStored, QUICK_REPLY_INTENT, quickReplyLabel, readTapLabels, writeTapLabels, type CardPaint, type CardProps, type LatestPaint, type PaintPart, type TapLabels, } from "@guuey/mcp-apps-host/narrowing";
26
32
  //# 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,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"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AACH,YAAY,EACV,YAAY,EACZ,WAAW,EACX,YAAY,EACZ,oBAAoB,EACpB,kBAAkB,EAClB,iBAAiB,EACjB,iBAAiB,EACjB,SAAS,EACT,aAAa,EACb,gBAAgB,EAChB,qBAAqB,EACrB,SAAS,EACT,iBAAiB,EACjB,UAAU,EACV,WAAW,EACX,eAAe,GAChB,MAAM,WAAW,CAAC;AACnB,OAAO,EAAE,kBAAkB,EAAE,mBAAmB,EAAE,MAAM,WAAW,CAAC;AACpE,OAAO,EACL,yBAAyB,EACzB,mBAAmB,EACnB,yBAAyB,EACzB,eAAe,EACf,gBAAgB,GACjB,MAAM,gBAAgB,CAAC;AACxB,OAAO,EACL,WAAW,EACX,0BAA0B,EAC1B,kCAAkC,EAClC,KAAK,eAAe,EACpB,KAAK,oBAAoB,EACzB,KAAK,gBAAgB,EACrB,KAAK,eAAe,EACpB,KAAK,kBAAkB,EACvB,KAAK,mBAAmB,EACxB,KAAK,iBAAiB,GACvB,MAAM,YAAY,CAAC;AACpB,OAAO,EAAE,yBAAyB,EAAE,MAAM,gBAAgB,CAAC;AAC3D,OAAO,EAAE,iBAAiB,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAC/D,OAAO,EACL,aAAa,EACb,WAAW,EACX,aAAa,EACb,qBAAqB,EACrB,aAAa,EACb,eAAe,EACf,KAAK,kBAAkB,EACvB,KAAK,eAAe,GACrB,MAAM,WAAW,CAAC;AACnB,OAAO,EACL,sBAAsB,EACtB,iBAAiB,EACjB,KAAK,kBAAkB,EACvB,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,YAAY,EACZ,KAAK,iBAAiB,EACtB,KAAK,eAAe,EACpB,KAAK,eAAe,EACpB,KAAK,qBAAqB,GAC3B,MAAM,oBAAoB,CAAC;AAC5B,OAAO,EACL,mBAAmB,EACnB,qBAAqB,EACrB,cAAc,EACd,aAAa,EACb,kBAAkB,EAClB,KAAK,UAAU,EACf,KAAK,kBAAkB,EACvB,gBAAgB,EAChB,kBAAkB,EAClB,sBAAsB,EACtB,8BAA8B,EAC9B,KAAK,WAAW,EAChB,KAAK,aAAa,EAClB,KAAK,cAAc,EACnB,KAAK,aAAa,EAClB,KAAK,gBAAgB,EACrB,mBAAmB,EACnB,eAAe,EACf,gBAAgB,EAChB,WAAW,EACX,iBAAiB,EACjB,eAAe,EACf,mBAAmB,EACnB,uBAAuB,EACvB,iBAAiB,EACjB,mBAAmB,EACnB,KAAK,kBAAkB,EACvB,KAAK,qBAAqB,EAC1B,KAAK,aAAa,EAClB,KAAK,eAAe,EACpB,sBAAsB,EACtB,cAAc,EACd,cAAc,EACd,oBAAoB,EACpB,uBAAuB,EACvB,iBAAiB,EACjB,2BAA2B,EAC3B,KAAK,MAAM,GACZ,MAAM,gBAAgB,CAAC;AAOxB,OAAO,EACL,mBAAmB,EACnB,gBAAgB,EAChB,kBAAkB,EAClB,kBAAkB,EAClB,eAAe,EACf,aAAa,EACb,cAAc,EACd,KAAK,SAAS,EACd,KAAK,SAAS,EACd,KAAK,WAAW,EAChB,KAAK,SAAS,EACd,KAAK,SAAS,GACf,MAAM,gCAAgC,CAAC"}
package/dist/index.js CHANGED
@@ -1,5 +1,17 @@
1
- export { ThreadStore, } from "./store.js";
1
+ export { THREAD_INPUT_KINDS, readThreadInputKind } from "./rows.js";
2
+ export { DYNAMODB_ITEM_LIMIT_BYTES, DYNAMODB_ROW_BUDGET, DYNAMODB_ROW_BUDGET_BYTES, dynamoItemBytes, dynamoNumberFits, } from "./item-size.js";
3
+ export { ThreadStore, STORED_TURN_TOO_LARGE_CODE, STORED_TURN_UNSTORABLE_NUMBER_CODE, } from "./store.js";
2
4
  export { InMemoryThreadPersistence } from "./in-memory.js";
5
+ export { ThreadClosedError, threadClosure } from "./closed.js";
6
+ export { hasSealedPart, isSeqSealed, previewDigest, readableThreadPreview, readSealState, threadSealState, } from "./seal.js";
3
7
  export { AGENT_ERROR_ROW_SUFFIX, classifyStoredRow, isBlankBlock, isInformationless, } from "./stored-row.js";
4
8
  export { HttpThreadPersistence, HttpThreadStoreError, } from "./http.js";
5
- export { agArtifactToCardRow, agMessageToRow, cardRowToAgArtifact, messageFullText, messageNarration, messageText, producingToolName, storedTextParts, joinStoredThreadMemory, reassembleFold, rowToAgMessage, seedEventsForReducer, splitStoredThreadMemory, toolNamesByCallId, uiCardArtifactsFromMessages, } from "./fold-rows.js";
9
+ export { planSnapshot, } from "./snapshot-plan.js";
10
+ export { agArtifactToCardRow, agMessageToBoundedRow, agMessageToRow, assertRowFits, cardUiDataByCallId, ELIDED_VALUE_KEY, isStoredRowRefusal, StoredRowTooLargeError, StoredRowUnstorableNumberError, cardRowToAgArtifact, messageFullText, messageNarration, messageText, producingToolName, storedTextParts, storedToolCallViews, storedToolResultCallIds, storedToolResults, toolResultCarriesUi, joinStoredThreadMemory, reassembleFold, rowToAgMessage, seedEventsForReducer, splitStoredThreadMemory, toolNamesByCallId, uiCardArtifactsFromMessages, } from "./fold-rows.js";
11
+ // guuey#2031 — the card-tap contract, re-exported for the persistence side (the
12
+ // runtime stamps a user row's tap labels; the read plane forwards them) so a
13
+ // server imports it from the package it already uses. The names live in
14
+ // `@guuey/mcp-apps-host`'s protocol-free subpath: the writer and the reader of a
15
+ // row's `tapLabels`, the one directive predicate, and the one paint reducer the
16
+ // runtime reads a tapped card with, with the quick-reply intent it labels under.
17
+ export { isViewDirectiveText, latestPaintProps, paintPartsOfStored, QUICK_REPLY_INTENT, quickReplyLabel, readTapLabels, writeTapLabels, } from "@guuey/mcp-apps-host/narrowing";
@@ -0,0 +1,56 @@
1
+ /**
2
+ * The size of a stored row as DynamoDB counts it, so a binding backed by
3
+ * DynamoDB can declare a {@link RowBudget} that matches the store's own item
4
+ * limit (400 KB).
5
+ *
6
+ * The count follows the published rules (DynamoDB Developer Guide, "Item
7
+ * sizes and formats"), walked over the row the way the DocumentClient
8
+ * marshals it (string → S, number → N, boolean → BOOL, null → NULL,
9
+ * array → L, object → M, `undefined` dropped):
10
+ *
11
+ * - a string is its UTF-8 bytes;
12
+ * - a boolean or null is 1 byte;
13
+ * - a number is published as 1 byte per two significant digits, plus 1. As
14
+ * measured against DynamoDB it is 1 exponent byte, plus the digits in pairs
15
+ * aligned on the decimal point (so up to half a pair more than the digits
16
+ * alone), plus 1 more byte when the number is negative: -1.5 is 4 bytes and
17
+ * -123.456 is 6. It is bounded here from above as half of ALL the digits in
18
+ * `String(n)`, rounded up, plus 3, so an exponent form can only over-count;
19
+ * - a List or Map is 3 bytes, plus 1 byte per element, plus each Map key's
20
+ * UTF-8 bytes and each element's own size;
21
+ * - a top-level attribute is its name's UTF-8 bytes plus its value, plus 1.
22
+ *
23
+ * The result is an UPPER bound on the stored size: it may count a little
24
+ * more than DynamoDB does, never less.
25
+ *
26
+ * Why not the length of the row's JSON: JSON under-counts the shapes a chart
27
+ * input is made of. A list of one-digit numbers costs 2 bytes per element as
28
+ * JSON (`1,`) and 3 by the rules above; pairs (`[1,2],`) cost 6 against 10.
29
+ * A row measured as 380 KB of JSON could be about 630 KB in DynamoDB.
30
+ */
31
+ import type { RowBudget } from "./rows.js";
32
+ /** DynamoDB's maximum item size: 400 KB. */
33
+ export declare const DYNAMODB_ITEM_LIMIT_BYTES = 409600;
34
+ /**
35
+ * The row budget a DynamoDB-backed binding declares: below the item limit,
36
+ * so the seq and timestamp the store assigns at write time, and the bound's
37
+ * own rounding, can never carry a row that fit over the limit.
38
+ */
39
+ export declare const DYNAMODB_ROW_BUDGET_BYTES = 380000;
40
+ /**
41
+ * Whether a DynamoDB-backed binding can store `n` as written. The
42
+ * DocumentClient refuses a number outside the safe-integer range, and NaN or
43
+ * an infinity, before it sends anything (a JSON number that large has already
44
+ * lost its digits to `JSON.parse`); DynamoDB itself refuses a non-zero
45
+ * magnitude below 1e-130. A row carrying such a number would fail its write
46
+ * whatever its size, so it is planned out like an oversized one.
47
+ */
48
+ export declare function dynamoNumberFits(n: number): boolean;
49
+ /** The row budget every DynamoDB-backed binding declares. */
50
+ export declare const DYNAMODB_ROW_BUDGET: RowBudget;
51
+ /**
52
+ * The stored size of an item (a `ThreadMessage` row, or any attribute bag the
53
+ * DocumentClient marshals), as an upper bound under DynamoDB's own rules.
54
+ */
55
+ export declare function dynamoItemBytes(item: object): number;
56
+ //# sourceMappingURL=item-size.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"item-size.d.ts","sourceRoot":"","sources":["../src/item-size.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6BG;AAEH,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,WAAW,CAAC;AAE3C,4CAA4C;AAC5C,eAAO,MAAM,yBAAyB,SAAU,CAAC;AAEjD;;;;GAIG;AACH,eAAO,MAAM,yBAAyB,SAAU,CAAC;AAKjD;;;;;;;GAOG;AACH,wBAAgB,gBAAgB,CAAC,CAAC,EAAE,MAAM,GAAG,OAAO,CAInD;AAED,6DAA6D;AAC7D,eAAO,MAAM,mBAAmB,EAAE,SAIjC,CAAC;AAsDF;;;GAGG;AACH,wBAAgB,eAAe,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAOpD"}
@@ -0,0 +1,125 @@
1
+ /**
2
+ * The size of a stored row as DynamoDB counts it, so a binding backed by
3
+ * DynamoDB can declare a {@link RowBudget} that matches the store's own item
4
+ * limit (400 KB).
5
+ *
6
+ * The count follows the published rules (DynamoDB Developer Guide, "Item
7
+ * sizes and formats"), walked over the row the way the DocumentClient
8
+ * marshals it (string → S, number → N, boolean → BOOL, null → NULL,
9
+ * array → L, object → M, `undefined` dropped):
10
+ *
11
+ * - a string is its UTF-8 bytes;
12
+ * - a boolean or null is 1 byte;
13
+ * - a number is published as 1 byte per two significant digits, plus 1. As
14
+ * measured against DynamoDB it is 1 exponent byte, plus the digits in pairs
15
+ * aligned on the decimal point (so up to half a pair more than the digits
16
+ * alone), plus 1 more byte when the number is negative: -1.5 is 4 bytes and
17
+ * -123.456 is 6. It is bounded here from above as half of ALL the digits in
18
+ * `String(n)`, rounded up, plus 3, so an exponent form can only over-count;
19
+ * - a List or Map is 3 bytes, plus 1 byte per element, plus each Map key's
20
+ * UTF-8 bytes and each element's own size;
21
+ * - a top-level attribute is its name's UTF-8 bytes plus its value, plus 1.
22
+ *
23
+ * The result is an UPPER bound on the stored size: it may count a little
24
+ * more than DynamoDB does, never less.
25
+ *
26
+ * Why not the length of the row's JSON: JSON under-counts the shapes a chart
27
+ * input is made of. A list of one-digit numbers costs 2 bytes per element as
28
+ * JSON (`1,`) and 3 by the rules above; pairs (`[1,2],`) cost 6 against 10.
29
+ * A row measured as 380 KB of JSON could be about 630 KB in DynamoDB.
30
+ */
31
+ /** DynamoDB's maximum item size: 400 KB. */
32
+ export const DYNAMODB_ITEM_LIMIT_BYTES = 409_600;
33
+ /**
34
+ * The row budget a DynamoDB-backed binding declares: below the item limit,
35
+ * so the seq and timestamp the store assigns at write time, and the bound's
36
+ * own rounding, can never carry a row that fit over the limit.
37
+ */
38
+ export const DYNAMODB_ROW_BUDGET_BYTES = 380_000;
39
+ /** The smallest non-zero magnitude DynamoDB stores; below it a write is refused as an underflow. */
40
+ const DYNAMODB_MIN_NUMBER_MAGNITUDE = 1e-130;
41
+ /**
42
+ * Whether a DynamoDB-backed binding can store `n` as written. The
43
+ * DocumentClient refuses a number outside the safe-integer range, and NaN or
44
+ * an infinity, before it sends anything (a JSON number that large has already
45
+ * lost its digits to `JSON.parse`); DynamoDB itself refuses a non-zero
46
+ * magnitude below 1e-130. A row carrying such a number would fail its write
47
+ * whatever its size, so it is planned out like an oversized one.
48
+ */
49
+ export function dynamoNumberFits(n) {
50
+ if (!Number.isFinite(n))
51
+ return false;
52
+ const magnitude = Math.abs(n);
53
+ return magnitude === 0 || (magnitude >= DYNAMODB_MIN_NUMBER_MAGNITUDE && magnitude <= Number.MAX_SAFE_INTEGER);
54
+ }
55
+ /** The row budget every DynamoDB-backed binding declares. */
56
+ export const DYNAMODB_ROW_BUDGET = {
57
+ maxBytes: DYNAMODB_ROW_BUDGET_BYTES,
58
+ bytesOf: dynamoItemBytes,
59
+ numberFits: dynamoNumberFits,
60
+ };
61
+ const LIST_OR_MAP_OVERHEAD = 3;
62
+ const ELEMENT_OVERHEAD = 1;
63
+ function utf8Bytes(s) {
64
+ return Buffer.byteLength(s, "utf8");
65
+ }
66
+ function numberBytes(n) {
67
+ let digits = 0;
68
+ for (const ch of String(n)) {
69
+ if (ch >= "0" && ch <= "9")
70
+ digits++;
71
+ }
72
+ // + 1 exponent byte, + 1 for a pair split by the decimal point, + 1 for a negative's terminator.
73
+ return Math.ceil(digits / 2) + 3;
74
+ }
75
+ /**
76
+ * The stored size of one attribute VALUE. `undefined` values are dropped by
77
+ * the marshaller, so they cost nothing and their element overhead is not
78
+ * counted either.
79
+ */
80
+ function valueBytes(value) {
81
+ if (value === null)
82
+ return 1;
83
+ switch (typeof value) {
84
+ case "string":
85
+ return utf8Bytes(value);
86
+ case "number":
87
+ return numberBytes(value);
88
+ case "boolean":
89
+ return 1;
90
+ case "object": {
91
+ let total = LIST_OR_MAP_OVERHEAD;
92
+ if (Array.isArray(value)) {
93
+ for (const el of value) {
94
+ if (el === undefined)
95
+ continue;
96
+ total += ELEMENT_OVERHEAD + valueBytes(el);
97
+ }
98
+ return total;
99
+ }
100
+ for (const [key, el] of Object.entries(value)) {
101
+ if (el === undefined)
102
+ continue;
103
+ total += ELEMENT_OVERHEAD + utf8Bytes(key) + valueBytes(el);
104
+ }
105
+ return total;
106
+ }
107
+ default:
108
+ // undefined (dropped by the marshaller); functions, symbols and bigints
109
+ // never reach a stored row.
110
+ return 0;
111
+ }
112
+ }
113
+ /**
114
+ * The stored size of an item (a `ThreadMessage` row, or any attribute bag the
115
+ * DocumentClient marshals), as an upper bound under DynamoDB's own rules.
116
+ */
117
+ export function dynamoItemBytes(item) {
118
+ let total = 0;
119
+ for (const [name, value] of Object.entries(item)) {
120
+ if (value === undefined)
121
+ continue;
122
+ total += utf8Bytes(name) + valueBytes(value) + 1;
123
+ }
124
+ return total;
125
+ }
package/dist/rows.d.ts CHANGED
@@ -10,6 +10,19 @@
10
10
  * thing" are the same mechanical guarantee — the `@guuey/state` pattern.
11
11
  */
12
12
  import type { AgMemoryRecord, JsonValue } from "@silverprotocol/core";
13
+ import type { TapLabels } from "@guuey/mcp-apps-host/narrowing";
14
+ /**
15
+ * guuey#2549: what a user turn's content rides on when its text is empty. Mirrors `@guuey/worker`'s
16
+ * `INPUT_KINDS` (this package does not depend on it); nocode-runtime's sync test pins them equal.
17
+ */
18
+ export declare const THREAD_INPUT_KINDS: readonly ["context-only"];
19
+ export type ThreadInputKind = (typeof THREAD_INPUT_KINDS)[number];
20
+ /**
21
+ * The reader contract for a stored row's `inputKind`: a known kind, or
22
+ * undefined for anything else (absent, malformed, a kind this release does not
23
+ * know). Never throws: a stored row is read across a rolling release.
24
+ */
25
+ export declare function readThreadInputKind(raw: unknown): ThreadInputKind | undefined;
13
26
  export type ThreadMessageRole = "user" | "agent" | "system";
14
27
  export type ThreadMessageKind = "text" | "card" | "event";
15
28
  /**
@@ -38,10 +51,65 @@ export interface ThreadRow {
38
51
  lastSeq: number;
39
52
  lastMessageAt: string;
40
53
  lastMessagePreview: string;
54
+ /**
55
+ * The seq of the message {@link lastMessagePreview} was taken from, written
56
+ * in the same atomic update. Optional: rows an older writer wrote carry
57
+ * none, and a reader treats that as "source unknown" (`readableThreadPreview`, `seal.ts`).
58
+ */
59
+ lastMessagePreviewSeq?: number;
60
+ /**
61
+ * `previewDigest(lastMessagePreview)` (`seal.ts`), written in the same update
62
+ * as the preview and its seq. It binds the seq to the text, so a preview an
63
+ * older writer replaced (leaving the previous seq) reads as "source unknown".
64
+ */
65
+ lastMessagePreviewDigest?: string;
66
+ /**
67
+ * The thread's seal intervals: the messages the app's operator may not
68
+ * read. Absent on every thread no seal was ever written for (a legacy,
69
+ * wholly readable thread). Read only through `readSealState` (`seal.ts`),
70
+ * never trusted as stored.
71
+ *
72
+ * Typed as stored, not as {@link SealInterval}[]: a binding hands the stored
73
+ * attribute on unchanged, and one this release cannot read must still reach
74
+ * `readSealState`, which reads it as wholly sealed (fail closed). A writer
75
+ * writes {@link SealInterval}[]; a reader gets intervals only from
76
+ * `readSealState` / `threadSealState`.
77
+ */
78
+ sealIntervals?: unknown;
79
+ /**
80
+ * The conversation was ended for a reason (a worker asked for it, e.g. a
81
+ * per-conversation limit): it takes no new message, and a client that sends
82
+ * on it starts a new conversation. All three are written together, once,
83
+ * and never cleared. `closedCode` is the machine-readable reason (a
84
+ * worker's code arrives prefixed `app:`), `closedMessage` what the person
85
+ * reads, `closedAt` when (ISO). Absent on every open thread and on every
86
+ * row written before closing existed. Read through `threadClosure`
87
+ * (`closed.ts`). Separate from {@link status}: an archived thread may also
88
+ * be closed.
89
+ */
90
+ closedCode?: string;
91
+ closedMessage?: string;
92
+ closedAt?: string;
41
93
  threadMode: string;
42
94
  createdAt: string;
43
95
  updatedAt: string;
44
96
  }
97
+ /** Why and when a conversation was ended (see {@link ThreadRow.closedAt}). */
98
+ export interface ThreadClosure {
99
+ code: string;
100
+ message: string;
101
+ /** ISO timestamp. */
102
+ at: string;
103
+ }
104
+ /**
105
+ * One sealed stretch of a thread: the messages from `fromSeq` to `toSeq`, both
106
+ * inclusive. No `toSeq` means the interval is still open, so it also covers
107
+ * every message written after `fromSeq`.
108
+ */
109
+ export interface SealInterval {
110
+ fromSeq: number;
111
+ toSeq?: number;
112
+ }
45
113
  /**
46
114
  * The human-handoff event payload (guuey#552 §3.2): the MACHINE half of a
47
115
  * `kind:'event'` handoff row — the notify Lambda's stream filter reads
@@ -132,10 +200,25 @@ export interface ThreadMessageRow {
132
200
  event?: ThreadMessageEvent;
133
201
  /** On a user row: how the turn was started. Absent on older rows. */
134
202
  turnOrigin?: TurnOrigin;
203
+ /**
204
+ * guuey#2549: set on a user row whose `text` is empty because the turn's content rode on the
205
+ * page context (`'context-only'`). The history replays it through the worker's one substitution,
206
+ * so the model sees the same words on every later turn. Absent on every other row.
207
+ */
208
+ inputKind?: ThreadInputKind;
135
209
  /** On a `card_action` user row: the render session id of the card the gesture answered. */
136
210
  answeredCardSessionId?: string;
137
211
  /** On a user row: the sending client's class. Absent on older rows. */
138
212
  clientClass?: ClientClass;
213
+ /**
214
+ * On a card-action user row (guuey#2031): the tapped controls' own words,
215
+ * one entry per tap the row stands for (`null` for a tap with none), as the
216
+ * sending client resolved them. DISPLAY-ONLY: `text` and `content` stay the
217
+ * directive the doorbell sent, and no model path reads this. Absent on rows
218
+ * written before it existed and on every row whose turn had no labels;
219
+ * readers read it through the reader contract (`readTapLabels`).
220
+ */
221
+ tapLabels?: TapLabels;
139
222
  }
140
223
  /** Latest-replace fold snapshot for a thread. */
141
224
  export interface ThreadSnapshotRow {
@@ -169,6 +252,25 @@ export interface StoredHistoryMessage {
169
252
  text: string | null;
170
253
  content: unknown;
171
254
  at: string;
255
+ /** guuey#2549: carried from the row (see {@link ThreadMessageRow.inputKind}). */
256
+ inputKind?: ThreadInputKind;
257
+ }
258
+ /**
259
+ * A bound on the size of one stored `ThreadMessage` row, declared by a
260
+ * binding whose store has an item-size limit. `bytesOf` measures a row the
261
+ * way the store counts it; `maxBytes` is the most a row may measure. The
262
+ * thread's snapshot row is planned against the same budget before a turn
263
+ * writes any row (guuey#2120), so `bytesOf` measures a {@link ThreadSnapshotRow}
264
+ * too (its stored form joins the two memory arrays into one, which is never
265
+ * larger).
266
+ * `numberFits`, when declared, says whether the store can hold a number as
267
+ * written; a row carrying one it cannot is refused like an oversized row.
268
+ * DynamoDB-backed bindings declare `DYNAMODB_ROW_BUDGET` (`item-size.ts`).
269
+ */
270
+ export interface RowBudget {
271
+ readonly maxBytes: number;
272
+ bytesOf(row: ThreadMessageRow | ThreadSnapshotRow): number;
273
+ numberFits?(n: number): boolean;
172
274
  }
173
275
  /**
174
276
  * The only surface {@link ThreadStore} needs from a backing store. Keep
@@ -176,6 +278,14 @@ export interface StoredHistoryMessage {
176
278
  * `@guuey/threads/testing`.
177
279
  */
178
280
  export interface ThreadPersistencePort {
281
+ /**
282
+ * The store's bound on one message row, when it has one. {@link ThreadStore}
283
+ * sizes every row of a turn against it before it writes any: an oversized
284
+ * tool-call input is replaced by an explicit marker, and a turn that still
285
+ * cannot fit is stored as one failure row (or refused), never written in
286
+ * part. Absent: no bound, and rows are written exactly as mapped.
287
+ */
288
+ readonly rowBudget?: RowBudget;
179
289
  /** Point-read a Thread by id; `undefined` when the row does not exist. */
180
290
  getThread(threadId: string): Promise<ThreadRow | undefined>;
181
291
  /** Put a new Thread row (conditional create — must reject an existing id). */
@@ -185,9 +295,20 @@ export interface ThreadPersistencePort {
185
295
  * EXISTING Thread and return the NEW `lastSeq`. `preview: null`
186
296
  * allocates the seq WITHOUT touching `lastMessagePreview` (card rows and
187
297
  * text-less agent turns must not clobber the last real preview);
188
- * a string preview replaces it.
298
+ * a string preview replaces it. On a closed thread
299
+ * ({@link ThreadRow.closedAt}) it allocates nothing and rejects with
300
+ * `ThreadClosedError`.
189
301
  */
190
302
  incrementSeq(threadId: string, preview: string | null, atIso: string): Promise<number>;
303
+ /**
304
+ * Close an EXISTING thread: write the three closure fields
305
+ * ({@link ThreadRow.closedAt}) in one conditional update that holds only
306
+ * while the thread exists and is still open. `already_closed` when it was
307
+ * closed before (the first closure is kept); rejects for a missing thread.
308
+ * Optional: a binding without it cannot close a conversation
309
+ * (`ThreadStore.closeThread` answers `unsupported`).
310
+ */
311
+ closeThread?(threadId: string, closure: ThreadClosure): Promise<"closed" | "already_closed">;
191
312
  /** Put a ThreadMessage row (conditional — must reject a duplicate seq). */
192
313
  putMessage(row: ThreadMessageRow): Promise<void>;
193
314
  /** Up to `limit` most-recent messages for a thread, returned seq-ASCending. */
@@ -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/rows.js CHANGED
@@ -1 +1,13 @@
1
- export {};
1
+ /**
2
+ * guuey#2549: what a user turn's content rides on when its text is empty. Mirrors `@guuey/worker`'s
3
+ * `INPUT_KINDS` (this package does not depend on it); nocode-runtime's sync test pins them equal.
4
+ */
5
+ export const THREAD_INPUT_KINDS = ['context-only'];
6
+ /**
7
+ * The reader contract for a stored row's `inputKind`: a known kind, or
8
+ * undefined for anything else (absent, malformed, a kind this release does not
9
+ * know). Never throws: a stored row is read across a rolling release.
10
+ */
11
+ export function readThreadInputKind(raw) {
12
+ return THREAD_INPUT_KINDS.find((kind) => kind === raw);
13
+ }
package/dist/seal.d.ts ADDED
@@ -0,0 +1,63 @@
1
+ import type { SealInterval, ThreadRow } from "./rows.js";
2
+ /** A thread's seal state as a reader sees it, after the stored attribute was checked. */
3
+ export type ThreadSealState =
4
+ /** No seal record: a legacy thread, wholly readable. */
5
+ {
6
+ kind: "none";
7
+ }
8
+ /** A well-formed record: these intervals (possibly empty) decide each message. */
9
+ | {
10
+ kind: "intervals";
11
+ intervals: readonly SealInterval[];
12
+ }
13
+ /** A record that is present but malformed: every message reads as sealed. */
14
+ | {
15
+ kind: "unreadable";
16
+ };
17
+ /**
18
+ * Check a stored `sealIntervals` attribute. Absent (`undefined` or `null`) is
19
+ * a legacy thread; anything else must be an array of well-formed intervals,
20
+ * or the whole thread reads as sealed.
21
+ */
22
+ export declare function readSealState(stored: unknown): ThreadSealState;
23
+ /** Whether the message at `seq` is sealed under `state`. */
24
+ export declare function isSeqSealed(state: ThreadSealState, seq: number): boolean;
25
+ /**
26
+ * Whether ANY part of the thread is, or was, sealed. A copy built from the
27
+ * whole conversation (a model-written summary, a report) is refused when this
28
+ * is true, since it may carry sealed messages whatever message triggered it.
29
+ */
30
+ export declare function hasSealedPart(state: ThreadSealState): boolean;
31
+ /**
32
+ * The digest a writer stores beside a preview (`lastMessagePreviewDigest`), in
33
+ * the same update that sets the preview and its source seq. It binds the seq to
34
+ * the text: a writer that replaces the preview without stamping its seq (one
35
+ * from before the stamp) leaves a digest that no longer matches, so a reader
36
+ * cannot mistake the old seq for the new text's source.
37
+ */
38
+ export declare function previewDigest(preview: string): string;
39
+ /** The thread-row slice {@link readableThreadPreview} reads, as stored. */
40
+ export interface StoredPreviewSlice {
41
+ lastMessagePreview?: string;
42
+ /** The seq of the message the preview was taken from; absent on rows a writer before it wrote. */
43
+ lastMessagePreviewSeq?: unknown;
44
+ /** {@link previewDigest} of the preview the seq was stamped with; absent on rows a writer before it wrote. */
45
+ lastMessagePreviewDigest?: unknown;
46
+ sealIntervals?: unknown;
47
+ }
48
+ /**
49
+ * The preview an operator-side reader may show: the stored preview, or `''`
50
+ * when the message it was taken from is sealed. On a thread with any sealed
51
+ * part, a preview whose source the reader cannot place is withheld too: no
52
+ * recorded seq, or a digest absent or not matching the stored text (an older
53
+ * writer replaced the text and left the previous seq beside it). A thread with
54
+ * no sealed part shows its preview as stored.
55
+ *
56
+ * One case reads as placed though it is not: an older writer that stored the
57
+ * very same text again. The text is then that of the stamped, readable
58
+ * message, so nothing new is shown.
59
+ */
60
+ export declare function readableThreadPreview(row: StoredPreviewSlice): string;
61
+ /** {@link readSealState} over a typed {@link ThreadRow}. */
62
+ export declare function threadSealState(row: Pick<ThreadRow, "sealIntervals">): ThreadSealState;
63
+ //# sourceMappingURL=seal.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"seal.d.ts","sourceRoot":"","sources":["../src/seal.ts"],"names":[],"mappings":"AAuBA,OAAO,KAAK,EAAE,YAAY,EAAE,SAAS,EAAE,MAAM,WAAW,CAAC;AAEzD,yFAAyF;AACzF,MAAM,MAAM,eAAe;AACzB,wDAAwD;AACtD;IAAE,IAAI,EAAE,MAAM,CAAA;CAAE;AAClB,kFAAkF;GAChF;IAAE,IAAI,EAAE,WAAW,CAAC;IAAC,SAAS,EAAE,SAAS,YAAY,EAAE,CAAA;CAAE;AAC3D,6EAA6E;GAC3E;IAAE,IAAI,EAAE,YAAY,CAAA;CAAE,CAAC;AAiB3B;;;;GAIG;AACH,wBAAgB,aAAa,CAAC,MAAM,EAAE,OAAO,GAAG,eAAe,CAU9D;AAED,4DAA4D;AAC5D,wBAAgB,WAAW,CAAC,KAAK,EAAE,eAAe,EAAE,GAAG,EAAE,MAAM,GAAG,OAAO,CAMxE;AAED;;;;GAIG;AACH,wBAAgB,aAAa,CAAC,KAAK,EAAE,eAAe,GAAG,OAAO,CAI7D;AAED;;;;;;GAMG;AACH,wBAAgB,aAAa,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAErD;AAED,2EAA2E;AAC3E,MAAM,WAAW,kBAAkB;IACjC,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAC5B,kGAAkG;IAClG,qBAAqB,CAAC,EAAE,OAAO,CAAC;IAChC,8GAA8G;IAC9G,wBAAwB,CAAC,EAAE,OAAO,CAAC;IACnC,aAAa,CAAC,EAAE,OAAO,CAAC;CACzB;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,qBAAqB,CAAC,GAAG,EAAE,kBAAkB,GAAG,MAAM,CASrE;AAED,4DAA4D;AAC5D,wBAAgB,eAAe,CAAC,GAAG,EAAE,IAAI,CAAC,SAAS,EAAE,eAAe,CAAC,GAAG,eAAe,CAEtF"}