@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.
@@ -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;AAEjE,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,CAiC1F"}
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
- const conversation = await hai.config.store.loadConversation(body.conversationId);
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
- await hai.config.store.saveConversation(conversation);
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
  };
@@ -1 +1 @@
1
- {"version":3,"file":"routes.js","sourceRoot":"","sources":["../src/routes.ts"],"names":[],"mappings":"AAIA;;;;;;GAMG;AACH,MAAM,UAAU,WAAW,CAAC,GAAQ,EAAE,QAAQ,GAAG,MAAM;IACrD,OAAO,KAAK,UAAU,MAAM,CAAC,GAAoB,EAAE,GAAmB;QACpE,MAAM,GAAG,GAAG,IAAI,GAAG,CAAC,GAAG,CAAC,GAAG,IAAI,GAAG,EAAE,kBAAkB,CAAC,CAAC;QACxD,IAAI,GAAG,CAAC,MAAM,KAAK,MAAM,IAAI,CAAC,GAAG,CAAC,QAAQ,CAAC,UAAU,CAAC,QAAQ,CAAC;YAAE,OAAO,KAAK,CAAC;QAE9E,MAAM,KAAK,GAAG,GAAG,CAAC,QAAQ,CAAC,KAAK,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC;QAClD,IAAI,KAAK,KAAK,OAAO,IAAI,KAAK,KAAK,WAAW;YAAE,OAAO,KAAK,CAAC;QAE7D,MAAM,IAAI,GAAG,MAAM,QAAQ,CAAC,GAAG,CAAC,CAAC;QACjC,MAAM,YAAY,GAAG,MAAM,GAAG,CAAC,MAAM,CAAC,KAAK,CAAC,gBAAgB,CAAC,IAAI,CAAC,cAAc,CAAC,CAAC;QAElF,MAAM,IAAI,GAAG,OAAO,CAAC,GAAG,CAAC,CAAC;QAC1B,IAAI,KAAK,KAAK,OAAO,EAAE,CAAC;YACtB,IAAI,CAAC,EAAE,IAAI,EAAE,OAAO,EAAE,cAAc,EAAE,YAAY,CAAC,EAAE,EAAE,KAAK,EAAE,GAAG,CAAC,MAAM,CAAC,KAAK,CAAC,EAAE,EAAE,CAAC,CAAC;QACvF,CAAC;QAED,IAAI,CAAC;YACH,IAAI,KAAK,KAAK,OAAO,EAAE,CAAC;gBACtB,MAAM,GAAG,CAAC,IAAI,CAAC,YAAY,EAAE,MAAM,CAAC,IAAI,CAAC,OAAO,IAAI,EAAE,CAAC,EAAE,IAAI,CAAC,CAAC;YACjE,CAAC;iBAAM,CAAC;gBACN,MAAM,GAAG,CAAC,QAAQ,CAChB,YAAY,EACZ,EAAE,MAAM,EAAE,IAAI,CAAC,MAAM,EAAE,MAAM,EAAE,IAAI,CAAC,MAAM,EAAE,KAAK,EAAE,IAAI,CAAC,KAAK,EAAE,EAC/D,IAAI,CACL,CAAC;YACJ,CAAC;QACH,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,IAAI,CAAC,EAAE,IAAI,EAAE,OAAO,EAAE,OAAO,EAAG,GAAa,CAAC,OAAO,EAAE,CAAC,CAAC;QAC3D,CAAC;QAED,MAAM,GAAG,CAAC,MAAM,CAAC,KAAK,CAAC,gBAAgB,CAAC,YAAY,CAAC,CAAC;QACtD,GAAG,CAAC,GAAG,EAAE,CAAC;QACV,OAAO,IAAI,CAAC;IACd,CAAC,CAAC;AACJ,CAAC;AAED,SAAS,OAAO,CAAC,GAAmB;IAClC,GAAG,CAAC,SAAS,CAAC,GAAG,EAAE;QACjB,cAAc,EAAE,mBAAmB;QACnC,eAAe,EAAE,UAAU;QAC3B,UAAU,EAAE,YAAY;KACzB,CAAC,CAAC;IACH,OAAO,CAAC,KAAgB,EAAE,EAAE;QAC1B,GAAG,CAAC,KAAK,CAAC,SAAS,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC;IAClD,CAAC,CAAC;AACJ,CAAC;AAED,KAAK,UAAU,QAAQ,CAAC,GAAoB;IAC1C,MAAM,MAAM,GAAa,EAAE,CAAC;IAC5B,IAAI,KAAK,EAAE,MAAM,KAAK,IAAI,GAAG;QAAE,MAAM,CAAC,IAAI,CAAC,KAAe,CAAC,CAAC;IAC5D,OAAO,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,QAAQ,EAAE,IAAI,IAAI,CAAC,CAAC;AAC9D,CAAC"}
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;
@@ -1 +1 @@
1
- {"version":3,"file":"runtime.d.ts","sourceRoot":"","sources":["../src/runtime.ts"],"names":[],"mappings":"AAAA,OAAO,EAGL,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;IACjB,+DAA+D;IAC/D,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAID,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;IAuDlB,IAAI,CAAC,YAAY,EAAE,YAAY,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC;IA0BzE,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;YAsDF,OAAO;YA0EP,UAAU;YA+DV,WAAW;CAa1B"}
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
- const catalogue = [...this.surfaces.values()]
25
- .map((s) => {
26
- const names = Object.keys(s.surface.queries);
27
- return names.length ? `${s.surface.name}: ${names.join(", ")}` : null;
28
- })
29
- .filter(Boolean)
30
- .join(" · ");
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 ? `Available queries — ${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: { type: "object" },
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
- await this.config.store.freezePayload(handle);
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
- await this.config.store.freezePayload(input.handle);
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
- state: "live",
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 handle of conversation.handles) {
266
- const record = await this.config.store.getPayload(handle, conversation.id);
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,
@@ -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 { StoreAdapter } from "@haikit/core";
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
@@ -1 +1 @@
1
- {"version":3,"file":"store.d.ts","sourceRoot":"","sources":["../src/store.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAA+B,YAAY,EAAE,MAAM,cAAc,CAAC;AAE9E;;;;;;GAMG;AACH,wBAAgB,WAAW,IAAI,YAAY,CA2C1C"}
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
- return conversations.get(id);
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: null,
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
- conversations.set(conversation.id, conversation);
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 freezePayload(handle) {
44
- const record = payloads.get(handle);
45
- if (record)
46
- record.state = "frozen";
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":"AAEA;;;;;;GAMG;AACH,MAAM,UAAU,WAAW;IACzB,MAAM,aAAa,GAAG,IAAI,GAAG,EAAwB,CAAC;IACtD,MAAM,QAAQ,GAAG,IAAI,GAAG,EAAyB,CAAC;IAClD,IAAI,OAAO,GAAG,CAAC,CAAC;IAChB,IAAI,SAAS,GAAG,CAAC,CAAC;IAElB,OAAO;QACL,KAAK,CAAC,gBAAgB,CAAC,EAAE;YACvB,IAAI,EAAE,IAAI,aAAa,CAAC,GAAG,CAAC,EAAE,CAAC;gBAAE,OAAO,aAAa,CAAC,GAAG,CAAC,EAAE,CAAE,CAAC;YAC/D,MAAM,YAAY,GAAiB;gBACjC,EAAE,EAAE,QAAQ,EAAE,OAAO,EAAE;gBACvB,MAAM,EAAE,MAAM;gBACd,QAAQ,EAAE,EAAE;gBACZ,OAAO,EAAE,EAAE;gBACX,OAAO,EAAE,IAAI;gBACb,UAAU,EAAE,IAAI;aACjB,CAAC;YACF,aAAa,CAAC,GAAG,CAAC,YAAY,CAAC,EAAE,EAAE,YAAY,CAAC,CAAC;YACjD,OAAO,YAAY,CAAC;QACtB,CAAC;QAED,KAAK,CAAC,gBAAgB,CAAC,YAAY;YACjC,aAAa,CAAC,GAAG,CAAC,YAAY,CAAC,EAAE,EAAE,YAAY,CAAC,CAAC;QACnD,CAAC;QAED,KAAK,CAAC,UAAU,CAAC,MAAM;YACrB,MAAM,MAAM,GAAG,MAAM,MAAM,CAAC,EAAE,SAAS,CAAC,CAAC,QAAQ,CAAC,CAAC,EAAE,GAAG,CAAC,EAAE,CAAC;YAC5D,QAAQ,CAAC,GAAG,CAAC,MAAM,EAAE,EAAE,GAAG,MAAM,EAAE,MAAM,EAAE,SAAS,EAAE,IAAI,CAAC,GAAG,EAAE,EAAE,CAAC,CAAC;YACnE,OAAO,MAAM,CAAC;QAChB,CAAC;QAED,mEAAmE;QACnE,KAAK,CAAC,UAAU,CAAC,MAAM,EAAE,cAAc;YACrC,MAAM,MAAM,GAAG,QAAQ,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC;YACpC,IAAI,CAAC,MAAM,IAAI,MAAM,CAAC,cAAc,KAAK,cAAc;gBAAE,OAAO,IAAI,CAAC;YACrE,OAAO,MAAM,CAAC;QAChB,CAAC;QAED,KAAK,CAAC,aAAa,CAAC,MAAM;YACxB,MAAM,MAAM,GAAG,QAAQ,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC;YACpC,IAAI,MAAM;gBAAE,MAAM,CAAC,KAAK,GAAG,QAAQ,CAAC;QACtC,CAAC;KACF,CAAC;AACJ,CAAC"}
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.1.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.1.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
- const conversation = await hai.config.store.loadConversation(body.conversationId);
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
- const catalogue = [...this.surfaces.values()]
52
- .map((s) => {
53
- const names = Object.keys(s.surface.queries);
54
- return names.length ? `${s.surface.name}: ${names.join(", ")}` : null;
55
- })
56
- .filter(Boolean)
57
- .join(" · ");
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 ? `Available queries — ${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: { type: "object" },
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
- await this.config.store.freezePayload(handle);
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
- await this.config.store.freezePayload(input.handle);
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
- conversationId: conversation.id,
286
- component: impl.surface.name,
287
- version: impl.surface.version,
288
- props: parsed,
289
- mode: surfaceMode,
290
- state: "live",
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 handle of conversation.handles) {
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 type { Conversation, PayloadRecord, StoreAdapter } from "@haikit/core";
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)) return conversations.get(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: null,
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
- conversations.set(conversation.id, conversation);
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 freezePayload(handle) {
49
- const record = payloads.get(handle);
50
- if (record) record.state = "frozen";
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
  }