@theoremjs/web 0.3.4-next.528.g44c76f9 → 0.3.4-next.540.g67b727f

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 CHANGED
@@ -282,7 +282,7 @@ The session store keeps gates, permissions, and operations. The two stores are s
282
282
  | `createSealedConversationStore({ keys, keyVersion })` | Nowhere on the server. The ref is the conversation itself, encrypted and signed with your key, and bound to the session. The browser cannot read it or change it. |
283
283
  | Your own `TheoremConversationStore` | Your database. |
284
284
 
285
- A ref that the store cannot open fails with `session.conversation_unknown`. This includes a ref from another session.
285
+ A ref that the store cannot open fails with `session.conversation_unknown`, kind `not_found` (HTTP 404). This includes a ref from another session. A route or method the handler does not serve also answers 404 with kind `not_found`.
286
286
 
287
287
  The sealed store:
288
288
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@theoremjs/web",
3
- "version": "0.3.4-next.528.g44c76f9",
3
+ "version": "0.3.4-next.540.g67b727f",
4
4
  "description": "Theorem on the web platform, with no UI framework: Web-standard server handlers that serve a profile, and the browser client that talks to them.",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -25,6 +25,6 @@
25
25
  },
26
26
  "peerDependencies": {
27
27
  "zod": "^4.6.5",
28
- "@theoremjs/agents": "^0.3.4-next.528.g44c76f9"
28
+ "@theoremjs/agents": "^0.3.4-next.540.g67b727f"
29
29
  }
30
30
  }
@@ -41,7 +41,7 @@ export function emptyConversation() {
41
41
  }
42
42
  /** Refuses a ref the store does not know or cannot open: the browser reads `session.conversation_unknown`. */
43
43
  export function unknownConversation(cause) {
44
- return new TheoremError('request', labelText('session.conversation_unknown'), {
44
+ return new TheoremError('not_found', labelText('session.conversation_unknown'), {
45
45
  copy: { key: 'session.conversation_unknown' },
46
46
  ...(cause === undefined ? {} : { cause }),
47
47
  });
package/server/handler.js CHANGED
@@ -34,7 +34,7 @@ import { createMemoryPageToolBroker, pageToolResultSchema, sessionPageDispatch,
34
34
  * @module
35
35
  */
36
36
  import { authorizeToolReplay, compactHistory, defaultKernelScope, defineProfile, invokeTool, jsonObjectSchema, labelText, registerProfile, runTurn, TheoremError, toolInvocationHash, } from '@theoremjs/agents';
37
- import { caughtStatus, clientError, forClient, HTTP_METHOD, } from '@theoremjs/agents/host';
37
+ import { caughtStatus, clientError, forClient, HTTP_NOT_FOUND, } from '@theoremjs/agents/host';
38
38
  import { clientProfileInterface, gatedToolsFromEvents, interfaceFromProfile, promotedToolIdsFromEvents, toolSnapshotFromEvents, } from '@theoremjs/agents/interface';
39
39
  import { answerGatedCall, resolveGateTtlMs, } from '@theoremjs/agents/kernel';
40
40
  import { hostInterface, theoremHostCallRequestSchema, } from "../client/host-transport.js";
@@ -589,9 +589,9 @@ function answerRequest(body) {
589
589
  }
590
590
  /** A route this profile doesn't serve. */
591
591
  function refused(ctx) {
592
- return jsonResponse(HTTP_METHOD, {
593
- error: labelText('error.request', {}, ctx.profile.labels),
594
- errorKind: 'request',
592
+ return jsonResponse(HTTP_NOT_FOUND, {
593
+ error: labelText('error.not_found', {}, ctx.profile.labels),
594
+ errorKind: 'not_found',
595
595
  });
596
596
  }
597
597
  async function* replayOperation(ctx, request, operation) {
@@ -646,9 +646,8 @@ async function settlingOf(ctx, request, session, body) {
646
646
  operation.callId === body.gateId &&
647
647
  operation.profileId === ctx.profile.id &&
648
648
  (operation.status === 'running' || operation.status === 'uncertain')) {
649
- throw new TheoremError('unavailable', labelText('tool.execution_uncertain'), {
650
- copy: { key: 'tool.execution_uncertain' },
651
- });
649
+ throw new TheoremError('conflict', `The tool operation is ${operation.status}, so it does not run again until the host settles it`, // labels-exempt: developer contract / internal diagnostic — not end-user or model copy (P2)
650
+ { copy: { key: 'tool.execution_uncertain' } });
652
651
  }
653
652
  if (operation?.status === 'settled' &&
654
653
  operation.profileId === ctx.profile.id &&
@@ -9,10 +9,9 @@ export const pageToolResultSchema = z.strictObject({
9
9
  z.strictObject({ timedOut: z.literal(true) }),
10
10
  ]),
11
11
  });
12
- function unavailable() {
13
- throw new TheoremError('unavailable', labelText('tool.execution_uncertain'), {
14
- copy: { key: 'tool.execution_uncertain' },
15
- });
12
+ /** The user reads that the action is unresolved; the builder reads why, under the kind of what happened. */
13
+ function refused(kind, reason) {
14
+ throw new TheoremError(kind, reason, { copy: { key: 'tool.execution_uncertain' } });
16
15
  }
17
16
  export function createMemoryPageToolBroker(maxPending = 128) {
18
17
  if (!Number.isSafeInteger(maxPending) || maxPending <= 0)
@@ -22,8 +21,12 @@ export function createMemoryPageToolBroker(maxPending = 128) {
22
21
  return {
23
22
  reserve(sessionId, grantId, signal) {
24
23
  const id = key(sessionId, grantId);
25
- if (signal?.aborted || waiting.has(id) || waiting.size >= maxPending)
26
- unavailable();
24
+ if (signal?.aborted)
25
+ refused('cancelled', 'The page dispatch was stopped before it was sent'); // labels-exempt: developer contract / internal diagnostic — not end-user or model copy (P2)
26
+ if (waiting.has(id))
27
+ refused('internal', 'The page broker already waits for this grant'); // labels-exempt: developer contract / internal diagnostic — not end-user or model copy (P2)
28
+ if (waiting.size >= maxPending)
29
+ refused('rate_limit', `The page broker already waits for its maxPending of ${maxPending} answers`); // labels-exempt: developer contract / internal diagnostic — not end-user or model copy (P2)
27
30
  let finish = () => { };
28
31
  const result = new Promise((resolve) => {
29
32
  finish = resolve;
@@ -44,7 +47,8 @@ export function createMemoryPageToolBroker(maxPending = 128) {
44
47
  deliver(sessionId, grantId, page) {
45
48
  const finish = waiting.get(key(sessionId, grantId));
46
49
  if (!finish)
47
- return Promise.reject(new TheoremError('unavailable', labelText('tool.execution_uncertain')));
50
+ return Promise.reject(new TheoremError('conflict', 'No dispatch in this process waits for the page answer to this grant', // labels-exempt: developer contract / internal diagnostic — not end-user or model copy (P2)
51
+ { copy: { key: 'tool.execution_uncertain' } }));
48
52
  finish(page);
49
53
  return Promise.resolve();
50
54
  },
@@ -57,7 +61,7 @@ export function sessionPageDispatch(store, sessionId, broker, timeoutMs = 20_000
57
61
  execution: {
58
62
  async dispatch(claim, signal) {
59
63
  if (!claim.invocation)
60
- unavailable();
64
+ refused('internal', 'The page dispatch got a claim with no invocation'); // labels-exempt: developer contract / internal diagnostic — not end-user or model copy (P2)
61
65
  const grantId = crypto.randomUUID();
62
66
  const expiresAt = Date.now() + timeoutMs;
63
67
  const pending = broker.reserve(sessionId, grantId, signal);
@@ -68,7 +72,7 @@ export function sessionPageDispatch(store, sessionId, broker, timeoutMs = 20_000
68
72
  record.claimToken !== claim.claimToken ||
69
73
  record.invocationHash !== claim.invocationHash ||
70
74
  record.pageGrant)
71
- unavailable();
75
+ refused('internal', 'The session store holds no running claim for this page dispatch');
72
76
  record.pageGrant = { grantId, expiresAt };
73
77
  });
74
78
  }
@@ -79,7 +83,9 @@ export function sessionPageDispatch(store, sessionId, broker, timeoutMs = 20_000
79
83
  const remaining = expiresAt - Date.now();
80
84
  if (remaining <= 0 || signal?.aborted) {
81
85
  pending.cancel();
82
- unavailable();
86
+ if (signal?.aborted)
87
+ refused('cancelled', 'The page dispatch was stopped before it was sent'); // labels-exempt: developer contract / internal diagnostic — not end-user or model copy (P2)
88
+ refused('timeout', 'The page dispatch used its timeoutMs before it was sent'); // labels-exempt: developer contract / internal diagnostic — not end-user or model copy (P2)
83
89
  }
84
90
  let expire = () => { };
85
91
  const expired = new Promise((resolve) => {
@@ -106,16 +112,18 @@ export function sessionPageDispatch(store, sessionId, broker, timeoutMs = 20_000
106
112
  record.status === 'ready' ||
107
113
  record.callId !== result.callId ||
108
114
  record.pageGrant?.grantId !== result.grantId)
109
- unavailable();
115
+ refused('request', 'The page answered a grant this session did not issue for that call'); // labels-exempt: developer contract / internal diagnostic — not end-user or model copy (P2)
110
116
  if (record.pageGrant.resultHash !== undefined) {
111
117
  if (record.pageGrant.resultHash !== hash)
112
- unavailable();
118
+ refused('request', 'The page answered this grant twice with different results'); // labels-exempt: developer contract / internal diagnostic — not end-user or model copy (P2)
113
119
  if (record.status === 'uncertain')
114
- unavailable();
120
+ refused('request', 'The page answered again for a call that is uncertain'); // labels-exempt: developer contract / internal diagnostic — not end-user or model copy (P2)
115
121
  return { duplicate: true, settled: record.status === 'settled' };
116
122
  }
117
- if (record.status !== 'running' || Date.now() >= record.pageGrant.expiresAt)
118
- unavailable();
123
+ if (record.status !== 'running')
124
+ refused('request', `The page answered a call that is ${record.status}, not running`); // labels-exempt: developer contract / internal diagnostic — not end-user or model copy (P2)
125
+ if (Date.now() >= record.pageGrant.expiresAt)
126
+ refused('timeout', 'The page answered after its grant expired'); // labels-exempt: developer contract / internal diagnostic — not end-user or model copy (P2)
119
127
  record.pageGrant.resultHash = hash;
120
128
  return { duplicate: false, settled: false };
121
129
  });
@@ -38,9 +38,8 @@ export async function createTheoremSessionBoundary(options) {
38
38
  const providerState = await requireOwnedCheckpoint(state.providerCheckpoints, prepared.providerState, state.operations);
39
39
  if (providerState &&
40
40
  Object.values(state.operations).some((operation) => operation.status !== 'settled')) {
41
- throw new TheoremError('unavailable', labelText('tool.execution_uncertain'), {
42
- copy: { key: 'tool.execution_uncertain' },
43
- });
41
+ throw new TheoremError('conflict', 'The session holds a tool operation that is not settled, so its provider state does not resume', // labels-exempt: developer contract / internal diagnostic — not end-user or model copy (P2)
42
+ { copy: { key: 'tool.execution_uncertain' } });
44
43
  }
45
44
  return {
46
45
  ...prepared,
@@ -6,7 +6,7 @@
6
6
  * @module
7
7
  */
8
8
  import { labelText } from '@theoremjs/agents';
9
- import { caughtStatus, clientError, HTTP_METHOD, } from '@theoremjs/agents/host';
9
+ import { caughtStatus, clientError, HTTP_NOT_FOUND, } from '@theoremjs/agents/host';
10
10
  import { jsonResponse } from "./json-response.js";
11
11
  export function singleActionHandler({ action, iface, labels, clientEvents, onError }, run) {
12
12
  const isAction = (request) => new URL(request.url).pathname.split('/').filter(Boolean).at(-1) === action;
@@ -15,9 +15,9 @@ export function singleActionHandler({ action, iface, labels, clientEvents, onErr
15
15
  if (request.method === 'GET' && !isAction(request))
16
16
  return jsonResponse(200, { interface: iface });
17
17
  if (request.method !== 'POST' || !isAction(request)) {
18
- return jsonResponse(HTTP_METHOD, {
19
- error: labelText('error.request', {}, labels),
20
- errorKind: 'request',
18
+ return jsonResponse(HTTP_NOT_FOUND, {
19
+ error: labelText('error.not_found', {}, labels),
20
+ errorKind: 'not_found',
21
21
  });
22
22
  }
23
23
  return jsonResponse(200, { result: await run(request) });