@guuey/threads 0.7.1 → 0.8.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/README.md +32 -2
- package/dist/http.d.ts +86 -0
- package/dist/http.d.ts.map +1 -0
- package/dist/http.js +106 -0
- package/dist/index.d.ts +4 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/store.d.ts +9 -0
- package/dist/store.d.ts.map +1 -1
- package/dist/store.js +12 -0
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -13,8 +13,9 @@ harnesses — share it:
|
|
|
13
13
|
latest-replace snapshot), and the prompt-lane history projection.
|
|
14
14
|
- **`ThreadPersistencePort`** — the narrow surface a binding implements.
|
|
15
15
|
`InMemoryThreadPersistence` ships in the box (dev, tests, CI); guuey's
|
|
16
|
-
hosted runtime binds DynamoDB;
|
|
17
|
-
|
|
16
|
+
hosted runtime binds DynamoDB; `HttpThreadPersistence` points a
|
|
17
|
+
self-hosted agent at guuey's hosted thread API ("eject the code, keep your
|
|
18
|
+
memory"); or implement the port against your own store.
|
|
18
19
|
- **fold ↔ row mapping** — `agMessageToRow`, `reassembleFold`,
|
|
19
20
|
`uiCardArtifactsFromMessages` and friends: byte-identity persistence of an
|
|
20
21
|
`@silverprotocol/core` `AgReduceResult`, including the projection that
|
|
@@ -48,3 +49,32 @@ const history = await store.loadHistory(threadId);
|
|
|
48
49
|
import { runThreadPersistenceContractSuite } from "@guuey/threads/testing";
|
|
49
50
|
runThreadPersistenceContractSuite("MyBinding", async () => ({ port: makeMyPort() }));
|
|
50
51
|
```
|
|
52
|
+
|
|
53
|
+
## Hosted binding — keep your memory after ejecting
|
|
54
|
+
|
|
55
|
+
`HttpThreadPersistence` implements the same port over guuey's thread API, so
|
|
56
|
+
an agent you run yourself reads and writes the SAME conversation rows the
|
|
57
|
+
hosted runtime does. It is scoped to one app and one end-user: pass the
|
|
58
|
+
end-user's bearer token — one your app's configured identity issuer minted
|
|
59
|
+
(your own IdP, or guuey's per-app widget issuer via `@guuey/widget-auth`).
|
|
60
|
+
The server verifies it against that issuer, derives the same end-user id the
|
|
61
|
+
hosted runtime uses, and confines every op to `(app, user)`.
|
|
62
|
+
|
|
63
|
+
```ts
|
|
64
|
+
import { HttpThreadPersistence, ThreadStore } from "@guuey/threads";
|
|
65
|
+
|
|
66
|
+
const appId = process.env.GUUEY_APP_ID!;
|
|
67
|
+
const port = new HttpThreadPersistence({
|
|
68
|
+
baseUrl: "https://api.us-east-1.guuey.com",
|
|
69
|
+
appId,
|
|
70
|
+
token: endUserToken, // per request — the end-user's own token
|
|
71
|
+
});
|
|
72
|
+
const { userId, region } = await port.scope(); // what the token resolves to
|
|
73
|
+
const store = new ThreadStore(port);
|
|
74
|
+
const threadId = await store.ensureThread({ threadId: clientThreadId, userId, appId, region });
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
Requires the app to run identified end-users (`userAuthMode: "byo"`); guests
|
|
78
|
+
and Cognito sessions are served on-platform only. Errors surface as
|
|
79
|
+
`HttpThreadStoreError` (`status`, `code`). Reads retry once on a network
|
|
80
|
+
failure or 5xx; writes never do.
|
package/dist/http.d.ts
ADDED
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Hosted HTTP binding — {@link ThreadPersistencePort} over guuey's thread
|
|
3
|
+
* API, `POST <base>/v1/thread-store/<op>` (guuey#208). The "eject the code,
|
|
4
|
+
* keep your memory" leg of the ejected ladder: point a `ThreadStore` at
|
|
5
|
+
* this binding with an end-user token and your self-hosted agent reads and
|
|
6
|
+
* writes the SAME conversation rows guuey's hosted runtime does.
|
|
7
|
+
*
|
|
8
|
+
* Wire protocol (the `@guuey/state` `HttpKv` shape):
|
|
9
|
+
* - Request: JSON body `{ context: { appId }, args: {...} }`, header
|
|
10
|
+
* `Authorization: Bearer <token>`.
|
|
11
|
+
* - Success: `200 { result }`.
|
|
12
|
+
* - Failure: `4xx/5xx { code, message }` → {@link HttpThreadStoreError}.
|
|
13
|
+
*
|
|
14
|
+
* Auth + tenancy: the token is an END-USER token for the app — one the
|
|
15
|
+
* app's configured identity issuer minted (your own IdP, or guuey's per-app
|
|
16
|
+
* widget issuer via `@guuey/widget-auth`). The server verifies it against
|
|
17
|
+
* that issuer, derives the same `byo_…` userId the hosted runtime would, and
|
|
18
|
+
* scopes every op to `(appId, userId)`: rows you write must carry that
|
|
19
|
+
* userId, threads you touch must belong to it. `context` on the wire is
|
|
20
|
+
* advisory — the token is authoritative. Call {@link scope} to learn the
|
|
21
|
+
* derived userId (and the API's region) before `ensureThread`.
|
|
22
|
+
*
|
|
23
|
+
* Retry policy: reads (`getThread`/`listRecentMessages`/
|
|
24
|
+
* `findByClientMessageId`/`getSnapshot`/`scope`) are idempotent and retried
|
|
25
|
+
* once on a network failure or a 5xx. Writes (`createThread`/`incrementSeq`/
|
|
26
|
+
* `putMessage`/`putSnapshot`) are NEVER retried automatically —
|
|
27
|
+
* `incrementSeq` is not idempotent, and a blind retry after a server-side
|
|
28
|
+
* apply would double-allocate a seq.
|
|
29
|
+
*/
|
|
30
|
+
import type { ThreadMessageRow, ThreadPersistencePort, ThreadRow, ThreadSnapshotRow } from "./rows.js";
|
|
31
|
+
/** What the token resolves to server-side — the scope every op is bound to. */
|
|
32
|
+
export interface ThreadScope {
|
|
33
|
+
appId: string;
|
|
34
|
+
/** The derived end-user id (`byo_…`) — the `userId` to build rows with. */
|
|
35
|
+
userId: string;
|
|
36
|
+
/** The API's serving region — the `region` to mint fresh threads with. */
|
|
37
|
+
region: string;
|
|
38
|
+
}
|
|
39
|
+
export interface HttpThreadPersistenceOptions {
|
|
40
|
+
/** API origin, e.g. `https://api.us-east-1.guuey.com` (no `/v1`). */
|
|
41
|
+
baseUrl: string;
|
|
42
|
+
/** The guuey app whose conversations these are. */
|
|
43
|
+
appId: string;
|
|
44
|
+
/** The end-user's bearer token (see the module doc). */
|
|
45
|
+
token: string;
|
|
46
|
+
/** Injection seam for tests / custom transports. Defaults to global `fetch`. */
|
|
47
|
+
fetchImpl?: typeof fetch;
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Thrown for every non-2xx response and every exhausted network failure.
|
|
51
|
+
* `code` is the server's flat error code (`UNAUTHORIZED`, `FORBIDDEN`,
|
|
52
|
+
* `INVALID_ARGUMENT`, `INVALID_CONTEXT`, `THREAD_NOT_FOUND`, `CONFLICT`,
|
|
53
|
+
* `NOT_FOUND`, `TRANSPORT`); `status` is the HTTP status (`0` for a network
|
|
54
|
+
* failure that never produced a response).
|
|
55
|
+
*/
|
|
56
|
+
export declare class HttpThreadStoreError extends Error {
|
|
57
|
+
readonly status: number;
|
|
58
|
+
readonly code: string;
|
|
59
|
+
constructor(status: number, code: string, message: string, options?: {
|
|
60
|
+
cause?: unknown;
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
export declare class HttpThreadPersistence implements ThreadPersistencePort {
|
|
64
|
+
private readonly baseUrl;
|
|
65
|
+
private readonly appId;
|
|
66
|
+
private readonly token;
|
|
67
|
+
private readonly fetchImpl;
|
|
68
|
+
constructor(opts: HttpThreadPersistenceOptions);
|
|
69
|
+
/** The `(appId, userId, region)` the server bound this token to. */
|
|
70
|
+
scope(): Promise<ThreadScope>;
|
|
71
|
+
getThread(threadId: string): Promise<ThreadRow | undefined>;
|
|
72
|
+
createThread(row: ThreadRow): Promise<void>;
|
|
73
|
+
incrementSeq(threadId: string, preview: string | null, atIso: string): Promise<number>;
|
|
74
|
+
putMessage(row: ThreadMessageRow): Promise<void>;
|
|
75
|
+
listRecentMessages(threadId: string, limit: number): Promise<ThreadMessageRow[]>;
|
|
76
|
+
findByClientMessageId(threadId: string, clientMessageId: string): Promise<ThreadMessageRow | undefined>;
|
|
77
|
+
getSnapshot(threadId: string): Promise<ThreadSnapshotRow | undefined>;
|
|
78
|
+
putSnapshot(row: ThreadSnapshotRow): Promise<void>;
|
|
79
|
+
/**
|
|
80
|
+
* One shared retry budget (`retried`) covers BOTH retry triggers — a
|
|
81
|
+
* network failure and a 5xx response — so a retryable op makes at most
|
|
82
|
+
* 2 total requests, never 3.
|
|
83
|
+
*/
|
|
84
|
+
private call;
|
|
85
|
+
}
|
|
86
|
+
//# sourceMappingURL=http.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"http.d.ts","sourceRoot":"","sources":["../src/http.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AACH,OAAO,KAAK,EACV,gBAAgB,EAChB,qBAAqB,EACrB,SAAS,EACT,iBAAiB,EAClB,MAAM,WAAW,CAAC;AAEnB,+EAA+E;AAC/E,MAAM,WAAW,WAAW;IAC1B,KAAK,EAAE,MAAM,CAAC;IACd,2EAA2E;IAC3E,MAAM,EAAE,MAAM,CAAC;IACf,0EAA0E;IAC1E,MAAM,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,WAAW,4BAA4B;IAC3C,qEAAqE;IACrE,OAAO,EAAE,MAAM,CAAC;IAChB,mDAAmD;IACnD,KAAK,EAAE,MAAM,CAAC;IACd,wDAAwD;IACxD,KAAK,EAAE,MAAM,CAAC;IACd,gFAAgF;IAChF,SAAS,CAAC,EAAE,OAAO,KAAK,CAAC;CAC1B;AAaD;;;;;;GAMG;AACH,qBAAa,oBAAqB,SAAQ,KAAK;IAE3C,QAAQ,CAAC,MAAM,EAAE,MAAM;IACvB,QAAQ,CAAC,IAAI,EAAE,MAAM;gBADZ,MAAM,EAAE,MAAM,EACd,IAAI,EAAE,MAAM,EACrB,OAAO,EAAE,MAAM,EACf,OAAO,CAAC,EAAE;QAAE,KAAK,CAAC,EAAE,OAAO,CAAA;KAAE;CAKhC;AAgBD,qBAAa,qBAAsB,YAAW,qBAAqB;IACjE,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAS;IACjC,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAS;IAC/B,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAS;IAC/B,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAe;gBAE7B,IAAI,EAAE,4BAA4B;IAO9C,oEAAoE;IAC9D,KAAK,IAAI,OAAO,CAAC,WAAW,CAAC;IAI7B,SAAS,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,SAAS,GAAG,SAAS,CAAC;IAI3D,YAAY,CAAC,GAAG,EAAE,SAAS,GAAG,OAAO,CAAC,IAAI,CAAC;IAI3C,YAAY,CAAC,QAAQ,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,IAAI,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;IAItF,UAAU,CAAC,GAAG,EAAE,gBAAgB,GAAG,OAAO,CAAC,IAAI,CAAC;IAIhD,kBAAkB,CAAC,QAAQ,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,gBAAgB,EAAE,CAAC;IAIhF,qBAAqB,CACzB,QAAQ,EAAE,MAAM,EAChB,eAAe,EAAE,MAAM,GACtB,OAAO,CAAC,gBAAgB,GAAG,SAAS,CAAC;IAUlC,WAAW,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,iBAAiB,GAAG,SAAS,CAAC;IAIrE,WAAW,CAAC,GAAG,EAAE,iBAAiB,GAAG,OAAO,CAAC,IAAI,CAAC;IAMxD;;;;OAIG;YACW,IAAI;CAqCnB"}
|
package/dist/http.js
ADDED
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Thrown for every non-2xx response and every exhausted network failure.
|
|
3
|
+
* `code` is the server's flat error code (`UNAUTHORIZED`, `FORBIDDEN`,
|
|
4
|
+
* `INVALID_ARGUMENT`, `INVALID_CONTEXT`, `THREAD_NOT_FOUND`, `CONFLICT`,
|
|
5
|
+
* `NOT_FOUND`, `TRANSPORT`); `status` is the HTTP status (`0` for a network
|
|
6
|
+
* failure that never produced a response).
|
|
7
|
+
*/
|
|
8
|
+
export class HttpThreadStoreError extends Error {
|
|
9
|
+
status;
|
|
10
|
+
code;
|
|
11
|
+
constructor(status, code, message, options) {
|
|
12
|
+
super(message, options);
|
|
13
|
+
this.status = status;
|
|
14
|
+
this.code = code;
|
|
15
|
+
this.name = "HttpThreadStoreError";
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
async function toError(res) {
|
|
19
|
+
let body;
|
|
20
|
+
try {
|
|
21
|
+
body = (await res.json());
|
|
22
|
+
}
|
|
23
|
+
catch {
|
|
24
|
+
body = {};
|
|
25
|
+
}
|
|
26
|
+
return new HttpThreadStoreError(res.status, body.code ?? "TRANSPORT", body.message ?? `guuey thread API error (HTTP ${res.status})`);
|
|
27
|
+
}
|
|
28
|
+
export class HttpThreadPersistence {
|
|
29
|
+
baseUrl;
|
|
30
|
+
appId;
|
|
31
|
+
token;
|
|
32
|
+
fetchImpl;
|
|
33
|
+
constructor(opts) {
|
|
34
|
+
this.baseUrl = opts.baseUrl.replace(/\/+$/, "");
|
|
35
|
+
this.appId = opts.appId;
|
|
36
|
+
this.token = opts.token;
|
|
37
|
+
this.fetchImpl = opts.fetchImpl ?? fetch;
|
|
38
|
+
}
|
|
39
|
+
/** The `(appId, userId, region)` the server bound this token to. */
|
|
40
|
+
async scope() {
|
|
41
|
+
return this.call("scope", {}, true);
|
|
42
|
+
}
|
|
43
|
+
async getThread(threadId) {
|
|
44
|
+
return (await this.call("getThread", { threadId }, true)) ?? undefined;
|
|
45
|
+
}
|
|
46
|
+
async createThread(row) {
|
|
47
|
+
await this.call("createThread", { row }, false);
|
|
48
|
+
}
|
|
49
|
+
async incrementSeq(threadId, preview, atIso) {
|
|
50
|
+
return this.call("incrementSeq", { threadId, preview, atIso }, false);
|
|
51
|
+
}
|
|
52
|
+
async putMessage(row) {
|
|
53
|
+
await this.call("putMessage", { row }, false);
|
|
54
|
+
}
|
|
55
|
+
async listRecentMessages(threadId, limit) {
|
|
56
|
+
return this.call("listRecentMessages", { threadId, limit }, true);
|
|
57
|
+
}
|
|
58
|
+
async findByClientMessageId(threadId, clientMessageId) {
|
|
59
|
+
return ((await this.call("findByClientMessageId", { threadId, clientMessageId }, true)) ?? undefined);
|
|
60
|
+
}
|
|
61
|
+
async getSnapshot(threadId) {
|
|
62
|
+
return (await this.call("getSnapshot", { threadId }, true)) ?? undefined;
|
|
63
|
+
}
|
|
64
|
+
async putSnapshot(row) {
|
|
65
|
+
await this.call("putSnapshot", { row }, false);
|
|
66
|
+
}
|
|
67
|
+
// ── internals ──────────────────────────────────────────────────────
|
|
68
|
+
/**
|
|
69
|
+
* One shared retry budget (`retried`) covers BOTH retry triggers — a
|
|
70
|
+
* network failure and a 5xx response — so a retryable op makes at most
|
|
71
|
+
* 2 total requests, never 3.
|
|
72
|
+
*/
|
|
73
|
+
async call(op, args, retryable) {
|
|
74
|
+
const doOnce = async () => this.fetchImpl(`${this.baseUrl}/v1/thread-store/${op}`, {
|
|
75
|
+
method: "POST",
|
|
76
|
+
headers: {
|
|
77
|
+
"content-type": "application/json",
|
|
78
|
+
authorization: `Bearer ${this.token}`,
|
|
79
|
+
},
|
|
80
|
+
body: JSON.stringify({ context: { appId: this.appId }, args }),
|
|
81
|
+
});
|
|
82
|
+
let res;
|
|
83
|
+
let retried = false;
|
|
84
|
+
for (;;) {
|
|
85
|
+
try {
|
|
86
|
+
res = await doOnce();
|
|
87
|
+
}
|
|
88
|
+
catch (err) {
|
|
89
|
+
if (!retryable || retried) {
|
|
90
|
+
throw new HttpThreadStoreError(0, "TRANSPORT", `network failure calling guuey thread API${retried ? " (after retry)" : ""}`, { cause: err });
|
|
91
|
+
}
|
|
92
|
+
retried = true;
|
|
93
|
+
continue;
|
|
94
|
+
}
|
|
95
|
+
if (res.status >= 500 && retryable && !retried) {
|
|
96
|
+
retried = true;
|
|
97
|
+
continue;
|
|
98
|
+
}
|
|
99
|
+
break;
|
|
100
|
+
}
|
|
101
|
+
if (!res.ok)
|
|
102
|
+
throw await toError(res);
|
|
103
|
+
const body = (await res.json());
|
|
104
|
+
return body.result;
|
|
105
|
+
}
|
|
106
|
+
}
|
package/dist/index.d.ts
CHANGED
|
@@ -8,7 +8,9 @@
|
|
|
8
8
|
* (messages + card rows + snapshot), prompt-lane history.
|
|
9
9
|
* - {@link ThreadPersistencePort} — the narrow surface a binding
|
|
10
10
|
* implements. `InMemoryThreadPersistence` ships here; guuey's hosted
|
|
11
|
-
* runtime binds DynamoDB;
|
|
11
|
+
* runtime binds DynamoDB; `HttpThreadPersistence` points an ejected
|
|
12
|
+
* agent at guuey's hosted thread API with an end-user token ("eject
|
|
13
|
+
* the code, keep your memory"); or bring your own store.
|
|
12
14
|
* - fold↔row mapping — `agMessageToRow`/`reassembleFold`/friends, the
|
|
13
15
|
* byte-identity persistence of an `AgReduceResult`, including the
|
|
14
16
|
* UI-card projection (`uiCardArtifactsFromMessages`, guuey#86).
|
|
@@ -18,5 +20,6 @@
|
|
|
18
20
|
export type { StoredHistoryMessage, ThreadMessageKind, ThreadMessageRole, ThreadMessageRow, ThreadPersistencePort, ThreadRow, ThreadSnapshotRow, } from "./rows.js";
|
|
19
21
|
export { ThreadStore, type AppendFoldInput, type AppendFoldResult, type AppendMessageInput, type AppendMessageResult, type EnsureThreadInput, } from "./store.js";
|
|
20
22
|
export { InMemoryThreadPersistence } from "./in-memory.js";
|
|
23
|
+
export { HttpThreadPersistence, HttpThreadStoreError, type HttpThreadPersistenceOptions, type ThreadScope, } from "./http.js";
|
|
21
24
|
export { agArtifactToCardRow, agMessageToRow, cardRowToAgArtifact, messageText, reassembleFold, rowToAgMessage, seedEventsForReducer, uiCardArtifactsFromMessages, type RowCtx, } from "./fold-rows.js";
|
|
22
25
|
//# 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
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AACH,YAAY,EACV,oBAAoB,EACpB,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,cAAc,EACd,cAAc,EACd,oBAAoB,EACpB,2BAA2B,EAC3B,KAAK,MAAM,GACZ,MAAM,gBAAgB,CAAC"}
|
package/dist/index.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
1
|
export { ThreadStore, } from "./store.js";
|
|
2
2
|
export { InMemoryThreadPersistence } from "./in-memory.js";
|
|
3
|
+
export { HttpThreadPersistence, HttpThreadStoreError, } from "./http.js";
|
|
3
4
|
export { agArtifactToCardRow, agMessageToRow, cardRowToAgArtifact, messageText, reassembleFold, rowToAgMessage, seedEventsForReducer, uiCardArtifactsFromMessages, } from "./fold-rows.js";
|
package/dist/store.d.ts
CHANGED
|
@@ -69,6 +69,15 @@ export declare class ThreadStore {
|
|
|
69
69
|
* client-chosen id, so squatting is impossible.
|
|
70
70
|
*/
|
|
71
71
|
ensureThread(input: EnsureThreadInput): Promise<string>;
|
|
72
|
+
/**
|
|
73
|
+
* Whether `threadId` exists AND is owned by `userId` — the SAME ownership
|
|
74
|
+
* predicate `ensureThread` applies before honouring a client-replayed id,
|
|
75
|
+
* exposed for doors that must BIND a client-echoed thread to the verified
|
|
76
|
+
* caller without minting anything (the pod's consent-answer door binds a
|
|
77
|
+
* `once` grant to a thread this way — guuey#207). No existence oracle: a
|
|
78
|
+
* missing thread and another user's thread are the same `false`.
|
|
79
|
+
*/
|
|
80
|
+
ownsThread(threadId: string, userId: string): Promise<boolean>;
|
|
72
81
|
/** Prior messages for the thread (seq-ASC, capped to the most recent N). */
|
|
73
82
|
loadHistory(threadId: string, limit?: number): Promise<StoredHistoryMessage[]>;
|
|
74
83
|
/**
|
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,MAAM,sBAAsB,CAAC;AAE3D,OAAO,KAAK,EACV,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;CAC1B;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;CACxB;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,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;IA4B5E,+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;CA8EpE"}
|
|
1
|
+
{"version":3,"file":"store.d.ts","sourceRoot":"","sources":["../src/store.ts"],"names":[],"mappings":"AAUA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AAE3D,OAAO,KAAK,EACV,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;CAC1B;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;CACxB;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;IA4B5E,+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;CA8EpE"}
|
package/dist/store.js
CHANGED
|
@@ -61,6 +61,18 @@ export class ThreadStore {
|
|
|
61
61
|
await this.db.createThread(row);
|
|
62
62
|
return row.id;
|
|
63
63
|
}
|
|
64
|
+
/**
|
|
65
|
+
* Whether `threadId` exists AND is owned by `userId` — the SAME ownership
|
|
66
|
+
* predicate `ensureThread` applies before honouring a client-replayed id,
|
|
67
|
+
* exposed for doors that must BIND a client-echoed thread to the verified
|
|
68
|
+
* caller without minting anything (the pod's consent-answer door binds a
|
|
69
|
+
* `once` grant to a thread this way — guuey#207). No existence oracle: a
|
|
70
|
+
* missing thread and another user's thread are the same `false`.
|
|
71
|
+
*/
|
|
72
|
+
async ownsThread(threadId, userId) {
|
|
73
|
+
const existing = await this.db.getThread(threadId);
|
|
74
|
+
return existing !== undefined && existing.userId === userId;
|
|
75
|
+
}
|
|
64
76
|
/** Prior messages for the thread (seq-ASC, capped to the most recent N). */
|
|
65
77
|
async loadHistory(threadId, limit = DEFAULT_HISTORY_LIMIT) {
|
|
66
78
|
const rows = await this.db.listRecentMessages(threadId, limit);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@guuey/threads",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.8.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",
|
|
@@ -23,7 +23,7 @@
|
|
|
23
23
|
},
|
|
24
24
|
"dependencies": {
|
|
25
25
|
"@silverprotocol/core": "0.5.0",
|
|
26
|
-
"@guuey/mcp-apps-host": "0.
|
|
26
|
+
"@guuey/mcp-apps-host": "0.8.0"
|
|
27
27
|
},
|
|
28
28
|
"devDependencies": {
|
|
29
29
|
"@types/node": "^24.0.0",
|