@theoremjs/web 0.3.4-next.755.g88665bd → 0.3.4-next.757.gde44a4d

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
@@ -273,7 +273,7 @@ const transport = withPageToolDispatch(
273
273
  | `notifier`, `notifyRetryMs` | Tell an open page that a background call ended, and how long the page waits to reopen a cut stream. Unset, the page reads the end on its next turn or reload. See [the background section](#share-session-authority). |
274
274
  | `credentialStore` | Where tool credentials are kept, by session. Default: process memory. |
275
275
  | `authorizationUrl` | Returns the sign-in URL for an OAuth gate. Without it, an OAuth gate has no URL. |
276
- | `gateTtlMs` | How long a gate waits for an answer. A later answer fails with `session.gate_expired`. Unset, a gate waits until it is answered. |
276
+ | `gateTtlMs` | How long a gate waits for an answer. A later answer fails with `session.gate_expired`, and the next message cancels the call. Unset, a gate waits until it is answered. |
277
277
  | `maxRequestBytes` | The largest browser request body, in bytes. Unset, a body of any size is read. |
278
278
  | `pageToolTimeoutMs` | How long the page has to answer a tool call it runs. A call it has not answered by then settles as timed out. Unset, the call waits until the page answers or the turn stops. The browser clients answer every call they get, so only a page function that never returns holds a turn. |
279
279
  | `operationRetentionMs` | How long a settled tool result is kept. Default: 24 hours. |
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@theoremjs/web",
3
- "version": "0.3.4-next.755.g88665bd",
3
+ "version": "0.3.4-next.757.gde44a4d",
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.755.g88665bd"
28
+ "@theoremjs/agents": "^0.3.4-next.757.gde44a4d"
29
29
  }
30
30
  }
@@ -107,8 +107,8 @@ export type TheoremHandlerOptions = {
107
107
  conversationStore?: TheoremConversationStore;
108
108
  /**
109
109
  * How long a gate (permission, confirmation, sign-in) waits for its answer, in
110
- * milliseconds. An answer after that is refused with `session.gate_expired`.
111
- * Unset, a gate waits until it is answered.
110
+ * milliseconds. An answer after that is refused with `session.gate_expired`,
111
+ * and the next message cancels the call. Unset, a gate waits until it is answered.
112
112
  */
113
113
  gateTtlMs?: number;
114
114
  /** Retain settled tool results in milliseconds. Default: 24 hours. */
package/server/handler.js CHANGED
@@ -331,10 +331,16 @@ function answerPendingGate(state, request, profileId) {
331
331
  state.permissions = answer.sessionPermissions;
332
332
  return { callId: request.callId, pending, answer };
333
333
  }
334
- function walkedCallOf(state, reference, profileId) {
334
+ function walkedCallOf(state, reference, profileId, paused) {
335
335
  const settled = Object.hasOwn(state.operations, reference.operationId)
336
336
  ? state.operations[reference.operationId]
337
337
  : undefined;
338
+ // why: A gate that lapsed is gone from the session while the reply still waits on its call. The call is only ever cancelled here, so the next message is never refused for it.
339
+ if (!settled && !Object.hasOwn(state.gates, reference.operationId)) {
340
+ const lapsed = pendingGatesFrom(paused, { profileId, turnInput: {} }).find(({ callId }) => callId === reference.callId);
341
+ if (lapsed)
342
+ state.gates[reference.operationId] = lapsed.gate;
343
+ }
338
344
  return settled?.status === 'settled' &&
339
345
  settled.callId === reference.callId &&
340
346
  settled.profileId === profileId
@@ -346,11 +352,12 @@ function walkedCallOf(state, reference, profileId) {
346
352
  * answered `abandon`: every one of them, or none when one is neither waiting
347
353
  * nor settled. Each comes back with the run that settles it.
348
354
  */
349
- async function takeWalkedAway(ctx, request, session, abandon) {
355
+ async function takeWalkedAway(ctx, request, session, abandon, conversation) {
356
+ const paused = conversation.paused?.events ?? [];
350
357
  // why: A throw leaves the session unsaved, so no call is taken unless all are.
351
358
  const calls = await ctx.sessions.mutate(session.id, (state) => abandon.map((reference) => ({
352
359
  reference,
353
- call: walkedCallOf(state, reference, ctx.profile.id),
360
+ call: walkedCallOf(state, reference, ctx.profile.id, paused),
354
361
  })));
355
362
  return calls.map(({ reference, call }) => ({
356
363
  ...reference,
@@ -530,7 +537,7 @@ async function turn(ctx, chat, request, session) {
530
537
  });
531
538
  // why: Take the walked-away calls before streaming, so a stale one is a reply status, not a stream error.
532
539
  const walkAway = body.abandon
533
- ? await takeWalkedAway(ctx, request, session, body.abandon)
540
+ ? await takeWalkedAway(ctx, request, session, body.abandon, opened.conversation)
534
541
  : undefined;
535
542
  return eventStream(ctx, request, () => messageReply({ host, opened, message, ...(walkAway ? { walkAway } : {}), runner }));
536
543
  }
@@ -664,7 +671,7 @@ async function rewind(ctx, request, session) {
664
671
  const opened = await openConversation(host, body.conversation, { kind: 'rewind', at: body.at });
665
672
  const waiting = waitingCalls(opened.conversation);
666
673
  const walkAway = waiting.length
667
- ? await takeWalkedAway(ctx, request, session, waiting)
674
+ ? await takeWalkedAway(ctx, request, session, waiting, opened.conversation)
668
675
  : undefined;
669
676
  const next = await rewindConversation({
670
677
  host,