@haikit/server 0.1.0 → 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/routes.d.ts.map +1 -1
- package/dist/routes.js +41 -2
- package/dist/routes.js.map +1 -1
- package/dist/runtime.d.ts +0 -2
- package/dist/runtime.d.ts.map +1 -1
- package/dist/runtime.js +55 -26
- package/dist/runtime.js.map +1 -1
- package/dist/store.d.ts +25 -3
- package/dist/store.d.ts.map +1 -1
- package/dist/store.js +68 -14
- package/dist/store.js.map +1 -1
- package/package.json +2 -2
- package/src/routes.ts +35 -2
- package/src/runtime.ts +70 -32
- package/src/store.ts +86 -13
package/dist/routes.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"routes.d.ts","sourceRoot":"","sources":["../src/routes.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,cAAc,EAAE,MAAM,WAAW,CAAC;
|
|
1
|
+
{"version":3,"file":"routes.d.ts","sourceRoot":"","sources":["../src/routes.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,cAAc,EAAE,MAAM,WAAW,CAAC;AAGjE,OAAO,KAAK,EAAE,GAAG,EAAE,MAAM,cAAc,CAAC;AAExC;;;;;;GAMG;AACH,wBAAgB,WAAW,CAAC,GAAG,EAAE,GAAG,EAAE,QAAQ,SAAS,IACxB,KAAK,eAAe,EAAE,KAAK,cAAc,KAAG,OAAO,CAAC,OAAO,CAAC,CAiE1F"}
|
package/dist/routes.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { isConversationBusy, isStaleLease } from "@haikit/core";
|
|
1
2
|
/**
|
|
2
3
|
* Node http handler for the two routes. Returns true if it handled the request.
|
|
3
4
|
*
|
|
@@ -14,11 +15,24 @@ export function nodeHandler(hai, basePath = "/hai") {
|
|
|
14
15
|
if (route !== "/chat" && route !== "/interact")
|
|
15
16
|
return false;
|
|
16
17
|
const body = await readJson(req);
|
|
17
|
-
|
|
18
|
+
// Before the SSE stream opens, because a rejected load has no stream to
|
|
19
|
+
// report into — and an uncaught throw here would take down the request.
|
|
20
|
+
let conversation;
|
|
21
|
+
try {
|
|
22
|
+
conversation = await hai.config.store.loadConversation(body.conversationId);
|
|
23
|
+
}
|
|
24
|
+
catch (err) {
|
|
25
|
+
if (!isConversationBusy(err))
|
|
26
|
+
throw err;
|
|
27
|
+
res.writeHead(409, { "content-type": "application/json" });
|
|
28
|
+
res.end(JSON.stringify({ error: err.message }));
|
|
29
|
+
return true;
|
|
30
|
+
}
|
|
18
31
|
const emit = openSSE(res);
|
|
19
32
|
if (route === "/chat") {
|
|
20
33
|
emit({ type: "hello", conversationId: conversation.id, model: hai.config.model.id });
|
|
21
34
|
}
|
|
35
|
+
let superseded = false;
|
|
22
36
|
try {
|
|
23
37
|
if (route === "/chat") {
|
|
24
38
|
await hai.send(conversation, String(body.message ?? ""), emit);
|
|
@@ -28,9 +42,34 @@ export function nodeHandler(hai, basePath = "/hai") {
|
|
|
28
42
|
}
|
|
29
43
|
}
|
|
30
44
|
catch (err) {
|
|
45
|
+
if (isStaleLease(err))
|
|
46
|
+
superseded = true;
|
|
31
47
|
emit({ type: "error", message: err.message });
|
|
32
48
|
}
|
|
33
|
-
|
|
49
|
+
finally {
|
|
50
|
+
// The lease lives exactly as long as the request. Releasing it any
|
|
51
|
+
// earlier — when a turn parks, say — lets the client's next interaction
|
|
52
|
+
// arrive before this request has finished writing, and the two turns
|
|
53
|
+
// interleave on one conversation.
|
|
54
|
+
//
|
|
55
|
+
// Only the expiry is cleared. The token has to survive so the save below
|
|
56
|
+
// can still prove this request is the rightful holder.
|
|
57
|
+
conversation.leaseUntil = null;
|
|
58
|
+
try {
|
|
59
|
+
// A turn that already hit a fence knows its save would be rejected too;
|
|
60
|
+
// attempting it only repeats the same error to the client.
|
|
61
|
+
if (!superseded)
|
|
62
|
+
await hai.config.store.saveConversation(conversation);
|
|
63
|
+
}
|
|
64
|
+
catch (err) {
|
|
65
|
+
// Overtaken while we were slow: another turn already wrote newer state
|
|
66
|
+
// under a fresh token. Dropping this write is the correct outcome, but
|
|
67
|
+
// the client asked for something it is not getting, so say so.
|
|
68
|
+
if (!isStaleLease(err))
|
|
69
|
+
throw err;
|
|
70
|
+
emit({ type: "error", message: err.message });
|
|
71
|
+
}
|
|
72
|
+
}
|
|
34
73
|
res.end();
|
|
35
74
|
return true;
|
|
36
75
|
};
|
package/dist/routes.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"routes.js","sourceRoot":"","sources":["../src/routes.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"routes.js","sourceRoot":"","sources":["../src/routes.ts"],"names":[],"mappings":"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"}
|
package/dist/runtime.d.ts
CHANGED
|
@@ -7,8 +7,6 @@ export interface HaiConfig {
|
|
|
7
7
|
surfaces: AnySurfaceImpl[];
|
|
8
8
|
/** Guard against runaway tool loops. */
|
|
9
9
|
maxHops?: number;
|
|
10
|
-
/** Turn lease duration; a dead process releases after this. */
|
|
11
|
-
leaseMs?: number;
|
|
12
10
|
}
|
|
13
11
|
export declare class Hai {
|
|
14
12
|
readonly config: HaiConfig;
|
package/dist/runtime.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"runtime.d.ts","sourceRoot":"","sources":["../src/runtime.ts"],"names":[],"mappings":"AAAA,OAAO,
|
|
1
|
+
{"version":3,"file":"runtime.d.ts","sourceRoot":"","sources":["../src/runtime.ts"],"names":[],"mappings":"AAAA,OAAO,EAIL,KAAK,cAAc,EACnB,KAAK,YAAY,EACjB,KAAK,IAAI,EACT,KAAK,YAAY,EACjB,KAAK,YAAY,EACjB,KAAK,IAAI,EAGV,MAAM,cAAc,CAAC;AAEtB,MAAM,WAAW,SAAS;IACxB,KAAK,EAAE,YAAY,CAAC;IACpB,KAAK,EAAE,YAAY,CAAC;IACpB,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,EAAE,IAAI,EAAE,CAAC;IACd,QAAQ,EAAE,cAAc,EAAE,CAAC;IAC3B,wCAAwC;IACxC,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAED,qBAAa,GAAG;IACd,QAAQ,CAAC,MAAM,EAAE,SAAS,CAAC;IAC3B,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAqC;IAC9D,OAAO,CAAC,QAAQ,CAAC,KAAK,CAA2B;IACjD,OAAO,CAAC,GAAG,CAAK;gBAEJ,MAAM,EAAE,SAAS;IAU7B,OAAO,CAAC,GAAG;IAMX,OAAO,CAAC,gBAAgB;IAkFlB,IAAI,CAAC,YAAY,EAAE,YAAY,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC;IA4BzE,QAAQ,CACZ,YAAY,EAAE,YAAY,EAC1B,KAAK,EAAE;QAAE,MAAM,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,OAAO,CAAA;KAAE,EACzD,IAAI,EAAE,IAAI,GACT,OAAO,CAAC,IAAI,CAAC;YA8DF,OAAO;YAuEP,UAAU;YAsEV,WAAW;CAa1B"}
|
package/dist/runtime.js
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
|
-
import { estTokens, makeCap, } from "@haikit/core";
|
|
2
|
-
const LEASE_MS = 120_000;
|
|
1
|
+
import { estTokens, isStaleLease, makeCap, } from "@haikit/core";
|
|
3
2
|
export class Hai {
|
|
4
3
|
config;
|
|
5
4
|
surfaces = new Map();
|
|
@@ -21,26 +20,45 @@ export class Hai {
|
|
|
21
20
|
}
|
|
22
21
|
// ───────────────────────────────────────────────── derived tool
|
|
23
22
|
buildQueryUiTool() {
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
.
|
|
30
|
-
.
|
|
23
|
+
// The model has to be told what each query TAKES, not just that it exists.
|
|
24
|
+
// Listing bare names leaves `args` opaque, and the model then calls the
|
|
25
|
+
// query with none — every filter becomes a no-op and it gets the whole
|
|
26
|
+
// collection back.
|
|
27
|
+
const entries = [...this.surfaces.values()].flatMap((s) => Object.entries(s.surface.queries).map(([name, spec]) => {
|
|
28
|
+
const parts = [`${s.surface.name}.${name}`];
|
|
29
|
+
if (spec.description)
|
|
30
|
+
parts.push(`— ${spec.description}`);
|
|
31
|
+
parts.push(spec.argsJsonSchema
|
|
32
|
+
? `args: ${JSON.stringify(spec.argsJsonSchema)}`
|
|
33
|
+
: "args: (undeclared — omit them)");
|
|
34
|
+
return { surface: s.surface.name, name, spec, line: parts.join(" ") };
|
|
35
|
+
}));
|
|
36
|
+
// A query whose args are invisible is a silent wrong-answer bug, not a
|
|
37
|
+
// missing nicety — say so once, at construction, rather than never.
|
|
38
|
+
const undeclared = entries.filter((e) => !e.spec.argsJsonSchema);
|
|
39
|
+
if (undeclared.length) {
|
|
40
|
+
console.warn(`[haikit] query_ui: no argsJsonSchema for ${undeclared.map((e) => `${e.surface}.${e.name}`).join(", ")}. ` +
|
|
41
|
+
`The model cannot see these parameters and will call the query with no arguments. ` +
|
|
42
|
+
`A query that genuinely takes none should declare { type: "object", properties: {} }.`);
|
|
43
|
+
}
|
|
44
|
+
const catalogue = entries.map((e) => `- ${e.line}`).join("\n");
|
|
31
45
|
const self = this;
|
|
32
46
|
return {
|
|
33
47
|
name: "query_ui",
|
|
34
48
|
description: "Dereference a rendered component's full dataset. The digest in your context holds only " +
|
|
35
49
|
"highlights; use this for anything beyond it. Never guess about rows you have not seen. " +
|
|
36
|
-
(catalogue ?
|
|
50
|
+
(catalogue ? `\n\nAvailable queries:\n${catalogue}` : ""),
|
|
37
51
|
input: { parse: (v) => v },
|
|
38
52
|
inputJsonSchema: {
|
|
39
53
|
type: "object",
|
|
40
54
|
properties: {
|
|
41
55
|
handle: { type: "string", description: "e.g. ui_01" },
|
|
42
|
-
query: { type: "string" },
|
|
43
|
-
args: {
|
|
56
|
+
query: { type: "string", description: "One of the query names listed in the description." },
|
|
57
|
+
args: {
|
|
58
|
+
type: "object",
|
|
59
|
+
description: "Arguments for the chosen query, matching that query's schema in the description. " +
|
|
60
|
+
"Pass the constraints the user actually asked for — omitting them matches everything.",
|
|
61
|
+
},
|
|
44
62
|
},
|
|
45
63
|
required: ["handle", "query"],
|
|
46
64
|
},
|
|
@@ -75,7 +93,9 @@ export class Hai {
|
|
|
75
93
|
// receive a tool_result, so close the pending one out honestly first.
|
|
76
94
|
if (conversation.status === "awaiting" && conversation.pending) {
|
|
77
95
|
const { toolUseId, handle, results, digest } = conversation.pending;
|
|
78
|
-
|
|
96
|
+
// Recorded on the conversation, not the payload, so it commits with the
|
|
97
|
+
// history that explains it — see Conversation.frozen.
|
|
98
|
+
conversation.frozen.push(handle);
|
|
79
99
|
emit({ type: "ui_state", handle, state: "frozen" });
|
|
80
100
|
conversation.messages.push({
|
|
81
101
|
role: "user",
|
|
@@ -95,11 +115,18 @@ export class Hai {
|
|
|
95
115
|
await this.runTurn(conversation, emit);
|
|
96
116
|
}
|
|
97
117
|
async interact(conversation, input, emit) {
|
|
118
|
+
// The payload row carries a conversation id, but that alone is not enough.
|
|
119
|
+
// A turn that rendered a surface and was then overtaken leaves a row behind:
|
|
120
|
+
// its conversation save is rejected, so the winning history never records
|
|
121
|
+
// the handle, yet the row and the browser that mounted it both still exist.
|
|
122
|
+
// Membership of the surviving `handles` is what makes those orphans inert.
|
|
123
|
+
if (!conversation.handles.includes(input.handle))
|
|
124
|
+
throw new Error("unknown handle");
|
|
125
|
+
if (conversation.frozen.includes(input.handle))
|
|
126
|
+
throw new Error("component is frozen");
|
|
98
127
|
const record = await this.config.store.getPayload(input.handle, conversation.id);
|
|
99
128
|
if (!record)
|
|
100
129
|
throw new Error("unknown handle");
|
|
101
|
-
if (record.state === "frozen")
|
|
102
|
-
throw new Error("component is frozen");
|
|
103
130
|
const impl = this.surfaces.get(record.component);
|
|
104
131
|
if (!impl)
|
|
105
132
|
throw new Error(`unknown component: ${record.component}`);
|
|
@@ -120,7 +147,7 @@ export class Hai {
|
|
|
120
147
|
if (spec.kind === "resolve") {
|
|
121
148
|
if (conversation.pending?.handle !== input.handle)
|
|
122
149
|
throw new Error("nothing awaiting this handle");
|
|
123
|
-
|
|
150
|
+
conversation.frozen.push(input.handle);
|
|
124
151
|
emit({ type: "ui_state", handle: input.handle, state: "frozen", selection: value });
|
|
125
152
|
emit({ type: "block_start", block: { kind: "interaction", id: this.nid("b"), handle: input.handle, label } });
|
|
126
153
|
conversation.messages.push({
|
|
@@ -147,7 +174,6 @@ export class Hai {
|
|
|
147
174
|
// ──────────────────────────────────────────────── the agent loop
|
|
148
175
|
async runTurn(conversation, emit) {
|
|
149
176
|
conversation.status = "streaming";
|
|
150
|
-
conversation.leaseUntil = Date.now() + (this.config.leaseMs ?? LEASE_MS);
|
|
151
177
|
emit({ type: "status", status: "streaming" });
|
|
152
178
|
const toolDefs = [...this.tools.values()].map((t) => ({
|
|
153
179
|
name: t.name,
|
|
@@ -197,7 +223,6 @@ export class Hai {
|
|
|
197
223
|
// Hold the resolved siblings too — the API is all-or-nothing per batch.
|
|
198
224
|
conversation.status = "awaiting";
|
|
199
225
|
conversation.pending = { ...parked, results };
|
|
200
|
-
conversation.leaseUntil = null;
|
|
201
226
|
emit({ type: "status", status: "awaiting" });
|
|
202
227
|
await this.emitContext(conversation, emit);
|
|
203
228
|
return;
|
|
@@ -205,7 +230,6 @@ export class Hai {
|
|
|
205
230
|
conversation.messages.push({ role: "user", content: results });
|
|
206
231
|
}
|
|
207
232
|
conversation.status = "idle";
|
|
208
|
-
conversation.leaseUntil = null;
|
|
209
233
|
emit({ type: "status", status: "idle" });
|
|
210
234
|
await this.emitContext(conversation, emit);
|
|
211
235
|
}
|
|
@@ -228,8 +252,7 @@ export class Hai {
|
|
|
228
252
|
version: impl.surface.version,
|
|
229
253
|
props: parsed,
|
|
230
254
|
mode: surfaceMode,
|
|
231
|
-
|
|
232
|
-
});
|
|
255
|
+
}, conversation.leaseToken);
|
|
233
256
|
conversation.handles.push(handle);
|
|
234
257
|
// The split, in two lines. Digest -> model. Payload -> browser.
|
|
235
258
|
const digest = impl.impl.digest(parsed, { handle });
|
|
@@ -257,16 +280,22 @@ export class Hai {
|
|
|
257
280
|
return { ret, mode };
|
|
258
281
|
}
|
|
259
282
|
catch (err) {
|
|
283
|
+
// A tool error is information for the model; a lost lease is not. Fencing
|
|
284
|
+
// exists so a superseded turn stops — converting StaleLease into "tool
|
|
285
|
+
// failed" would hand it back to the model and keep paying for hops whose
|
|
286
|
+
// output the fenced save is going to discard anyway.
|
|
287
|
+
if (isStaleLease(err))
|
|
288
|
+
throw err;
|
|
260
289
|
return { ret: { model: `${call.name} failed: ${err.message}` }, mode: null };
|
|
261
290
|
}
|
|
262
291
|
}
|
|
263
292
|
async emitContext(conversation, emit) {
|
|
293
|
+
// One batched read, not one per handle: this runs on every turn, and a
|
|
294
|
+
// conversation accumulates handles for as long as it lives.
|
|
295
|
+
const records = await this.config.store.getPayloads(conversation.handles, conversation.id);
|
|
264
296
|
let uiTokens = 0;
|
|
265
|
-
for (const
|
|
266
|
-
|
|
267
|
-
if (record)
|
|
268
|
-
uiTokens += estTokens(record.props);
|
|
269
|
-
}
|
|
297
|
+
for (const record of records)
|
|
298
|
+
uiTokens += estTokens(record.props);
|
|
270
299
|
emit({
|
|
271
300
|
type: "context",
|
|
272
301
|
messages: conversation.messages,
|
package/dist/runtime.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"runtime.js","sourceRoot":"","sources":["../src/runtime.ts"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"runtime.js","sourceRoot":"","sources":["../src/runtime.ts"],"names":[],"mappings":"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"}
|
package/dist/store.d.ts
CHANGED
|
@@ -1,10 +1,32 @@
|
|
|
1
|
-
import type
|
|
1
|
+
import { type StoreAdapter } from "@haikit/core";
|
|
2
|
+
export interface MemoryStoreOptions {
|
|
3
|
+
/**
|
|
4
|
+
* Turn lease TTL. A process that dies mid-turn strands the conversation for
|
|
5
|
+
* this long; too short and a slow turn is overtaken while still running.
|
|
6
|
+
*
|
|
7
|
+
* This is the single authority on lease duration — the runtime does not renew
|
|
8
|
+
* or override it. Being overtaken is safe rather than merely unlikely: the
|
|
9
|
+
* superseded turn's save is rejected by its fencing token.
|
|
10
|
+
*/
|
|
11
|
+
leaseMs?: number;
|
|
12
|
+
}
|
|
2
13
|
/**
|
|
3
|
-
* In-memory store. Implements the same interface a Postgres store would
|
|
14
|
+
* In-memory store. Implements the same interface a Postgres store would, and
|
|
15
|
+
* enforces the same rules — scoping, leasing and fencing are all checked here
|
|
16
|
+
* even though this store could get away without them. A reference
|
|
17
|
+
* implementation that only enforces what it happens to need is what let three
|
|
18
|
+
* of these bugs exist in the first place.
|
|
19
|
+
*
|
|
20
|
+
* Conversations are copied in and out rather than shared by reference. That is
|
|
21
|
+
* not an optimisation detail: a store that hands back a live object cannot
|
|
22
|
+
* detect a stale writer at all, because the caller's copy *is* the stored one.
|
|
23
|
+
* The copy is a JSON round trip specifically because that is what a `jsonb`
|
|
24
|
+
* column does — anything a real store would silently drop (undefined, a Date's
|
|
25
|
+
* type, a class instance) gets dropped here too, in development.
|
|
4
26
|
*
|
|
5
27
|
* Swapping this for a durable store is the single highest-value change for
|
|
6
28
|
* production: a parked `elicit` turn is durable state, and losing `pending`
|
|
7
29
|
* makes a conversation permanently unsendable.
|
|
8
30
|
*/
|
|
9
|
-
export declare function memoryStore(): StoreAdapter;
|
|
31
|
+
export declare function memoryStore(options?: MemoryStoreOptions): StoreAdapter;
|
|
10
32
|
//# sourceMappingURL=store.d.ts.map
|
package/dist/store.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"store.d.ts","sourceRoot":"","sources":["../src/store.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,
|
|
1
|
+
{"version":3,"file":"store.d.ts","sourceRoot":"","sources":["../src/store.ts"],"names":[],"mappings":"AAAA,OAAO,EAKL,KAAK,YAAY,EAClB,MAAM,cAAc,CAAC;AAItB,MAAM,WAAW,kBAAkB;IACjC;;;;;;;OAOG;IACH,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAED;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAgB,WAAW,CAAC,OAAO,GAAE,kBAAuB,GAAG,YAAY,CAqF1E"}
|
package/dist/store.js
CHANGED
|
@@ -1,36 +1,86 @@
|
|
|
1
|
+
import { ConversationBusy, StaleLease, } from "@haikit/core";
|
|
2
|
+
const LEASE_MS = 120_000;
|
|
1
3
|
/**
|
|
2
|
-
* In-memory store. Implements the same interface a Postgres store would
|
|
4
|
+
* In-memory store. Implements the same interface a Postgres store would, and
|
|
5
|
+
* enforces the same rules — scoping, leasing and fencing are all checked here
|
|
6
|
+
* even though this store could get away without them. A reference
|
|
7
|
+
* implementation that only enforces what it happens to need is what let three
|
|
8
|
+
* of these bugs exist in the first place.
|
|
9
|
+
*
|
|
10
|
+
* Conversations are copied in and out rather than shared by reference. That is
|
|
11
|
+
* not an optimisation detail: a store that hands back a live object cannot
|
|
12
|
+
* detect a stale writer at all, because the caller's copy *is* the stored one.
|
|
13
|
+
* The copy is a JSON round trip specifically because that is what a `jsonb`
|
|
14
|
+
* column does — anything a real store would silently drop (undefined, a Date's
|
|
15
|
+
* type, a class instance) gets dropped here too, in development.
|
|
3
16
|
*
|
|
4
17
|
* Swapping this for a durable store is the single highest-value change for
|
|
5
18
|
* production: a parked `elicit` turn is durable state, and losing `pending`
|
|
6
19
|
* makes a conversation permanently unsendable.
|
|
7
20
|
*/
|
|
8
|
-
export function memoryStore() {
|
|
21
|
+
export function memoryStore(options = {}) {
|
|
22
|
+
const leaseMs = options.leaseMs ?? LEASE_MS;
|
|
9
23
|
const conversations = new Map();
|
|
10
24
|
const payloads = new Map();
|
|
11
25
|
let convSeq = 0;
|
|
12
26
|
let handleSeq = 0;
|
|
27
|
+
const copy = (value) => JSON.parse(JSON.stringify(value));
|
|
28
|
+
const newToken = () => globalThis.crypto.randomUUID();
|
|
29
|
+
/** Every durable mutation presents the token it was issued. A holder that has
|
|
30
|
+
* been superseded must stop writing rather than run to completion and be
|
|
31
|
+
* discarded at the end.
|
|
32
|
+
*
|
|
33
|
+
* Atomic here only because nothing awaits between this check and the write
|
|
34
|
+
* that follows it: JavaScript cannot interleave another request into a
|
|
35
|
+
* synchronous run. Put an `await` between them and the compare-and-set the
|
|
36
|
+
* contract requires is silently gone. */
|
|
37
|
+
const fence = (conversationId, leaseToken) => {
|
|
38
|
+
const stored = conversations.get(conversationId);
|
|
39
|
+
// Mirrors the SQL shape in the contract: no matching row fails the
|
|
40
|
+
// compare-and-set exactly like a superseded token does. A null token needs
|
|
41
|
+
// no case of its own — a stored lease is never null, so it cannot match,
|
|
42
|
+
// just as `NULL = x` is never true. What must not happen is treating a
|
|
43
|
+
// missing token as "unfenced" and skipping this comparison.
|
|
44
|
+
if (!stored)
|
|
45
|
+
throw new StaleLease(conversationId, "does not exist — no lease was ever issued for it");
|
|
46
|
+
if (stored.leaseToken !== leaseToken)
|
|
47
|
+
throw new StaleLease(conversationId);
|
|
48
|
+
};
|
|
13
49
|
return {
|
|
14
50
|
async loadConversation(id) {
|
|
15
|
-
if (id && conversations.has(id))
|
|
16
|
-
|
|
51
|
+
if (id && conversations.has(id)) {
|
|
52
|
+
const stored = conversations.get(id);
|
|
53
|
+
if (stored.leaseUntil !== null && stored.leaseUntil > Date.now()) {
|
|
54
|
+
throw new ConversationBusy(stored.id);
|
|
55
|
+
}
|
|
56
|
+
// Reissuing the token is what fences out whoever held it before.
|
|
57
|
+
stored.leaseUntil = Date.now() + leaseMs;
|
|
58
|
+
stored.leaseToken = newToken();
|
|
59
|
+
return copy(stored);
|
|
60
|
+
}
|
|
17
61
|
const conversation = {
|
|
18
62
|
id: `conv_${++convSeq}`,
|
|
19
63
|
status: "idle",
|
|
20
64
|
messages: [],
|
|
21
65
|
handles: [],
|
|
66
|
+
frozen: [],
|
|
22
67
|
pending: null,
|
|
23
|
-
leaseUntil:
|
|
68
|
+
leaseUntil: Date.now() + leaseMs,
|
|
69
|
+
leaseToken: newToken(),
|
|
24
70
|
};
|
|
25
71
|
conversations.set(conversation.id, conversation);
|
|
26
|
-
return conversation;
|
|
72
|
+
return copy(conversation);
|
|
27
73
|
},
|
|
28
74
|
async saveConversation(conversation) {
|
|
29
|
-
|
|
75
|
+
// Same rule as every other fenced write. Not an upsert: a conversation
|
|
76
|
+
// this store never issued a lease for cannot be created by saving it.
|
|
77
|
+
fence(conversation.id, conversation.leaseToken);
|
|
78
|
+
conversations.set(conversation.id, copy(conversation));
|
|
30
79
|
},
|
|
31
|
-
async putPayload(record) {
|
|
80
|
+
async putPayload(record, leaseToken) {
|
|
81
|
+
fence(record.conversationId, leaseToken);
|
|
32
82
|
const handle = `ui_${String(++handleSeq).padStart(2, "0")}`;
|
|
33
|
-
payloads.set(handle, { ...record, handle, createdAt: Date.now() });
|
|
83
|
+
payloads.set(handle, copy({ ...record, handle, createdAt: Date.now() }));
|
|
34
84
|
return handle;
|
|
35
85
|
},
|
|
36
86
|
/** Scoped: a handle from another conversation must not resolve. */
|
|
@@ -38,12 +88,16 @@ export function memoryStore() {
|
|
|
38
88
|
const record = payloads.get(handle);
|
|
39
89
|
if (!record || record.conversationId !== conversationId)
|
|
40
90
|
return null;
|
|
41
|
-
return record;
|
|
91
|
+
return copy(record);
|
|
42
92
|
},
|
|
43
|
-
async
|
|
44
|
-
const
|
|
45
|
-
|
|
46
|
-
record
|
|
93
|
+
async getPayloads(handles, conversationId) {
|
|
94
|
+
const out = [];
|
|
95
|
+
for (const handle of handles) {
|
|
96
|
+
const record = payloads.get(handle);
|
|
97
|
+
if (record && record.conversationId === conversationId)
|
|
98
|
+
out.push(copy(record));
|
|
99
|
+
}
|
|
100
|
+
return out;
|
|
47
101
|
},
|
|
48
102
|
};
|
|
49
103
|
}
|
package/dist/store.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"store.js","sourceRoot":"","sources":["../src/store.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"store.js","sourceRoot":"","sources":["../src/store.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@haikit/server",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.0",
|
|
4
4
|
"description": "The haikit server runtime: agent loop, elicit state machine, derived query_ui, and the interaction security boundary.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"haikit",
|
|
@@ -36,7 +36,7 @@
|
|
|
36
36
|
"prepack": "npm --prefix ../.. run build"
|
|
37
37
|
},
|
|
38
38
|
"dependencies": {
|
|
39
|
-
"@haikit/core": "0.
|
|
39
|
+
"@haikit/core": "0.3.0",
|
|
40
40
|
"@types/node": ">=22"
|
|
41
41
|
},
|
|
42
42
|
"publishConfig": {
|
package/src/routes.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { IncomingMessage, ServerResponse } from "node:http";
|
|
2
|
+
import { isConversationBusy, isStaleLease } from "@haikit/core";
|
|
2
3
|
import type { Emit, WireEvent } from "@haikit/core";
|
|
3
4
|
import type { Hai } from "./runtime.js";
|
|
4
5
|
|
|
@@ -18,13 +19,25 @@ export function nodeHandler(hai: Hai, basePath = "/hai") {
|
|
|
18
19
|
if (route !== "/chat" && route !== "/interact") return false;
|
|
19
20
|
|
|
20
21
|
const body = await readJson(req);
|
|
21
|
-
|
|
22
|
+
|
|
23
|
+
// Before the SSE stream opens, because a rejected load has no stream to
|
|
24
|
+
// report into — and an uncaught throw here would take down the request.
|
|
25
|
+
let conversation;
|
|
26
|
+
try {
|
|
27
|
+
conversation = await hai.config.store.loadConversation(body.conversationId);
|
|
28
|
+
} catch (err) {
|
|
29
|
+
if (!isConversationBusy(err)) throw err;
|
|
30
|
+
res.writeHead(409, { "content-type": "application/json" });
|
|
31
|
+
res.end(JSON.stringify({ error: (err as Error).message }));
|
|
32
|
+
return true;
|
|
33
|
+
}
|
|
22
34
|
|
|
23
35
|
const emit = openSSE(res);
|
|
24
36
|
if (route === "/chat") {
|
|
25
37
|
emit({ type: "hello", conversationId: conversation.id, model: hai.config.model.id });
|
|
26
38
|
}
|
|
27
39
|
|
|
40
|
+
let superseded = false;
|
|
28
41
|
try {
|
|
29
42
|
if (route === "/chat") {
|
|
30
43
|
await hai.send(conversation, String(body.message ?? ""), emit);
|
|
@@ -36,10 +49,30 @@ export function nodeHandler(hai: Hai, basePath = "/hai") {
|
|
|
36
49
|
);
|
|
37
50
|
}
|
|
38
51
|
} catch (err) {
|
|
52
|
+
if (isStaleLease(err)) superseded = true;
|
|
39
53
|
emit({ type: "error", message: (err as Error).message });
|
|
54
|
+
} finally {
|
|
55
|
+
// The lease lives exactly as long as the request. Releasing it any
|
|
56
|
+
// earlier — when a turn parks, say — lets the client's next interaction
|
|
57
|
+
// arrive before this request has finished writing, and the two turns
|
|
58
|
+
// interleave on one conversation.
|
|
59
|
+
//
|
|
60
|
+
// Only the expiry is cleared. The token has to survive so the save below
|
|
61
|
+
// can still prove this request is the rightful holder.
|
|
62
|
+
conversation.leaseUntil = null;
|
|
63
|
+
try {
|
|
64
|
+
// A turn that already hit a fence knows its save would be rejected too;
|
|
65
|
+
// attempting it only repeats the same error to the client.
|
|
66
|
+
if (!superseded) await hai.config.store.saveConversation(conversation);
|
|
67
|
+
} catch (err) {
|
|
68
|
+
// Overtaken while we were slow: another turn already wrote newer state
|
|
69
|
+
// under a fresh token. Dropping this write is the correct outcome, but
|
|
70
|
+
// the client asked for something it is not getting, so say so.
|
|
71
|
+
if (!isStaleLease(err)) throw err;
|
|
72
|
+
emit({ type: "error", message: (err as Error).message });
|
|
73
|
+
}
|
|
40
74
|
}
|
|
41
75
|
|
|
42
|
-
await hai.config.store.saveConversation(conversation);
|
|
43
76
|
res.end();
|
|
44
77
|
return true;
|
|
45
78
|
};
|
package/src/runtime.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import {
|
|
2
2
|
estTokens,
|
|
3
|
+
isStaleLease,
|
|
3
4
|
makeCap,
|
|
4
5
|
type AnySurfaceImpl,
|
|
5
6
|
type Conversation,
|
|
@@ -19,12 +20,8 @@ export interface HaiConfig {
|
|
|
19
20
|
surfaces: AnySurfaceImpl[];
|
|
20
21
|
/** Guard against runaway tool loops. */
|
|
21
22
|
maxHops?: number;
|
|
22
|
-
/** Turn lease duration; a dead process releases after this. */
|
|
23
|
-
leaseMs?: number;
|
|
24
23
|
}
|
|
25
24
|
|
|
26
|
-
const LEASE_MS = 120_000;
|
|
27
|
-
|
|
28
25
|
export class Hai {
|
|
29
26
|
readonly config: HaiConfig;
|
|
30
27
|
private readonly surfaces = new Map<string, AnySurfaceImpl>();
|
|
@@ -48,13 +45,35 @@ export class Hai {
|
|
|
48
45
|
// ───────────────────────────────────────────────── derived tool
|
|
49
46
|
|
|
50
47
|
private buildQueryUiTool(): Tool<any> {
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
.
|
|
57
|
-
|
|
48
|
+
// The model has to be told what each query TAKES, not just that it exists.
|
|
49
|
+
// Listing bare names leaves `args` opaque, and the model then calls the
|
|
50
|
+
// query with none — every filter becomes a no-op and it gets the whole
|
|
51
|
+
// collection back.
|
|
52
|
+
const entries = [...this.surfaces.values()].flatMap((s) =>
|
|
53
|
+
Object.entries(s.surface.queries).map(([name, spec]) => {
|
|
54
|
+
const parts = [`${s.surface.name}.${name}`];
|
|
55
|
+
if (spec.description) parts.push(`— ${spec.description}`);
|
|
56
|
+
parts.push(
|
|
57
|
+
spec.argsJsonSchema
|
|
58
|
+
? `args: ${JSON.stringify(spec.argsJsonSchema)}`
|
|
59
|
+
: "args: (undeclared — omit them)",
|
|
60
|
+
);
|
|
61
|
+
return { surface: s.surface.name, name, spec, line: parts.join(" ") };
|
|
62
|
+
}),
|
|
63
|
+
);
|
|
64
|
+
|
|
65
|
+
// A query whose args are invisible is a silent wrong-answer bug, not a
|
|
66
|
+
// missing nicety — say so once, at construction, rather than never.
|
|
67
|
+
const undeclared = entries.filter((e) => !e.spec.argsJsonSchema);
|
|
68
|
+
if (undeclared.length) {
|
|
69
|
+
console.warn(
|
|
70
|
+
`[haikit] query_ui: no argsJsonSchema for ${undeclared.map((e) => `${e.surface}.${e.name}`).join(", ")}. ` +
|
|
71
|
+
`The model cannot see these parameters and will call the query with no arguments. ` +
|
|
72
|
+
`A query that genuinely takes none should declare { type: "object", properties: {} }.`,
|
|
73
|
+
);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
const catalogue = entries.map((e) => `- ${e.line}`).join("\n");
|
|
58
77
|
|
|
59
78
|
const self = this;
|
|
60
79
|
return {
|
|
@@ -62,14 +81,19 @@ export class Hai {
|
|
|
62
81
|
description:
|
|
63
82
|
"Dereference a rendered component's full dataset. The digest in your context holds only " +
|
|
64
83
|
"highlights; use this for anything beyond it. Never guess about rows you have not seen. " +
|
|
65
|
-
(catalogue ?
|
|
84
|
+
(catalogue ? `\n\nAvailable queries:\n${catalogue}` : ""),
|
|
66
85
|
input: { parse: (v) => v as any },
|
|
67
86
|
inputJsonSchema: {
|
|
68
87
|
type: "object",
|
|
69
88
|
properties: {
|
|
70
89
|
handle: { type: "string", description: "e.g. ui_01" },
|
|
71
|
-
query: { type: "string" },
|
|
72
|
-
args: {
|
|
90
|
+
query: { type: "string", description: "One of the query names listed in the description." },
|
|
91
|
+
args: {
|
|
92
|
+
type: "object",
|
|
93
|
+
description:
|
|
94
|
+
"Arguments for the chosen query, matching that query's schema in the description. " +
|
|
95
|
+
"Pass the constraints the user actually asked for — omitting them matches everything.",
|
|
96
|
+
},
|
|
73
97
|
},
|
|
74
98
|
required: ["handle", "query"],
|
|
75
99
|
},
|
|
@@ -107,7 +131,9 @@ export class Hai {
|
|
|
107
131
|
// receive a tool_result, so close the pending one out honestly first.
|
|
108
132
|
if (conversation.status === "awaiting" && conversation.pending) {
|
|
109
133
|
const { toolUseId, handle, results, digest } = conversation.pending;
|
|
110
|
-
|
|
134
|
+
// Recorded on the conversation, not the payload, so it commits with the
|
|
135
|
+
// history that explains it — see Conversation.frozen.
|
|
136
|
+
conversation.frozen.push(handle);
|
|
111
137
|
emit({ type: "ui_state", handle, state: "frozen" });
|
|
112
138
|
conversation.messages.push({
|
|
113
139
|
role: "user",
|
|
@@ -133,9 +159,17 @@ export class Hai {
|
|
|
133
159
|
input: { handle: string; action: string; value: unknown },
|
|
134
160
|
emit: Emit,
|
|
135
161
|
): Promise<void> {
|
|
162
|
+
// The payload row carries a conversation id, but that alone is not enough.
|
|
163
|
+
// A turn that rendered a surface and was then overtaken leaves a row behind:
|
|
164
|
+
// its conversation save is rejected, so the winning history never records
|
|
165
|
+
// the handle, yet the row and the browser that mounted it both still exist.
|
|
166
|
+
// Membership of the surviving `handles` is what makes those orphans inert.
|
|
167
|
+
if (!conversation.handles.includes(input.handle)) throw new Error("unknown handle");
|
|
168
|
+
|
|
169
|
+
if (conversation.frozen.includes(input.handle)) throw new Error("component is frozen");
|
|
170
|
+
|
|
136
171
|
const record = await this.config.store.getPayload(input.handle, conversation.id);
|
|
137
172
|
if (!record) throw new Error("unknown handle");
|
|
138
|
-
if (record.state === "frozen") throw new Error("component is frozen");
|
|
139
173
|
|
|
140
174
|
const impl = this.surfaces.get(record.component);
|
|
141
175
|
if (!impl) throw new Error(`unknown component: ${record.component}`);
|
|
@@ -157,7 +191,7 @@ export class Hai {
|
|
|
157
191
|
|
|
158
192
|
if (spec.kind === "resolve") {
|
|
159
193
|
if (conversation.pending?.handle !== input.handle) throw new Error("nothing awaiting this handle");
|
|
160
|
-
|
|
194
|
+
conversation.frozen.push(input.handle);
|
|
161
195
|
emit({ type: "ui_state", handle: input.handle, state: "frozen", selection: value });
|
|
162
196
|
emit({ type: "block_start", block: { kind: "interaction", id: this.nid("b"), handle: input.handle, label } });
|
|
163
197
|
|
|
@@ -188,7 +222,6 @@ export class Hai {
|
|
|
188
222
|
|
|
189
223
|
private async runTurn(conversation: Conversation, emit: Emit): Promise<void> {
|
|
190
224
|
conversation.status = "streaming";
|
|
191
|
-
conversation.leaseUntil = Date.now() + (this.config.leaseMs ?? LEASE_MS);
|
|
192
225
|
emit({ type: "status", status: "streaming" });
|
|
193
226
|
|
|
194
227
|
const toolDefs = [...this.tools.values()].map((t) => ({
|
|
@@ -245,7 +278,6 @@ export class Hai {
|
|
|
245
278
|
// Hold the resolved siblings too — the API is all-or-nothing per batch.
|
|
246
279
|
conversation.status = "awaiting";
|
|
247
280
|
conversation.pending = { ...parked, results };
|
|
248
|
-
conversation.leaseUntil = null;
|
|
249
281
|
emit({ type: "status", status: "awaiting" });
|
|
250
282
|
await this.emitContext(conversation, emit);
|
|
251
283
|
return;
|
|
@@ -255,7 +287,6 @@ export class Hai {
|
|
|
255
287
|
}
|
|
256
288
|
|
|
257
289
|
conversation.status = "idle";
|
|
258
|
-
conversation.leaseUntil = null;
|
|
259
290
|
emit({ type: "status", status: "idle" });
|
|
260
291
|
await this.emitContext(conversation, emit);
|
|
261
292
|
}
|
|
@@ -281,14 +312,16 @@ export class Hai {
|
|
|
281
312
|
// Validate before storing: props may originate outside this process.
|
|
282
313
|
const parsed = impl.surface.props.parse(props);
|
|
283
314
|
|
|
284
|
-
const handle = await this.config.store.putPayload(
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
315
|
+
const handle = await this.config.store.putPayload(
|
|
316
|
+
{
|
|
317
|
+
conversationId: conversation.id,
|
|
318
|
+
component: impl.surface.name,
|
|
319
|
+
version: impl.surface.version,
|
|
320
|
+
props: parsed,
|
|
321
|
+
mode: surfaceMode,
|
|
322
|
+
},
|
|
323
|
+
conversation.leaseToken,
|
|
324
|
+
);
|
|
292
325
|
|
|
293
326
|
conversation.handles.push(handle);
|
|
294
327
|
|
|
@@ -319,16 +352,21 @@ export class Hai {
|
|
|
319
352
|
const ret = await tool.run(input, ctx);
|
|
320
353
|
return { ret, mode };
|
|
321
354
|
} catch (err) {
|
|
355
|
+
// A tool error is information for the model; a lost lease is not. Fencing
|
|
356
|
+
// exists so a superseded turn stops — converting StaleLease into "tool
|
|
357
|
+
// failed" would hand it back to the model and keep paying for hops whose
|
|
358
|
+
// output the fenced save is going to discard anyway.
|
|
359
|
+
if (isStaleLease(err)) throw err;
|
|
322
360
|
return { ret: { model: `${call.name} failed: ${(err as Error).message}` }, mode: null };
|
|
323
361
|
}
|
|
324
362
|
}
|
|
325
363
|
|
|
326
364
|
private async emitContext(conversation: Conversation, emit: Emit) {
|
|
365
|
+
// One batched read, not one per handle: this runs on every turn, and a
|
|
366
|
+
// conversation accumulates handles for as long as it lives.
|
|
367
|
+
const records = await this.config.store.getPayloads(conversation.handles, conversation.id);
|
|
327
368
|
let uiTokens = 0;
|
|
328
|
-
for (const
|
|
329
|
-
const record = await this.config.store.getPayload(handle, conversation.id);
|
|
330
|
-
if (record) uiTokens += estTokens(record.props);
|
|
331
|
-
}
|
|
369
|
+
for (const record of records) uiTokens += estTokens(record.props);
|
|
332
370
|
emit({
|
|
333
371
|
type: "context",
|
|
334
372
|
messages: conversation.messages,
|
package/src/store.ts
CHANGED
|
@@ -1,40 +1,109 @@
|
|
|
1
|
-
import
|
|
1
|
+
import {
|
|
2
|
+
ConversationBusy,
|
|
3
|
+
StaleLease,
|
|
4
|
+
type Conversation,
|
|
5
|
+
type PayloadRecord,
|
|
6
|
+
type StoreAdapter,
|
|
7
|
+
} from "@haikit/core";
|
|
8
|
+
|
|
9
|
+
const LEASE_MS = 120_000;
|
|
10
|
+
|
|
11
|
+
export interface MemoryStoreOptions {
|
|
12
|
+
/**
|
|
13
|
+
* Turn lease TTL. A process that dies mid-turn strands the conversation for
|
|
14
|
+
* this long; too short and a slow turn is overtaken while still running.
|
|
15
|
+
*
|
|
16
|
+
* This is the single authority on lease duration — the runtime does not renew
|
|
17
|
+
* or override it. Being overtaken is safe rather than merely unlikely: the
|
|
18
|
+
* superseded turn's save is rejected by its fencing token.
|
|
19
|
+
*/
|
|
20
|
+
leaseMs?: number;
|
|
21
|
+
}
|
|
2
22
|
|
|
3
23
|
/**
|
|
4
|
-
* In-memory store. Implements the same interface a Postgres store would
|
|
24
|
+
* In-memory store. Implements the same interface a Postgres store would, and
|
|
25
|
+
* enforces the same rules — scoping, leasing and fencing are all checked here
|
|
26
|
+
* even though this store could get away without them. A reference
|
|
27
|
+
* implementation that only enforces what it happens to need is what let three
|
|
28
|
+
* of these bugs exist in the first place.
|
|
29
|
+
*
|
|
30
|
+
* Conversations are copied in and out rather than shared by reference. That is
|
|
31
|
+
* not an optimisation detail: a store that hands back a live object cannot
|
|
32
|
+
* detect a stale writer at all, because the caller's copy *is* the stored one.
|
|
33
|
+
* The copy is a JSON round trip specifically because that is what a `jsonb`
|
|
34
|
+
* column does — anything a real store would silently drop (undefined, a Date's
|
|
35
|
+
* type, a class instance) gets dropped here too, in development.
|
|
5
36
|
*
|
|
6
37
|
* Swapping this for a durable store is the single highest-value change for
|
|
7
38
|
* production: a parked `elicit` turn is durable state, and losing `pending`
|
|
8
39
|
* makes a conversation permanently unsendable.
|
|
9
40
|
*/
|
|
10
|
-
export function memoryStore(): StoreAdapter {
|
|
41
|
+
export function memoryStore(options: MemoryStoreOptions = {}): StoreAdapter {
|
|
42
|
+
const leaseMs = options.leaseMs ?? LEASE_MS;
|
|
11
43
|
const conversations = new Map<string, Conversation>();
|
|
12
44
|
const payloads = new Map<string, PayloadRecord>();
|
|
13
45
|
let convSeq = 0;
|
|
14
46
|
let handleSeq = 0;
|
|
15
47
|
|
|
48
|
+
const copy = <T>(value: T): T => JSON.parse(JSON.stringify(value)) as T;
|
|
49
|
+
const newToken = () => globalThis.crypto.randomUUID();
|
|
50
|
+
|
|
51
|
+
/** Every durable mutation presents the token it was issued. A holder that has
|
|
52
|
+
* been superseded must stop writing rather than run to completion and be
|
|
53
|
+
* discarded at the end.
|
|
54
|
+
*
|
|
55
|
+
* Atomic here only because nothing awaits between this check and the write
|
|
56
|
+
* that follows it: JavaScript cannot interleave another request into a
|
|
57
|
+
* synchronous run. Put an `await` between them and the compare-and-set the
|
|
58
|
+
* contract requires is silently gone. */
|
|
59
|
+
const fence = (conversationId: string, leaseToken: string | null) => {
|
|
60
|
+
const stored = conversations.get(conversationId);
|
|
61
|
+
// Mirrors the SQL shape in the contract: no matching row fails the
|
|
62
|
+
// compare-and-set exactly like a superseded token does. A null token needs
|
|
63
|
+
// no case of its own — a stored lease is never null, so it cannot match,
|
|
64
|
+
// just as `NULL = x` is never true. What must not happen is treating a
|
|
65
|
+
// missing token as "unfenced" and skipping this comparison.
|
|
66
|
+
if (!stored) throw new StaleLease(conversationId, "does not exist — no lease was ever issued for it");
|
|
67
|
+
if (stored.leaseToken !== leaseToken) throw new StaleLease(conversationId);
|
|
68
|
+
};
|
|
69
|
+
|
|
16
70
|
return {
|
|
17
71
|
async loadConversation(id) {
|
|
18
|
-
if (id && conversations.has(id))
|
|
72
|
+
if (id && conversations.has(id)) {
|
|
73
|
+
const stored = conversations.get(id)!;
|
|
74
|
+
if (stored.leaseUntil !== null && stored.leaseUntil > Date.now()) {
|
|
75
|
+
throw new ConversationBusy(stored.id);
|
|
76
|
+
}
|
|
77
|
+
// Reissuing the token is what fences out whoever held it before.
|
|
78
|
+
stored.leaseUntil = Date.now() + leaseMs;
|
|
79
|
+
stored.leaseToken = newToken();
|
|
80
|
+
return copy(stored);
|
|
81
|
+
}
|
|
19
82
|
const conversation: Conversation = {
|
|
20
83
|
id: `conv_${++convSeq}`,
|
|
21
84
|
status: "idle",
|
|
22
85
|
messages: [],
|
|
23
86
|
handles: [],
|
|
87
|
+
frozen: [],
|
|
24
88
|
pending: null,
|
|
25
|
-
leaseUntil:
|
|
89
|
+
leaseUntil: Date.now() + leaseMs,
|
|
90
|
+
leaseToken: newToken(),
|
|
26
91
|
};
|
|
27
92
|
conversations.set(conversation.id, conversation);
|
|
28
|
-
return conversation;
|
|
93
|
+
return copy(conversation);
|
|
29
94
|
},
|
|
30
95
|
|
|
31
96
|
async saveConversation(conversation) {
|
|
32
|
-
|
|
97
|
+
// Same rule as every other fenced write. Not an upsert: a conversation
|
|
98
|
+
// this store never issued a lease for cannot be created by saving it.
|
|
99
|
+
fence(conversation.id, conversation.leaseToken);
|
|
100
|
+
conversations.set(conversation.id, copy(conversation));
|
|
33
101
|
},
|
|
34
102
|
|
|
35
|
-
async putPayload(record) {
|
|
103
|
+
async putPayload(record, leaseToken) {
|
|
104
|
+
fence(record.conversationId, leaseToken);
|
|
36
105
|
const handle = `ui_${String(++handleSeq).padStart(2, "0")}`;
|
|
37
|
-
payloads.set(handle, { ...record, handle, createdAt: Date.now() });
|
|
106
|
+
payloads.set(handle, copy({ ...record, handle, createdAt: Date.now() }));
|
|
38
107
|
return handle;
|
|
39
108
|
},
|
|
40
109
|
|
|
@@ -42,12 +111,16 @@ export function memoryStore(): StoreAdapter {
|
|
|
42
111
|
async getPayload(handle, conversationId) {
|
|
43
112
|
const record = payloads.get(handle);
|
|
44
113
|
if (!record || record.conversationId !== conversationId) return null;
|
|
45
|
-
return record;
|
|
114
|
+
return copy(record);
|
|
46
115
|
},
|
|
47
116
|
|
|
48
|
-
async
|
|
49
|
-
const
|
|
50
|
-
|
|
117
|
+
async getPayloads(handles, conversationId) {
|
|
118
|
+
const out: PayloadRecord[] = [];
|
|
119
|
+
for (const handle of handles) {
|
|
120
|
+
const record = payloads.get(handle);
|
|
121
|
+
if (record && record.conversationId === conversationId) out.push(copy(record));
|
|
122
|
+
}
|
|
123
|
+
return out;
|
|
51
124
|
},
|
|
52
125
|
};
|
|
53
126
|
}
|