@code-yeongyu/senpi 2026.11.6-2 → 2026.11.9-2

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 (244) hide show
  1. package/CHANGELOG.md +64 -0
  2. package/dist/bundle/chunks/{bun-extension-importer-RCGPQTI2.js → bun-extension-importer-GQ6IKEXD.js} +2 -2
  3. package/dist/bundle/chunks/{chunk-RQ6CHXQ2.js → chunk-3TZQXZKH.js} +1 -1
  4. package/dist/bundle/chunks/{chunk-QN2BHP3M.js → chunk-44BJPS5I.js} +1 -1
  5. package/dist/bundle/chunks/{chunk-WGRKM3N6.js → chunk-45MVD7BU.js} +1 -1
  6. package/dist/bundle/chunks/chunk-4JIBEZZE.js +106 -0
  7. package/dist/bundle/chunks/{chunk-CF5OGBC4.js → chunk-4NYNMAYF.js} +1 -1
  8. package/dist/bundle/chunks/{chunk-X5ZVYNOL.js → chunk-4UR6UFBH.js} +1 -1
  9. package/dist/bundle/chunks/{chunk-LNPAT22L.js → chunk-527Q2UYM.js} +9 -8
  10. package/dist/bundle/chunks/{chunk-HLM46XEB.js → chunk-622VYTRI.js} +1 -1
  11. package/dist/bundle/chunks/{chunk-D5MRJWUK.js → chunk-7CFORZPE.js} +221 -229
  12. package/dist/bundle/chunks/{chunk-GKAYPJ4E.js → chunk-7PROV6BB.js} +1 -1
  13. package/dist/bundle/chunks/{chunk-4E7GDG4H.js → chunk-7VMEE62L.js} +1 -1
  14. package/dist/bundle/chunks/{chunk-C7OKUC7L.js → chunk-K3UHGDUQ.js} +1 -1
  15. package/dist/bundle/chunks/{chunk-7III44PV.js → chunk-LHSWAJJL.js} +1 -1
  16. package/dist/bundle/chunks/{chunk-47PPZKPO.js → chunk-LM6DOWQH.js} +4 -4
  17. package/dist/bundle/chunks/chunk-LPYBKMPT.js +2 -0
  18. package/dist/bundle/chunks/{chunk-2FDW2LQL.js → chunk-M7GF73FO.js} +2 -2
  19. package/dist/bundle/chunks/{chunk-ECW7VBMB.js → chunk-NDSWTAYJ.js} +1 -1
  20. package/dist/bundle/chunks/{chunk-GSKCWXXG.js → chunk-Q6NR6644.js} +1 -1
  21. package/dist/bundle/chunks/{chunk-TIPKAMSS.js → chunk-SGAFIMAU.js} +1 -1
  22. package/dist/bundle/chunks/{chunk-7PSBD5KS.js → chunk-STGOYIXC.js} +1 -1
  23. package/dist/bundle/chunks/{chunk-DSWDJ7HT.js → chunk-WQ4RBPMS.js} +1 -1
  24. package/dist/bundle/chunks/chunk-XZDCMDUH.js +2 -0
  25. package/dist/bundle/chunks/{chunk-GNSMNSL7.js → chunk-Y5L4UFMI.js} +2 -2
  26. package/dist/bundle/chunks/{cli-main-T74YJGIF.js → cli-main-XBEFQW5A.js} +1 -1
  27. package/dist/bundle/chunks/{dispatch-BAK2YZKD.js → dispatch-VDIYSXQN.js} +1 -1
  28. package/dist/bundle/chunks/{hand-entry-AYRUGE2A.js → hand-entry-QFEY3JMQ.js} +1 -1
  29. package/dist/bundle/chunks/{headless-host-ES74ZB6M.js → headless-host-YXUGONR3.js} +1 -1
  30. package/dist/bundle/chunks/{host-command-YRXZE4TV.js → host-command-VHX2ILLM.js} +1 -1
  31. package/dist/bundle/chunks/{host-session-command-N7IN7ZDV.js → host-session-command-P3MRW63Z.js} +1 -1
  32. package/dist/bundle/chunks/interactive-mode-YGL3QHU6.js +2 -0
  33. package/dist/bundle/chunks/main-5SD4DHMC.js +2 -0
  34. package/dist/bundle/chunks/{multi-session-host-XSUJDHFZ.js → multi-session-host-PGRHY3JP.js} +20 -17
  35. package/dist/bundle/chunks/{package-manager-cli-GLUNPQJU.js → package-manager-cli-SADLU7QX.js} +1 -1
  36. package/dist/bundle/chunks/{role-RRFFHZF5.js → role-4BUU46VW.js} +3 -3
  37. package/dist/bundle/chunks/{rotation-stream-BN2YK2MC.js → rotation-stream-TODBIHXJ.js} +1 -1
  38. package/dist/bundle/chunks/rpc-mode-27TC7W6K.js +2 -0
  39. package/dist/bundle/chunks/{run-attached-tui-W236SEOR.js → run-attached-tui-MXCC5IZR.js} +1 -1
  40. package/dist/bundle/chunks/{runtime-session-OBXYK4PI.js → runtime-session-VO3X4OE3.js} +1 -1
  41. package/dist/bundle/chunks/{schedule-command-B2EJTBOE.js → schedule-command-MX56D6UE.js} +1 -1
  42. package/dist/bundle/chunks/{session-control-endpoint-YIMLHGLG.js → session-control-endpoint-IL6ERD4U.js} +1 -1
  43. package/dist/bundle/chunks/{session-picker-E6ZONUJB.js → session-picker-6VRC25MJ.js} +1 -1
  44. package/dist/bundle/chunks/session-worker.js +114 -110
  45. package/dist/bundle/cli.js +2 -2
  46. package/dist/bundle/index.js +1 -1
  47. package/dist/bundle/rpc-entry.js +1 -1
  48. package/dist/bundle/runtime-manifest.json +1 -1
  49. package/dist/core/agent-session-runtime.d.ts +2 -0
  50. package/dist/core/agent-session-runtime.js +9 -2
  51. package/dist/core/agent-session-services.d.ts +2 -0
  52. package/dist/core/agent-session-services.js +1 -0
  53. package/dist/core/agent-session.d.ts +32 -3
  54. package/dist/core/agent-session.js +142 -29
  55. package/dist/core/credential-pool/rotation-slots.d.ts +3 -3
  56. package/dist/core/credential-pool/state-store.d.ts +1 -1
  57. package/dist/core/egress/classify.d.ts +8 -3
  58. package/dist/core/egress/classify.js +177 -158
  59. package/dist/core/egress/console-redactor.d.ts +7 -0
  60. package/dist/core/egress/console-redactor.js +74 -0
  61. package/dist/core/egress/credentials.d.ts +73 -0
  62. package/dist/core/egress/credentials.js +20 -0
  63. package/dist/core/egress/device-custody.d.ts +1 -1
  64. package/dist/core/egress/device-custody.js +18 -15
  65. package/dist/core/egress/device-proxy-call.d.ts +59 -0
  66. package/dist/core/egress/device-proxy-call.js +148 -0
  67. package/dist/core/egress/device-proxy-lost-calls.d.ts +27 -0
  68. package/dist/core/egress/device-proxy-lost-calls.js +28 -0
  69. package/dist/core/egress/device-proxy-ports.d.ts +49 -0
  70. package/dist/core/egress/device-proxy-ports.js +2 -0
  71. package/dist/core/egress/device-proxy-request.d.ts +34 -0
  72. package/dist/core/egress/device-proxy-request.js +69 -0
  73. package/dist/core/egress/device-proxy-waits.d.ts +40 -0
  74. package/dist/core/egress/device-proxy-waits.js +67 -0
  75. package/dist/core/egress/device-proxy.d.ts +3 -0
  76. package/dist/core/egress/device-proxy.js +214 -0
  77. package/dist/core/egress/endpoint.js +2 -1
  78. package/dist/core/egress/entry-verify.d.ts +13 -0
  79. package/dist/core/egress/entry-verify.js +33 -0
  80. package/dist/core/egress/hpke.d.ts +63 -0
  81. package/dist/core/egress/hpke.js +192 -0
  82. package/dist/core/egress/key-policy-template.d.ts +7 -1
  83. package/dist/core/egress/ledger-schema.d.ts +80 -0
  84. package/dist/core/egress/ledger-schema.js +117 -0
  85. package/dist/core/egress/ledger.d.ts +55 -0
  86. package/dist/core/egress/ledger.js +159 -0
  87. package/dist/core/egress/pinned-infra.d.ts +23 -2
  88. package/dist/core/egress/pinned-infra.js +9 -2
  89. package/dist/core/egress/placement-claim.d.ts +69 -0
  90. package/dist/core/egress/placement-claim.js +115 -0
  91. package/dist/core/egress/pool-state.d.ts +18 -0
  92. package/dist/core/egress/pool-state.js +5 -0
  93. package/dist/core/egress/redact.d.ts +10 -0
  94. package/dist/core/egress/redact.js +134 -0
  95. package/dist/core/egress/sealed-format.d.ts +101 -0
  96. package/dist/core/egress/sealed-format.js +174 -0
  97. package/dist/core/egress/sealed-source.d.ts +35 -0
  98. package/dist/core/egress/sealed-source.js +140 -0
  99. package/dist/core/egress/verified-keys.js +1 -1
  100. package/dist/core/egress/wire.d.ts +228 -0
  101. package/dist/core/egress/wire.js +49 -0
  102. package/dist/core/extensions/builtin/ask-user/escape-residue.d.ts +50 -0
  103. package/dist/core/extensions/builtin/ask-user/escape-residue.js +113 -0
  104. package/dist/core/extensions/builtin/ask-user/extension.js +7 -1
  105. package/dist/core/extensions/builtin/ask-user/resume.js +2 -1
  106. package/dist/core/extensions/builtin/ask-user/tool.d.ts +3 -0
  107. package/dist/core/extensions/builtin/ask-user/tool.js +8 -0
  108. package/dist/core/extensions/builtin/goal/index.js +46 -0
  109. package/dist/core/extensions/builtin/goal/stop-pause.d.ts +1 -0
  110. package/dist/core/extensions/builtin/goal/stop-pause.js +4 -2
  111. package/dist/core/extensions/builtin/monitor-state-event.d.ts +26 -0
  112. package/dist/core/extensions/builtin/monitor-state-event.js +2 -0
  113. package/dist/core/extensions/builtin/permission-system/index.js +10 -4
  114. package/dist/core/extensions/builtin/permission-system/preset-registry.d.ts +2 -0
  115. package/dist/core/extensions/builtin/permission-system/preset-registry.js +11 -0
  116. package/dist/core/extensions/builtin/permission-system/settings.d.ts +2 -0
  117. package/dist/core/extensions/builtin/permission-system/settings.js +10 -1
  118. package/dist/core/extensions/builtin/terminal/background-age.d.ts +25 -0
  119. package/dist/core/extensions/builtin/terminal/background-age.js +91 -0
  120. package/dist/core/extensions/builtin/terminal/extension-state.d.ts +1 -1
  121. package/dist/core/extensions/builtin/terminal/extension-state.js +21 -3
  122. package/dist/core/extensions/builtin/terminal/notify.d.ts +6 -0
  123. package/dist/core/extensions/builtin/terminal/notify.js +39 -5
  124. package/dist/core/extensions/builtin/terminal/session-bundle.d.ts +9 -2
  125. package/dist/core/extensions/builtin/terminal/session-bundle.js +36 -1
  126. package/dist/core/extensions/builtin/terminal/settings.d.ts +2 -0
  127. package/dist/core/extensions/builtin/terminal/settings.js +8 -0
  128. package/dist/core/extensions/builtin/terminal/tools/background-auto-reap.d.ts +15 -0
  129. package/dist/core/extensions/builtin/terminal/tools/background-auto-reap.js +87 -0
  130. package/dist/core/extensions/builtin/terminal/tools/bash.d.ts +3 -1
  131. package/dist/core/extensions/builtin/terminal/tools/bash.js +44 -15
  132. package/dist/core/extensions/builtin/terminal/tools/child-placement.d.ts +8 -0
  133. package/dist/core/extensions/builtin/terminal/tools/child-placement.js +43 -6
  134. package/dist/core/extensions/builtin/terminal/tools/context.d.ts +14 -2
  135. package/dist/core/extensions/bun-extension-importer.js +4 -3
  136. package/dist/core/extensions/bun-extension-load-turns.d.ts +4 -0
  137. package/dist/core/extensions/bun-extension-load-turns.js +45 -0
  138. package/dist/core/extensions/bun-extension-registry.d.ts +1 -0
  139. package/dist/core/extensions/bun-extension-registry.js +19 -1
  140. package/dist/core/extensions/loader.js +5 -0
  141. package/dist/core/extensions/session-control-types.d.ts +15 -0
  142. package/dist/core/extensions/session-control-types.js +11 -0
  143. package/dist/core/extensions/session-dialog-api.d.ts +5 -0
  144. package/dist/core/extensions/session-dialog-api.js +13 -0
  145. package/dist/core/extensions/types.d.ts +14 -1
  146. package/dist/core/external-admission.d.ts +16 -2
  147. package/dist/core/external-admission.js +22 -4
  148. package/dist/core/permission-decision.d.ts +22 -0
  149. package/dist/core/permission-decision.js +53 -0
  150. package/dist/core/permission-mode.d.ts +56 -0
  151. package/dist/core/permission-mode.js +133 -0
  152. package/dist/core/retry-fallback/controller-types.d.ts +6 -0
  153. package/dist/core/retry-fallback/controller.d.ts +9 -0
  154. package/dist/core/retry-fallback/controller.js +34 -3
  155. package/dist/core/retry-fallback/unusable.d.ts +25 -1
  156. package/dist/core/retry-fallback/unusable.js +87 -2
  157. package/dist/core/retry-fallback/usage-limit.d.ts +6 -1
  158. package/dist/core/retry-fallback/usage-limit.js +12 -4
  159. package/dist/core/sdk.d.ts +6 -1
  160. package/dist/core/sdk.js +3 -0
  161. package/dist/core/session-log.js +1 -1
  162. package/dist/core/stall-warning.d.ts +41 -0
  163. package/dist/core/stall-warning.js +72 -0
  164. package/dist/core/terminal-settings.d.ts +1 -0
  165. package/dist/core/turn-origin.d.ts +31 -0
  166. package/dist/core/turn-origin.js +64 -0
  167. package/dist/modes/interactive/components/remote-delivery-message.d.ts +2 -1
  168. package/dist/modes/interactive/components/remote-delivery-message.js +13 -5
  169. package/dist/modes/rpc/admit-external-message-command.d.ts +37 -0
  170. package/dist/modes/rpc/admit-external-message-command.js +61 -0
  171. package/dist/modes/rpc/admit-external-message-wire.d.ts +42 -0
  172. package/dist/modes/rpc/admit-external-message-wire.js +55 -0
  173. package/dist/modes/rpc/client-input-handler.d.ts +2 -0
  174. package/dist/modes/rpc/client-input-handler.js +2 -0
  175. package/dist/modes/rpc/connection-handler.js +30 -5
  176. package/dist/modes/rpc/connection-roles.d.ts +6 -0
  177. package/dist/modes/rpc/connection-roles.js +13 -0
  178. package/dist/modes/rpc/delegated-drive-origin.d.ts +9 -0
  179. package/dist/modes/rpc/delegated-drive-origin.js +16 -0
  180. package/dist/modes/rpc/effect-commands.d.ts +7 -1
  181. package/dist/modes/rpc/host-bare-endpoint-registration.d.ts +7 -0
  182. package/dist/modes/rpc/host-bare-endpoint-registration.js +35 -0
  183. package/dist/modes/rpc/host-capabilities.d.ts +2 -0
  184. package/dist/modes/rpc/host-capabilities.js +3 -0
  185. package/dist/modes/rpc/host-handover-parked.d.ts +11 -0
  186. package/dist/modes/rpc/host-handover-parked.js +17 -0
  187. package/dist/modes/rpc/host-handover-reopen.js +18 -6
  188. package/dist/modes/rpc/host-handover-resident-writer.d.ts +16 -0
  189. package/dist/modes/rpc/host-handover-resident-writer.js +57 -0
  190. package/dist/modes/rpc/host-idle-handover.js +1 -0
  191. package/dist/modes/rpc/host-warm.d.ts +16 -1
  192. package/dist/modes/rpc/host-warm.js +33 -6
  193. package/dist/modes/rpc/method-classes-schema-delivery.d.ts +32 -0
  194. package/dist/modes/rpc/method-classes-schema-delivery.js +22 -0
  195. package/dist/modes/rpc/method-classes-schema-session.d.ts +36 -0
  196. package/dist/modes/rpc/method-classes-schema-session.js +3 -0
  197. package/dist/modes/rpc/method-classes-schema.d.ts +36 -0
  198. package/dist/modes/rpc/method-classes.d.ts +2 -0
  199. package/dist/modes/rpc/method-classes.js +4 -0
  200. package/dist/modes/rpc/multi-session-host.js +6 -1
  201. package/dist/modes/rpc/permission-preset-command.d.ts +6 -0
  202. package/dist/modes/rpc/permission-preset-command.js +27 -0
  203. package/dist/modes/rpc/rpc-input-validation.js +4 -3
  204. package/dist/modes/rpc/rpc-mutation-response-types.d.ts +7 -0
  205. package/dist/modes/rpc/rpc-response-types.d.ts +2 -1
  206. package/dist/modes/rpc/rpc-session-command-types.d.ts +6 -1
  207. package/dist/modes/rpc/rpc-session-event-types.d.ts +5 -2
  208. package/dist/modes/rpc/rpc-session-state-types.d.ts +2 -1
  209. package/dist/modes/rpc/rpc-session-state.js +2 -0
  210. package/dist/modes/rpc/session-attachment-commands.js +3 -3
  211. package/dist/modes/rpc/session-event-writer-connections.js +1 -0
  212. package/dist/modes/rpc/session-event-writer.d.ts +1 -1
  213. package/dist/modes/rpc/session-registry-attach.d.ts +0 -9
  214. package/dist/modes/rpc/session-registry-attach.js +16 -3
  215. package/dist/modes/rpc/session-registry-types.d.ts +2 -0
  216. package/dist/modes/rpc/session-registry.d.ts +2 -0
  217. package/dist/modes/rpc/session-registry.js +16 -3
  218. package/dist/modes/rpc/session-router-close.js +43 -6
  219. package/dist/modes/rpc/session-router-context.js +2 -0
  220. package/dist/modes/rpc/session-router-occupancy.js +2 -11
  221. package/dist/modes/rpc/session-router-open-profile.js +4 -0
  222. package/dist/modes/rpc/session-router-open.d.ts +1 -1
  223. package/dist/modes/rpc/session-router-open.js +6 -17
  224. package/dist/modes/rpc/session-router-residency.d.ts +24 -0
  225. package/dist/modes/rpc/session-router-residency.js +90 -0
  226. package/dist/modes/rpc/session-router-routing.js +7 -1
  227. package/dist/modes/rpc/session-status.js +4 -1
  228. package/dist/modes/rpc/session-worker.js +4 -0
  229. package/dist/modes/rpc/tool-child/terminal-jobs.d.ts +1 -0
  230. package/dist/modes/rpc/tool-child/terminal-jobs.js +17 -1
  231. package/dist/modes/rpc/worker-session-registry.js +5 -2
  232. package/docs/engine-socket/fixtures/admission-routes/h-legacy.json +1 -1
  233. package/docs/engine-socket/fixtures/rpc-inbound-samples.json +29 -2
  234. package/docs/extensions.md +12 -0
  235. package/docs/rpc.md +129 -12
  236. package/docs/terminal-tools.md +25 -1
  237. package/native/socket-secret/prebuilds/win32-arm64/senpi_socket_secret.win32-arm64.node +0 -0
  238. package/native/socket-secret/prebuilds/win32-x64/senpi_socket_secret.win32-x64.node +0 -0
  239. package/package.json +6 -6
  240. package/dist/bundle/chunks/chunk-C7HEEBFH.js +0 -2
  241. package/dist/bundle/chunks/chunk-XCMII2VQ.js +0 -98
  242. package/dist/bundle/chunks/interactive-mode-B5SQDLH7.js +0 -2
  243. package/dist/bundle/chunks/main-UQFVFCNY.js +0 -2
  244. package/dist/bundle/chunks/rpc-mode-JYN3E7P7.js +0 -2
@@ -0,0 +1,214 @@
1
+ import { createAssistantMessageEventStream, } from "@earendil-works/pi-ai";
2
+ import { followCall } from "./device-proxy-call.js";
3
+ import { lostCallPacer } from "./device-proxy-lost-calls.js";
4
+ import { ABORT_TEXT, admittedRequestJson, failureMessage, parseStoredRequest, planDispatch, } from "./device-proxy-request.js";
5
+ import { createAbortion, nextKeyBackoffMs, stepWaits } from "./device-proxy-waits.js";
6
+ /** The `done` reason of a finished, successful stop; `undefined` (never replayed) for error, aborted or pending. */
7
+ const doneReason = (stopReason) => stopReason === "error" || stopReason === "aborted" || stopReason === "pending" ? undefined : stopReason;
8
+ export function createDeviceProxyEgress(deps) {
9
+ const { ledger, route, host } = deps;
10
+ const abortion = createAbortion();
11
+ const waitsOf = (run) => stepWaits(run.out, host, run.step, run.signal, abortion);
12
+ const callContext = (run, deviceId) => ({
13
+ out: run.out,
14
+ model: run.model,
15
+ ledger,
16
+ channel: deps.hands.channel(deviceId),
17
+ offlineFor: deps.hands.offlineFor,
18
+ waits: waitsOf(run),
19
+ conditions: deps.conditions,
20
+ clock: deps.clock,
21
+ bounds: deps.bounds,
22
+ signal: run.signal,
23
+ abortion,
24
+ claim: deps.claims.current,
25
+ live: run.live,
26
+ });
27
+ const servedDecision = async (run) => {
28
+ for (;;) {
29
+ const decision = route.decide(run.model.provider, await ledger.variants(run.model.provider));
30
+ const { deviceId } = decision;
31
+ if (decision.servedNow && decision.route === "device" && deviceId !== undefined)
32
+ return { ...decision, deviceId };
33
+ if (decision.servedNow)
34
+ throw new Error(`the device route cannot serve a ${decision.route} decision`);
35
+ const waitFor = decision.waitFor ?? [];
36
+ const wait = { reason: decision.reason, steer: decision.steer ?? "", waitFor };
37
+ await waitsOf(run).hold(wait, deps.conditions.until(waitFor, run.signal));
38
+ }
39
+ };
40
+ /** 4.6: renew an expired or superseded claim, back off on an unknown registry key, and restart the same call. */
41
+ const startWithClaim = async (run, dispatched, decision, plan) => {
42
+ let row = dispatched;
43
+ const deviceId = row.deviceId ?? "";
44
+ const expectedSourceDigest = decision.serveOwnDefinition ? undefined : route.headDigest(row.providerId);
45
+ const maxOutputTokens = run.options?.maxTokens;
46
+ let backoff;
47
+ for (;;) {
48
+ const start = {
49
+ t: "provider.call.start",
50
+ callId: row.callId,
51
+ sessionId: deps.sessionId,
52
+ call: row.call,
53
+ claim: await deps.claims.current(),
54
+ providerId: row.providerId,
55
+ modelId: row.modelId,
56
+ ...(expectedSourceDigest === undefined ? {} : { expectedSourceDigest }),
57
+ hooks: plan.hooks,
58
+ ...(deps.cwd === undefined ? {} : { cwd: deps.cwd }),
59
+ request: parseStoredRequest(plan.requestJson),
60
+ budget: {
61
+ deadlineMs: deps.bounds.deadlineMs,
62
+ ...(maxOutputTokens === undefined ? {} : { maxOutputTokens }),
63
+ },
64
+ };
65
+ const outcome = await followCall(callContext(run, deviceId), row, { start });
66
+ if (outcome.kind !== "refused")
67
+ return outcome;
68
+ await deps.hands.channel(deviceId).ack({ t: "provider.call.ack", callId: row.callId });
69
+ const waitFor = [{ kind: "claim-renewed" }];
70
+ const code = outcome.code;
71
+ if (code === "registry_key_unknown") {
72
+ backoff = nextKeyBackoffMs(backoff);
73
+ const pause = deps.clock.sleep(backoff, run.signal);
74
+ await waitsOf(run).hold({ reason: "claim-expired", steer: outcome.message, waitFor }, pause);
75
+ }
76
+ else {
77
+ const steer = "renewing this session's placement claim";
78
+ await waitsOf(run).hold({ reason: "claim-expired", steer, waitFor }, deps.claims.renew(code));
79
+ }
80
+ row = await ledger.replaceRefused(outcome.row, crypto.randomUUID());
81
+ }
82
+ };
83
+ /** A lost (`unknown`) or mismatched call is superseded; a mismatch records the device variant in the same write. */
84
+ const retire = async (run, outcome) => {
85
+ const { row } = outcome;
86
+ const deviceId = row.deviceId ?? "";
87
+ if (outcome.kind === "unknown")
88
+ await deps.hands
89
+ .channel(deviceId)
90
+ .cancel({ t: "provider.call.cancel", callId: row.callId, reason: "superseded" });
91
+ const headDigest = route.headDigest(row.providerId);
92
+ const variant = outcome.kind === "mismatch" && headDigest !== undefined
93
+ ? {
94
+ providerId: row.providerId,
95
+ deviceId,
96
+ stepId: run.step.stepId,
97
+ headDigest,
98
+ liveAtMismatch: route.liveDigest(deviceId, row.providerId),
99
+ }
100
+ : undefined;
101
+ const superseded = await ledger.supersede(row, variant);
102
+ run.live.row = superseded;
103
+ deps.notify({ t: "step.reset", sessionId: deps.sessionId, stepId: run.step.stepId, cause: "superseded" });
104
+ return superseded;
105
+ };
106
+ const serve = async (run, pacer, firstRequest, firstPrevious) => {
107
+ let stored = firstRequest;
108
+ let previous = firstPrevious;
109
+ let attempt = previous && previous.call.kind === "step" ? previous.call.attempt + 1 : run.step.attempt;
110
+ const providerId = run.model.provider;
111
+ for (;;) {
112
+ if (previous !== undefined && route.capability(providerId) === "callback-stream") {
113
+ const waitFor = [{ kind: "user-choice", choiceId: `${run.step.stepId}:${attempt}` }];
114
+ const steer = "re-run this step (tools may run again) or select another device";
115
+ await waitsOf(run).hold({ reason: "capability:callback-stream", steer, waitFor }, deps.conditions.until(waitFor, run.signal));
116
+ }
117
+ const decision = await servedDecision(run);
118
+ const plan = await planDispatch(decision, stored, deps.requestHooks);
119
+ const next = {
120
+ callId: crypto.randomUUID(),
121
+ sessionId: deps.sessionId,
122
+ call: { kind: "step", stepId: run.step.stepId, attempt },
123
+ route: "device",
124
+ deviceId: decision.deviceId,
125
+ providerId,
126
+ modelId: run.model.id,
127
+ epoch: run.step.fence.epoch,
128
+ capability: route.capability(providerId),
129
+ hooksApplied: plan.hooksApplied,
130
+ };
131
+ const row = await ledger.dispatch(next, plan.requestJson, previous);
132
+ const outcome = await startWithClaim(run, row, decision, plan);
133
+ if (outcome.kind === "ended")
134
+ return;
135
+ previous = await retire(run, outcome);
136
+ stored = { requestJson: await ledger.readRequest(previous), hooksApplied: previous.hooksApplied };
137
+ attempt += 1;
138
+ if (outcome.kind === "unknown")
139
+ await pacer.beforeReissue(decision.deviceId, attempt);
140
+ }
141
+ };
142
+ /** The 4.5 replay rules for a step that already has a row, applied before any route decision or request hook. */
143
+ const runStep = async (run, context) => {
144
+ const { stepId } = run.step;
145
+ if (await host.historyHasStep(stepId))
146
+ throw new Error(`step ${stepId} is already committed; resume from history`);
147
+ if (await ledger.isCancelled(stepId)) {
148
+ run.out.push({ type: "error", reason: "aborted", error: failureMessage(run.model, "aborted", ABORT_TEXT) });
149
+ return;
150
+ }
151
+ const latest = (await ledger.rowsForStep(stepId)).at(-1);
152
+ const reason = latest?.result?.kind === "message" ? doneReason(latest.result.stopReason) : undefined;
153
+ if (latest?.state === "ended" && reason !== undefined) {
154
+ const message = await ledger.readMessage(latest);
155
+ run.out.push({ type: "start", partial: { ...message } });
156
+ run.out.push({ type: "done", reason, message });
157
+ return;
158
+ }
159
+ const askUser = route.capability(run.model.provider) !== "callback-stream";
160
+ const pacer = lostCallPacer({ ...deps, stepId, waits: waitsOf(run), signal: run.signal, askUser });
161
+ const resumable = latest?.state === "dispatched" || latest?.state === "ended" || latest?.state === "superseded";
162
+ if (!latest || !resumable) {
163
+ const admitted = { requestJson: admittedRequestJson(context, run.options), hooksApplied: false };
164
+ return serve(run, pacer, admitted, undefined);
165
+ }
166
+ let previous = latest;
167
+ // The resumed row is live until the next W4 retires it: an abort in the waits before that must delete its chunks.
168
+ run.live.row = latest;
169
+ if (latest.state === "dispatched") {
170
+ const deviceId = latest.deviceId ?? "";
171
+ if (deps.hands.offlineFor(deviceId) >= deps.bounds.resumeWindowMs) {
172
+ const waitFor = [{ kind: "device-online", deviceId }];
173
+ const pinned = deps.conditions.until(waitFor, run.signal);
174
+ await waitsOf(run).hold({ reason: "device-offline", steer: `open omo on ${deviceId}`, waitFor }, pinned);
175
+ }
176
+ const outcome = await followCall(callContext(run, deviceId), latest, { resume: true });
177
+ if (outcome.kind === "ended")
178
+ return;
179
+ if (outcome.kind === "refused")
180
+ throw new Error(`device ${deviceId} refused the placement claim: ${outcome.message}`);
181
+ previous = await retire(run, outcome);
182
+ if (outcome.kind === "unknown" && latest.call.kind === "step")
183
+ await pacer.beforeReissue(deviceId, latest.call.attempt + 1);
184
+ }
185
+ const stored = { requestJson: await ledger.readRequest(previous), hooksApplied: previous.hooksApplied };
186
+ return serve(run, pacer, stored, previous);
187
+ };
188
+ const abortFailure = async (run) => {
189
+ const row = run.live.row;
190
+ let cause;
191
+ await ledger.cancelStep(run.step.stepId, row).catch((error) => {
192
+ cause = error;
193
+ });
194
+ if (row?.deviceId !== undefined && row.state === "dispatched")
195
+ await deps.hands
196
+ .channel(row.deviceId)
197
+ .cancel({ t: "provider.call.cancel", callId: row.callId, reason: "user" });
198
+ return new Error(ABORT_TEXT, cause === undefined ? undefined : { cause });
199
+ };
200
+ return {
201
+ stream: (model, context, options) => {
202
+ const out = createAssistantMessageEventStream();
203
+ const signal = options?.signal ?? new AbortController().signal;
204
+ const run = { out, model, options, step: host.currentStep(), signal, live: { row: undefined } };
205
+ out.trackLocalWork(runStep(run, context)).catch(async (error) => {
206
+ if (!signal.aborted)
207
+ return out.fail(error);
208
+ out.fail(await abortFailure(run).catch((cause) => new Error(ABORT_TEXT, { cause })));
209
+ });
210
+ return out;
211
+ },
212
+ };
213
+ }
214
+ //# sourceMappingURL=device-proxy.js.map
@@ -36,7 +36,8 @@ function isTemplatedUrlPart(raw) {
36
36
  return isCommandConfigValue(raw) || getConfigValueEnvVarNames(raw).length > 0;
37
37
  }
38
38
  function isKeyishQueryName(name) {
39
- return KEYISH_QUERY_NAMES.includes(name.toLowerCase());
39
+ const lower = name.toLowerCase();
40
+ return KEYISH_QUERY_NAMES.some((keyish) => keyish === lower);
40
41
  }
41
42
  export function splitEndpoint(baseUrl, secretUrlParts = []) {
42
43
  const match = URL_SHAPE.exec(baseUrl);
@@ -0,0 +1,13 @@
1
+ export declare const ENTRY_SIG_DOMAIN = "omo-custody/provider-entry/v1";
2
+ /** r11.1 C.P5: entrySig signs ASCII(domain) || 0x00 || ASCII(entryDigest), entryDigest in lowercase hex. */
3
+ export declare function entrySigPayload(entryDigest: string): Uint8Array;
4
+ export interface EntrySigInput {
5
+ readonly entryDigest: string;
6
+ /** Canonical base64url without padding. */
7
+ readonly entrySig: string;
8
+ /** The owner's enrolled Ed25519 public key (32 raw bytes). */
9
+ readonly ownerPublicKey: Uint8Array;
10
+ }
11
+ /** The brain-side check before dialing (custody design 3.2); a false result is a wait, never a dial. */
12
+ export declare function verifyEntrySig(input: EntrySigInput): Promise<boolean>;
13
+ //# sourceMappingURL=entry-verify.d.ts.map
@@ -0,0 +1,33 @@
1
+ import { strictEd25519Verify } from "../../modes/rpc/ed25519-strict.js";
2
+ export const ENTRY_SIG_DOMAIN = "omo-custody/provider-entry/v1";
3
+ const LOWER_HEX = /^[0-9a-f]+$/u;
4
+ const BASE64URL_NO_PAD = /^[A-Za-z0-9_-]+$/u;
5
+ const ED25519_PUBLIC_KEY_BYTES = 32;
6
+ const ED25519_SIGNATURE_BYTES = 64;
7
+ /** r11.1 C.P5: entrySig signs ASCII(domain) || 0x00 || ASCII(entryDigest), entryDigest in lowercase hex. */
8
+ export function entrySigPayload(entryDigest) {
9
+ if (!LOWER_HEX.test(entryDigest))
10
+ throw new TypeError("entryDigest must be lowercase hexadecimal");
11
+ const domain = Buffer.from(ENTRY_SIG_DOMAIN, "ascii");
12
+ return new Uint8Array(Buffer.concat([domain, Buffer.from([0]), Buffer.from(entryDigest, "ascii")]));
13
+ }
14
+ function decodeSignature(entrySig) {
15
+ if (!BASE64URL_NO_PAD.test(entrySig))
16
+ return undefined;
17
+ const bytes = Buffer.from(entrySig, "base64url");
18
+ if (bytes.length !== ED25519_SIGNATURE_BYTES || bytes.toString("base64url") !== entrySig)
19
+ return undefined;
20
+ return new Uint8Array(bytes);
21
+ }
22
+ /** The brain-side check before dialing (custody design 3.2); a false result is a wait, never a dial. */
23
+ export async function verifyEntrySig(input) {
24
+ if (input.ownerPublicKey.length !== ED25519_PUBLIC_KEY_BYTES)
25
+ return false;
26
+ if (!LOWER_HEX.test(input.entryDigest))
27
+ return false;
28
+ const signature = decodeSignature(input.entrySig);
29
+ if (signature === undefined)
30
+ return false;
31
+ return strictEd25519Verify(input.ownerPublicKey, entrySigPayload(input.entryDigest), signature);
32
+ }
33
+ //# sourceMappingURL=entry-verify.js.map
@@ -0,0 +1,63 @@
1
+ /**
2
+ * HPKE base mode (RFC 9180): DHKEM(P-256, HKDF-SHA256), HKDF-SHA256, AES-256-GCM, on Web Crypto only so the same code
3
+ * runs on Bun, Node, compiled binaries, the desktop renderer and Workers. Every wrap is single-shot (sequence 0).
4
+ *
5
+ * The recipient side takes a Diffie-Hellman function instead of a private key, so a recipient whose key never leaves
6
+ * hardware (a KMS key that only performs ECDH) opens wraps through the same code as a device key.
7
+ *
8
+ * Base mode authenticates nothing about the sender: anyone holding a recipient public key can produce a wrap that opens.
9
+ * A successful open proves only that the wrap was made for this key, so origin is established above this layer
10
+ * (signed wraps documents, per-dekId owners), never by an open.
11
+ */
12
+ export type SealedFormatErrorCode = "open_failed" | "unsupported_version" | "invalid_public_key" | "invalid_private_key" | "invalid_key" | "invalid_aad" | "empty_secret";
13
+ /** Errors carry a code only: no key material, ciphertext or plaintext is ever echoed. */
14
+ export declare class SealedFormatError extends Error {
15
+ readonly code: SealedFormatErrorCode;
16
+ constructor(code: SealedFormatErrorCode);
17
+ }
18
+ /** A P-256 HPKE recipient. `dh` returns the 32-byte x-coordinate of the ECDH shared point with `peer`. */
19
+ export interface HpkeRecipient {
20
+ /** Uncompressed SEC1 encoding (65 bytes, leading 0x04). */
21
+ readonly publicKey: Uint8Array;
22
+ dh(peer: Uint8Array): Promise<Uint8Array>;
23
+ }
24
+ export interface HpkeSealInput {
25
+ readonly info: Uint8Array;
26
+ readonly aad: Uint8Array;
27
+ readonly pt: Uint8Array;
28
+ }
29
+ export interface HpkeOpenInput {
30
+ readonly enc: Uint8Array;
31
+ readonly ct: Uint8Array;
32
+ readonly info: Uint8Array;
33
+ readonly aad: Uint8Array;
34
+ }
35
+ export interface HpkeSealed {
36
+ readonly enc: Uint8Array;
37
+ readonly ct: Uint8Array;
38
+ }
39
+ type Bytes = Uint8Array<ArrayBuffer>;
40
+ /** A recipient whose private scalar is held in this process (device keys, tests). */
41
+ export declare function p256Recipient(privateKey: Uint8Array, publicKey: Uint8Array): Promise<HpkeRecipient>;
42
+ /** RFC 9180 Decap: the shared secret for `enc` addressed to `recipient`. */
43
+ export declare function hpkeDecap(recipient: HpkeRecipient, enc: Uint8Array): Promise<Bytes>;
44
+ /**
45
+ * RFC 9180 Encap with a caller-supplied ephemeral key pair. KNOWN-ANSWER TESTS ONLY: reusing an ephemeral key
46
+ * across wraps breaks HPKE's guarantees. Production sealing goes through {@link hpkeSealBase}, which mints one.
47
+ * @internal
48
+ */
49
+ export declare function hpkeEncapDeterministic(recipientPublicKey: Uint8Array, ephemeralPrivateKey: Uint8Array, ephemeralPublicKey: Uint8Array): Promise<{
50
+ readonly enc: Uint8Array;
51
+ readonly sharedSecret: Uint8Array;
52
+ }>;
53
+ /** RFC 9180 KeySchedule for mode_base (no PSK). The exporter secret is not derived: the format never exports. */
54
+ export declare function hpkeKeySchedule(sharedSecret: Uint8Array, info: Uint8Array): Promise<{
55
+ readonly key: Bytes;
56
+ readonly baseNonce: Bytes;
57
+ }>;
58
+ /** Single-shot HPKE seal (sequence 0) to `recipientPublicKey` under a freshly generated ephemeral key. */
59
+ export declare function hpkeSealBase(recipientPublicKey: Uint8Array, input: HpkeSealInput): Promise<HpkeSealed>;
60
+ /** Single-shot HPKE open (sequence 0). Any failure is the one code `open_failed`. */
61
+ export declare function hpkeOpenBase(recipient: HpkeRecipient, input: HpkeOpenInput): Promise<Bytes>;
62
+ export {};
63
+ //# sourceMappingURL=hpke.d.ts.map
@@ -0,0 +1,192 @@
1
+ /**
2
+ * HPKE base mode (RFC 9180): DHKEM(P-256, HKDF-SHA256), HKDF-SHA256, AES-256-GCM, on Web Crypto only so the same code
3
+ * runs on Bun, Node, compiled binaries, the desktop renderer and Workers. Every wrap is single-shot (sequence 0).
4
+ *
5
+ * The recipient side takes a Diffie-Hellman function instead of a private key, so a recipient whose key never leaves
6
+ * hardware (a KMS key that only performs ECDH) opens wraps through the same code as a device key.
7
+ *
8
+ * Base mode authenticates nothing about the sender: anyone holding a recipient public key can produce a wrap that opens.
9
+ * A successful open proves only that the wrap was made for this key, so origin is established above this layer
10
+ * (signed wraps documents, per-dekId owners), never by an open.
11
+ */
12
+ const HPKE_V1 = "HPKE-v1";
13
+ const KEM_ID = 0x0010; // DHKEM(P-256, HKDF-SHA256)
14
+ const KDF_ID = 0x0001; // HKDF-SHA256
15
+ const AEAD_ID = 0x0002; // AES-256-GCM
16
+ const MODE_BASE = 0x00;
17
+ const N_SECRET = 32;
18
+ const N_DH = 32;
19
+ const N_K = 32;
20
+ const N_N = 12;
21
+ const N_T = 16;
22
+ const N_PK = 65;
23
+ const HASH_LEN = 32;
24
+ /** Errors carry a code only: no key material, ciphertext or plaintext is ever echoed. */
25
+ export class SealedFormatError extends Error {
26
+ constructor(code) {
27
+ super(`sealed format: ${code}`);
28
+ this.name = "SealedFormatError";
29
+ this.code = code;
30
+ }
31
+ }
32
+ const encoder = new TextEncoder();
33
+ const own = (view) => Uint8Array.from(view);
34
+ function i2osp(value, length) {
35
+ const out = new Uint8Array(length);
36
+ for (let index = length - 1, rest = value; index >= 0; index--, rest = Math.floor(rest / 256))
37
+ out[index] = rest & 0xff;
38
+ return out;
39
+ }
40
+ function concat(...parts) {
41
+ const out = new Uint8Array(parts.reduce((total, part) => total + part.length, 0));
42
+ let offset = 0;
43
+ for (const part of parts) {
44
+ out.set(part, offset);
45
+ offset += part.length;
46
+ }
47
+ return out;
48
+ }
49
+ function kemSuiteId() {
50
+ return concat(encoder.encode("KEM"), i2osp(KEM_ID, 2));
51
+ }
52
+ function hpkeSuiteId() {
53
+ return concat(encoder.encode("HPKE"), i2osp(KEM_ID, 2), i2osp(KDF_ID, 2), i2osp(AEAD_ID, 2));
54
+ }
55
+ async function hmacSha256(key, data) {
56
+ const hmacKey = await crypto.subtle.importKey("raw", key, { name: "HMAC", hash: "SHA-256" }, false, ["sign"]);
57
+ return new Uint8Array(await crypto.subtle.sign("HMAC", hmacKey, data));
58
+ }
59
+ // RFC 5869: an absent salt is HashLen zero bytes.
60
+ async function hkdfExtract(salt, ikm) {
61
+ return hmacSha256(salt.length === 0 ? new Uint8Array(HASH_LEN) : own(salt), ikm);
62
+ }
63
+ async function hkdfExpand(prk, info, length) {
64
+ const out = new Uint8Array(length);
65
+ let previous = new Uint8Array();
66
+ for (let block = 1, offset = 0; offset < length; block++) {
67
+ previous = await hmacSha256(prk, concat(previous, info, Uint8Array.of(block)));
68
+ out.set(previous.subarray(0, Math.min(previous.length, length - offset)), offset);
69
+ offset += previous.length;
70
+ }
71
+ return out;
72
+ }
73
+ function labeledExtract(suiteId, salt, label, ikm) {
74
+ return hkdfExtract(salt, concat(encoder.encode(HPKE_V1), suiteId, encoder.encode(label), ikm));
75
+ }
76
+ function labeledExpand(suiteId, prk, label, info, length) {
77
+ return hkdfExpand(prk, concat(i2osp(length, 2), encoder.encode(HPKE_V1), suiteId, encoder.encode(label), info), length);
78
+ }
79
+ async function extractAndExpand(dh, kemContext) {
80
+ const eaePrk = await labeledExtract(kemSuiteId(), new Uint8Array(), "eae_prk", dh);
81
+ return labeledExpand(kemSuiteId(), eaePrk, "shared_secret", kemContext, N_SECRET);
82
+ }
83
+ async function importPublicKey(raw) {
84
+ if (raw.length !== N_PK || raw[0] !== 0x04)
85
+ throw new SealedFormatError("invalid_public_key");
86
+ try {
87
+ return await crypto.subtle.importKey("raw", own(raw), { name: "ECDH", namedCurve: "P-256" }, true, []);
88
+ }
89
+ catch {
90
+ throw new SealedFormatError("invalid_public_key");
91
+ }
92
+ }
93
+ function base64Url(bytes) {
94
+ let binary = "";
95
+ for (const byte of bytes)
96
+ binary += String.fromCharCode(byte);
97
+ return btoa(binary).replaceAll("+", "-").replaceAll("/", "_").replace(/=+$/, "");
98
+ }
99
+ async function importPrivateKey(privateKey, publicKey) {
100
+ if (privateKey.length !== 32 || publicKey.length !== N_PK || publicKey[0] !== 0x04) {
101
+ throw new SealedFormatError("invalid_private_key");
102
+ }
103
+ try {
104
+ return await crypto.subtle.importKey("jwk", {
105
+ kty: "EC",
106
+ crv: "P-256",
107
+ d: base64Url(privateKey),
108
+ x: base64Url(publicKey.subarray(1, 33)),
109
+ y: base64Url(publicKey.subarray(33, 65)),
110
+ }, { name: "ECDH", namedCurve: "P-256" }, false, ["deriveBits"]);
111
+ }
112
+ catch {
113
+ throw new SealedFormatError("invalid_private_key");
114
+ }
115
+ }
116
+ async function ecdh(privateKey, peer) {
117
+ const peerKey = await importPublicKey(peer);
118
+ return new Uint8Array(await crypto.subtle.deriveBits({ name: "ECDH", public: peerKey }, privateKey, N_DH * 8));
119
+ }
120
+ /** A recipient whose private scalar is held in this process (device keys, tests). */
121
+ export async function p256Recipient(privateKey, publicKey) {
122
+ const key = await importPrivateKey(privateKey, publicKey);
123
+ return { publicKey: Uint8Array.from(publicKey), dh: (peer) => ecdh(key, peer) };
124
+ }
125
+ /** RFC 9180 Decap: the shared secret for `enc` addressed to `recipient`. */
126
+ export async function hpkeDecap(recipient, enc) {
127
+ await importPublicKey(enc);
128
+ const dh = await recipient.dh(enc);
129
+ return extractAndExpand(dh, concat(enc, recipient.publicKey));
130
+ }
131
+ /**
132
+ * RFC 9180 Encap with a caller-supplied ephemeral key pair. KNOWN-ANSWER TESTS ONLY: reusing an ephemeral key
133
+ * across wraps breaks HPKE's guarantees. Production sealing goes through {@link hpkeSealBase}, which mints one.
134
+ * @internal
135
+ */
136
+ export async function hpkeEncapDeterministic(recipientPublicKey, ephemeralPrivateKey, ephemeralPublicKey) {
137
+ const ephemeral = await importPrivateKey(ephemeralPrivateKey, ephemeralPublicKey);
138
+ return encapWith(recipientPublicKey, ephemeral, Uint8Array.from(ephemeralPublicKey));
139
+ }
140
+ async function encapWith(recipientPublicKey, ephemeral, enc) {
141
+ const dh = await ecdh(ephemeral, recipientPublicKey);
142
+ return { enc, sharedSecret: await extractAndExpand(dh, concat(enc, recipientPublicKey)) };
143
+ }
144
+ /** RFC 9180 KeySchedule for mode_base (no PSK). The exporter secret is not derived: the format never exports. */
145
+ export async function hpkeKeySchedule(sharedSecret, info) {
146
+ const empty = new Uint8Array();
147
+ const pskIdHash = await labeledExtract(hpkeSuiteId(), empty, "psk_id_hash", empty);
148
+ const infoHash = await labeledExtract(hpkeSuiteId(), empty, "info_hash", info);
149
+ const context = concat(Uint8Array.of(MODE_BASE), pskIdHash, infoHash);
150
+ const secret = await labeledExtract(hpkeSuiteId(), sharedSecret, "secret", empty);
151
+ return {
152
+ key: await labeledExpand(hpkeSuiteId(), secret, "key", context, N_K),
153
+ baseNonce: await labeledExpand(hpkeSuiteId(), secret, "base_nonce", context, N_N),
154
+ };
155
+ }
156
+ async function aesGcmKey(key, usage) {
157
+ return crypto.subtle.importKey("raw", key, { name: "AES-GCM", length: N_K * 8 }, false, [usage]);
158
+ }
159
+ /** Single-shot HPKE seal (sequence 0) to `recipientPublicKey` under a freshly generated ephemeral key. */
160
+ export async function hpkeSealBase(recipientPublicKey, input) {
161
+ await importPublicKey(recipientPublicKey);
162
+ const ephemeral = await crypto.subtle.generateKey({ name: "ECDH", namedCurve: "P-256" }, false, ["deriveBits"]);
163
+ const enc = new Uint8Array(await crypto.subtle.exportKey("raw", ephemeral.publicKey));
164
+ const { sharedSecret } = await encapWith(recipientPublicKey, ephemeral.privateKey, enc);
165
+ const { key, baseNonce } = await hpkeKeySchedule(sharedSecret, input.info);
166
+ const ct = await crypto.subtle.encrypt({ name: "AES-GCM", iv: baseNonce, additionalData: own(input.aad), tagLength: N_T * 8 }, await aesGcmKey(key, "encrypt"), own(input.pt));
167
+ sharedSecret.fill(0);
168
+ key.fill(0);
169
+ return { enc, ct: new Uint8Array(ct) };
170
+ }
171
+ /** Single-shot HPKE open (sequence 0). Any failure is the one code `open_failed`. */
172
+ export async function hpkeOpenBase(recipient, input) {
173
+ if (input.ct.length < N_T)
174
+ throw new SealedFormatError("open_failed");
175
+ let sharedSecret;
176
+ let key;
177
+ try {
178
+ sharedSecret = await hpkeDecap(recipient, input.enc);
179
+ const schedule = await hpkeKeySchedule(sharedSecret, input.info);
180
+ key = schedule.key;
181
+ const pt = await crypto.subtle.decrypt({ name: "AES-GCM", iv: schedule.baseNonce, additionalData: own(input.aad), tagLength: N_T * 8 }, await aesGcmKey(key, "decrypt"), own(input.ct));
182
+ return new Uint8Array(pt);
183
+ }
184
+ catch {
185
+ throw new SealedFormatError("open_failed");
186
+ }
187
+ finally {
188
+ sharedSecret?.fill(0);
189
+ key?.fill(0);
190
+ }
191
+ }
192
+ //# sourceMappingURL=hpke.js.map
@@ -13,5 +13,11 @@ export interface KeyPolicyTemplate {
13
13
  readonly roles: CustodyRoleArns;
14
14
  }): string;
15
15
  }
16
- export declare const KEY_POLICY_TEMPLATE_V1: KeyPolicyTemplate;
16
+ export declare const KEY_POLICY_TEMPLATE_V1: {
17
+ readonly render: ({ keyKind, roles }: {
18
+ readonly accountId: string;
19
+ readonly keyKind: KeyPolicyKind;
20
+ readonly roles: CustodyRoleArns;
21
+ }) => string;
22
+ };
17
23
  //# sourceMappingURL=key-policy-template.d.ts.map
@@ -0,0 +1,80 @@
1
+ /**
2
+ * Schema of the brain's `egress_call` ledger (row CK9; custody design 4.5, 2.3, 4.2, 15.7). One row per dialed model
3
+ * call with its admitted request and, once `ended`, its assembled message stored beside it in 96 KiB chunks (never the
4
+ * event log: every streamed event carries the whole message so far), the step-cancelled marker (W14), and the
5
+ * per-session device variants (`session/variants`, read by the route). Every value here is written by `ledger.ts`
6
+ * through the host's admitted mutations; this module only names keys and converts rows to and from store values.
7
+ */
8
+ import type { DeviceId, ProviderId } from "@earendil-works/pi-agent-core";
9
+ import type { StoreValue } from "@earendil-works/pi-agent-core/conformance";
10
+ import type { AssistantMessage, StopReason, Usage } from "@earendil-works/pi-ai";
11
+ import { type ProviderCallErrorCode, type ProviderCallKind } from "./wire.ts";
12
+ /** `dispatched` -> `ended` -> `closed` (by the step commit, W9); `superseded` and `cancelled` are final. */
13
+ export type EgressCallState = "dispatched" | "ended" | "superseded" | "cancelled" | "closed";
14
+ /** `callback-stream` providers run tools inside the stream, so every reissue of their step needs a user choice. */
15
+ export type EgressCallCapability = "callback-stream" | "stream";
16
+ /** `message`: the assembled message lives in `chunks` result chunks; the row keeps only its stop reason and usage. */
17
+ export type EgressCallResult = {
18
+ readonly kind: "message";
19
+ readonly stopReason: StopReason;
20
+ readonly usage: Usage;
21
+ readonly chunks: number;
22
+ } | {
23
+ readonly kind: "error";
24
+ readonly code: ProviderCallErrorCode;
25
+ readonly message: string;
26
+ readonly retryable: boolean;
27
+ readonly usage?: Usage;
28
+ };
29
+ /** What `ended` records: the assembled message of a finished call, or the hand's refusal or failure. */
30
+ export type EgressCallEnd = {
31
+ readonly kind: "message";
32
+ readonly message: AssistantMessage;
33
+ } | Extract<EgressCallResult, {
34
+ readonly kind: "error";
35
+ }>;
36
+ export interface EgressCallRow {
37
+ readonly callId: string;
38
+ readonly sessionId: string;
39
+ readonly call: ProviderCallKind;
40
+ readonly route: "sealed" | "device";
41
+ readonly deviceId?: DeviceId;
42
+ readonly providerId: ProviderId;
43
+ readonly modelId: string;
44
+ readonly epoch: number;
45
+ readonly capability: EgressCallCapability;
46
+ /** The stored request already carries this session's request hooks (4.5). */
47
+ readonly hooksApplied: boolean;
48
+ readonly state: EgressCallState;
49
+ readonly requestChunks: number;
50
+ readonly result?: EgressCallResult;
51
+ }
52
+ /** Durable `session/variants[P][deviceId]` (4.2); `mismatches` counts per (device, step) and restarts on a new head. */
53
+ export interface VariantRecord {
54
+ readonly headDigest: string;
55
+ readonly liveAtMismatch: string | undefined;
56
+ readonly mismatches: number;
57
+ }
58
+ export declare const REQUEST_CHUNK_BYTES: number;
59
+ export declare const callKey: (callId: string) => string;
60
+ export declare const requestPrefix: (callId: string) => string;
61
+ export declare const requestChunkKey: (callId: string, index: number) => string;
62
+ export declare const resultPrefix: (callId: string) => string;
63
+ export declare const resultChunkKey: (callId: string, index: number) => string;
64
+ export declare const stepIndexPrefix: (stepId: string) => string;
65
+ export declare const stepIndexKey: (stepId: string, attempt: number) => string;
66
+ export declare const cancelledKey: (stepId: string) => string;
67
+ export declare const variantPrefix: (providerId: ProviderId) => string;
68
+ export declare const variantKey: (providerId: ProviderId, deviceId: DeviceId) => string;
69
+ /** Splits UTF-8 text into chunks of at most `REQUEST_CHUNK_BYTES` bytes, never inside a code point. */
70
+ export declare function splitUtf8(text: string): string[];
71
+ /** The stored message text is the ledger's own JSON; the shape check guards against a truncated or foreign chunk set. */
72
+ export declare function isStoredMessage(value: unknown): value is AssistantMessage;
73
+ export declare function encodeCallRow(row: EgressCallRow): StoreValue;
74
+ export declare function decodeCallRow(value: StoreValue | undefined): EgressCallRow | undefined;
75
+ export interface StoredVariant extends VariantRecord {
76
+ readonly stepId: string;
77
+ }
78
+ export declare function decodeVariant(value: StoreValue | undefined): StoredVariant | undefined;
79
+ export declare const encodeVariant: (variant: StoredVariant) => StoreValue;
80
+ //# sourceMappingURL=ledger-schema.d.ts.map