@demlik/tea 0.4.0 → 0.5.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.
Files changed (116) hide show
  1. package/dist/agent/index.d.ts +5 -5
  2. package/dist/agent/index.js +10 -10
  3. package/dist/authed-call/index.d.ts +2 -2
  4. package/dist/authed-call/index.js +7 -7
  5. package/dist/await-terminal/index.d.ts +2 -2
  6. package/dist/await-terminal/index.js +2 -2
  7. package/dist/batch-window/index.d.ts +3 -3
  8. package/dist/batch-window/index.js +3 -3
  9. package/dist/cache/index.d.ts +2 -2
  10. package/dist/cache/index.js +4 -4
  11. package/dist/{chunk-2AHAR7CX.js → chunk-362SZ6CS.js} +3 -3
  12. package/dist/{chunk-2AHAR7CX.js.map → chunk-362SZ6CS.js.map} +1 -1
  13. package/dist/{chunk-JFFVBEDI.js → chunk-62QBHM3T.js} +3 -3
  14. package/dist/{chunk-JFFVBEDI.js.map → chunk-62QBHM3T.js.map} +1 -1
  15. package/dist/{chunk-JXYYRKD6.js → chunk-7A74NSG4.js} +3 -3
  16. package/dist/{chunk-JXYYRKD6.js.map → chunk-7A74NSG4.js.map} +1 -1
  17. package/dist/{chunk-2KNLKQVN.js → chunk-7UU5P2CX.js} +3 -3
  18. package/dist/{chunk-2KNLKQVN.js.map → chunk-7UU5P2CX.js.map} +1 -1
  19. package/dist/{chunk-ET6QQNSQ.js → chunk-CIDS43FH.js} +3 -3
  20. package/dist/{chunk-ET6QQNSQ.js.map → chunk-CIDS43FH.js.map} +1 -1
  21. package/dist/{chunk-PBDYW2TU.js → chunk-DBOJ2UC6.js} +14 -3
  22. package/dist/chunk-DBOJ2UC6.js.map +1 -0
  23. package/dist/{chunk-5W7CNAHB.js → chunk-KEFMI24G.js} +7 -7
  24. package/dist/{chunk-5W7CNAHB.js.map → chunk-KEFMI24G.js.map} +1 -1
  25. package/dist/{chunk-SPWVPIFQ.js → chunk-KWPB6BM6.js} +4 -4
  26. package/dist/{chunk-SPWVPIFQ.js.map → chunk-KWPB6BM6.js.map} +1 -1
  27. package/dist/{chunk-SSHDBOXF.js → chunk-LFW66IIV.js} +4 -4
  28. package/dist/{chunk-SSHDBOXF.js.map → chunk-LFW66IIV.js.map} +1 -1
  29. package/dist/{chunk-D4NJBQAH.js → chunk-PBAYOJJO.js} +91 -24
  30. package/dist/chunk-PBAYOJJO.js.map +1 -0
  31. package/dist/{chunk-TWDHNIY6.js → chunk-PUTHY7LR.js} +3 -3
  32. package/dist/{chunk-TWDHNIY6.js.map → chunk-PUTHY7LR.js.map} +1 -1
  33. package/dist/{chunk-LYLDCIPH.js → chunk-QKUWDC4P.js} +32 -25
  34. package/dist/chunk-QKUWDC4P.js.map +1 -0
  35. package/dist/{chunk-FCKP6N7Y.js → chunk-T6HDAPMR.js} +3 -3
  36. package/dist/{chunk-FCKP6N7Y.js.map → chunk-T6HDAPMR.js.map} +1 -1
  37. package/dist/{chunk-B5RAZVXW.js → chunk-W2S4HK7J.js} +6 -6
  38. package/dist/{chunk-B5RAZVXW.js.map → chunk-W2S4HK7J.js.map} +1 -1
  39. package/dist/{chunk-LJJV6ZD5.js → chunk-ZILCH3WS.js} +4 -4
  40. package/dist/{chunk-LJJV6ZD5.js.map → chunk-ZILCH3WS.js.map} +1 -1
  41. package/dist/{chunk-RMX4EOQK.js → chunk-ZT3MGQB7.js} +3 -3
  42. package/dist/{chunk-RMX4EOQK.js.map → chunk-ZT3MGQB7.js.map} +1 -1
  43. package/dist/{core-42kapW0n.d.ts → core-DPYOyOJv.d.ts} +8 -5
  44. package/dist/deadline/index.d.ts +2 -2
  45. package/dist/deadline/index.js +3 -3
  46. package/dist/do/index.d.ts +6 -6
  47. package/dist/do/index.js +10 -10
  48. package/dist/{durable-effects-CFRngTNA.d.ts → durable-effects-B0YpoCHg.d.ts} +1 -1
  49. package/dist/extension/index.d.ts +2 -2
  50. package/dist/extension/index.js +3 -3
  51. package/dist/extension/react.js +3 -3
  52. package/dist/extension/subs/index.d.ts +2 -2
  53. package/dist/fan-out/index.d.ts +2 -2
  54. package/dist/idempotent-intake/index.d.ts +2 -2
  55. package/dist/index.d.ts +18 -4
  56. package/dist/index.js +2 -2
  57. package/dist/llm-call/index.d.ts +2 -2
  58. package/dist/llm-call/index.js +7 -7
  59. package/dist/{machine-B_BF4jij.d.ts → machine-Q_Bsm7Vu.d.ts} +1 -1
  60. package/dist/machine-viz/index.d.ts +1 -1
  61. package/dist/machine-viz/index.js +2 -2
  62. package/dist/mem/index.d.ts +2 -2
  63. package/dist/monitored-run/index.d.ts +3 -3
  64. package/dist/monitored-run/index.js +5 -5
  65. package/dist/node/index.d.ts +2 -2
  66. package/dist/paginated-walk/index.d.ts +2 -2
  67. package/dist/paginated-walk/index.js +7 -7
  68. package/dist/parity/index.d.ts +2 -2
  69. package/dist/parity/index.js +3 -3
  70. package/dist/pbt/arbitraries/index.d.ts +1 -1
  71. package/dist/pbt/index.d.ts +1 -1
  72. package/dist/pbt/index.js +3 -3
  73. package/dist/pbt/runners/index.d.ts +1 -1
  74. package/dist/pbt/runners/index.js +3 -3
  75. package/dist/poller/index.d.ts +2 -2
  76. package/dist/poller/index.js +3 -3
  77. package/dist/prediction/index.d.ts +1 -1
  78. package/dist/prediction/index.js +2 -2
  79. package/dist/pure/index.d.ts +1 -1
  80. package/dist/pure/index.js +2 -2
  81. package/dist/react/index.d.ts +2 -2
  82. package/dist/react/index.js +2 -2
  83. package/dist/reconciler/index.d.ts +2 -2
  84. package/dist/reconciler/index.js +7 -7
  85. package/dist/recorder/index.d.ts +2 -2
  86. package/dist/resilient-call/index.d.ts +2 -2
  87. package/dist/resilient-call/index.js +6 -6
  88. package/dist/{runtime-types-CukV8kMX.d.ts → runtime-types-uBuVvZqa.d.ts} +50 -5
  89. package/dist/saga/index.d.ts +2 -2
  90. package/dist/snapshot/index.d.ts +1 -1
  91. package/dist/snapshot/index.js +3 -3
  92. package/dist/subs/index.d.ts +16 -9
  93. package/dist/subs/index.js +3 -3
  94. package/dist/testing/index.d.ts +2 -2
  95. package/dist/testing/index.js +2 -2
  96. package/dist/throttled-input/index.d.ts +2 -2
  97. package/dist/throttled-input/index.js +6 -6
  98. package/dist/token-refresh/index.d.ts +1 -1
  99. package/dist/token-refresh/index.js +3 -3
  100. package/dist/trace-replay/index.d.ts +2 -2
  101. package/dist/trace-replay/index.js +3 -3
  102. package/dist/{types-KbL3W1mj.d.ts → types-C6mXL9wA.d.ts} +1 -1
  103. package/dist/with-deadline/index.d.ts +1 -1
  104. package/dist/with-deadline/index.js +2 -2
  105. package/dist/with-resilience/index.d.ts +2 -2
  106. package/dist/with-resilience/index.js +6 -6
  107. package/dist/with-telemetry/index.d.ts +1 -1
  108. package/dist/with-telemetry/index.js +2 -2
  109. package/dist/work-queue/adapter.d.ts +2 -2
  110. package/dist/work-queue/index.d.ts +2 -2
  111. package/dist/work-queue/ops.d.ts +2 -2
  112. package/dist/workflow/index.d.ts +3 -3
  113. package/package.json +1 -1
  114. package/dist/chunk-D4NJBQAH.js.map +0 -1
  115. package/dist/chunk-LYLDCIPH.js.map +0 -1
  116. package/dist/chunk-PBDYW2TU.js.map +0 -1
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/subs/define-listener.ts","../src/subs/from-broadcast-channel.ts","../src/subs/from-event-source.ts","../src/subs/from-event-target.ts","../src/subs/from-interval.ts","../src/subs/from-port.ts","../src/subs/from-reconnecting-web-socket.ts","../src/subs/from-web-socket.ts","../src/subs/managed-resource.ts","../src/subs/transport.ts"],"names":[],"mappings":"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","file":"chunk-QKUWDC4P.js","sourcesContent":["// ---------------------------------------------------------------------------\n// defineListener — listener-as-resource. The kernel primitive that turns an\n// imperative `add`/`remove` listener pair into a substrate-owned Sub whose\n// cleanup is MANDATORY and DERIVED — a forgotten or no-op disposer is\n// unrepresentable.\n//\n// The leak it kills: every author who wires a raw listener hand-rolls the SAME\n// skeleton —\n//\n// return (sub, _ctx, dispatch) => {\n// const listener = (e) => { const m = msgFn(e, sub); if (m !== null) dispatch(m); };\n// target.addListener(listener);\n// return () => target.removeListener(listener); // ← author-driven\n// };\n//\n// — and each re-write can SILENTLY LEAK two ways the type system never sees:\n//\n// 1. The disposer is a no-op (`return () => {}`) — the listener stays armed\n// forever. Nothing forces the returned cleanup to undo the add.\n// 2. The disposer calls `remove` with a DIFFERENT function than `add`\n// received (a fresh closure, a re-bound `this`) — the original listener\n// is never removed; it leaks while a phantom remove succeeds silently.\n//\n// `fromBroadcastChannel`, `fromEventTarget`, and every host-side bridge\n// hand-roll this skeleton today. They happen to be correct — but the\n// correctness is convention (rung 5), not structure. `defineListener` moves it\n// to rung 1: the listener the substrate builds is the SAME reference handed to\n// both `add` and `remove`, and the cleanup IS `() => remove(thatListener, ...)`.\n// You cannot write a no-op disposer because you do not write the disposer at\n// all.\n//\n// Shape:\n//\n// // Args is the tuple chrome invokes the listener with — here a single\n// // [Alarm]. The substrate builds ONE listener of this exact native shape.\n// const fromChromeAlarm = defineListener<[chrome.alarms.Alarm], S>({\n// add: (l) => chrome.alarms.onAlarm.addListener(l),\n// remove: (l) => chrome.alarms.onAlarm.removeListener(l),\n// });\n//\n// // ...then at the subscribe cell, supply the Msg projection:\n// subscribe: { tick: fromChromeAlarm<M>((sub, alarm) => ({ type: \"tick\", ... })) }\n//\n// - `add`/`remove` receive `(listener, sub, ctx)`. The `listener` is the\n// EXACT function the substrate built; pass it straight to the platform's\n// addListener/removeListener — NO wrapper. A wrapper inside `add` would\n// break reference identity (the platform stores the wrapper, but the\n// substrate hands `remove` the unwrapped listener → silent leak). Carrying\n// the platform's native `Args` tuple on the listener is what lets it be\n// registered directly. `sub`/`ctx` let the target be pulled from `ctx` or\n// keyed on the `sub` (the `getTarget()`-from-ctx shape fromPort needs).\n//\n// - `msgFn(sub, ...args) => M | null` — the universal drop-on-null filter\n// every factory in this subpath carries. `null` drops the dispatch; the\n// listener stays armed. `sub` is first (always present); the platform args\n// follow in their native order.\n//\n// Generalizes the `from*` pattern: a closure-owned disposer makes a forgotten\n// cleanup a syntax error, and this brings it to every host/satellite.\n//\n// Strengthens invariant 4 (external events enter as Subs; the lifecycle —\n// add on subscribe, remove on cleanup — is owned by the substrate, never the\n// author) and invariant 6 (no silent looseness: a leaked listener is a silent\n// failure, and this primitive removes the way to write one).\n// ---------------------------------------------------------------------------\n\nimport type { Sub } from \"../index\";\nimport { dispatchIfPresent, type SubscribeHandler } from \"./types\";\n\n/**\n * The imperative listener target, expressed as the `add`/`remove` pair the\n * substrate pairs into a reconciled resource. Both halves receive the SAME\n * `listener` reference — the substrate builds it once — so `remove` can never\n * be handed a different function than `add` saw.\n *\n * `add`/`remove` also receive `(sub, ctx)` so the target may be resolved from\n * `ctx` (a runtime, a port, a DOM node looked up lazily) or keyed on the Sub\n * (an alarm name, a channel name). Resolving the target inside `add` and\n * closing over nothing but the listener keeps a target swap from leaking the\n * old target's listener — same rationale as `fromEventTarget`'s captured\n * target.\n *\n * `Args` is the tuple the platform invokes the listener with — `[Alarm]` for\n * `chrome.alarms.onAlarm`, `[message, sender, sendResponse]` for\n * `chrome.runtime.onMessage`, `[changes, areaName]` for\n * `chrome.storage.onChanged`. The substrate builds ONE function of this exact\n * shape and threads the IDENTICAL reference to both `add` and `remove` — so a\n * platform that matches `removeListener` by reference (every chrome surface)\n * removes exactly what it added. A consumer that wrapped the listener inside\n * `add` would break reference identity (leak #2 above); carrying the native\n * `Args` on the listener itself removes the need for any wrapper.\n */\nexport interface ListenerTarget<\n Args extends readonly unknown[],\n S extends Sub,\n Ctx,\n> {\n add: (listener: (...args: Args) => void, sub: S, ctx: Ctx) => void;\n remove: (listener: (...args: Args) => void, sub: S, ctx: Ctx) => void;\n}\n\n/**\n * Build a listener-backed Sub factory whose cleanup is mandatory and derived.\n *\n * `Args`, `S`, `Ctx` are fixed when the target is defined — they describe the\n * one listener topology this factory owns (the platform arg tuple, which Sub\n * shape it filters on, what context resolves it). `M` is supplied at the\n * subscribe cell because the same listener feeds different Msgs in different\n * machines. Pinning `S` here (rather than at the `msgFn` step) lets\n * `add`/`remove` read the concrete Sub's fields — `sub.alarmName`,\n * `sub.channel` — with NO cast at the substrate edge: the narrowing happens in\n * author-land where the Sub shape is in scope.\n *\n * The returned function is a `SubscribeHandler` factory in the exact shape of\n * every other `@demlik/tea/subs` battery: supply the `msgFn` projection, get a\n * `(sub, ctx, dispatch) => cleanup` handler assignable to `machine.subscribe`.\n * The cleanup the author NEVER writes is `() => remove(listener, sub, ctx)`,\n * with `listener` the identical reference passed to `add`.\n */\nexport function defineListener<\n Args extends readonly unknown[],\n S extends Sub = Sub,\n Ctx = unknown,\n>(\n target: ListenerTarget<Args, S, Ctx>,\n): <M>(\n msgFn: (sub: S, ...args: Args) => M | null,\n) => SubscribeHandler<S, M, Ctx> {\n return <M>(\n msgFn: (sub: S, ...args: Args) => M | null,\n ): SubscribeHandler<S, M, Ctx> => {\n return (sub, ctx, dispatch) => {\n const listener = (...args: Args): void => {\n dispatchIfPresent(dispatch, msgFn(sub, ...args));\n };\n // ONE listener, identical reference to both halves — chrome's\n // removeListener matches by reference, so this removes exactly what\n // add registered. No wrapper, no WeakMap, no reference drift.\n target.add(listener, sub, ctx);\n return () => {\n target.remove(listener, sub, ctx);\n };\n };\n };\n}\n","// ---------------------------------------------------------------------------\n// fromBroadcastChannel — universal `BroadcastChannel`-shaped Sub factory.\n//\n// `BroadcastChannel` is a same-origin pub/sub primitive available in\n// every modern browser context (window, worker, service worker) AND in\n// Cloudflare Workers (with origin scoped to the worker isolate). The\n// constructor opens the channel; the `message` event fires whenever\n// another participant on the same channel name posts a message; the\n// `close()` method releases the OS resource.\n//\n// The factory carries `channelName` on the Sub itself (not in the\n// closure) so the same factory instance handles every BroadcastChannel\n// Sub in a machine — one Sub per channel, named by id, sharing the\n// factory's lifecycle code. Renaming a channel requires a new Sub.id\n// (the reconcile pass otherwise leaves the existing channel open).\n//\n// `msgFn` returns `M | null` — same drop-on-null pattern as\n// `fromEventTarget`. The MessageEvent's `data` is `unknown` from the\n// platform's perspective; the caller decides whether the payload is a\n// recognizable shape, and returns `null` to drop unknown messages\n// without polluting the reducer with parse-discard cells.\n//\n// Cleanup calls `channel.close()`, which removes the listener AND\n// releases the channel reference — `removeEventListener` alone would\n// leak the BroadcastChannel handle. The pair is intentional.\n// ---------------------------------------------------------------------------\n\nimport type { Sub } from \"../index\";\nimport type {\n MinimalBroadcastChannelCtor,\n MinimalMessageEvent,\n} from \"./platform\";\nimport { dispatchIfPresent, type SubscribeHandler } from \"./types\";\n\ntype BroadcastSubData = { channelName: string };\n\ndeclare const BroadcastChannel: MinimalBroadcastChannelCtor;\n\nexport function fromBroadcastChannel<S extends Sub & BroadcastSubData, M>(\n msgFn: (event: MinimalMessageEvent, sub: S) => M | null,\n): SubscribeHandler<S, M, unknown> {\n return (sub, _ctx, dispatch) => {\n const channel = new BroadcastChannel(sub.channelName);\n const listener = (event: MinimalMessageEvent): void => {\n dispatchIfPresent(dispatch, msgFn(event, sub));\n };\n channel.addEventListener(\"message\", listener);\n return () => {\n channel.removeEventListener(\"message\", listener);\n channel.close();\n };\n };\n}\n","// ---------------------------------------------------------------------------\n// fromEventSource — universal `EventSource` (server-sent events) Sub\n// factory.\n//\n// SSE is the asymmetric streaming protocol used by the agent log /\n// audit log pipelines: HTTP-shaped open, server pushes events forever,\n// client receives until close. `EventSource` is the standard browser\n// API; the constructor opens the connection, `message` fires per\n// server-pushed event, `error` fires on disconnect or non-2xx, `open`\n// fires once on initial handshake, `close()` tears the connection\n// down.\n//\n// Three callbacks because three observable events ARE three concerns\n// the caller routinely cares about:\n//\n// - `onMessage(data, sub)` — required. The SSE payload's `data` field\n// is `string` from the platform; the caller decides parse + Msg\n// shape. Returns `M | null`; null drops the dispatch.\n//\n// - `onError(event, sub)` — optional. SSE's `error` event fires both\n// on transient network drops (the browser auto-reconnects) AND on\n// permanent failures. Most consumers want to surface ONE of these\n// as a Msg (e.g. \"stream-disconnected\") without folding both into\n// `onMessage`'s discriminant. Omitting the callback drops error\n// events silently.\n//\n// - `onOpen(sub)` — optional. The connection's initial handshake\n// succeeded. Useful for \"show 'connected' indicator\" Msgs. Omitting\n// drops open events silently. NOT fired on auto-reconnect by every\n// browser implementation — treat the event as best-effort.\n//\n// `url` lives on the Sub itself (`EventSourceSubData`). Same id +\n// different url = the substrate's reconcile pass leaves the existing\n// connection alone (the URL change is silent). Changing the URL\n// requires a new Sub.id.\n//\n// Cleanup is `close()` + `removeEventListener` for all three —\n// belt-and-suspenders against EventSource implementations that\n// dispatch events into already-closed sources.\n// ---------------------------------------------------------------------------\n\nimport type { Sub } from \"../index\";\nimport type {\n MinimalEvent,\n MinimalEventSourceCtor,\n MinimalMessageEvent,\n} from \"./platform\";\nimport { dispatchIfPresent, type SubscribeHandler } from \"./types\";\n\ntype EventSourceSubData = { url: string };\n\ndeclare const EventSource: MinimalEventSourceCtor;\n\nexport interface EventSourceFactoryOpts<S, M> {\n onMessage: (data: string, sub: S) => M | null;\n onError?: (event: MinimalEvent, sub: S) => M | null;\n onOpen?: (sub: S) => M | null;\n}\n\nexport function fromEventSource<S extends Sub & EventSourceSubData, M>(\n opts: EventSourceFactoryOpts<S, M>,\n): SubscribeHandler<S, M, unknown> {\n return (sub, _ctx, dispatch) => {\n const source = new EventSource(sub.url);\n\n const messageListener = (event: MinimalMessageEvent): void => {\n // The SSE `data` payload is a string per the platform contract.\n // Cast at the boundary: callers express `(data: string) => M | null`\n // and never see `unknown` flowing in from the platform global.\n dispatchIfPresent(dispatch, opts.onMessage(event.data as string, sub));\n };\n source.addEventListener(\"message\", messageListener);\n\n const onError = opts.onError;\n const errorListener = onError\n ? (event: MinimalEvent): void => {\n dispatchIfPresent(dispatch, onError(event, sub));\n }\n : undefined;\n if (errorListener) source.addEventListener(\"error\", errorListener);\n\n const onOpen = opts.onOpen;\n const openListener = onOpen\n ? (): void => {\n dispatchIfPresent(dispatch, onOpen(sub));\n }\n : undefined;\n if (openListener) source.addEventListener(\"open\", openListener);\n\n return () => {\n source.removeEventListener(\"message\", messageListener);\n if (errorListener) source.removeEventListener(\"error\", errorListener);\n if (openListener) source.removeEventListener(\"open\", openListener);\n source.close();\n };\n };\n}\n","// ---------------------------------------------------------------------------\n// fromEventTarget — universal addEventListener/removeEventListener Sub\n// factory over any `EventTarget` (DOM nodes, `window`, `document`,\n// `BroadcastChannel`, `MessagePort`, `Worker`, `ServiceWorker`, etc.).\n//\n// Two non-obvious shape choices:\n//\n// - `getTarget: () => EventTarget` is a thunk, not a direct value. The\n// factory MUST work in environments where the target is not available\n// at module-load time — service workers run before any `document`\n// exists; some targets are constructed lazily from `ctx`. Deferring\n// the lookup to subscribe time also means a state-driven swap of the\n// target requires only a new Sub.id; the factory pulls the current\n// target on subscribe.\n//\n// - `msgFn` may return `null` — that emission is *dropped*. The event\n// fired, the factory called `msgFn`, but the caller decided \"this\n// event is not interesting given current state.\" No dispatch is made;\n// the listener stays armed for the next event. This is the explicit\n// filter point at the Sub boundary — the alternative (filtering inside\n// the reducer via a noop cell) costs a transition + reconcile pass per\n// filtered event.\n//\n// Cleanup uses the SAME `getTarget()` instance captured at subscribe time\n// so a target swap mid-Sub doesn't leak the listener on the old target.\n// We close over the resolved target rather than calling `getTarget()`\n// again at cleanup — the alternative (re-resolving) is the cause of\n// silent listener leaks in long-lived runtimes.\n// ---------------------------------------------------------------------------\n\nimport type { Sub } from \"../index\";\nimport type { MinimalEvent, MinimalEventTarget } from \"./platform\";\nimport { dispatchIfPresent, type SubscribeHandler } from \"./types\";\n\nexport function fromEventTarget<S extends Sub, M>(\n getTarget: () => MinimalEventTarget,\n eventName: string,\n msgFn: (event: MinimalEvent, sub: S) => M | null,\n): SubscribeHandler<S, M, unknown> {\n return (sub, _ctx, dispatch) => {\n const target = getTarget();\n const listener = (event: MinimalEvent): void => {\n dispatchIfPresent(dispatch, msgFn(event, sub));\n };\n target.addEventListener(eventName, listener);\n return () => {\n target.removeEventListener(eventName, listener);\n };\n };\n}\n","// ---------------------------------------------------------------------------\n// fromInterval — universal `setInterval`-shaped Sub factory.\n//\n// The recurring shape at every `subscribe[type]` cell that wraps a periodic\n// timer is the same five lines: `setInterval` the dispatch, return a\n// `clearInterval` cleanup. The factory absorbs both halves of the lifecycle\n// (subscribe + cleanup); the caller keeps the intent — which Sub variant,\n// which Msg to dispatch.\n//\n// Timing data (`intervalMs`) lives on the Sub itself, not in the closure.\n// The substrate already reconciles Subs by id on every transition; carrying\n// the period on the Sub means a `subscriptions(state)` function that\n// returns `{ id, type, intervalMs: 30_000 }` in one state and the same id\n// with `intervalMs: 60_000` later... will NOT re-wire the interval (the id\n// is unchanged, the reconcile pass leaves it running). That's a tradeoff\n// the substrate's reconcile contract pins, not this factory — if you want\n// the interval to change, change the Sub's id too.\n//\n// Strengthens invariant 9 (named, small surface) and invariant 2 (the Sub\n// carries the data; the lifecycle is in the factory; the reducer never\n// sees the timer).\n// ---------------------------------------------------------------------------\n\nimport type { Sub } from \"../index\";\nimport type { SubscribeHandler } from \"./types\";\n\ntype IntervalSubData = { intervalMs: number };\n\nexport function fromInterval<S extends Sub & IntervalSubData, M>(\n msgFn: (sub: S) => M,\n): SubscribeHandler<S, M, unknown> {\n return (sub, _ctx, dispatch) => {\n const handle = setInterval(() => {\n dispatch(msgFn(sub));\n }, sub.intervalMs);\n return () => {\n clearInterval(handle);\n };\n };\n}\n","// ---------------------------------------------------------------------------\n// fromPort — universal `Runtime.subscribePort`-shaped Sub factory.\n//\n// The canonical cross-runtime coordination pattern in @demlik/tea: one\n// runtime emits to a typed Port; another runtime's machine declares an\n// `audit-idle`-shaped Sub whose subscribe handler attaches to the\n// publisher's Port. The recurring 4-line shape at every such cell:\n//\n// \"audit-idle\": (_sub, ctx, dispatch) =>\n// ctx.auditRuntime.subscribePort(auditIdlePort, (value) => {\n// dispatch(buildMsg(value));\n// });\n//\n// The factory absorbs the cleanup-returning subscribePort call AND the\n// dispatch-on-emit wiring. The caller keeps the three pieces of intent:\n//\n// 1. `selectRuntime(ctx)` — which sibling Runtime publishes the Port.\n// This is a selector (not a direct ctx.auditRuntime reference) so\n// the factory does NOT pin the ctx shape — any machine whose ctx\n// exposes some Runtime<...> reference is compatible by selecting it.\n// A direct `Runtime` parameter would have made the factory shape\n// depend on having that Runtime in scope at machine-definition\n// time, which is a worse match for the \"ctx is the boundary\"\n// contract this substrate already commits to.\n//\n// 2. `port: Port<T>` — the typed channel itself. Identity is by\n// reference (the substrate enforces unique names at definePort\n// time); pass the same `Port<T>` value the publisher emits to.\n//\n// 3. `msgFn(value, sub) => M | null` — the value-to-Msg mapping, with\n// drop-on-null semantics matching every other factory in this\n// subpath. The published value's type `T` flows through to `msgFn`'s\n// first param so the caller never re-asserts the payload shape.\n//\n// Strengthens invariant 9 (the cross-runtime Port topology is named at\n// one factory site instead of redrawn at every subscribe cell).\n// ---------------------------------------------------------------------------\n\nimport type { Port, Runtime, Sub } from \"../index\";\nimport { dispatchIfPresent, type SubscribeHandler } from \"./types\";\n\n// `RS` / `RM` carry the sibling Runtime's concrete State and Msg through the\n// selector: a caller that selects a `Runtime<AuditState, AuditMsg>` keeps that\n// type at the seam instead of collapsing it to `Runtime<unknown, {type:string}>`.\n// They default to the prior broad pair so existing call sites that select a\n// less-specific Runtime still compile — the factory still only ever calls\n// `subscribePort`, so it does not read RS/RM, but the boundary no longer\n// hardcodes the widest type when the caller knows a narrower one.\nexport function fromPort<\n S extends Sub,\n M,\n Ctx,\n T,\n RS = unknown,\n RM extends { type: string } = { type: string },\n>(\n selectRuntime: (ctx: Ctx) => Runtime<RS, RM>,\n port: Port<T>,\n msgFn: (value: T, sub: S) => M | null,\n): SubscribeHandler<S, M, Ctx> {\n return (sub, ctx, dispatch) => {\n const runtime = selectRuntime(ctx);\n return runtime.subscribePort(port, (value) => {\n dispatchIfPresent(dispatch, msgFn(value, sub));\n });\n };\n}\n","// ---------------------------------------------------------------------------\n// fromReconnectingWebSocket — a `WebSocket` Sub factory that survives drops.\n//\n// `fromWebSocket` is the SSE-sibling ONE-SHOT factory: its cleanup `close()`s\n// the socket and it never re-opens on an unexpected drop (issue #188). That is\n// the right shape for a connection the caller manages, but any tea client over\n// an unreliable transport — games, presence, collab — needs the connection to\n// recover transparently, without a page reload. Rather than re-roll a backoff\n// loop in every consumer (vortex's `ReconnectingSocket` is the proven shape,\n// services/vortex/src/reconnecting-socket.ts), this is the reconnecting variant\n// folded into the substrate's Sub surface.\n//\n// The reconnect contract, mirroring vortex's wrapper:\n//\n// - On an UNEXPECTED close (`onclose` the substrate didn't initiate) it\n// re-opens after a CAPPED exponential backoff: `min(base * 2^attempt, max)`.\n// A successful handshake resets `attempt` to 0, so the next drop retries\n// fast again.\n//\n// - A CALLER-INITIATED close (the Sub's cleanup, e.g. the machine\n// transitioned the Sub away) suppresses reconnect: handlers are detached\n// BEFORE `close(1000)` so the teardown can't fire `onClose` or schedule a\n// reconnect. After cleanup the factory never re-opens.\n//\n// - `connect` and `schedule` are INJECTABLE (defaulting to the global\n// `WebSocket` and `setTimeout`/`clearTimeout`), the test seam that lets the\n// reconnect/backoff behavior be unit-tested with a fake socket + a manual\n// clock — no DOM, no real network, no timers — exactly as vortex's wrapper\n// is tested.\n//\n// Callback contract (same `M | null` \"null drops the dispatch\" shape as\n// `fromWebSocket`, plus one reconnect-specific hook):\n//\n// - `onMessage(data, sub)` — required. Raw `unknown` frame; the caller parses.\n// - `onOpen(sub)` — optional. Fires on EVERY successful handshake — the first\n// open AND every reconnect — so a \"connected\" indicator stays accurate.\n// - `onError(event, sub)` — optional. The `error` event; `close` still drives\n// the reconnect (one path), so this is purely a notification.\n// - `onClose(code, reason, sub)` — optional. Fires on every close the\n// substrate observes (a drop that will be retried, too) — NOT on the\n// caller's own teardown, whose handlers are detached first.\n// - `onReconnect(attempt, sub)` — optional. Fires when a RE-open (any open\n// after the first) succeeds, carrying the 1-based reconnect count. This is\n// the resync hook: the place to dispatch a \"re-fetch the authoritative\n// snapshot, discard predicted state\" Msg after the transport recovered.\n//\n// Like `fromWebSocket`, `wsUrl` lives on the Sub (`WebSocketSubData`); every\n// reconnect reads `sub.wsUrl` afresh, so a reconcile that re-subscribes with a\n// new url reconnects to the new endpoint.\n// ---------------------------------------------------------------------------\n\nimport type { Sub } from \"../index\";\nimport type { WebSocketSubData } from \"./from-web-socket\";\nimport type {\n MinimalEvent,\n MinimalWebSocket,\n MinimalWebSocketCtor,\n} from \"./platform\";\nimport { dispatchIfPresent, type SubscribeHandler } from \"./types\";\n\ndeclare const WebSocket: MinimalWebSocketCtor;\n\n/** Cancels a scheduled reconnect timer (the inverse of `schedule`). */\nexport type CancelTimer = () => void;\n\nexport interface ReconnectingWebSocketFactoryOpts<S, M> {\n onMessage: (data: unknown, sub: S) => M | null;\n onOpen?: (sub: S) => M | null;\n onError?: (event: MinimalEvent, sub: S) => M | null;\n onClose?: (code: number, reason: string, sub: S) => M | null;\n /**\n * Fires when a reconnection (any open after the first) succeeds, carrying the\n * 1-based reconnect count — the resync hook. Omitting drops reconnect events\n * silently.\n */\n onReconnect?: (attempt: number, sub: S) => M | null;\n /** First backoff delay in ms (doubles each failed attempt). Default 250. */\n backoffBaseMs?: number;\n /** Backoff ceiling in ms. Default 5000. */\n backoffMaxMs?: number;\n /** Inject the socket constructor (defaults to the global `WebSocket`); the test seam. */\n connect?: (url: string) => MinimalWebSocket;\n /** Inject the timer (defaults to `setTimeout`/`clearTimeout`); the test seam. */\n schedule?: (fn: () => void, ms: number) => CancelTimer;\n}\n\nconst defaultConnect = (url: string): MinimalWebSocket => new WebSocket(url);\n\nconst defaultSchedule = (fn: () => void, ms: number): CancelTimer => {\n const handle = setTimeout(fn, ms);\n return () => clearTimeout(handle);\n};\n\nexport function fromReconnectingWebSocket<S extends Sub & WebSocketSubData, M>(\n opts: ReconnectingWebSocketFactoryOpts<S, M>,\n): SubscribeHandler<S, M, unknown> {\n const backoffBaseMs = opts.backoffBaseMs ?? 250;\n const backoffMaxMs = opts.backoffMaxMs ?? 5000;\n const connect = opts.connect ?? defaultConnect;\n const schedule = opts.schedule ?? defaultSchedule;\n\n const { onMessage, onOpen, onError, onClose, onReconnect } = opts;\n\n return (sub, _ctx, dispatch) => {\n let ws: MinimalWebSocket | null = null;\n let attempt = 0;\n let opened = false;\n let closedByCaller = false;\n let cancelReconnect: CancelTimer | null = null;\n\n const emit = (msg: M | null): void => dispatchIfPresent(dispatch, msg);\n\n const handleClose = (code: number, reason: string): void => {\n ws = null;\n if (onClose) emit(onClose(code, reason, sub));\n // A teardown the substrate initiated detaches handlers first, so this\n // path only runs for an UNEXPECTED close — reconnect with capped backoff.\n if (closedByCaller) return;\n const delay = Math.min(backoffBaseMs * 2 ** attempt, backoffMaxMs);\n attempt += 1;\n cancelReconnect = schedule(() => {\n cancelReconnect = null;\n if (!closedByCaller) open();\n }, delay);\n };\n\n function open(): void {\n const socket = connect(sub.wsUrl);\n ws = socket;\n\n socket.onopen = (): void => {\n // `opened` BEFORE this open marks it a reconnect; `attempt` is the\n // pending count for the drop that triggered it (1-based once we reset).\n const reconnected = opened;\n const reconnectCount = attempt;\n attempt = 0;\n opened = true;\n if (onOpen) emit(onOpen(sub));\n if (reconnected && onReconnect) emit(onReconnect(reconnectCount, sub));\n };\n\n socket.onmessage = (event): void => {\n emit(onMessage(event.data, sub));\n };\n\n // `error` precedes `close`; let `close` drive the reconnect so there is\n // one path. The handler is still wired (as a no-op when the caller didn't\n // supply `onError`) so a platform error event has a sink.\n socket.onerror = onError\n ? (event): void => emit(onError(event, sub))\n : (): void => {};\n\n socket.onclose = (event): void => {\n handleClose(event.code, event.reason);\n };\n }\n\n open();\n\n return () => {\n // Deliberate teardown: cancel any pending reconnect, detach handlers so\n // the cleanup `close()` can't fire `onClose` or schedule a reconnect,\n // then close the live socket. After this the factory never re-opens.\n closedByCaller = true;\n if (cancelReconnect !== null) {\n cancelReconnect();\n cancelReconnect = null;\n }\n const socket = ws;\n ws = null;\n if (socket !== null) {\n socket.onopen = null;\n socket.onmessage = null;\n socket.onerror = null;\n socket.onclose = null;\n // 1000 = \"normal closure\" per RFC 6455 §7.4.1.\n socket.close(1000);\n }\n };\n };\n}\n","// ---------------------------------------------------------------------------\n// fromWebSocket — universal `WebSocket` Sub factory. The symmetric sibling\n// of `fromEventSource`.\n//\n// WebSocket is the bidirectional streaming protocol used wherever the\n// server pushes events AND the client pushes commands on the same\n// connection. The constructor opens the connection, `onopen` fires once\n// on handshake, `onmessage` fires per server-pushed frame, `onerror`\n// fires on transient or permanent failure, `onclose` fires once on\n// teardown carrying `code` + `reason`. `close(code)` initiates a clean\n// shutdown from the client side.\n//\n// Four callbacks because four observable events ARE four concerns the\n// caller routinely cares about. The Sub's `wsUrl` lives on the Sub itself\n// (`WebSocketSubData`) — same id + different `wsUrl` is silent under the\n// reconcile pass, identical to `fromEventSource`'s `url` contract.\n//\n// - `onMessage(data, sub)` — required. The platform delivers `data` as\n// `string | Blob | ArrayBuffer` depending on `binaryType` and the\n// server's frame type. The factory does NOT bake in a parse contract:\n// the callback receives the raw `unknown` and decides parse + Msg\n// shape. Returns `M | null`; null drops the dispatch.\n//\n// - `onOpen(sub)` — optional. Connection handshake succeeded. Useful\n// for \"show 'connected' indicator\" Msgs. Omitting drops open events\n// silently.\n//\n// - `onError(event, sub)` — optional. WebSocket's `error` event fires\n// on transient drops AND permanent failures; like SSE, most consumers\n// want one Msg (\"connection-errored\") without folding both into\n// `onMessage`'s discriminant. Omitting drops error events silently.\n//\n// - `onClose(code, reason, sub)` — optional. The connection is gone.\n// `code` is the WebSocket close code (1000 = clean, 1006 = abnormal,\n// etc.). `reason` is the server-supplied string (often empty).\n// Omitting drops close events silently.\n//\n// Cleanup is `close(1000)` — the IANA-registered clean-shutdown code.\n// The factory intentionally does NOT bake in registry semantics (e.g.\n// the extension's `ctx.wsRegistry` so `interpret.send_ws` can reach the\n// live socket): that's consumer glue. A consumer that needs the live\n// socket for outbound sends wires it from inside the subscribe handler\n// or keeps a hand-written Sub alongside this factory.\n// ---------------------------------------------------------------------------\n\nimport type { Sub } from \"../index\";\nimport type {\n MinimalCloseEvent,\n MinimalEvent,\n MinimalMessageEvent,\n MinimalWebSocketCtor,\n} from \"./platform\";\nimport { dispatchIfPresent, type SubscribeHandler } from \"./types\";\n\nexport type WebSocketSubData = { wsUrl: string };\n\ndeclare const WebSocket: MinimalWebSocketCtor;\n\nexport interface WebSocketFactoryOpts<S, M> {\n onMessage: (data: unknown, sub: S) => M | null;\n onOpen?: (sub: S) => M | null;\n onError?: (event: MinimalEvent, sub: S) => M | null;\n onClose?: (code: number, reason: string, sub: S) => M | null;\n}\n\nexport function fromWebSocket<S extends Sub & WebSocketSubData, M>(\n opts: WebSocketFactoryOpts<S, M>,\n): SubscribeHandler<S, M, unknown> {\n return (sub, _ctx, dispatch) => {\n const ws = new WebSocket(sub.wsUrl);\n\n ws.onmessage = (event: MinimalMessageEvent): void => {\n // The platform delivers `data` as `unknown` — the factory hands it\n // through untouched and the caller chooses the parse strategy.\n dispatchIfPresent(dispatch, opts.onMessage(event.data, sub));\n };\n\n const onOpen = opts.onOpen;\n if (onOpen) {\n ws.onopen = (): void => {\n dispatchIfPresent(dispatch, onOpen(sub));\n };\n }\n\n const onError = opts.onError;\n if (onError) {\n ws.onerror = (event: MinimalEvent): void => {\n dispatchIfPresent(dispatch, onError(event, sub));\n };\n }\n\n const onClose = opts.onClose;\n if (onClose) {\n ws.onclose = (event: MinimalCloseEvent): void => {\n dispatchIfPresent(dispatch, onClose(event.code, event.reason, sub));\n };\n }\n\n return () => {\n // Detach the lifecycle handlers BEFORE close() so the consumer's\n // `onClose` Msg isn't fired from a teardown the substrate initiated.\n // Mirrors fromEventSource's removeEventListener pass.\n ws.onmessage = null;\n ws.onopen = null;\n ws.onerror = null;\n ws.onclose = null;\n // 1000 = \"normal closure\" per RFC 6455 §7.4.1.\n ws.close(1000);\n };\n };\n}\n","// ---------------------------------------------------------------------------\n// defineManagedResource — a Model-gated resource with a MANDATORY release.\n//\n// foldkit's ManagedResource, ported. The recurring shape: a resource whose\n// lifetime is \"while the machine is in phase X, hold Y open; on leaving X,\n// tear Y down\" — a checkpoint store, a span, a lease, a pooled connection.\n// Rule 9 says that is a Sub the substrate reconciles, NOT a start/end Cmd\n// pair hand-driven across cells. Hand-driving leaks: every transition cell\n// has to remember to end-then-start, and one missed cell leaks the resource\n// forever (the checkpoint-namespace leak this battery was built to kill).\n//\n// The author writes three things — `{ name, acquire, release }` — and gets\n// back a `.sub(key)` to drop into `subscriptions(state)` plus a `.subscribe`\n// handler to drop into the machine's `subscribe` record. The substrate's\n// existing `reconcileSubs` (packages/tea/src/run.ts) does the rest:\n// - sub APPEARS in the desired set → handler runs → `acquire(key, ctx)`.\n// - sub DISAPPEARS (phase left) → cleanup runs → `release(handle)`.\n// - sub KEY changes (id changes) → old cleanup (release) + new acquire.\n// Same reconcile mechanism `fromTransport` rides for open/close — no new\n// kernel hook.\n//\n// THE ACQUIRE-AS-SUCCESS-VALUE DISCIPLINE\n// ----------------------------------------\n// `release` receives ONLY what `acquire` returned (the Handle). Nothing else\n// is in scope. So:\n// - An `acquire` that throws never produces a Handle, so `release` is never\n// called with a half-built resource — an interrupted acquire can't dangle.\n// - Whatever `release` needs to tear down must be captured INTO the Handle\n// by `acquire`. The Handle is the closure over the live resource.\n// `release` is NOT optional in the type. A managed resource without a teardown\n// is just a Cmd (Rule 5); the whole point of this battery is the guaranteed\n// teardown, so the type forces the author to write one.\n//\n// NEUTRAL MECHANISM — no policy. The battery knows `acquire`, `release`,\n// `name`, `key`. It does not know what a checkpoint, a span, or a socket is.\n// Could a reasonable machine want the opposite of any choice here? The only\n// choices are \"release is mandatory\" (yes — that's the contract) and \"release\n// runs on disappear OR key-change\" (yes — that's reconcile semantics, shared\n// with every other Sub). No Cloudflare, no audit domain.\n//\n// Strengthens invariant 4 (external lifecycle owned by the substrate — the\n// acquire/release pair rides reconcileSubs, never hand-driven by a cell) and\n// invariant 6 (no silent looseness — every live resource is a Sub in the\n// desired set, visible in the Sub topology, and an unroutable one throws).\n// ---------------------------------------------------------------------------\n\nimport type { DepKeyedSub, Dispose, Sub, SubId } from \"../index\";\nimport { structuralHash, subId } from \"../index\";\n\n/**\n * The Sub the battery builds. The consumer's `Sub` union includes\n * `ManagedResourceSub<TKey>` directly — the substrate reconciles by `id` and\n * `type` like any other Sub.\n */\nexport interface ManagedResourceSub<TKey> extends Sub<\"managed_resource\"> {\n /** The lifetime key. Stable while the resource should be held; a change\n * retires the old resource (release) and acquires a fresh one. */\n readonly key: TKey;\n}\n\n/**\n * A battery paired with a state-gate. Self-contained: it knows its `name`\n * (for the unique-name assert), how to derive its live Sub from a state\n * (`subFor`), how to handle its own `subscribe`, and whether it `owns` a given\n * Sub id.\n *\n * Generic in the consumer state `S`, the shared lifetime key `TKey`, and the\n * shared `Ctx`. `Handle` is the ONLY thing erased — each battery's handle type\n * differs (a checkpoint saver vs. a tool-call bridge) but never crosses this\n * interface, so a `combineManagedResources` array holds entries of\n * heterogeneous handle types with no cast. The sub type stays\n * `ManagedResourceSub<TKey>` end to end, so the substrate's narrowed Sub flows\n * straight in.\n */\nexport interface GatedManagedResource<S, TKey, Ctx> {\n /** The owning battery's `name`. The combinator asserts these are unique. */\n readonly name: string;\n\n /**\n * Derive this resource's live Sub for a given state: the battery's\n * `.sub(key)` when `when(state)` returns a key, or `null` when it returns\n * `null` (the resource should be torn down in that state).\n */\n readonly subFor: (state: S) => ManagedResourceSub<TKey> | null;\n\n /**\n * This battery's own `.subscribe`. The combinator routes an incoming\n * `managed_resource` Sub to the entry whose `owns(sub)` is true, then calls\n * this.\n */\n readonly subscribe: (\n sub: ManagedResourceSub<TKey>,\n ctx: Ctx,\n dispatch: (msg: never) => void,\n ) => Dispose;\n\n /**\n * Does this entry own the given Sub? `sub.id === subIdFor(sub.key)` encodes\n * the battery `name`, so a match is unambiguous.\n */\n readonly owns: (sub: ManagedResourceSub<TKey>) => boolean;\n}\n\n/**\n * What `combineManagedResources` returns: the single managed-resource\n * `subscribe` cell for the machine's `subscribe` record, and a `subs(state)`\n * builder for `subscriptions(state)`.\n */\nexport interface CombinedManagedResources<S, TKey, Ctx> {\n /**\n * Map every entry through its `when(state)`, drop the `null`s, and build the\n * live Subs. Spread into `subscriptions(state)`. The derived list and the\n * `subscribe` routing share one source — the entries — so they never drift.\n */\n readonly subs: (state: S) => ManagedResourceSub<TKey>[];\n\n /**\n * The SINGLE `subscribe.managed_resource` handler. Routes an incoming Sub to\n * the owning battery by matching `sub.id === battery.subIdFor(sub.key)` (the\n * id encodes the battery `name`). Throws loudly on an unrouted id — a wiring\n * bug, never a silent no-op resource.\n */\n readonly subscribe: (\n sub: ManagedResourceSub<TKey>,\n ctx: Ctx,\n dispatch: (msg: never) => void,\n ) => Dispose;\n}\n\nexport interface DefineManagedResourceOpts<TKey, Handle, Ctx> {\n /**\n * A short, stable name. The Sub id is `managed:${name}:${stringify(key)}`,\n * so two managed resources in one machine never collide and neither\n * collides with a `fromTransport` Sub keyed on the same identity.\n */\n readonly name: string;\n\n /**\n * Build the resource. Runs when the Sub appears in `subscriptions(state)`.\n * The returned Handle is the ONLY thing `release` receives — capture\n * everything teardown needs into it. If `acquire` throws, the substrate's\n * reconcile does NOT register the Sub and `release` is never called (no\n * dangling half-built resource).\n */\n readonly acquire: (key: TKey, ctx: Ctx) => Handle;\n\n /**\n * Tear the resource down. Runs when the Sub leaves the desired set (phase\n * exited) or when its key changes. MANDATORY — the guaranteed teardown is\n * the reason this battery exists.\n *\n * May be sync or async, and an async one is really awaited: the promise is\n * returned to the substrate, which tracks it and drains it inside `stop()`\n * (bounded by `run({ disposeTimeoutMs })`). So `await runtime.stop()` means\n * the release finished — the reading a host evicting an isolate acts on.\n * MID-RUN teardown (a phase exit, a key change) is still fire-and-forget\n * (Rule 2): the reconcile pass is synchronous by construction, so it starts\n * the release and moves on. A rejection is never a Msg — it reaches the\n * runtime's `onError` sink under `phase: \"sub-cleanup\"`.\n */\n readonly release: (handle: Handle) => void | Promise<void>;\n}\n\n/**\n * What the battery returns: a `.sub(key)` builder for `subscriptions`, the\n * `.subscribe` handler for the machine's `subscribe` record, a `.get(key)`\n * accessor so Cmd handlers can reach the live Handle while the resource is\n * held, and a `.subIdFor(key)` for tests.\n */\nexport interface ManagedResourceBattery<TKey, Handle, Ctx> {\n /**\n * Build the Sub for `subscriptions(state)`. The consumer calls this from\n * inside their `subscriptions` cell for the phases where the resource\n * should be live.\n */\n readonly sub: (key: TKey) => ManagedResourceSub<TKey>;\n\n /**\n * Pair this battery with a state-gate, producing a self-contained entry for\n * `combineManagedResources`. `when(state)` returns the resource's KEY when\n * it should be live in that state, or `null` when it should be torn down.\n *\n * Folding \"is it active?\" and \"what key?\" into ONE declaration at the\n * battery's own definition site is the point: a central `subscriptions(state)`\n * list can silently forget to gate a new phase, but a per-resource gate\n * travels with the resource. The combinator derives BOTH the active-sub list\n * and the subscribe routing from these entries, so the two can never disagree.\n *\n * Generic in the consumer's state `S` (each `.gated` call binds its own `S`).\n * The returned entry erases this battery's `Handle` — `combineManagedResources`\n * holds entries of heterogeneous handle types with no cast — while keeping\n * `TKey` and `Ctx` so the substrate's narrowed `ManagedResourceSub<TKey>`\n * flows straight into the entry's `subscribe`.\n */\n readonly gated: <S>(\n when: (state: S) => TKey | null,\n ) => GatedManagedResource<S, TKey, Ctx>;\n\n /**\n * Dep-keyed entry for the machine's `subs` array. `when(state)` returns the\n * resource's KEY when it should be held, or `null` when it should be torn\n * down — the exact gate `.gated` takes, now producing a `DepKeyedSub` the\n * kernel folds directly instead of an entry the `combineManagedResources`\n * router multiplexes.\n *\n * This is the simplification the dep-keyed kernel unlocks: two managed\n * resources used to COLLIDE on the single `subscribe.managed_resource` key\n * (both subs share `type: \"managed_resource\"`), forcing\n * `combineManagedResources` to route by SubId. A dep-keyed Sub has its OWN\n * reconcile slot (its array position), so there is no shared `type` to route\n * — each resource is one `subs` entry, no combinator, no router. `acquire` /\n * `release` still ride the kernel's dispose-on-change / dispose-on-null\n * reconcile, now via the dep-keyed source's `Dispose` instead of the\n * `subscribe` cell's cleanup.\n */\n readonly depKeyed: <S>(\n when: (state: S) => TKey | null,\n ) => DepKeyedSub<S, never, Ctx>;\n\n /**\n * The `subscribe.managed_resource` handler. Drop straight into the\n * machine's `subscribe` record. Runs `acquire` on install, holds the\n * Handle in the battery's handle table, and returns a cleanup that runs\n * `release` (and forgets the Handle) on reconcile-out.\n */\n readonly subscribe: (\n sub: ManagedResourceSub<TKey>,\n ctx: Ctx,\n // The managed resource dispatches no Msgs — acquire/release are pure\n // lifecycle. `dispatch` is accepted to match the substrate's\n // `subscribe[type]` shape and is intentionally unused.\n dispatch: (msg: never) => void,\n ) => Dispose;\n\n /**\n * Accessor for the live Handle, keyed on `key`. Returns `undefined` when\n * the resource is not currently held (no active sub for that key) — the\n * caller decides whether that is an error. A Cmd handler wires its\n * `ctx.getX(key)` through this, so the handler reads the SAME owner the\n * reconciler holds, never a hand-built duplicate.\n */\n readonly get: (key: TKey) => Handle | undefined;\n\n /** Pre-derived subId for `(key)` — handy for tests. */\n readonly subIdFor: (key: TKey) => SubId;\n}\n\n/**\n * Build the battery. One call per managed resource at the machine's host\n * file.\n *\n * @example\n * const checkpointStore = defineManagedResource<RunId, CheckpointSaver, Ctx>({\n * name: \"checkpoint\",\n * acquire: (runId, ctx) =>\n * new CheckpointSaver({ storage: ctx.storage, bucket: ctx.bucket, runId }),\n * release: (saver) => saver.clear(),\n * });\n *\n * // In the machine:\n * subscriptions: (state) =>\n * state.type === \"running\" ? [checkpointStore.sub(state.runId)] : [],\n * subscribe: { managed_resource: checkpointStore.subscribe },\n *\n * // In interpret (read the live handle the reconciler holds):\n * start_graph: async (cmd, ctx) => {\n * const saver = checkpointStore.get(cmd.runId); // same owner, no rebuild\n * ...\n * }\n */\nexport function defineManagedResource<TKey, Handle, Ctx>(\n opts: DefineManagedResourceOpts<TKey, Handle, Ctx>,\n): ManagedResourceBattery<TKey, Handle, Ctx> {\n // Handle table — one live resource per key. Lives in this closure for the\n // lifetime of the battery (the machine's lifetime). Interpret-local; never\n // crosses into reducer state. Same shape as `fromTransport`'s `live` map.\n const held = new Map<string, Handle>();\n\n // Single-sourced on the kernel's `structuralHash`: `\"1\"` (string) and `1`\n // (number) render distinctly, so a key collision can't return/release the\n // wrong held handle; non-JSON keys throw loudly instead of silently returning\n // undefined.\n const keyString = (k: TKey): string => structuralHash(k);\n\n const subIdFor = (key: TKey): SubId =>\n subId(`managed:${opts.name}:${keyString(key)}`);\n\n const subBuilder = (key: TKey): ManagedResourceSub<TKey> => ({\n id: subIdFor(key),\n type: \"managed_resource\",\n key,\n });\n\n const subscribeHandler = (\n sub: ManagedResourceSub<TKey>,\n ctx: Ctx,\n _dispatch: (msg: never) => void,\n ): Dispose => {\n // Acquire. A throw here propagates out of reconcileSubs (the substrate\n // records it and re-throws after the diff pass) and the Sub is NOT\n // registered — so no cleanup runs and `held` stays clean. The\n // acquire-as-success-value discipline: no Handle, no release.\n const handle = opts.acquire(sub.key, ctx);\n const k = keyString(sub.key);\n held.set(k, handle);\n\n // Cleanup runs when the substrate's reconcileSubs leaves the phase OR\n // the key changes. Forget the Handle first so a concurrent `get` after\n // teardown begins sees `undefined`, then release.\n //\n // An async `release` is RETURNED, not swallowed: the substrate tracks the\n // promise and `stop()` awaits it (bounded), so `await runtime.stop()` means\n // the release finished. Attaching a `.catch` here and returning `void` was\n // the defect — a host doing `await stop(); env.evict()` dropped the isolate\n // mid-release, which is the leak this battery exists to prevent, moved to\n // shutdown. A rejection now reaches the runtime's `onError` under\n // `phase: \"sub-cleanup\"` instead of a `console.warn` nobody configured.\n return () => {\n held.delete(k);\n return opts.release(handle);\n };\n };\n\n return {\n sub: subBuilder,\n\n // Pair the battery with a state-gate. `TKey` / `Handle` / `Ctx` are in\n // scope here, so `subFor` builds the fully-typed sub and `subscribe`\n // forwards straight to the battery's own typed `subscribe`. The entry\n // erases ONLY `Handle` for the combinator (`TKey` and `Ctx` stay), so the\n // substrate's narrowed `ManagedResourceSub<TKey>` flows in with no cast.\n gated: <S>(\n when: (state: S) => TKey | null,\n ): GatedManagedResource<S, TKey, Ctx> => ({\n name: opts.name,\n owns: (sub) => sub.id === subIdFor(sub.key),\n subFor: (state) => {\n const key = when(state);\n return key === null ? null : subBuilder(key);\n },\n subscribe: subscribeHandler,\n }),\n\n // Dep-keyed entry. `when` is the gate (the deps = the KEY); the source runs\n // the SAME `acquire` → hold → `release`-on-dispose lifecycle the `subscribe`\n // cell does, now driven by the kernel's dep-keyed reconcile. The Msg type is\n // `never` (a managed resource dispatches nothing); the ignored `_dispatch`\n // matches the source signature.\n depKeyed: <S>(\n when: (state: S) => TKey | null,\n ): DepKeyedSub<S, never, Ctx> => ({\n deps: when,\n source: (state, _dispatch, ctx) => {\n const key = when(state);\n if (key === null) return () => {};\n return subscribeHandler(subBuilder(key), ctx, () => {});\n },\n }),\n\n subscribe: subscribeHandler,\n\n get: (key) => held.get(keyString(key)),\n\n subIdFor,\n };\n}\n\n// ---------------------------------------------------------------------------\n// combineManagedResources — fold N gated batteries into ONE subscribe cell + a\n// derived active-sub list.\n//\n// Two managed resources colliding on the single `subscribe.managed_resource`\n// key forced the machine to hand-roll a router (`if (sub.id === X.subIdFor(...))`).\n// A third copy-pastes another `if`. That router is glue that belongs in the\n// substrate, not the machine. AND the resources' active-phase gating lived in\n// a central `subscriptions(state)` list a new phase could silently forget.\n//\n// This combinator closes both gaps. Each entry carries its OWN gate at its\n// definition site (`battery.gated((s) => active(s) ? key : null)`), and the\n// combinator derives BOTH the active-sub list (`subs`) and the routing\n// (`subscribe`) from the same entries — so they can never disagree, and a new\n// phase that the gate doesn't cover simply produces no sub (no silent leak of\n// the central-list-forgot kind).\n//\n// NEUTRAL MECHANISM — no domain knowledge. The combinator knows `name` (for\n// the unique assert), `owns` (for routing), `subFor` (for the list), and\n// `subscribe` (to forward). It does not know what a checkpoint, a bridge, or a\n// Cloudflare anything is.\n//\n// Strengthens invariant 4 (external lifecycle owned by the substrate — both\n// the list and the routing are derived, never hand-driven) and invariant 6 (no\n// silent looseness — ONE managed-resource cell, and an unrouted id throws\n// loudly instead of silently installing a no-op).\n// ---------------------------------------------------------------------------\nexport function combineManagedResources<S, TKey, Ctx>(\n entries: readonly GatedManagedResource<S, TKey, Ctx>[],\n): CombinedManagedResources<S, TKey, Ctx> {\n // Assert unique names at construction — two batteries with the same `name`\n // collide on Sub id (`managed:${name}:${key}`), so the router could not tell\n // them apart. Throw immediately (mirrors foldkit's `combineSubscriptions` on\n // a duplicate key) rather than mis-route at runtime.\n const seen = new Set<string>();\n for (const entry of entries) {\n if (seen.has(entry.name)) {\n throw new Error(\n `[combineManagedResources] duplicate managed-resource name \"${entry.name}\" — names must be unique (the Sub id encodes the name; two batteries sharing it collide)`,\n );\n }\n seen.add(entry.name);\n }\n\n return {\n subs: (state) => {\n const live: ManagedResourceSub<TKey>[] = [];\n for (const entry of entries) {\n const sub = entry.subFor(state);\n if (sub !== null) live.push(sub);\n }\n return live;\n },\n\n subscribe: (sub, ctx, dispatch) => {\n const owner = entries.find((entry) => entry.owns(sub));\n if (owner === undefined) {\n // No battery owns this managed-resource SubId — a wiring bug. Surface\n // loudly rather than silently install a no-op resource (invariant 6).\n throw new Error(\n `[combineManagedResources] no battery for sub id ${sub.id}`,\n );\n }\n return owner.subscribe(sub, ctx, dispatch);\n },\n };\n}\n","// ---------------------------------------------------------------------------\n// fromTransport — the SEAM battery.\n//\n// At every seam between two TEA-shaped processes (a DO ↔ its hands container,\n// a SW background ↔ a content script, a parent ↔ a child runtime), there is\n// the SAME hand-rolled boilerplate:\n//\n// 1. The inbound message stream — a Sub keyed on a phase, opened when the\n// machine enters the phase, closed when the machine leaves it.\n// 2. A transport-close detector — when the underlying channel drops, a\n// single Msg is dispatched (`*_lost`). NOT a Sub the consumer\n// re-publishes; the lifetime IS the inbound Sub's lifetime.\n// 3. The outbound side — a Cmd whose handler hands a value to the live\n// transport, looked up by an interpret-local handle table keyed on the\n// phase's run-identity.\n//\n// Every hand-rolled seam (`hands-inbound.ts`, `forwardOutbound`, the\n// `client_disconnected` Msg arm, the WS handle map) is the same three\n// pieces with different vocabularies. The battery bakes those three in.\n//\n// What the author writes:\n//\n// 1. The vocabulary (Inbound / Outbound types).\n// 2. ONE call: `fromTransport({ name, openTransport, parseInbound, ... })`.\n//\n// What the author NEVER writes again:\n//\n// - The `Sub` shape, the `subscribe` cell, the `cleanup` Promise dance.\n// - The transport-close → `*_lost` Msg wiring.\n// - The \"find the live socket from inside an interpret handler\" lookup.\n// - The keepalive / heartbeat / sequence-number ceremony (none of those\n// belong in the machine — that's Rule 9: lifetime-bound resources are\n// Subs, not state-machine cells).\n//\n// Host-pluggable: the battery takes a `TransportFactory`, not a concrete\n// WebSocket. A Durable Object passes a workerd `WebSocket` adapter; a worker\n// thread can pass a `MessagePort` adapter; an in-process test can pass a\n// stub. Same battery; three different hosts.\n//\n// Distinct from `fromWebSocket` / `fromReconnectingWebSocket` in this same\n// subpath, which own a CONCRETE `WebSocket` and are inbound-only. This battery\n// is transport-agnostic and duplex: it also owns the outbound handle table, so\n// a Cmd handler can reach the live channel without a hand-written registry.\n//\n// Strengthens invariant 4 (external events / observation enter as Subs;\n// lifecycle owned by the substrate — the inbound stream, the close\n// detection, AND the outbound handle are all owned by the battery's Sub\n// lifecycle, not hand-driven by the consumer's cells).\n// ---------------------------------------------------------------------------\n\nimport type { DepKeyedSub, Sub, SubId } from \"../index\";\nimport { structuralHash, subId } from \"../index\";\n\n/**\n * Duplex transport. The host (DO, worker thread, in-process bus) constructs\n * one from whatever the platform offers. `fromTransport` does not know what\n * a WebSocket is — it knows `send`, `onMessage`, `onClose`.\n */\nexport interface Transport {\n /** Send a string frame. Best-effort; failures are logged at the boundary. */\n send(data: string): void;\n /**\n * Subscribe to inbound frames. Returns a cleanup that removes the listener.\n * The battery wires this in its Sub's subscribe handler; cleanup runs when\n * the phase exits (the substrate's reconcileSubs).\n */\n onMessage(listener: (data: string) => void): () => void;\n /**\n * Subscribe to a one-shot close. Returns a cleanup that removes the\n * listener. The battery dispatches `lostMsg` on close.\n */\n onClose(listener: () => void): () => void;\n /** Close the underlying channel cleanly. Called by the Sub cleanup. */\n close(): void;\n}\n\n/**\n * Factory the consumer wires to a platform-specific transport. The battery\n * passes the runtime-derived key (`runId`, or whatever identity is on the\n * phase) so per-run transports can be addressed.\n */\nexport type TransportFactory<K> = (key: K) => Transport;\n\n/**\n * The Sub the battery builds. The consumer's `Sub` union includes\n * `TransportSub<TKey>` directly — no wrapping needed, the substrate\n * reconciles by `id` and `type` like any other Sub.\n */\nexport interface TransportSub<TKey> extends Sub<\"transport\"> {\n /** The phase / run identity. Stable for the lifetime of the seam. */\n readonly key: TKey;\n}\n\nexport interface FromTransportOpts<TKey, Inbound, Outbound, M, Ctx> {\n /**\n * A short, stable name for this seam. The Sub id is derived from\n * `${name}:${stringify(key)}` so two seams in the same machine (e.g.\n * `hands` + `worker`) never collide.\n */\n readonly name: string;\n\n /**\n * Build the transport. The battery owns the lifetime; the factory is the\n * platform-specific constructor (`new WebSocket(...)` adapter on a DO,\n * `MessagePort` adapter in a worker, in-memory stub in tests).\n */\n readonly openTransport: (key: TKey, ctx: Ctx) => Transport;\n\n /**\n * Parse one inbound frame. The battery hands `unknown` through (boundary\n * parse is the consumer's responsibility per invariant 8). Returning\n * `null` drops the frame silently — useful for frames the seam doesn't\n * recognize at this layer (e.g. transport-level keepalive).\n */\n readonly parseInbound: (raw: string, key: TKey) => Inbound | null;\n\n /**\n * Map a parsed inbound to a domain Msg. Returning `null` drops the\n * dispatch (e.g. a transport-level pong that doesn't change domain state).\n */\n readonly onInbound: (inbound: Inbound, key: TKey) => M | null;\n\n /**\n * The Msg dispatched when the transport closes (peer gone, network\n * dropped, normal close). ONE Msg arm, not a Sub, not a heartbeat-derived\n * deadline — the transport's own close signal is the truth.\n */\n readonly lostMsg: (key: TKey) => M;\n\n /**\n * Serialize an outbound. Called by the Cmd handler the battery wires.\n * Pure data → string at the seam boundary.\n */\n readonly serializeOutbound: (outbound: Outbound) => string;\n}\n\n/**\n * What the battery returns: a Sub builder (for `subscriptions`), the Sub's\n * `subscribe` handler (for `subscribe.transport`), and a `send(key, outbound)`\n * helper the consumer's Cmd handler calls. Plus a `subIdFor` factory the\n * consumer can use when keying related Subs (e.g. a deadline) to the same seam\n * identity.\n */\nexport interface TransportBattery<TKey, Outbound, M, Ctx> {\n /**\n * Build the Sub for `subscriptions(state)`. The consumer calls this from\n * inside their `subscriptions` cell when the seam should be open.\n */\n readonly sub: (key: TKey) => TransportSub<TKey>;\n\n /**\n * The `subscribe.transport` handler. Drop straight into the machine's\n * `subscribe` record — the battery wires inbound + close in one shot.\n */\n readonly subscribe: (\n sub: TransportSub<TKey>,\n ctx: Ctx,\n dispatch: (msg: M) => void,\n ) => () => void;\n\n /**\n * Outbound send. The Cmd handler calls this; the battery's handle table\n * (built when subscribe ran) routes to the live transport. If the seam\n * is closed (no active sub), the send is dropped honestly (logged, not\n * thrown — the reducer already moved past caring, same shape as Rule 2\n * fire-and-forget Cmds).\n */\n readonly send: (key: TKey, outbound: Outbound) => void;\n\n /** Pre-derived subId for `(key)` — handy for deadline Subs keyed to the same seam. */\n readonly subIdFor: (key: TKey) => SubId;\n\n /**\n * Pair this seam with a state-gate, producing a dep-keyed Sub for the\n * machine's `subs` array. `when(state)` returns the seam's KEY when it\n * should be open, or `null` when it should be torn down. The kernel derives\n * the id from `structuralHash(key)` and reconciles the seam's lifetime — so\n * the author neither lists this Sub in a central `subscriptions(state)` nor\n * writes its id. Replaces `subscriptions: (s) => active(s) ? [hands.sub(...)] : []`\n * + `subscribe: { transport: hands.subscribe }` with one `subs` entry.\n *\n * The `source` closes over the battery's own `subscribe` (open inbound +\n * close, wire the handle table) using the same `ctx` the kernel passes every\n * Sub source.\n */\n readonly depKeyed: <S>(\n when: (state: S) => TKey | null,\n ) => DepKeyedSub<S, M, Ctx>;\n}\n\n/**\n * Build the battery. One call per seam at the machine's host file.\n *\n * @example\n * const handsSeam = fromTransport<RunId, HandsInbound, HandsOutbound, Msg, Ctx>({\n * name: \"hands\",\n * openTransport: (runId, ctx) => ctx.openHandsWs(runId),\n * parseInbound: (raw) => parseHandsInbound(JSON.parse(raw)),\n * onInbound: (inbound, runId) => ({ type: \"hands_said\", runId, inbound }),\n * lostMsg: (runId) => ({ type: \"hands_lost\", runId }),\n * serializeOutbound: (out) => JSON.stringify(out),\n * });\n *\n * // In the machine:\n * subscriptions: (state) => state.type === \"auditing\" ? [handsSeam.sub(state.runId)] : [],\n * subscribe: { transport: handsSeam.subscribe },\n *\n * // In interpret:\n * send_hands: async (cmd) => { handsSeam.send(cmd.runId, cmd.outbound); },\n */\nexport function fromTransport<TKey, Inbound, Outbound, M, Ctx>(\n opts: FromTransportOpts<TKey, Inbound, Outbound, M, Ctx>,\n): TransportBattery<TKey, Outbound, M, Ctx> {\n // Handle table — one live transport per key. Lives in this closure for the\n // lifetime of the battery (i.e. the machine's lifetime). Interpret-local;\n // never crosses into reducer state. The same shape as a brain's\n // `pending: Map<callId, deferred>`: imperative handles in a Map keyed on\n // run-identity, not on durable state.\n const live = new Map<string, Transport>();\n\n // Single-sourced on the kernel's `structuralHash`: `\"1\"` (string) and `1`\n // (number) render distinctly, so a key collision can't mis-route an outbound\n // send or overwrite the wrong live transport; non-JSON keys throw loudly.\n const keyString = (k: TKey): string => structuralHash(k);\n\n const subIdFor = (key: TKey): SubId =>\n subId(`${opts.name}:${keyString(key)}`);\n\n const sub = (key: TKey): TransportSub<TKey> => ({\n id: subIdFor(key),\n type: \"transport\",\n key,\n });\n\n const subscribe: TransportBattery<TKey, Outbound, M, Ctx>[\"subscribe\"] = (\n s,\n ctx,\n dispatch,\n ) => {\n // Open the transport. The battery owns the lifetime.\n const t = opts.openTransport(s.key, ctx);\n const k = keyString(s.key);\n\n // ACQUIRE-AS-SUCCESS-VALUE: wire EVERYTHING first, publish to the handle\n // table last. An adapter over an already-CLOSING socket throws while\n // wiring; when `live.set` ran first, that left the transport in the table\n // with NO sub registered — so no cleanup ever ran, `send` wrote into a\n // half-wired seam with no inbound listener, and every subsequent reconcile\n // opened another one: an unbounded leak from one recoverable throw. On the\n // way out we undo exactly what we did (drop the listeners, close the\n // transport) and rethrow, so the substrate records the failure and the\n // battery holds nothing. Same discipline `defineManagedResource` states in\n // its own header — no Handle, no release.\n let offMessage: (() => void) | undefined;\n let offClose: (() => void) | undefined;\n try {\n // Wire inbound. Parse boundary lives here per invariant 8.\n offMessage = t.onMessage((raw) => {\n const parsed = opts.parseInbound(raw, s.key);\n if (parsed === null) return;\n const msg = opts.onInbound(parsed, s.key);\n if (msg === null) return;\n dispatch(msg);\n });\n\n // Wire close. ONE Msg arm — `*_lost`. No heartbeat, no sequence\n // numbers, no \"is the peer still alive\" derivation: the transport's\n // own close IS the liveness signal.\n offClose = t.onClose(() => {\n dispatch(opts.lostMsg(s.key));\n });\n } catch (err) {\n offMessage?.();\n offClose?.();\n try {\n t.close();\n } catch (closeErr) {\n // Boundary log: the wiring failure is the fact worth surfacing, and it\n // is about to be rethrown. A close that also fails must not replace it.\n console.warn(\n `[fromTransport:${opts.name}] close during failed wiring failed`,\n closeErr,\n );\n }\n throw err;\n }\n\n live.set(k, t);\n\n // Cleanup runs when the substrate's reconcileSubs leaves the phase.\n return () => {\n offMessage?.();\n offClose?.();\n live.delete(k);\n try {\n t.close();\n } catch (err) {\n // Boundary log: cleanup-time close failures are fire-and-forget\n // (Rule 2). The reducer is already past caring.\n console.warn(`[fromTransport:${opts.name}] close failed`, err);\n }\n };\n };\n\n return {\n sub,\n subscribe,\n\n send: (key, outbound) => {\n const t = live.get(keyString(key));\n if (t === undefined) {\n // Honest drop. The reducer's Cmd burst can race a `*_lost`\n // transition; the Cmd is fire-and-forget at that point.\n console.warn(\n `[fromTransport:${opts.name}] send dropped — no live transport for key`,\n keyString(key),\n );\n return;\n }\n t.send(opts.serializeOutbound(outbound));\n },\n\n subIdFor,\n\n // Dep-keyed entry: `when` is the gate (the deps slice → the seam key);\n // `source` re-runs `when(state)` to recover the typed key and opens the seam\n // via the battery's own `subscribe`. The kernel only calls `source` when\n // `deps(state)` was non-null, so `when` is non-null here; the key type never\n // escapes the entry (cast-free existential).\n depKeyed: <S>(when: (state: S) => TKey | null): DepKeyedSub<S, M, Ctx> => ({\n deps: when,\n source: (state, dispatch, ctx) => {\n const key = when(state);\n if (key === null) return () => {};\n return subscribe(sub(key), ctx, dispatch);\n },\n }),\n };\n}\n"]}
@@ -1,4 +1,4 @@
1
- import { createResilientCall, liftResilience } from './chunk-B5RAZVXW.js';
1
+ import { createResilientCall, liftResilience } from './chunk-W2S4HK7J.js';
2
2
  import { MsgType } from './chunk-JELSZN25.js';
3
3
  import { describeError } from './chunk-G5E74IJB.js';
4
4
 
@@ -84,5 +84,5 @@ function liftLlmCall(state, result) {
84
84
  }
85
85
 
86
86
  export { createLlmCall, liftLlmCall };
87
- //# sourceMappingURL=chunk-FCKP6N7Y.js.map
88
- //# sourceMappingURL=chunk-FCKP6N7Y.js.map
87
+ //# sourceMappingURL=chunk-T6HDAPMR.js.map
88
+ //# sourceMappingURL=chunk-T6HDAPMR.js.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/llm-call/index.ts"],"names":["msg"],"mappings":";;;;;AA+UO,SAAS,aAAA,CAId,MAAA,EAAkC,GAAA,GAAoB,IAAA,CAAK,MAAA,EAAQ;AAQnE,EAAA,MAAM,eAAA,GAAmC;AAAA,IACvC,GAAI,OAAO,KAAA,KAAU,MAAA,GAAY,EAAC,GAAI,EAAE,KAAA,EAAO,MAAA,CAAO,KAAA;AAAM,GAC9D;AACA,EAAA,MAAM,EAAA,GAAK,mBAAA,CAA6C,eAAA,EAAiB,GAAG,CAAA;AAM5E,EAAA,SAAS,IAAA,GAAc;AACrB,IAAA,OAAO,GAAG,IAAA,EAAK;AAAA,EACjB;AAQA,EAAA,SAAS,QACP,CAAA,EACA,KAAA,EACA,EAAA,EACA,GAAA,GAAc,MAAM,OAAA,EACuB;AAC3C,IAAA,OAAO,EAAA,CAAG,OAAA,CAAQ,CAAA,EAAG,GAAA,EAAK,OAAO,EAAE,CAAA;AAAA,EACrC;AAGA,EAAA,SAAS,OAAA,CACP,CAAA,EACA,GAAA,EACA,GAAA,EAC2C;AAC3C,IAAA,OAAO,EAAA,CAAG,OAAA,CAAQ,CAAA,EAAG,GAAA,EAAK,GAAG,CAAA;AAAA,EAC/B;AAOA,EAAA,SAAS,IAAA,CACP,CAAA,EACA,GAAA,EACA,GAAA,EAC2C;AAC3C,IAAA,OAAO,EAAA,CAAG,IAAA,CAAK,CAAA,EAAG,GAAA,EAAK,GAAG,CAAA;AAAA,EAC5B;AAGA,EAAA,SAAS,OAAA,CACP,GACA,GAAA,EAC2C;AAC3C,IAAA,OAAO,EAAA,CAAG,OAAA,CAAQ,CAAA,EAAG,GAAG,CAAA;AAAA,EAC1B;AAGA,EAAA,SAAS,KAAK,CAAA,EAAkC;AAC9C,IAAA,OAAO,EAAA,CAAG,KAAK,CAAC,CAAA;AAAA,EAClB;AAiBA,EAAA,eAAe,UAAU,KAAA,EAAyC;AAChE,IAAA,MAAM,KAAA,GAAQ,MAAA,CAAO,KAAA,CAAM,KAAA,CAAM,KAAK,CAAA;AACtC,IAAA,MAAM,QAAA,GAAW,OAAO,YAAA,GACpB,MAAM,OAAO,YAAA,CAAa,KAAK,IAC/B,EAAC;AACL,IAAA,MAAM,MAAA,GAAS,MAAA,CAAO,OAAA,CAAQ,KAAA,CAAM,OAAO,CAAA;AAC3C,IAAA,MAAM,UAAA,GAAa,KAAA,CAAM,oBAAA,CAAqB,MAAM,CAAA;AACpD,IAAA,MAAM,GAAA,GAAM,MAAM,UAAA,CAAW,MAAA,CAAO,QAAQ,CAAA;AAG5C,IAAA,MAAM,MAAA,GAAS,MAAA,CAAO,KAAA,CAAM,GAAG,CAAA;AAC/B,IAAA,OAAO,EAAE,GAAA,EAAK,KAAA,CAAM,SAAS,OAAA,EAAS,KAAA,CAAM,SAAS,MAAA,EAAO;AAAA,EAC9D;AAsCA,EAAA,MAAM,mBAAA,GAAsB,GAAG,QAAA,CAAS;AAAA,IACtC,GAAA,EAAK,CAAC,KAAA,KAAU,SAAA,CAAU,KAAK;AAAA,GAChC,CAAA,CAAE,aAAA;AAOH,EAAA,eAAe,SACb,GAAA,EAC8C;AAM9C,IAAA,MAAM,MAAA,GAAS,MAAM,mBAAA,CAAoB,GAAA,EAAK,EAAE,CAAA;AAChD,IAAA,IAAI,MAAA,CAAO,IAAA,KAAS,OAAA,CAAQ,WAAA,EAAa;AAOvC,MAAA,OAAO,EAAE,GAAG,MAAA,EAAQ,MAAA,EAAQ,EAAE,GAAG,MAAA,CAAO,MAAA,EAAQ,GAAA,EAAK,GAAA,CAAI,GAAA,EAAI,EAAE;AAAA,IACjE;AAIA,IAAA,MAAM,WAAoB,MAAA,CAAO,KAAA;AACjC,IAAA,MAAM,GAAA,GAAiB;AAAA,MACrB,KAAK,GAAA,CAAI,GAAA;AAAA,MACT,OAAA,EAAS,IAAI,KAAA,CAAM,OAAA;AAAA,MACnB,MAAA,EAAQ,cAAc,QAAQ,CAAA;AAAA,MAC9B,KAAA,EAAO;AAAA,KACT;AACA,IAAA,OAAO;AAAA,MACL,MAAM,OAAA,CAAQ,YAAA;AAAA,MACd,KAAK,MAAA,CAAO,GAAA;AAAA,MACZ,KAAA,EAAO,GAAA;AAAA,MACP,IAAI,MAAA,CAAO;AAAA,KACb;AAAA,EACF;AAmCA,EAAA,SAAS,SAAY,KAAA,EAA+B;AAClD,IAAA,IAAI,UAAU,MAAA,EAAW;AAEvB,MAAA,OAAO,EAAE,aAAA,EAAe,CAAC,GAAA,KAAsB,QAAA,CAAS,GAAG,CAAA,EAAE;AAAA,IAC/D;AAGA,IAAA,OAAO;AAAA,MACL,aAAA,EAAe,CACb,GAAA,EACA,GAAA,KAIS;AACT,QAAA,MAAM,SAAS,YAAY;AACzB,UAAA,MAAM,MAAA,GAAS,MAAM,QAAA,CAAS,GAAG,CAAA;AACjC,UAAA,IAAI,MAAA,CAAO,IAAA,KAAS,OAAA,CAAQ,WAAA,EAAa;AACvC,YAAA,MAAMA,IAAAA,GAAM,KAAA,CAAM,IAAA,CAAK,MAAA,CAAO,MAAM,CAAA;AACpC,YAAA,IAAIA,IAAAA,KAAQ,MAAA,EAAW,MAAM,GAAA,CAAI,SAASA,IAAG,CAAA;AAC7C,YAAA;AAAA,UACF;AACA,UAAA,MAAM,GAAA,GAAM,KAAA,CAAM,KAAA,CAAM,MAAA,CAAO,KAAK,CAAA;AACpC,UAAA,IAAI,GAAA,KAAQ,MAAA,EAAW,MAAM,GAAA,CAAI,SAAS,GAAG,CAAA;AAAA,QAC/C,CAAA,GAAG;AACH,QAAA,GAAA,CAAI,UAAU,KAAK,CAAA;AAAA,MACrB;AAAA,KACF;AAAA,EACF;AAEA,EAAA,OAAO,EAAE,MAAM,OAAA,EAAS,OAAA,EAAS,MAAM,OAAA,EAAS,IAAA,EAAM,UAAU,SAAA,EAAU;AAC5E;AAOO,SAAS,WAAA,CAMd,OACA,MAAA,EAC4B;AAC5B,EAAA,OAAO,cAAA,CAAe,OAAO,MAAM,CAAA;AACrC","file":"chunk-FCKP6N7Y.js","sourcesContent":["/**\n * @packageDocumentation\n * @demlik/tea/llm-call — `resilient-call` + structured-output parse + a typed\n * failure variant, around a purpose-discriminated LLM invocation.\n *\n * This is the ~60-line `call_llm` handler from the audit-agent seed\n * (`interpret.ts` / `effects.ts`) collapsed into the uniform L2 knob: a config\n * object, the resilient-call slice it inherits, and one interpret handler that\n * assembles messages, binds the structured-output schema for the call's\n * `purpose`, invokes the model (retry composed from `../resilient-call`, NOT\n * reinvented), parses the output, and RETURNS the enriched resilient settle Msg\n * (`resilient_ok` / `resilient_err`) so it re-enters the host reducer and drives\n * the inherited succeed/fail → backoff → onTimer loop.\n *\n * ## What it adds over `../resilient-call`\n *\n * - **Two DI ports** (the seed's two seams):\n * 1. `model: (modelId) => LLM` — the model factory. Tests pass a fake so\n * the handler runs end-to-end without touching a real provider; the\n * production worker wires `createChatModel(env, …)`.\n * 2. `loadMessages: Loader` — the SDK / message loader. The seed lazy-\n * imports `@langchain/core/messages` (a top-level `import type` blows\n * up the workers test runner); the loader keeps that import lazy and\n * stubbable. It produces the `BaseMessage[]` for a given call.\n * - **Structured-output parse** — each `purpose` maps to a `Schema` in\n * `config.schemas`. The handler binds `model.withStructuredOutput(schema)`\n * so the invoke resolves to a typed object; a `Schema.parse` that throws\n * becomes a `resilient_err` carrying an `LlmErr` (a parse failure is a\n * failure, not a stall — \"errors are data\"). `withStructuredOutput` is the\n * seed's brain-only path; the fake model in tests returns the typed object\n * directly.\n * - **A typed failure variant** — every failure (model throw, timeout-via-\n * retry-exhaustion, schema parse) is surfaced as the `LlmErr` carried on the\n * enriched `resilient_err` settle Msg, tagged with the `purpose` so the\n * consumer's reducer routes per-stage. The seed dispatched\n * `llm_failed{purpose}`; the consumer rebuilds that shape in its reducer arm\n * from `m.error: LlmErr`.\n *\n * ## Inheriting resilient-call by composition (not reinvention)\n *\n * The retry / backoff lives in `../resilient-call`: `createLlmCall` builds a\n * `createResilientCall` knob over the model-invoke port and DELEGATES `init`,\n * `attempt`, `succeed`, `fail`, `onTimer`, `subs` to it verbatim — the slice is\n * literally resilient-call's slice (the spec's \"retry lives here\"). There is no\n * second backoff implementation here. The handler runs the invoke through the\n * resilient-call handler so a transient model failure backs off exactly as a\n * resilient HTTP call would; the `purpose`-branch + structured parse wrap the\n * port the resilient handler drives.\n *\n * ## The two non-negotiables (canon, inherited)\n *\n * - **Durable** — the slice is resilient-call's plain-data slice (a Model\n * field). Input carried on the `resilient_run` Cmd is the plain\n * `LlmCall` request (purpose + modelId + payload) — no closures.\n * - **Replayable** — every transition is a resilient-call verb; the handler\n * is the only impurity, and the one clock read is `Date.now()` at the\n * effect boundary (inside the handler), exactly as the seed stamped its\n * `llm_responded` / `llm_failed` Msgs.\n *\n * ## Typical wiring\n *\n * const llm = createLlmCall<MyPurpose, MyOutputs>({\n * model: (id) => createChatModel(env, id),\n * schemas: { plan: planSchema, report: reportSchema },\n * retry: defaultRetryPolicy,\n * loadMessages: defaultMessagesLoader,\n * });\n *\n * // in the machine:\n * init: () => [{ resilience: llm.init() }, []],\n * update: {\n * call_llm: (s, m) => lift(s, llm.attempt(s.resilience, m.input, m.at)),\n * // `resilient_ok` / `resilient_err` RE-ENTER from `handlers` (the settle\n * // Msg the interpret handler returns). Run the inherited verb FIRST so the\n * // succeed/fail → backoff → onTimer loop advances, THEN fold the enriched\n * // payload (`m.result: LlmOk` / `m.error: LlmErr`) into the host's own\n * // state — the enrichment lives in the reducer arm, not the handler.\n * resilient_ok: (s, m) => {\n * const [slice, cmds] = llm.succeed(s.resilience, m.key, m);\n * return [{ ...s, resilience: slice, output: m.result.output }, cmds];\n * },\n * resilient_err: (s, m) => {\n * const [slice, cmds] = llm.fail(s.resilience, m.key, m);\n * return [{ ...s, resilience: slice, failure: m.error }, cmds];\n * },\n * deadline_exceeded: (s, m) => lift(s, llm.onTimer(s.resilience, m)),\n * },\n * subscriptions: (s) => llm.subs(s.resilience),\n * subscribe: { deadline: subscribeDeadline },\n * interpret: llm.handlers(),\n */\n\nimport { describeError } from \"../describe-error\";\nimport type { Cmd } from \"../index\";\nimport { MsgType } from \"../protocol\";\nimport {\n createResilientCall,\n type DeadlineExceeded,\n type DeadlineSub,\n deadlineSub,\n type FailMsg,\n liftResilience,\n type ResilientConfig,\n type ResilientState,\n type RunCmd,\n type SucceedMsg,\n subscribeDeadline,\n} from \"../resilient-call\";\nimport type { RetryPolicy } from \"../retry-backoff\";\n\n// ===========================================================================\n// The DI port surfaces — model factory + structured output + message loader.\n// ===========================================================================\n\n/**\n * The minimal structured-output schema contract: `parse(unknown) => T`, the\n * zod-style call the handler uses to validate the model's output before it\n * settles `resilient_ok`. A throwing `parse` (the zod contract on invalid\n * input) propagates out of `invokeOne` and is caught by the resilient handler,\n * which settles `resilient_err` (enriched to `LlmErr`) — a parse failure is a\n * failure, never a silent pass. Structural so a real `z.ZodType<T>` satisfies\n * it without an import.\n */\nexport interface Schema<T> {\n /** Validate + narrow `value` to `T`, or throw on mismatch (the zod contract). */\n parse(value: unknown): T;\n}\n\n/**\n * The minimal chat-model contract every model the handler talks to must\n * satisfy — the seed's `InjectableChatModel`, trimmed to the one operation\n * llm-call drives for brain-only stages:\n *\n * `withStructuredOutput(schema)` → a runnable whose `invoke(messages)`\n * resolves to a typed object matching `schema`.\n *\n * `BaseChatModel` from `@langchain/core` is the runtime type; this surface\n * names only what llm-call calls so a test fake can ignore the rest. Generic\n * over the message shape `Msg` so a consumer's loader and model agree on it\n * without llm-call inspecting messages.\n */\nexport interface Llm<Msg> {\n withStructuredOutput<T>(schema: Schema<T>): {\n invoke(messages: readonly Msg[]): Promise<T>;\n };\n}\n\n/**\n * Build the `Msg[]` the handler hands to the bound model for a given call. The\n * SDK / message-assembly seam — the seed's `buildBaseMessages` + the lazy\n * `loadMessages` loader rolled into one injected port. Async because the seed\n * lazy-imports the SDK (a top-level `import type` of the langchain messages\n * package explodes the workers test runner). Receives the full `LlmCall` so it\n * can branch on `purpose` exactly as the seed's `buildBaseMessages` did.\n */\nexport type MessageLoader<P extends string, Msg> = (\n call: LlmCall<P>,\n) => Promise<readonly Msg[]>;\n\n/**\n * The model factory — the first DI port. `(modelId) => Llm`. Tests pass a fake\n * builder; production wires `createChatModel(env, getModelConfig(id))`. `null`\n * means \"the host's default model\" (the seed's `string | null`).\n */\nexport type ModelFactory<Msg> = (modelId: string | null) => Llm<Msg>;\n\n// ===========================================================================\n// Config — the knob. `model` + `schemas` required (the two DI ports + the\n// per-purpose parse target); `retry` + `loadMessages` optional.\n// ===========================================================================\n\n/**\n * The llm-call knob. `model` and `schemas` are the load-bearing pair — the\n * model factory DI port and the per-purpose structured-output targets; the\n * rest is optional, exactly the resilient-call \"omit a brick → omit its gate\"\n * story for `retry`:\n *\n * - `model` — DI port 1: the model factory.\n * - `schemas` — one `Schema` per `Purpose`. The handler binds\n * `schemas[call.purpose]` as the structured-output target.\n * - `retry` — backoff policy, composed straight into `../resilient-\n * call`. Omit it and a model failure is terminal (no\n * backoff), exactly as resilient-call with no retry brick.\n * - `loadMessages` — DI port 2: the SDK / message loader. Omit it and the\n * handler invokes the bound model with `[]` (a degenerate\n * but valid call) — provide it to assemble real messages.\n *\n * `P` is the purpose union (e.g. `\"plan\" | \"report\"`); `O` maps each purpose to\n * its parsed output type; `Msg` is the model's message shape (threaded through\n * the loader + model so llm-call never inspects a message).\n */\nexport interface LlmCallConfig<\n P extends string,\n O extends Record<P, unknown>,\n Msg = unknown,\n> {\n /** DI port 1 — the model factory. `(modelId) => Llm`. */\n readonly model: ModelFactory<Msg>;\n /** One structured-output schema per purpose; the parse target the handler binds. */\n readonly schemas: { readonly [K in P]: Schema<O[K]> };\n /** Backoff policy, composed into `../resilient-call`. Omit → no backoff. */\n readonly retry?: RetryPolicy;\n /** DI port 2 — the SDK / message loader. Omit → the handler invokes with `[]`. */\n readonly loadMessages?: MessageLoader<P, Msg>;\n}\n\n// ===========================================================================\n// The request / response shapes the knob speaks.\n// ===========================================================================\n\n/**\n * One LLM call request — the resilient-call `input` for this knob, carried on\n * the `resilient_run` Cmd as plain data (no closures, invariant 3). Mirrors the\n * seed's purpose-discriminated `CallLlmCmd`: a `purpose` selecting the schema +\n * prompt assembly, the `model` id, and the opaque per-purpose `payload` the\n * loader consumes.\n *\n * `key` defaults to `purpose` when the consumer calls `attempt(s, purpose, …)`,\n * so one in-flight call per purpose is tracked under the resilient-call slice —\n * the common single-call-per-stage shape. A consumer that fans out many calls\n * of one purpose passes a distinct `key`.\n */\nexport interface LlmCall<P extends string> {\n /** The stage / schema selector — drives both `schemas[purpose]` and message assembly. */\n readonly purpose: P;\n /** The model id to invoke; `null` = the host's default model. */\n readonly model: string | null;\n /** The per-purpose prompt payload the `MessageLoader` consumes. Opaque to the knob. */\n readonly payload: unknown;\n}\n\n/** The parsed, typed success carried on the `resilient_ok` settle Msg, tagged with its purpose. */\nexport interface LlmOk<P extends string, O extends Record<P, unknown>> {\n readonly key: string;\n readonly purpose: P;\n readonly output: O[P];\n}\n\n/** The typed failure variant — every failure path surfaces this, tagged by purpose. */\nexport interface LlmErr<P extends string> {\n readonly key: string;\n readonly purpose: P;\n /** A human-readable cause (model throw, retry-exhaustion, or schema parse). */\n readonly reason: string;\n /** The original error, carried untouched for the consumer to inspect. */\n readonly error: unknown;\n}\n\n/**\n * The settle Msgs llm-call's handler RETURNS from `interpret` so the substrate\n * enqueues them as follow-up Msgs (re-entry) into the host reducer — exactly as\n * `../resilient-call` does. They are the resilient-call settle Msgs with the\n * payloads ENRICHED to llm-call's typed variants: `result` is the parsed,\n * purpose-tagged `LlmOk`; `error` is the typed `LlmErr` (purpose + reason +\n * raw). The host wires `resilient_ok` → `succeed` and `resilient_err` → `fail`\n * reducer arms (the doc-comment wiring), and folds the enriched payload into its\n * own state there — llm-call never forces a Msg vocabulary on the host, and the\n * settle Msg drives the inherited succeed/fail → backoff → onTimer loop instead\n * of bypassing it.\n */\n//\n// The success Msg is `../resilient-call`'s `SucceedMsg` specialized to the parsed\n// `LlmOk` result — NOT a re-declared shape — so the verb returns (`succeed`'s\n// argument, the handler's resolve) thread through with no cast. The failure Msg\n// is the resilient `FailMsg` with its `error: unknown` NARROWED to the typed\n// `LlmErr`: every value the handler enriches is an `LlmErr`, and `LlmErr` is\n// assignable to `unknown`, so `LlmFailMsg` flows into `rc.fail` (which takes the\n// wide `FailMsg`) directly while the host reducer reads the narrow `error` type.\nexport type LlmSucceedMsg<\n P extends string,\n O extends Record<P, unknown>,\n> = SucceedMsg<LlmOk<P, O>>;\nexport type LlmFailMsg<P extends string> = Omit<FailMsg, \"error\"> & {\n readonly error: LlmErr<P>;\n};\n\n/** The retry / deadline timer Msg — `DeadlineExceeded`, inherited from resilient-call. */\nexport type LlmTimerMsg = DeadlineExceeded;\n\n/**\n * The ports the LEGACY detached `handlers(ports)` form takes. Two outbound Msg\n * builders the handler hands the typed Ok / Err; the consumer returns its own\n * Msg (or `undefined`). KEPT only for `../agent`, which still inherits the\n * detached shape; that path does NOT drive the retry loop (it dispatches the\n * consumer's Msg directly and never re-enters the resilient settle Msg). New\n * consumers use the no-arg `handlers()` form, which RETURNS the settle Msg for\n * re-entry and drives the inherited succeed/fail → backoff → onTimer loop.\n * `../agent` carries the same retry-loop gap and is fixed separately.\n *\n * The builders return `M | undefined` — `undefined` is the \"dispatch nothing\"\n * sentinel the detached form checks (`!== undefined`), spelled as `undefined`\n * rather than `void` so the union is unambiguous (a `void` member of a union is\n * confusing and reads as \"may return anything\").\n */\nexport interface LlmCallPorts<\n P extends string,\n O extends Record<P, unknown>,\n M,\n> {\n /** Build the Msg dispatched on a parsed success. Return `undefined` to dispatch nothing. */\n readonly onOk: (ok: LlmOk<P, O>) => M | undefined;\n /** Build the Msg dispatched on any failure (throw / retry-exhausted / parse). */\n readonly onErr: (err: LlmErr<P>) => M | undefined;\n}\n\n/**\n * The effect Cmd the knob emits: run the LLM call for `key` with `input`. The\n * `input` is the plain `LlmCall` request — the handler reads `purpose` /\n * `model` / `payload` off it (invariant 3: Cmds are data).\n *\n * It is `../resilient-call`'s `RunCmd` specialized to the `LlmCall` input — NOT a\n * re-declared shape — so the resilient verbs' return tuples (`[State, RunCmd[]]`)\n * are this exact type and thread through `attempt` / `succeed` / `fail` /\n * `onTimer` with no cast.\n */\nexport type LlmRunCmd<P extends string> = RunCmd<LlmCall<P>>;\n\n// ===========================================================================\n// The knob factory.\n// ===========================================================================\n\n/**\n * Build an llm-call knob from `config`. `rng` is injected for the inherited\n * retry jitter (pass a fixed `() => 0.5` in tests to pin backoff; defaults to\n * `Math.random`, read only at the resilient-call verb boundary).\n *\n * Returns the uniform L2 knob contract. The slice + verbs (`init`, `attempt`,\n * `succeed`, `fail`, `onTimer`, `subs`) are DELEGATED to a `../resilient-call`\n * knob built over the structured-output model-invoke port — no second backoff\n * here. `handlers` is the one new piece: the purpose-branching, schema-parsing\n * wrapper that returns the enriched resilient settle Msg for re-entry.\n *\n * `P` is the purpose union, `O` the purpose→output map, `Msg` the model's\n * message shape.\n */\nexport function createLlmCall<\n P extends string,\n O extends Record<P, unknown>,\n Msg = unknown,\n>(config: LlmCallConfig<P, O, Msg>, rng: () => number = Math.random) {\n // ---- The composed resilient-call knob ----------------------------------\n //\n // The resilient-call input is the full `LlmCall` request; its result is the\n // PARSED, typed `LlmOk`. Only the `retry` brick is forwarded — llm-call does\n // not expose circuit / rate-limit / cache / deadline knobs (a stage call is\n // a single brain invocation, not a keyed downstream target). Omitting those\n // bricks omits their gates, exactly as resilient-call documents.\n const resilientConfig: ResilientConfig = {\n ...(config.retry === undefined ? {} : { retry: config.retry }),\n };\n const rc = createResilientCall<LlmCall<P>, LlmOk<P, O>>(resilientConfig, rng);\n\n /** The slice this knob owns — resilient-call's slice verbatim. */\n type State = ResilientState<LlmCall<P>, LlmOk<P, O>>;\n\n /** The starting slice — resilient-call's. */\n function init(): State {\n return rc.init();\n }\n\n /**\n * Start (or restart) an LLM call. `keyOrPurpose` defaults the resilient-call\n * `key` to the call's `purpose` so one in-flight call per stage is tracked\n * under the slice (the common shape); pass a distinct key to fan out. PURE —\n * delegates straight to resilient-call's gate.\n */\n function attempt(\n s: State,\n input: LlmCall<P>,\n at: number,\n key: string = input.purpose,\n ): readonly [State, readonly LlmRunCmd<P>[]] {\n return rc.attempt(s, key, input, at);\n }\n\n /** Record a parsed success for `key`. PURE — resilient-call's `succeed`. */\n function succeed(\n s: State,\n key: string,\n msg: LlmSucceedMsg<P, O>,\n ): readonly [State, readonly LlmRunCmd<P>[]] {\n return rc.succeed(s, key, msg);\n }\n\n /**\n * Record a failure for `key`: back off via the inherited retry, or settle\n * `failed`. PURE — resilient-call's `fail`. The `error` is the typed\n * `LlmErr`, carried on the call's `failed` phase for the consumer to read.\n */\n function fail(\n s: State,\n key: string,\n msg: LlmFailMsg<P>,\n ): readonly [State, readonly LlmRunCmd<P>[]] {\n return rc.fail(s, key, msg);\n }\n\n /** A retry / deadline timer fired. PURE — resilient-call's `onTimer`. */\n function onTimer(\n s: State,\n msg: LlmTimerMsg,\n ): readonly [State, readonly LlmRunCmd<P>[]] {\n return rc.onTimer(s, msg);\n }\n\n /** Pre-wired subs — resilient-call's retry-timer subscriptions. */\n function subs(s: State): readonly DeadlineSub[] {\n return rc.subs(s);\n }\n\n // ---- The model-invoke port the resilient handler drives ----------------\n\n /**\n * Run ONE LLM call: assemble messages via the loader, bind the purpose's\n * structured-output schema, invoke, and parse. Returns the typed `LlmOk` on\n * success; throws on any failure (model throw OR schema parse) so the\n * resilient-call handler routes it to a failure Msg + backoff. This is the\n * single port resilient-call wraps — the retry / timeout machinery lives in\n * resilient-call, never here (\"do NOT reinvent backoff\").\n *\n * The seed's two paths collapse to one: every brain-only purpose goes through\n * `withStructuredOutput(schema)`. The `parse` re-validates the output (a fake\n * model might skip validation, and a real provider can drift) so a malformed\n * structured response is a failure, not a corrupt success.\n */\n async function invokeOne(input: LlmCall<P>): Promise<LlmOk<P, O>> {\n const model = config.model(input.model);\n const messages = config.loadMessages\n ? await config.loadMessages(input)\n : [];\n const schema = config.schemas[input.purpose];\n const structured = model.withStructuredOutput(schema);\n const raw = await structured.invoke(messages);\n // Re-validate at the boundary — a parse throw becomes a `resilient_err`\n // (enriched to `LlmErr`) settle Msg upstream, never a corrupt success.\n const output = schema.parse(raw);\n return { key: input.purpose, purpose: input.purpose, output };\n }\n\n // ---- Handlers — the one new piece, composed onto resilient-call --------\n\n /**\n * Pre-wired interpret handler for `resilient_run`. Composes\n * `../resilient-call`'s `resilient_run` handler over the `invokeOne` port and\n * RETURNS the resilient settle Msg — it does NOT dispatch the consumer's Msg\n * itself. Returning the settle Msg is the whole point: the substrate enqueues\n * an interpret handler's returned Msg as a FOLLOW-UP (re-entry) onto the\n * dispatch tail, so the host reducer's `resilient_ok` / `resilient_err` arms\n * run `succeed` / `fail`, which advances the inherited retry loop\n * (succeed/fail → backoff → onTimer → re-issue). A handler that instead\n * `dispatch`ed an enriched Msg directly would settle ONE invoke and bypass\n * that loop entirely — the slice would never leave `running`, the breaker\n * would never trip/close, and the retry counter would never reset.\n *\n * Resilience is composed, not reimplemented: the body delegates to the\n * resilient-call handler's Railway-routed invoke (Ok → `resilient_ok`, Err →\n * `resilient_err`), so a transient model failure backs off via the inherited\n * retry exactly as resilient-call decides. The ONE enrichment llm-call adds is\n * mapping the settle PAYLOADS into its typed variants:\n *\n * - success → `result` is already the parsed `LlmOk` (`invokeOne` returns\n * it), so the resilient `resilient_ok` Msg is the llm-call `LlmSucceedMsg`\n * as-is.\n * - failure → the resilient `resilient_err` carries the RAW throw (a model\n * 503 or a `schema.parse` throw). We rebuild it into the typed `LlmErr`\n * (purpose + reason + raw) so the host reducer routes per-stage.\n *\n * The host folds the enriched payload into its own state in the reducer arm\n * (the doc-comment wiring), NOT here — the handler stays the one impurity\n * (the `invokeOne` await + the `Date.now()` stamp the resilient handler makes)\n * and the reducer stays pure.\n */\n // The resilient-call `resilient_run` handler over `invokeOne` — the shared\n // Railway-routed invoke (Ok → resilient_ok Msg, Err → resilient_err Msg). Both\n // handler forms below build on it; the settle shape is composed, not reinvented.\n const resilientRunHandler = rc.handlers({\n run: (input) => invokeOne(input),\n }).resilient_run;\n\n /**\n * Await one composed invoke and resolve to the ENRICHED resilient settle Msg\n * (`resilient_ok` carrying the parsed `LlmOk`, or `resilient_err` carrying the\n * typed `LlmErr`). The shared core both handler forms reuse.\n */\n async function settleOf(\n cmd: LlmRunCmd<P>,\n ): Promise<LlmSucceedMsg<P, O> | LlmFailMsg<P>> {\n // `resilientRunHandler` never rejects (tryInterpret contract) — it resolves\n // to a `resilient_ok` / `resilient_err` settle Msg stamped with `Date.now()`.\n // Its `ctx` slot is `NoCtx` (the resilient-call work fn reads no ctx — a\n // DELIBERATE context-free seam, not accidental `unknown`), so the empty\n // record satisfies it without a cast.\n const settle = await resilientRunHandler(cmd, {});\n if (settle.type === MsgType.ResilientOk) {\n // `result` is the parsed `LlmOk` from `invokeOne`, which hardcoded its\n // `key` to `purpose` (the port receives no key). Re-key it to `cmd.key` —\n // the fan-out key `attempt` threaded in — so a fanned-out success carries\n // the SAME key its failure would (`settleOf`'s err path below uses\n // `cmd.key`), never the collapsed purpose. `settle.key` is already\n // `cmd.key`; this aligns the enriched `LlmOk.key` inside it.\n return { ...settle, result: { ...settle.result, key: cmd.key } };\n }\n // resilient_err — enrich the raw throw into the typed `LlmErr`, keeping the\n // resilient Msg's `key` / `at` so the host reducer's `fail` verb re-issues\n // from the remembered input and backs off on schedule.\n const rawError: unknown = settle.error;\n const err: LlmErr<P> = {\n key: cmd.key,\n purpose: cmd.input.purpose,\n reason: describeError(rawError),\n error: rawError,\n };\n return {\n type: MsgType.ResilientErr,\n key: settle.key,\n error: err,\n at: settle.at,\n };\n }\n\n /**\n * Pre-wired interpret handler for `resilient_run`. TWO forms:\n *\n * - `handlers()` (no args, the FIXED primary) — RETURNS the enriched\n * resilient settle Msg. The substrate enqueues an interpret handler's\n * returned Msg as a FOLLOW-UP (re-entry) onto the dispatch tail, so the\n * host reducer's `resilient_ok` / `resilient_err` arms run `succeed` /\n * `fail`, advancing the inherited retry loop (succeed/fail → backoff →\n * onTimer → re-issue). This is the correct wiring; the module doc shows it.\n *\n * - `handlers(ports)` (LEGACY, detached) — runs the invoke inside\n * `ctx.waitUntil` and dispatches the consumer's `onOk` / `onErr` Msg\n * directly. KEPT only for `../agent`, which still inherits this shape. It\n * does NOT drive the retry loop (it never re-enters the settle Msg, so\n * `succeed` / `fail` never run) — `../agent` carries the same gap and is\n * fixed in its own pass. New consumers MUST use the no-arg form.\n *\n * Resilience is composed, not reimplemented: both forms delegate to the\n * resilient-call handler's Railway-routed invoke and add only the typed\n * `LlmOk` / `LlmErr` enrichment. The ONE impurity is the `invokeOne` await +\n * the `Date.now()` stamp the resilient handler makes; the reducer stays pure.\n */\n function handlers(): {\n resilient_run: (\n cmd: LlmRunCmd<P>,\n ) => Promise<LlmSucceedMsg<P, O> | LlmFailMsg<P>>;\n };\n function handlers<M>(ports: LlmCallPorts<P, O, M>): {\n resilient_run: (\n cmd: LlmRunCmd<P>,\n ctx: { waitUntil(p: Promise<unknown>): void; dispatch(msg: M): unknown },\n ) => void;\n };\n function handlers<M>(ports?: LlmCallPorts<P, O, M>) {\n if (ports === undefined) {\n // FIXED primary: return the settle Msg for re-entry — drives the loop.\n return { resilient_run: (cmd: LlmRunCmd<P>) => settleOf(cmd) };\n }\n // LEGACY detached form — kept for `../agent` (see doc above). Dispatches the\n // consumer's Msg directly; does not re-enter the settle Msg.\n return {\n resilient_run: (\n cmd: LlmRunCmd<P>,\n ctx: {\n waitUntil(p: Promise<unknown>): void;\n dispatch(msg: M): unknown;\n },\n ): void => {\n const fired = (async () => {\n const settle = await settleOf(cmd);\n if (settle.type === MsgType.ResilientOk) {\n const msg = ports.onOk(settle.result);\n if (msg !== undefined) await ctx.dispatch(msg);\n return;\n }\n const msg = ports.onErr(settle.error);\n if (msg !== undefined) await ctx.dispatch(msg);\n })();\n ctx.waitUntil(fired);\n },\n };\n }\n\n return { init, attempt, succeed, fail, onTimer, subs, handlers, invokeOne };\n}\n\n/**\n * Lift a knob result `[slice, cmds]` into a host `[State, cmds]` where the\n * slice lives at `state.resilience` — the same convenience `../resilient-call`\n * ships, re-typed for the llm-call slice so consumers wire one import. Pure.\n */\nexport function liftLlmCall<\n S extends { resilience: ResilientState<LlmCall<P>, LlmOk<P, O>> },\n P extends string,\n O extends Record<P, unknown>,\n C extends Cmd,\n>(\n state: S,\n result: readonly [ResilientState<LlmCall<P>, LlmOk<P, O>>, readonly C[]],\n): readonly [S, readonly C[]] {\n return liftResilience(state, result);\n}\n\n/**\n * Re-export the deadline Sub primitives (inherited from resilient-call) so\n * consumers wire one import: `subscribeDeadline` is the `subscribe` cell,\n * `deadlineSub` builds the Sub literal `subs` emits.\n */\nexport { subscribeDeadline, deadlineSub };\nexport type { DeadlineSub, DeadlineExceeded, ResilientState };\n"]}
1
+ {"version":3,"sources":["../src/llm-call/index.ts"],"names":["msg"],"mappings":"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","file":"chunk-T6HDAPMR.js","sourcesContent":["/**\n * @packageDocumentation\n * @demlik/tea/llm-call — `resilient-call` + structured-output parse + a typed\n * failure variant, around a purpose-discriminated LLM invocation.\n *\n * This is the ~60-line `call_llm` handler from the audit-agent seed\n * (`interpret.ts` / `effects.ts`) collapsed into the uniform L2 knob: a config\n * object, the resilient-call slice it inherits, and one interpret handler that\n * assembles messages, binds the structured-output schema for the call's\n * `purpose`, invokes the model (retry composed from `../resilient-call`, NOT\n * reinvented), parses the output, and RETURNS the enriched resilient settle Msg\n * (`resilient_ok` / `resilient_err`) so it re-enters the host reducer and drives\n * the inherited succeed/fail → backoff → onTimer loop.\n *\n * ## What it adds over `../resilient-call`\n *\n * - **Two DI ports** (the seed's two seams):\n * 1. `model: (modelId) => LLM` — the model factory. Tests pass a fake so\n * the handler runs end-to-end without touching a real provider; the\n * production worker wires `createChatModel(env, …)`.\n * 2. `loadMessages: Loader` — the SDK / message loader. The seed lazy-\n * imports `@langchain/core/messages` (a top-level `import type` blows\n * up the workers test runner); the loader keeps that import lazy and\n * stubbable. It produces the `BaseMessage[]` for a given call.\n * - **Structured-output parse** — each `purpose` maps to a `Schema` in\n * `config.schemas`. The handler binds `model.withStructuredOutput(schema)`\n * so the invoke resolves to a typed object; a `Schema.parse` that throws\n * becomes a `resilient_err` carrying an `LlmErr` (a parse failure is a\n * failure, not a stall — \"errors are data\"). `withStructuredOutput` is the\n * seed's brain-only path; the fake model in tests returns the typed object\n * directly.\n * - **A typed failure variant** — every failure (model throw, timeout-via-\n * retry-exhaustion, schema parse) is surfaced as the `LlmErr` carried on the\n * enriched `resilient_err` settle Msg, tagged with the `purpose` so the\n * consumer's reducer routes per-stage. The seed dispatched\n * `llm_failed{purpose}`; the consumer rebuilds that shape in its reducer arm\n * from `m.error: LlmErr`.\n *\n * ## Inheriting resilient-call by composition (not reinvention)\n *\n * The retry / backoff lives in `../resilient-call`: `createLlmCall` builds a\n * `createResilientCall` knob over the model-invoke port and DELEGATES `init`,\n * `attempt`, `succeed`, `fail`, `onTimer`, `subs` to it verbatim — the slice is\n * literally resilient-call's slice (the spec's \"retry lives here\"). There is no\n * second backoff implementation here. The handler runs the invoke through the\n * resilient-call handler so a transient model failure backs off exactly as a\n * resilient HTTP call would; the `purpose`-branch + structured parse wrap the\n * port the resilient handler drives.\n *\n * ## The two non-negotiables (canon, inherited)\n *\n * - **Durable** — the slice is resilient-call's plain-data slice (a Model\n * field). Input carried on the `resilient_run` Cmd is the plain\n * `LlmCall` request (purpose + modelId + payload) — no closures.\n * - **Replayable** — every transition is a resilient-call verb; the handler\n * is the only impurity, and the one clock read is `Date.now()` at the\n * effect boundary (inside the handler), exactly as the seed stamped its\n * `llm_responded` / `llm_failed` Msgs.\n *\n * ## Typical wiring\n *\n * const llm = createLlmCall<MyPurpose, MyOutputs>({\n * model: (id) => createChatModel(env, id),\n * schemas: { plan: planSchema, report: reportSchema },\n * retry: defaultRetryPolicy,\n * loadMessages: defaultMessagesLoader,\n * });\n *\n * // in the machine:\n * init: () => [{ resilience: llm.init() }, []],\n * update: {\n * call_llm: (s, m) => lift(s, llm.attempt(s.resilience, m.input, m.at)),\n * // `resilient_ok` / `resilient_err` RE-ENTER from `handlers` (the settle\n * // Msg the interpret handler returns). Run the inherited verb FIRST so the\n * // succeed/fail → backoff → onTimer loop advances, THEN fold the enriched\n * // payload (`m.result: LlmOk` / `m.error: LlmErr`) into the host's own\n * // state — the enrichment lives in the reducer arm, not the handler.\n * resilient_ok: (s, m) => {\n * const [slice, cmds] = llm.succeed(s.resilience, m.key, m);\n * return [{ ...s, resilience: slice, output: m.result.output }, cmds];\n * },\n * resilient_err: (s, m) => {\n * const [slice, cmds] = llm.fail(s.resilience, m.key, m);\n * return [{ ...s, resilience: slice, failure: m.error }, cmds];\n * },\n * deadline_exceeded: (s, m) => lift(s, llm.onTimer(s.resilience, m)),\n * },\n * subscriptions: (s) => llm.subs(s.resilience),\n * subscribe: { deadline: subscribeDeadline },\n * interpret: llm.handlers(),\n */\n\nimport { describeError } from \"../describe-error\";\nimport type { Cmd } from \"../index\";\nimport { MsgType } from \"../protocol\";\nimport {\n createResilientCall,\n type DeadlineExceeded,\n type DeadlineSub,\n deadlineSub,\n type FailMsg,\n liftResilience,\n type ResilientConfig,\n type ResilientState,\n type RunCmd,\n type SucceedMsg,\n subscribeDeadline,\n} from \"../resilient-call\";\nimport type { RetryPolicy } from \"../retry-backoff\";\n\n// ===========================================================================\n// The DI port surfaces — model factory + structured output + message loader.\n// ===========================================================================\n\n/**\n * The minimal structured-output schema contract: `parse(unknown) => T`, the\n * zod-style call the handler uses to validate the model's output before it\n * settles `resilient_ok`. A throwing `parse` (the zod contract on invalid\n * input) propagates out of `invokeOne` and is caught by the resilient handler,\n * which settles `resilient_err` (enriched to `LlmErr`) — a parse failure is a\n * failure, never a silent pass. Structural so a real `z.ZodType<T>` satisfies\n * it without an import.\n */\nexport interface Schema<T> {\n /** Validate + narrow `value` to `T`, or throw on mismatch (the zod contract). */\n parse(value: unknown): T;\n}\n\n/**\n * The minimal chat-model contract every model the handler talks to must\n * satisfy — the seed's `InjectableChatModel`, trimmed to the one operation\n * llm-call drives for brain-only stages:\n *\n * `withStructuredOutput(schema)` → a runnable whose `invoke(messages)`\n * resolves to a typed object matching `schema`.\n *\n * `BaseChatModel` from `@langchain/core` is the runtime type; this surface\n * names only what llm-call calls so a test fake can ignore the rest. Generic\n * over the message shape `Msg` so a consumer's loader and model agree on it\n * without llm-call inspecting messages.\n */\nexport interface Llm<Msg> {\n withStructuredOutput<T>(schema: Schema<T>): {\n invoke(messages: readonly Msg[]): Promise<T>;\n };\n}\n\n/**\n * Build the `Msg[]` the handler hands to the bound model for a given call. The\n * SDK / message-assembly seam — the seed's `buildBaseMessages` + the lazy\n * `loadMessages` loader rolled into one injected port. Async because the seed\n * lazy-imports the SDK (a top-level `import type` of the langchain messages\n * package explodes the workers test runner). Receives the full `LlmCall` so it\n * can branch on `purpose` exactly as the seed's `buildBaseMessages` did.\n */\nexport type MessageLoader<P extends string, Msg> = (\n call: LlmCall<P>,\n) => Promise<readonly Msg[]>;\n\n/**\n * The model factory — the first DI port. `(modelId) => Llm`. Tests pass a fake\n * builder; production wires `createChatModel(env, getModelConfig(id))`. `null`\n * means \"the host's default model\" (the seed's `string | null`).\n */\nexport type ModelFactory<Msg> = (modelId: string | null) => Llm<Msg>;\n\n// ===========================================================================\n// Config — the knob. `model` + `schemas` required (the two DI ports + the\n// per-purpose parse target); `retry` + `loadMessages` optional.\n// ===========================================================================\n\n/**\n * The llm-call knob. `model` and `schemas` are the load-bearing pair — the\n * model factory DI port and the per-purpose structured-output targets; the\n * rest is optional, exactly the resilient-call \"omit a brick → omit its gate\"\n * story for `retry`:\n *\n * - `model` — DI port 1: the model factory.\n * - `schemas` — one `Schema` per `Purpose`. The handler binds\n * `schemas[call.purpose]` as the structured-output target.\n * - `retry` — backoff policy, composed straight into `../resilient-\n * call`. Omit it and a model failure is terminal (no\n * backoff), exactly as resilient-call with no retry brick.\n * - `loadMessages` — DI port 2: the SDK / message loader. Omit it and the\n * handler invokes the bound model with `[]` (a degenerate\n * but valid call) — provide it to assemble real messages.\n *\n * `P` is the purpose union (e.g. `\"plan\" | \"report\"`); `O` maps each purpose to\n * its parsed output type; `Msg` is the model's message shape (threaded through\n * the loader + model so llm-call never inspects a message).\n */\nexport interface LlmCallConfig<\n P extends string,\n O extends Record<P, unknown>,\n Msg = unknown,\n> {\n /** DI port 1 — the model factory. `(modelId) => Llm`. */\n readonly model: ModelFactory<Msg>;\n /** One structured-output schema per purpose; the parse target the handler binds. */\n readonly schemas: { readonly [K in P]: Schema<O[K]> };\n /** Backoff policy, composed into `../resilient-call`. Omit → no backoff. */\n readonly retry?: RetryPolicy;\n /** DI port 2 — the SDK / message loader. Omit → the handler invokes with `[]`. */\n readonly loadMessages?: MessageLoader<P, Msg>;\n}\n\n// ===========================================================================\n// The request / response shapes the knob speaks.\n// ===========================================================================\n\n/**\n * One LLM call request — the resilient-call `input` for this knob, carried on\n * the `resilient_run` Cmd as plain data (no closures, invariant 3). Mirrors the\n * seed's purpose-discriminated `CallLlmCmd`: a `purpose` selecting the schema +\n * prompt assembly, the `model` id, and the opaque per-purpose `payload` the\n * loader consumes.\n *\n * `key` defaults to `purpose` when the consumer calls `attempt(s, purpose, …)`,\n * so one in-flight call per purpose is tracked under the resilient-call slice —\n * the common single-call-per-stage shape. A consumer that fans out many calls\n * of one purpose passes a distinct `key`.\n */\nexport interface LlmCall<P extends string> {\n /** The stage / schema selector — drives both `schemas[purpose]` and message assembly. */\n readonly purpose: P;\n /** The model id to invoke; `null` = the host's default model. */\n readonly model: string | null;\n /** The per-purpose prompt payload the `MessageLoader` consumes. Opaque to the knob. */\n readonly payload: unknown;\n}\n\n/** The parsed, typed success carried on the `resilient_ok` settle Msg, tagged with its purpose. */\nexport interface LlmOk<P extends string, O extends Record<P, unknown>> {\n readonly key: string;\n readonly purpose: P;\n readonly output: O[P];\n}\n\n/** The typed failure variant — every failure path surfaces this, tagged by purpose. */\nexport interface LlmErr<P extends string> {\n readonly key: string;\n readonly purpose: P;\n /** A human-readable cause (model throw, retry-exhaustion, or schema parse). */\n readonly reason: string;\n /** The original error, carried untouched for the consumer to inspect. */\n readonly error: unknown;\n}\n\n/**\n * The settle Msgs llm-call's handler RETURNS from `interpret` so the substrate\n * enqueues them as follow-up Msgs (re-entry) into the host reducer — exactly as\n * `../resilient-call` does. They are the resilient-call settle Msgs with the\n * payloads ENRICHED to llm-call's typed variants: `result` is the parsed,\n * purpose-tagged `LlmOk`; `error` is the typed `LlmErr` (purpose + reason +\n * raw). The host wires `resilient_ok` → `succeed` and `resilient_err` → `fail`\n * reducer arms (the doc-comment wiring), and folds the enriched payload into its\n * own state there — llm-call never forces a Msg vocabulary on the host, and the\n * settle Msg drives the inherited succeed/fail → backoff → onTimer loop instead\n * of bypassing it.\n */\n//\n// The success Msg is `../resilient-call`'s `SucceedMsg` specialized to the parsed\n// `LlmOk` result — NOT a re-declared shape — so the verb returns (`succeed`'s\n// argument, the handler's resolve) thread through with no cast. The failure Msg\n// is the resilient `FailMsg` with its `error: unknown` NARROWED to the typed\n// `LlmErr`: every value the handler enriches is an `LlmErr`, and `LlmErr` is\n// assignable to `unknown`, so `LlmFailMsg` flows into `rc.fail` (which takes the\n// wide `FailMsg`) directly while the host reducer reads the narrow `error` type.\nexport type LlmSucceedMsg<\n P extends string,\n O extends Record<P, unknown>,\n> = SucceedMsg<LlmOk<P, O>>;\nexport type LlmFailMsg<P extends string> = Omit<FailMsg, \"error\"> & {\n readonly error: LlmErr<P>;\n};\n\n/** The retry / deadline timer Msg — `DeadlineExceeded`, inherited from resilient-call. */\nexport type LlmTimerMsg = DeadlineExceeded;\n\n/**\n * The ports the LEGACY detached `handlers(ports)` form takes. Two outbound Msg\n * builders the handler hands the typed Ok / Err; the consumer returns its own\n * Msg (or `undefined`). KEPT only for `../agent`, which still inherits the\n * detached shape; that path does NOT drive the retry loop (it dispatches the\n * consumer's Msg directly and never re-enters the resilient settle Msg). New\n * consumers use the no-arg `handlers()` form, which RETURNS the settle Msg for\n * re-entry and drives the inherited succeed/fail → backoff → onTimer loop.\n * `../agent` carries the same retry-loop gap and is fixed separately.\n *\n * The builders return `M | undefined` — `undefined` is the \"dispatch nothing\"\n * sentinel the detached form checks (`!== undefined`), spelled as `undefined`\n * rather than `void` so the union is unambiguous (a `void` member of a union is\n * confusing and reads as \"may return anything\").\n */\nexport interface LlmCallPorts<\n P extends string,\n O extends Record<P, unknown>,\n M,\n> {\n /** Build the Msg dispatched on a parsed success. Return `undefined` to dispatch nothing. */\n readonly onOk: (ok: LlmOk<P, O>) => M | undefined;\n /** Build the Msg dispatched on any failure (throw / retry-exhausted / parse). */\n readonly onErr: (err: LlmErr<P>) => M | undefined;\n}\n\n/**\n * The effect Cmd the knob emits: run the LLM call for `key` with `input`. The\n * `input` is the plain `LlmCall` request — the handler reads `purpose` /\n * `model` / `payload` off it (invariant 3: Cmds are data).\n *\n * It is `../resilient-call`'s `RunCmd` specialized to the `LlmCall` input — NOT a\n * re-declared shape — so the resilient verbs' return tuples (`[State, RunCmd[]]`)\n * are this exact type and thread through `attempt` / `succeed` / `fail` /\n * `onTimer` with no cast.\n */\nexport type LlmRunCmd<P extends string> = RunCmd<LlmCall<P>>;\n\n// ===========================================================================\n// The knob factory.\n// ===========================================================================\n\n/**\n * Build an llm-call knob from `config`. `rng` is injected for the inherited\n * retry jitter (pass a fixed `() => 0.5` in tests to pin backoff; defaults to\n * `Math.random`, read only at the resilient-call verb boundary).\n *\n * Returns the uniform L2 knob contract. The slice + verbs (`init`, `attempt`,\n * `succeed`, `fail`, `onTimer`, `subs`) are DELEGATED to a `../resilient-call`\n * knob built over the structured-output model-invoke port — no second backoff\n * here. `handlers` is the one new piece: the purpose-branching, schema-parsing\n * wrapper that returns the enriched resilient settle Msg for re-entry.\n *\n * `P` is the purpose union, `O` the purpose→output map, `Msg` the model's\n * message shape.\n */\nexport function createLlmCall<\n P extends string,\n O extends Record<P, unknown>,\n Msg = unknown,\n>(config: LlmCallConfig<P, O, Msg>, rng: () => number = Math.random) {\n // ---- The composed resilient-call knob ----------------------------------\n //\n // The resilient-call input is the full `LlmCall` request; its result is the\n // PARSED, typed `LlmOk`. Only the `retry` brick is forwarded — llm-call does\n // not expose circuit / rate-limit / cache / deadline knobs (a stage call is\n // a single brain invocation, not a keyed downstream target). Omitting those\n // bricks omits their gates, exactly as resilient-call documents.\n const resilientConfig: ResilientConfig = {\n ...(config.retry === undefined ? {} : { retry: config.retry }),\n };\n const rc = createResilientCall<LlmCall<P>, LlmOk<P, O>>(resilientConfig, rng);\n\n /** The slice this knob owns — resilient-call's slice verbatim. */\n type State = ResilientState<LlmCall<P>, LlmOk<P, O>>;\n\n /** The starting slice — resilient-call's. */\n function init(): State {\n return rc.init();\n }\n\n /**\n * Start (or restart) an LLM call. `keyOrPurpose` defaults the resilient-call\n * `key` to the call's `purpose` so one in-flight call per stage is tracked\n * under the slice (the common shape); pass a distinct key to fan out. PURE —\n * delegates straight to resilient-call's gate.\n */\n function attempt(\n s: State,\n input: LlmCall<P>,\n at: number,\n key: string = input.purpose,\n ): readonly [State, readonly LlmRunCmd<P>[]] {\n return rc.attempt(s, key, input, at);\n }\n\n /** Record a parsed success for `key`. PURE — resilient-call's `succeed`. */\n function succeed(\n s: State,\n key: string,\n msg: LlmSucceedMsg<P, O>,\n ): readonly [State, readonly LlmRunCmd<P>[]] {\n return rc.succeed(s, key, msg);\n }\n\n /**\n * Record a failure for `key`: back off via the inherited retry, or settle\n * `failed`. PURE — resilient-call's `fail`. The `error` is the typed\n * `LlmErr`, carried on the call's `failed` phase for the consumer to read.\n */\n function fail(\n s: State,\n key: string,\n msg: LlmFailMsg<P>,\n ): readonly [State, readonly LlmRunCmd<P>[]] {\n return rc.fail(s, key, msg);\n }\n\n /** A retry / deadline timer fired. PURE — resilient-call's `onTimer`. */\n function onTimer(\n s: State,\n msg: LlmTimerMsg,\n ): readonly [State, readonly LlmRunCmd<P>[]] {\n return rc.onTimer(s, msg);\n }\n\n /** Pre-wired subs — resilient-call's retry-timer subscriptions. */\n function subs(s: State): readonly DeadlineSub[] {\n return rc.subs(s);\n }\n\n // ---- The model-invoke port the resilient handler drives ----------------\n\n /**\n * Run ONE LLM call: assemble messages via the loader, bind the purpose's\n * structured-output schema, invoke, and parse. Returns the typed `LlmOk` on\n * success; throws on any failure (model throw OR schema parse) so the\n * resilient-call handler routes it to a failure Msg + backoff. This is the\n * single port resilient-call wraps — the retry / timeout machinery lives in\n * resilient-call, never here (\"do NOT reinvent backoff\").\n *\n * The seed's two paths collapse to one: every brain-only purpose goes through\n * `withStructuredOutput(schema)`. The `parse` re-validates the output (a fake\n * model might skip validation, and a real provider can drift) so a malformed\n * structured response is a failure, not a corrupt success.\n */\n async function invokeOne(input: LlmCall<P>): Promise<LlmOk<P, O>> {\n const model = config.model(input.model);\n const messages = config.loadMessages\n ? await config.loadMessages(input)\n : [];\n const schema = config.schemas[input.purpose];\n const structured = model.withStructuredOutput(schema);\n const raw = await structured.invoke(messages);\n // Re-validate at the boundary — a parse throw becomes a `resilient_err`\n // (enriched to `LlmErr`) settle Msg upstream, never a corrupt success.\n const output = schema.parse(raw);\n return { key: input.purpose, purpose: input.purpose, output };\n }\n\n // ---- Handlers — the one new piece, composed onto resilient-call --------\n\n /**\n * Pre-wired interpret handler for `resilient_run`. Composes\n * `../resilient-call`'s `resilient_run` handler over the `invokeOne` port and\n * RETURNS the resilient settle Msg — it does NOT dispatch the consumer's Msg\n * itself. Returning the settle Msg is the whole point: the substrate enqueues\n * an interpret handler's returned Msg as a FOLLOW-UP (re-entry) onto the\n * dispatch tail, so the host reducer's `resilient_ok` / `resilient_err` arms\n * run `succeed` / `fail`, which advances the inherited retry loop\n * (succeed/fail → backoff → onTimer → re-issue). A handler that instead\n * `dispatch`ed an enriched Msg directly would settle ONE invoke and bypass\n * that loop entirely — the slice would never leave `running`, the breaker\n * would never trip/close, and the retry counter would never reset.\n *\n * Resilience is composed, not reimplemented: the body delegates to the\n * resilient-call handler's Railway-routed invoke (Ok → `resilient_ok`, Err →\n * `resilient_err`), so a transient model failure backs off via the inherited\n * retry exactly as resilient-call decides. The ONE enrichment llm-call adds is\n * mapping the settle PAYLOADS into its typed variants:\n *\n * - success → `result` is already the parsed `LlmOk` (`invokeOne` returns\n * it), so the resilient `resilient_ok` Msg is the llm-call `LlmSucceedMsg`\n * as-is.\n * - failure → the resilient `resilient_err` carries the RAW throw (a model\n * 503 or a `schema.parse` throw). We rebuild it into the typed `LlmErr`\n * (purpose + reason + raw) so the host reducer routes per-stage.\n *\n * The host folds the enriched payload into its own state in the reducer arm\n * (the doc-comment wiring), NOT here — the handler stays the one impurity\n * (the `invokeOne` await + the `Date.now()` stamp the resilient handler makes)\n * and the reducer stays pure.\n */\n // The resilient-call `resilient_run` handler over `invokeOne` — the shared\n // Railway-routed invoke (Ok → resilient_ok Msg, Err → resilient_err Msg). Both\n // handler forms below build on it; the settle shape is composed, not reinvented.\n const resilientRunHandler = rc.handlers({\n run: (input) => invokeOne(input),\n }).resilient_run;\n\n /**\n * Await one composed invoke and resolve to the ENRICHED resilient settle Msg\n * (`resilient_ok` carrying the parsed `LlmOk`, or `resilient_err` carrying the\n * typed `LlmErr`). The shared core both handler forms reuse.\n */\n async function settleOf(\n cmd: LlmRunCmd<P>,\n ): Promise<LlmSucceedMsg<P, O> | LlmFailMsg<P>> {\n // `resilientRunHandler` never rejects (tryInterpret contract) — it resolves\n // to a `resilient_ok` / `resilient_err` settle Msg stamped with `Date.now()`.\n // Its `ctx` slot is `NoCtx` (the resilient-call work fn reads no ctx — a\n // DELIBERATE context-free seam, not accidental `unknown`), so the empty\n // record satisfies it without a cast.\n const settle = await resilientRunHandler(cmd, {});\n if (settle.type === MsgType.ResilientOk) {\n // `result` is the parsed `LlmOk` from `invokeOne`, which hardcoded its\n // `key` to `purpose` (the port receives no key). Re-key it to `cmd.key` —\n // the fan-out key `attempt` threaded in — so a fanned-out success carries\n // the SAME key its failure would (`settleOf`'s err path below uses\n // `cmd.key`), never the collapsed purpose. `settle.key` is already\n // `cmd.key`; this aligns the enriched `LlmOk.key` inside it.\n return { ...settle, result: { ...settle.result, key: cmd.key } };\n }\n // resilient_err — enrich the raw throw into the typed `LlmErr`, keeping the\n // resilient Msg's `key` / `at` so the host reducer's `fail` verb re-issues\n // from the remembered input and backs off on schedule.\n const rawError: unknown = settle.error;\n const err: LlmErr<P> = {\n key: cmd.key,\n purpose: cmd.input.purpose,\n reason: describeError(rawError),\n error: rawError,\n };\n return {\n type: MsgType.ResilientErr,\n key: settle.key,\n error: err,\n at: settle.at,\n };\n }\n\n /**\n * Pre-wired interpret handler for `resilient_run`. TWO forms:\n *\n * - `handlers()` (no args, the FIXED primary) — RETURNS the enriched\n * resilient settle Msg. The substrate enqueues an interpret handler's\n * returned Msg as a FOLLOW-UP (re-entry) onto the dispatch tail, so the\n * host reducer's `resilient_ok` / `resilient_err` arms run `succeed` /\n * `fail`, advancing the inherited retry loop (succeed/fail → backoff →\n * onTimer → re-issue). This is the correct wiring; the module doc shows it.\n *\n * - `handlers(ports)` (LEGACY, detached) — runs the invoke inside\n * `ctx.waitUntil` and dispatches the consumer's `onOk` / `onErr` Msg\n * directly. KEPT only for `../agent`, which still inherits this shape. It\n * does NOT drive the retry loop (it never re-enters the settle Msg, so\n * `succeed` / `fail` never run) — `../agent` carries the same gap and is\n * fixed in its own pass. New consumers MUST use the no-arg form.\n *\n * Resilience is composed, not reimplemented: both forms delegate to the\n * resilient-call handler's Railway-routed invoke and add only the typed\n * `LlmOk` / `LlmErr` enrichment. The ONE impurity is the `invokeOne` await +\n * the `Date.now()` stamp the resilient handler makes; the reducer stays pure.\n */\n function handlers(): {\n resilient_run: (\n cmd: LlmRunCmd<P>,\n ) => Promise<LlmSucceedMsg<P, O> | LlmFailMsg<P>>;\n };\n function handlers<M>(ports: LlmCallPorts<P, O, M>): {\n resilient_run: (\n cmd: LlmRunCmd<P>,\n ctx: { waitUntil(p: Promise<unknown>): void; dispatch(msg: M): unknown },\n ) => void;\n };\n function handlers<M>(ports?: LlmCallPorts<P, O, M>) {\n if (ports === undefined) {\n // FIXED primary: return the settle Msg for re-entry — drives the loop.\n return { resilient_run: (cmd: LlmRunCmd<P>) => settleOf(cmd) };\n }\n // LEGACY detached form — kept for `../agent` (see doc above). Dispatches the\n // consumer's Msg directly; does not re-enter the settle Msg.\n return {\n resilient_run: (\n cmd: LlmRunCmd<P>,\n ctx: {\n waitUntil(p: Promise<unknown>): void;\n dispatch(msg: M): unknown;\n },\n ): void => {\n const fired = (async () => {\n const settle = await settleOf(cmd);\n if (settle.type === MsgType.ResilientOk) {\n const msg = ports.onOk(settle.result);\n if (msg !== undefined) await ctx.dispatch(msg);\n return;\n }\n const msg = ports.onErr(settle.error);\n if (msg !== undefined) await ctx.dispatch(msg);\n })();\n ctx.waitUntil(fired);\n },\n };\n }\n\n return { init, attempt, succeed, fail, onTimer, subs, handlers, invokeOne };\n}\n\n/**\n * Lift a knob result `[slice, cmds]` into a host `[State, cmds]` where the\n * slice lives at `state.resilience` — the same convenience `../resilient-call`\n * ships, re-typed for the llm-call slice so consumers wire one import. Pure.\n */\nexport function liftLlmCall<\n S extends { resilience: ResilientState<LlmCall<P>, LlmOk<P, O>> },\n P extends string,\n O extends Record<P, unknown>,\n C extends Cmd,\n>(\n state: S,\n result: readonly [ResilientState<LlmCall<P>, LlmOk<P, O>>, readonly C[]],\n): readonly [S, readonly C[]] {\n return liftResilience(state, result);\n}\n\n/**\n * Re-export the deadline Sub primitives (inherited from resilient-call) so\n * consumers wire one import: `subscribeDeadline` is the `subscribe` cell,\n * `deadlineSub` builds the Sub literal `subs` emits.\n */\nexport { subscribeDeadline, deadlineSub };\nexport type { DeadlineSub, DeadlineExceeded, ResilientState };\n"]}
@@ -1,11 +1,11 @@
1
1
  import { MsgType } from './chunk-JELSZN25.js';
2
- import { deadlineSub } from './chunk-RMX4EOQK.js';
2
+ import { deadlineSub } from './chunk-ZT3MGQB7.js';
3
3
  import { asRng, recordFailure, shouldRetry, nextDelayMs, initRetry } from './chunk-Q2UM7BXU.js';
4
4
  import { initBucket, tryConsume } from './chunk-S2EGRPCM.js';
5
5
  import { initCircuit, onSuccess, onFailure, canPass } from './chunk-UF4MZSLT.js';
6
- import { initCache, set, get } from './chunk-2KNLKQVN.js';
7
- import { tryInterpret } from './chunk-D4NJBQAH.js';
8
- import { without } from './chunk-PBDYW2TU.js';
6
+ import { initCache, set, get } from './chunk-7UU5P2CX.js';
7
+ import { tryInterpret } from './chunk-PBAYOJJO.js';
8
+ import { without } from './chunk-DBOJ2UC6.js';
9
9
 
10
10
  // src/resilient-call/index.ts
11
11
  function circuitPolicy(config) {
@@ -219,5 +219,5 @@ function liftResilience(state, [slice, cmds]) {
219
219
  }
220
220
 
221
221
  export { createResilientCall, liftResilience };
222
- //# sourceMappingURL=chunk-B5RAZVXW.js.map
223
- //# sourceMappingURL=chunk-B5RAZVXW.js.map
222
+ //# sourceMappingURL=chunk-W2S4HK7J.js.map
223
+ //# sourceMappingURL=chunk-W2S4HK7J.js.map