@gajae-code/coding-agent 0.13.2 → 0.14.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 (610) hide show
  1. package/CHANGELOG.md +215 -8
  2. package/README.md +2 -0
  3. package/bin/gajaessi.js +4 -0
  4. package/dist/types/async/job-manager.d.ts +55 -2
  5. package/dist/types/async/support.d.ts +14 -0
  6. package/dist/types/capability/mcp.d.ts +6 -0
  7. package/dist/types/capability/types.d.ts +6 -0
  8. package/dist/types/cli/accounts-cli.d.ts +19 -0
  9. package/dist/types/cli/args.d.ts +3 -0
  10. package/dist/types/cli/config-cli.d.ts +6 -0
  11. package/dist/types/cli/crash-cli.d.ts +10 -0
  12. package/dist/types/cli/customize-doctor.d.ts +145 -0
  13. package/dist/types/cli/daemon-cli.d.ts +3 -1
  14. package/dist/types/cli/mcp-cli.d.ts +11 -0
  15. package/dist/types/cli/notify-cli.d.ts +1 -1
  16. package/dist/types/cli/root-flags.d.ts +12 -0
  17. package/dist/types/cli/setup-cli.d.ts +3 -1
  18. package/dist/types/cli/skills-cli.d.ts +2 -1
  19. package/dist/types/cli/update-cli.d.ts +19 -1
  20. package/dist/types/cli.d.ts +6 -0
  21. package/dist/types/commands/accounts.d.ts +40 -0
  22. package/dist/types/commands/crash.d.ts +24 -0
  23. package/dist/types/commands/customize.d.ts +22 -0
  24. package/dist/types/commands/daemon.d.ts +1 -1
  25. package/dist/types/commands/launch.d.ts +6 -0
  26. package/dist/types/commands/setup.d.ts +6 -0
  27. package/dist/types/commands/skills.d.ts +6 -1
  28. package/dist/types/commands/update.d.ts +7 -0
  29. package/dist/types/config/atomic-yaml-patch-worker.d.ts +25 -0
  30. package/dist/types/config/atomic-yaml-patch.d.ts +62 -2
  31. package/dist/types/config/config-schema-version.d.ts +10 -0
  32. package/dist/types/config/file-lock.d.ts +18 -0
  33. package/dist/types/config/model-profile-activation.d.ts +3 -1
  34. package/dist/types/config/model-registry.d.ts +10 -1
  35. package/dist/types/config/models-config-schema.d.ts +2 -0
  36. package/dist/types/config/settings-schema.d.ts +73 -9
  37. package/dist/types/config/settings.d.ts +63 -0
  38. package/dist/types/config/skill-settings-defaults.d.ts +26 -0
  39. package/dist/types/config/task-delegation.d.ts +39 -0
  40. package/dist/types/config/update-channel.d.ts +10 -0
  41. package/dist/types/coordinator-mcp/server.d.ts +5 -1
  42. package/dist/types/crash/index-store.d.ts +87 -0
  43. package/dist/types/crash/nudge.d.ts +50 -0
  44. package/dist/types/crash/record-loader.d.ts +17 -0
  45. package/dist/types/crash/report.d.ts +134 -0
  46. package/dist/types/crash/sanitize.d.ts +25 -0
  47. package/dist/types/cursor.d.ts +13 -2
  48. package/dist/types/customization/import.d.ts +37 -0
  49. package/dist/types/customization/index.d.ts +4 -0
  50. package/dist/types/customization/inventory.d.ts +25 -0
  51. package/dist/types/customization/mutations.d.ts +32 -0
  52. package/dist/types/customization/types.d.ts +115 -0
  53. package/dist/types/daemon/control-types.d.ts +8 -1
  54. package/dist/types/daemon/operator-contract.d.ts +4 -4
  55. package/dist/types/discovery/claude.d.ts +39 -1
  56. package/dist/types/discovery/codex.d.ts +39 -1
  57. package/dist/types/discovery/index.d.ts +2 -0
  58. package/dist/types/discovery/mcp-compat.d.ts +30 -0
  59. package/dist/types/edit/renderer.d.ts +23 -0
  60. package/dist/types/extensibility/extensions/runner.d.ts +3 -0
  61. package/dist/types/extensibility/extensions/types.d.ts +21 -4
  62. package/dist/types/extensibility/gjc-plugins/observability.d.ts +6 -1
  63. package/dist/types/extensibility/gjc-plugins/registry.d.ts +8 -1
  64. package/dist/types/extensibility/hooks/loader.d.ts +19 -5
  65. package/dist/types/extensibility/runtime-skill-discovery.d.ts +22 -1
  66. package/dist/types/extensibility/skill-management.d.ts +107 -0
  67. package/dist/types/gjc-runtime/deep-interview-runtime.d.ts +3 -1
  68. package/dist/types/gjc-runtime/launch-worktree.d.ts +8 -0
  69. package/dist/types/gjc-runtime/linux-proc.d.ts +3 -0
  70. package/dist/types/gjc-runtime/ralplan-runtime.d.ts +10 -6
  71. package/dist/types/gjc-runtime/session-state-lock.d.ts +104 -0
  72. package/dist/types/gjc-runtime/session-state-sidecar.d.ts +120 -2
  73. package/dist/types/gjc-runtime/state-writer.d.ts +10 -1
  74. package/dist/types/gjc-runtime/ultragoal-guard.d.ts +4 -2
  75. package/dist/types/gjc-runtime/ultragoal-runtime.d.ts +8 -6
  76. package/dist/types/gjc-runtime/workflow-settings.d.ts +109 -0
  77. package/dist/types/hooks/events.d.ts +60 -0
  78. package/dist/types/hooks/normalize.d.ts +83 -0
  79. package/dist/types/hooks/skill-state.d.ts +9 -2
  80. package/dist/types/main.d.ts +2 -1
  81. package/dist/types/modes/acp/abort-scope.d.ts +13 -0
  82. package/dist/types/modes/acp/acp-agent.d.ts +7 -4
  83. package/dist/types/modes/components/customization/customization-dashboard.d.ts +44 -0
  84. package/dist/types/modes/components/customization/import-wizard.d.ts +21 -0
  85. package/dist/types/modes/components/customization/index.d.ts +1 -0
  86. package/dist/types/modes/components/dynamic-theme-text.d.ts +9 -0
  87. package/dist/types/modes/components/gajae-pet-widget.d.ts +6 -28
  88. package/dist/types/modes/components/irc-sidebar.d.ts +1 -0
  89. package/dist/types/modes/components/iterm-pet-transport.d.ts +122 -0
  90. package/dist/types/modes/components/oauth-selector.d.ts +10 -4
  91. package/dist/types/modes/components/pet-capability.d.ts +7 -21
  92. package/dist/types/modes/components/settings-selector.d.ts +5 -0
  93. package/dist/types/modes/components/status-line/gh.d.ts +6 -2
  94. package/dist/types/modes/controllers/extension-ui-controller.d.ts +1 -1
  95. package/dist/types/modes/controllers/selector-controller.d.ts +7 -8
  96. package/dist/types/modes/controllers/stt-controller.d.ts +1 -1
  97. package/dist/types/modes/interactive-mode.d.ts +1 -0
  98. package/dist/types/modes/theme/defaults/index.d.ts +118 -0
  99. package/dist/types/modes/types.d.ts +1 -0
  100. package/dist/types/modes/utils/injected-user-submission.d.ts +10 -0
  101. package/dist/types/runtime-mcp/config.d.ts +9 -0
  102. package/dist/types/runtime-mcp/manager.d.ts +26 -1
  103. package/dist/types/runtime-mcp/oauth-discovery.d.ts +4 -0
  104. package/dist/types/runtime-mcp/oauth-flow.d.ts +24 -1
  105. package/dist/types/runtime-mcp/pool-key.d.ts +2 -0
  106. package/dist/types/runtime-mcp/protocol.d.ts +278 -0
  107. package/dist/types/runtime-mcp/scope-config.d.ts +29 -0
  108. package/dist/types/runtime-mcp/tool-bridge.d.ts +15 -13
  109. package/dist/types/runtime-mcp/transports/http.d.ts +9 -0
  110. package/dist/types/runtime-mcp/types.d.ts +102 -10
  111. package/dist/types/sdk/acp/adapter.d.ts +26 -5
  112. package/dist/types/sdk/broker/broker.d.ts +3 -55
  113. package/dist/types/sdk/broker/lifecycle.d.ts +18 -12
  114. package/dist/types/sdk/broker/session-index.d.ts +14 -5
  115. package/dist/types/sdk/broker/startup-failure.d.ts +16 -1
  116. package/dist/types/sdk/bus/chat-daemon-control.d.ts +51 -1
  117. package/dist/types/sdk/bus/chat-daemon-runtime.d.ts +4 -41
  118. package/dist/types/sdk/bus/config.d.ts +11 -0
  119. package/dist/types/sdk/bus/conversation-store.d.ts +0 -1
  120. package/dist/types/sdk/bus/daemon-paths.d.ts +6 -0
  121. package/dist/types/sdk/bus/discord-conversation.d.ts +2 -0
  122. package/dist/types/sdk/bus/discord-daemon.d.ts +17 -12
  123. package/dist/types/sdk/bus/existing-thread-readiness.d.ts +4 -34
  124. package/dist/types/sdk/bus/inbound-reaction-ordering.d.ts +61 -0
  125. package/dist/types/sdk/bus/index.d.ts +85 -99
  126. package/dist/types/sdk/bus/kind-aware-reconciliation.d.ts +29 -7
  127. package/dist/types/sdk/bus/managed-daemon.d.ts +2 -32
  128. package/dist/types/sdk/bus/notification-service.d.ts +52 -0
  129. package/dist/types/sdk/bus/operator-runtime.d.ts +1 -1
  130. package/dist/types/sdk/bus/prompt-reconciliation.d.ts +16 -5
  131. package/dist/types/sdk/bus/reconciliation-store.d.ts +113 -5
  132. package/dist/types/sdk/bus/session-control.d.ts +25 -1
  133. package/dist/types/sdk/bus/slack-conversation.d.ts +4 -0
  134. package/dist/types/sdk/bus/slack-daemon.d.ts +9 -17
  135. package/dist/types/sdk/bus/slack-live-provider.d.ts +3 -1
  136. package/dist/types/sdk/bus/slack-provider.d.ts +4 -0
  137. package/dist/types/sdk/bus/slack-thread-binding.d.ts +6 -40
  138. package/dist/types/sdk/bus/telegram-adoption-intent.d.ts +30 -104
  139. package/dist/types/sdk/bus/telegram-daemon-cli.d.ts +10 -0
  140. package/dist/types/sdk/bus/telegram-daemon-contract.d.ts +262 -19
  141. package/dist/types/sdk/bus/telegram-daemon-orphan-reap.d.ts +125 -0
  142. package/dist/types/sdk/bus/telegram-daemon-owner-registry.d.ts +22 -0
  143. package/dist/types/sdk/bus/telegram-daemon.d.ts +119 -310
  144. package/dist/types/sdk/bus/telegram-reference.d.ts +1 -47
  145. package/dist/types/sdk/bus/threaded-render.d.ts +1 -0
  146. package/dist/types/sdk/bus/topic-registry.d.ts +33 -17
  147. package/dist/types/sdk/cli/rows.d.ts +2 -11
  148. package/dist/types/sdk/cli/session-cli.d.ts +15 -25
  149. package/dist/types/sdk/client/client.d.ts +97 -1
  150. package/dist/types/sdk/host/control/dispatch.d.ts +0 -7
  151. package/dist/types/sdk/host/control/index.d.ts +1 -1
  152. package/dist/types/sdk/host/control/operations.d.ts +18 -5
  153. package/dist/types/sdk/host/control/runtime-gate.d.ts +19 -0
  154. package/dist/types/sdk/host/host.d.ts +15 -1
  155. package/dist/types/sdk/host/query/handlers.d.ts +5 -0
  156. package/dist/types/sdk/host/reverse-leases.d.ts +1 -1
  157. package/dist/types/sdk/host/session-runtime.d.ts +127 -7
  158. package/dist/types/sdk/host/types.d.ts +1 -1
  159. package/dist/types/sdk/host/websocket-transport.d.ts +1 -1
  160. package/dist/types/sdk/index.d.ts +2 -1
  161. package/dist/types/sdk/lifecycle/broker-client.d.ts +8 -0
  162. package/dist/types/sdk/lifecycle/client.d.ts +59 -0
  163. package/dist/types/sdk/lifecycle/index.d.ts +3 -0
  164. package/dist/types/sdk/lifecycle/service.d.ts +232 -0
  165. package/dist/types/sdk/mcp/server.d.ts +9 -6
  166. package/dist/types/sdk/prompt-deadline-lease.d.ts +43 -0
  167. package/dist/types/sdk/prompt-deadline-manager.d.ts +28 -0
  168. package/dist/types/sdk/prompt-status.d.ts +10 -3
  169. package/dist/types/sdk/protocol/operation-registry.d.ts +5 -0
  170. package/dist/types/sdk/receipt-state.d.ts +17 -0
  171. package/dist/types/sdk/reconciliation-extensions.d.ts +2 -0
  172. package/dist/types/sdk/router/index.d.ts +1 -0
  173. package/dist/types/sdk/router/session-router.d.ts +152 -0
  174. package/dist/types/sdk/session-list.d.ts +22 -0
  175. package/dist/types/sdk/session-reconnect.d.ts +43 -1
  176. package/dist/types/sdk/session.d.ts +34 -1
  177. package/dist/types/sdk/transport/serve-cli.d.ts +1 -4
  178. package/dist/types/session/account-inventory.d.ts +86 -0
  179. package/dist/types/session/agent-session.d.ts +145 -6
  180. package/dist/types/session/agent-storage.d.ts +10 -0
  181. package/dist/types/session/coordinator-tool-label.d.ts +28 -0
  182. package/dist/types/session/internal/managed-session-storage.d.ts +23 -4
  183. package/dist/types/session/messages.d.ts +1 -1
  184. package/dist/types/session/secure-token-file.d.ts +3 -0
  185. package/dist/types/session/session-manager.d.ts +47 -1
  186. package/dist/types/session/startup-auth-config.d.ts +44 -0
  187. package/dist/types/session/terminal-abort.d.ts +378 -0
  188. package/dist/types/session/yield-queue.d.ts +7 -0
  189. package/dist/types/session-import/claude.d.ts +26 -0
  190. package/dist/types/session-import/codex.d.ts +88 -0
  191. package/dist/types/session-import/command.d.ts +35 -0
  192. package/dist/types/session-import/detect.d.ts +22 -0
  193. package/dist/types/session-import/index.d.ts +7 -0
  194. package/dist/types/session-import/provider-codex.d.ts +22 -0
  195. package/dist/types/session-import/provider-service.d.ts +38 -0
  196. package/dist/types/session-import/redact.d.ts +23 -0
  197. package/dist/types/session-import/service.d.ts +47 -0
  198. package/dist/types/session-import/types.d.ts +120 -0
  199. package/dist/types/setup/paseo/check.d.ts +16 -0
  200. package/dist/types/setup/paseo/install-saga.d.ts +115 -0
  201. package/dist/types/setup/paseo/json-publisher.d.ts +76 -0
  202. package/dist/types/setup/paseo/orchestration-preferences.d.ts +24 -0
  203. package/dist/types/setup/paseo/paseo-ownership.d.ts +87 -0
  204. package/dist/types/setup/paseo/paseo-setup.d.ts +29 -0
  205. package/dist/types/setup/paseo/provider-config.d.ts +35 -0
  206. package/dist/types/setup/paseo/remove.d.ts +14 -0
  207. package/dist/types/setup/paseo/result-types.d.ts +59 -0
  208. package/dist/types/setup/paseo/setup-deps.d.ts +73 -0
  209. package/dist/types/setup/paseo/skills-bridge.d.ts +44 -0
  210. package/dist/types/skill-state/active-state.d.ts +5 -1
  211. package/dist/types/slash-commands/acp-builtins.d.ts +5 -1
  212. package/dist/types/slash-commands/builtin-registry.d.ts +2 -0
  213. package/dist/types/slash-commands/helpers/credential-switch.d.ts +12 -0
  214. package/dist/types/slash-commands/helpers/usage-report.d.ts +5 -6
  215. package/dist/types/slash-commands/types.d.ts +7 -1
  216. package/dist/types/task/executor.d.ts +10 -0
  217. package/dist/types/task/index.d.ts +3 -1
  218. package/dist/types/tools/ask-answer-registry.d.ts +6 -3
  219. package/dist/types/tools/bash-sleep-advisory.d.ts +14 -0
  220. package/dist/types/tools/bash.d.ts +2 -6
  221. package/dist/types/tools/browser/attach.d.ts +11 -0
  222. package/dist/types/tools/browser/launch.d.ts +20 -0
  223. package/dist/types/tools/browser/profile-discovery.d.ts +6 -0
  224. package/dist/types/tools/browser/render.d.ts +1 -0
  225. package/dist/types/tools/browser.d.ts +3 -1
  226. package/dist/types/tools/image-gen.d.ts +10 -2
  227. package/dist/types/tools/index.d.ts +8 -0
  228. package/dist/types/tools/monitor.d.ts +1 -1
  229. package/dist/types/tools/skill-discovery.d.ts +8 -1
  230. package/dist/types/tools/subagent.d.ts +2 -0
  231. package/dist/types/tools/ultragoal-ask-guard.d.ts +2 -2
  232. package/dist/types/utils/herdr-pane.d.ts +76 -0
  233. package/package.json +19 -9
  234. package/scripts/build-binary.ts +2 -1
  235. package/scripts/build-sdk-package-smoke.ts +2 -13
  236. package/scripts/capture-sticky-viewport-showcase.ts +0 -1
  237. package/scripts/compile-args.ts +2 -0
  238. package/scripts/generate-sdk-adapter-parity-manifest.ts +31 -16
  239. package/scripts/generate-sdk-operation-inventory.ts +28 -0
  240. package/scripts/generate-telegram-baseline-manifest.ts +1 -0
  241. package/scripts/generate-tool-catalog.ts +6 -4
  242. package/scripts/verify-gjc-sdk-canonicalization.ts +5 -39
  243. package/src/async/job-manager.ts +352 -17
  244. package/src/async/support.ts +25 -0
  245. package/src/autoresearch/dashboard.ts +28 -13
  246. package/src/capability/index.ts +3 -1
  247. package/src/capability/mcp.ts +6 -0
  248. package/src/capability/types.ts +6 -0
  249. package/src/cli/accounts-cli.ts +395 -0
  250. package/src/cli/args.ts +23 -0
  251. package/src/cli/auth-broker-cli.ts +136 -66
  252. package/src/cli/auth-gateway-cli.ts +94 -70
  253. package/src/cli/config-cli.ts +27 -2
  254. package/src/cli/crash-cli.ts +78 -0
  255. package/src/cli/customize-doctor.ts +1605 -0
  256. package/src/cli/daemon-cli.ts +12 -3
  257. package/src/cli/fast-help.ts +1 -0
  258. package/src/cli/mcp-cli.ts +128 -60
  259. package/src/cli/notify-cli.ts +8 -14
  260. package/src/cli/root-flags.ts +8 -0
  261. package/src/cli/setup-cli.ts +145 -0
  262. package/src/cli/skills-cli.ts +38 -2
  263. package/src/cli/update-cli.ts +167 -12
  264. package/src/cli.ts +4 -0
  265. package/src/commands/accounts.ts +71 -0
  266. package/src/commands/crash.ts +35 -0
  267. package/src/commands/customize.ts +84 -0
  268. package/src/commands/daemon.ts +2 -2
  269. package/src/commands/deep-interview.ts +2 -0
  270. package/src/commands/launch.ts +1 -0
  271. package/src/commands/mcp.ts +12 -7
  272. package/src/commands/ralplan.ts +2 -0
  273. package/src/commands/sdk.ts +65 -32
  274. package/src/commands/setup.ts +5 -0
  275. package/src/commands/skills.ts +14 -4
  276. package/src/commands/ultragoal.ts +10 -1
  277. package/src/commands/update.ts +26 -15
  278. package/src/config/atomic-yaml-patch-worker.ts +57 -0
  279. package/src/config/atomic-yaml-patch.ts +769 -29
  280. package/src/config/config-schema-version.ts +10 -0
  281. package/src/config/file-lock.ts +70 -1
  282. package/src/config/mcp-schema.json +52 -14
  283. package/src/config/model-profile-activation.ts +23 -2
  284. package/src/config/model-profiles.ts +88 -21
  285. package/src/config/model-registry.ts +147 -21
  286. package/src/config/models-config-schema.ts +1 -1
  287. package/src/config/settings-schema.ts +77 -7
  288. package/src/config/settings.ts +4038 -199
  289. package/src/config/skill-settings-defaults.ts +41 -3
  290. package/src/config/task-delegation.ts +87 -0
  291. package/src/config/update-channel.ts +23 -0
  292. package/src/coordinator-mcp/server.ts +377 -308
  293. package/src/crash/index-store.ts +723 -0
  294. package/src/crash/nudge.ts +89 -0
  295. package/src/crash/record-loader.ts +149 -0
  296. package/src/crash/report.ts +551 -0
  297. package/src/crash/sanitize.ts +129 -0
  298. package/src/cursor.ts +36 -10
  299. package/src/customization/import.ts +946 -0
  300. package/src/customization/index.ts +4 -0
  301. package/src/customization/inventory.ts +335 -0
  302. package/src/customization/mutations.ts +141 -0
  303. package/src/customization/types.ts +223 -0
  304. package/src/daemon/control-types.ts +8 -1
  305. package/src/daemon/operator-contract.ts +6 -6
  306. package/src/defaults/gjc/extensions/grok-cli-vendor/src/models/catalog.ts +31 -6
  307. package/src/defaults/gjc/skills/deep-interview/SKILL.md +11 -22
  308. package/src/defaults/gjc/skills/ralplan/SKILL.md +36 -23
  309. package/src/defaults/gjc/skills/ultragoal/SKILL.md +16 -0
  310. package/src/discovery/claude-hooks.ts +38 -0
  311. package/src/discovery/claude.ts +142 -209
  312. package/src/discovery/codex-hooks.ts +38 -0
  313. package/src/discovery/codex.ts +132 -198
  314. package/src/discovery/helpers.ts +8 -1
  315. package/src/discovery/index.ts +2 -0
  316. package/src/discovery/mcp-compat.ts +234 -0
  317. package/src/discovery/mcp-json.ts +6 -0
  318. package/src/edit/renderer.ts +40 -0
  319. package/src/edit/streaming.ts +4 -2
  320. package/src/exec/bash-executor.ts +24 -20
  321. package/src/export/html/index.ts +51 -2
  322. package/src/extensibility/extensions/loader.ts +9 -1
  323. package/src/extensibility/extensions/runner.ts +17 -4
  324. package/src/extensibility/extensions/types.ts +31 -6
  325. package/src/extensibility/gjc-plugins/constrained-hooks.ts +33 -0
  326. package/src/extensibility/gjc-plugins/installer.ts +9 -5
  327. package/src/extensibility/gjc-plugins/observability.ts +8 -2
  328. package/src/extensibility/gjc-plugins/registry.ts +13 -2
  329. package/src/extensibility/gjc-plugins/runtime-adapters.ts +3 -1
  330. package/src/extensibility/gjc-plugins/schema.ts +222 -39
  331. package/src/extensibility/hooks/loader.ts +154 -10
  332. package/src/extensibility/runtime-skill-discovery.ts +257 -60
  333. package/src/extensibility/skill-management.ts +368 -0
  334. package/src/extensibility/skills.ts +52 -16
  335. package/src/gjc-runtime/deep-interview-runtime.ts +45 -61
  336. package/src/gjc-runtime/launch-worktree.ts +56 -2
  337. package/src/gjc-runtime/linux-proc.ts +7 -1
  338. package/src/gjc-runtime/ralplan-runtime.ts +100 -164
  339. package/src/gjc-runtime/session-state-lock.ts +992 -0
  340. package/src/gjc-runtime/session-state-sidecar.ts +644 -142
  341. package/src/gjc-runtime/state-writer.ts +39 -14
  342. package/src/gjc-runtime/tmux-owner-isolation.ts +5 -4
  343. package/src/gjc-runtime/ultragoal-guard.ts +11 -4
  344. package/src/gjc-runtime/ultragoal-runtime.ts +44 -41
  345. package/src/gjc-runtime/workflow-settings.ts +596 -0
  346. package/src/goals/tools/goal-tool.ts +6 -1
  347. package/src/hooks/events.ts +291 -0
  348. package/src/hooks/native-skill-hook.ts +4 -0
  349. package/src/hooks/normalize.ts +369 -0
  350. package/src/hooks/skill-state.ts +165 -46
  351. package/src/internal-urls/docs-index.generated.ts +41 -32
  352. package/src/main.ts +36 -5
  353. package/src/migrate/mcp-mapper.ts +25 -2
  354. package/src/modes/acp/abort-scope.ts +29 -0
  355. package/src/modes/acp/acp-agent.ts +423 -98
  356. package/src/modes/components/assistant-message.ts +6 -1
  357. package/src/modes/components/chrome.ts +4 -3
  358. package/src/modes/components/customization/customization-dashboard.ts +443 -0
  359. package/src/modes/components/customization/import-wizard.ts +372 -0
  360. package/src/modes/components/customization/index.ts +1 -0
  361. package/src/modes/components/dynamic-theme-text.ts +22 -0
  362. package/src/modes/components/gajae-pet-widget.ts +607 -193
  363. package/src/modes/components/irc-sidebar.ts +11 -0
  364. package/src/modes/components/iterm-pet-transport.ts +585 -0
  365. package/src/modes/components/model-selector.ts +1 -6
  366. package/src/modes/components/oauth-selector.ts +277 -86
  367. package/src/modes/components/pet-capability.ts +35 -31
  368. package/src/modes/components/settings-selector.ts +29 -5
  369. package/src/modes/components/status-line/gh.ts +45 -1
  370. package/src/modes/components/status-line/segments.ts +19 -4
  371. package/src/modes/components/tool-status-header.ts +13 -3
  372. package/src/modes/controllers/command-controller.ts +20 -8
  373. package/src/modes/controllers/extension-ui-controller.ts +56 -257
  374. package/src/modes/controllers/input-controller.ts +10 -0
  375. package/src/modes/controllers/runtime-mcp-command-controller.ts +16 -3
  376. package/src/modes/controllers/selector-controller.ts +238 -77
  377. package/src/modes/controllers/stt-controller.ts +3 -3
  378. package/src/modes/interactive-mode.ts +88 -16
  379. package/src/modes/print-mode.ts +35 -6
  380. package/src/modes/runtime-init.ts +21 -68
  381. package/src/modes/theme/defaults/index.ts +2 -0
  382. package/src/modes/theme/defaults/ouroboros.json +118 -0
  383. package/src/modes/types.ts +1 -0
  384. package/src/modes/utils/injected-user-submission.ts +25 -0
  385. package/src/modes/utils/terminal-bell.ts +15 -1
  386. package/src/prompts/agent-fragments/ralplan-persistence.md +1 -1
  387. package/src/prompts/agent-fragments/restricted-bash.md +1 -1
  388. package/src/prompts/agents/critic.md +1 -1
  389. package/src/prompts/system/rlm-research.md +1 -1
  390. package/src/prompts/system/system-prompt.md +0 -2
  391. package/src/prompts/tools/bash.md +7 -1
  392. package/src/prompts/tools/browser.md +1 -1
  393. package/src/prompts/tools/cron.md +6 -6
  394. package/src/prompts/tools/read.md +0 -2
  395. package/src/prompts/tools/skill-discovery.md +4 -2
  396. package/src/runtime-mcp/client.ts +427 -23
  397. package/src/runtime-mcp/config.ts +37 -5
  398. package/src/runtime-mcp/manager-pool.test.ts +56 -0
  399. package/src/runtime-mcp/manager.ts +52 -2
  400. package/src/runtime-mcp/oauth-discovery.ts +13 -0
  401. package/src/runtime-mcp/oauth-flow.ts +46 -1
  402. package/src/runtime-mcp/pool-key.ts +5 -1
  403. package/src/runtime-mcp/pool.test.ts +9 -0
  404. package/src/runtime-mcp/protocol.test.ts +353 -0
  405. package/src/runtime-mcp/protocol.ts +624 -0
  406. package/src/runtime-mcp/scope-config.ts +78 -0
  407. package/src/runtime-mcp/tool-bridge.ts +44 -10
  408. package/src/runtime-mcp/transports/http.ts +84 -13
  409. package/src/runtime-mcp/types.ts +123 -12
  410. package/src/sdk/acp/adapter.ts +328 -97
  411. package/src/sdk/broker/broker.ts +160 -741
  412. package/src/sdk/broker/ensure.ts +9 -1
  413. package/src/sdk/broker/lifecycle.ts +510 -116
  414. package/src/sdk/broker/operation-receipt-ledger.ts +11 -2
  415. package/src/sdk/broker/process-incarnation.ts +19 -0
  416. package/src/sdk/broker/session-index.ts +516 -117
  417. package/src/sdk/broker/startup-failure.ts +28 -5
  418. package/src/sdk/broker/transport.ts +1 -13
  419. package/src/sdk/bus/chat-daemon-cli.ts +4 -2
  420. package/src/sdk/bus/chat-daemon-control.ts +67 -6
  421. package/src/sdk/bus/chat-daemon-runtime.ts +183 -859
  422. package/src/sdk/bus/config.ts +13 -0
  423. package/src/sdk/bus/conversation-store.ts +59 -171
  424. package/src/sdk/bus/daemon-paths.ts +20 -0
  425. package/src/sdk/bus/discord-conversation.ts +2 -0
  426. package/src/sdk/bus/discord-daemon.ts +524 -126
  427. package/src/sdk/bus/discord-live-provider.ts +206 -79
  428. package/src/sdk/bus/existing-thread-readiness.ts +4 -97
  429. package/src/sdk/bus/inbound-reaction-ordering.ts +122 -0
  430. package/src/sdk/bus/index.ts +2480 -456
  431. package/src/sdk/bus/kind-aware-reconciliation.ts +204 -34
  432. package/src/sdk/bus/lifecycle-commands.ts +2 -2
  433. package/src/sdk/bus/managed-daemon.ts +2 -145
  434. package/src/sdk/bus/notification-service.ts +196 -0
  435. package/src/sdk/bus/operator-runtime.ts +1 -1
  436. package/src/sdk/bus/prompt-reconciliation.ts +24 -15
  437. package/src/sdk/bus/reconciliation-store.ts +553 -24
  438. package/src/sdk/bus/session-control.ts +49 -3
  439. package/src/sdk/bus/slack-conversation.ts +4 -0
  440. package/src/sdk/bus/slack-daemon.ts +733 -134
  441. package/src/sdk/bus/slack-live-provider.ts +65 -15
  442. package/src/sdk/bus/slack-provider.ts +4 -0
  443. package/src/sdk/bus/slack-thread-binding.ts +9 -104
  444. package/src/sdk/bus/telegram-adoption-intent.ts +299 -209
  445. package/src/sdk/bus/telegram-daemon-cli.ts +194 -7
  446. package/src/sdk/bus/telegram-daemon-contract.ts +265 -19
  447. package/src/sdk/bus/telegram-daemon-control.ts +3 -13
  448. package/src/sdk/bus/telegram-daemon-orphan-reap.ts +498 -0
  449. package/src/sdk/bus/telegram-daemon-owner-registry.ts +176 -0
  450. package/src/sdk/bus/telegram-daemon.ts +2552 -2280
  451. package/src/sdk/bus/telegram-reference.ts +1 -226
  452. package/src/sdk/bus/telegram-setup.ts +2 -2
  453. package/src/sdk/bus/threaded-render.ts +1 -0
  454. package/src/sdk/bus/topic-registry.ts +279 -146
  455. package/src/sdk/cli/rows.ts +2 -35
  456. package/src/sdk/cli/session-cli.ts +776 -700
  457. package/src/sdk/client/client.ts +892 -1
  458. package/src/sdk/host/control/dispatch.ts +93 -58
  459. package/src/sdk/host/control/index.ts +1 -1
  460. package/src/sdk/host/control/operations.ts +18 -10
  461. package/src/sdk/host/control/runtime-gate.ts +40 -0
  462. package/src/sdk/host/host.ts +63 -12
  463. package/src/sdk/host/query/handlers.ts +66 -7
  464. package/src/sdk/host/reverse-leases.ts +2 -2
  465. package/src/sdk/host/sdk-surface-parity.test.ts +1 -1
  466. package/src/sdk/host/session-runtime.test.ts +2787 -34
  467. package/src/sdk/host/session-runtime.ts +1489 -101
  468. package/src/sdk/host/surface-policy.ts +12 -8
  469. package/src/sdk/host/types.ts +1 -1
  470. package/src/sdk/host/websocket-transport.lifecycle.test.ts +31 -59
  471. package/src/sdk/host/websocket-transport.ts +85 -50
  472. package/src/sdk/index.ts +2 -1
  473. package/src/sdk/lifecycle/broker-client.ts +53 -0
  474. package/src/sdk/lifecycle/client.ts +234 -0
  475. package/src/sdk/lifecycle/index.ts +3 -0
  476. package/src/sdk/{bus/recent-activity.ts → lifecycle/recent-sessions.ts} +5 -2
  477. package/src/sdk/lifecycle/service.ts +720 -0
  478. package/src/sdk/mcp/server.ts +226 -188
  479. package/src/sdk/prompt-deadline-lease.ts +55 -0
  480. package/src/sdk/prompt-deadline-manager.ts +148 -0
  481. package/src/sdk/prompt-status.ts +11 -3
  482. package/src/sdk/protocol/operation-inventory.generated.json +272 -129
  483. package/src/sdk/protocol/operation-registry.ts +71 -8
  484. package/src/sdk/receipt-state.ts +43 -0
  485. package/src/sdk/reconciliation-extensions.ts +14 -0
  486. package/src/sdk/router/index.ts +1 -0
  487. package/src/sdk/router/session-router.ts +1658 -0
  488. package/src/sdk/session-list.ts +93 -0
  489. package/src/sdk/session-reconnect.ts +45 -1
  490. package/src/sdk/session.ts +716 -120
  491. package/src/sdk/transport/serve-cli.ts +18 -34
  492. package/src/session/account-inventory.ts +603 -0
  493. package/src/session/agent-session.ts +2873 -559
  494. package/src/session/agent-storage.ts +35 -0
  495. package/src/session/blob-store.ts +32 -1
  496. package/src/session/coordinator-tool-label.ts +40 -0
  497. package/src/session/internal/managed-session-scope.ts +4 -0
  498. package/src/session/internal/managed-session-storage.ts +199 -31
  499. package/src/session/messages.ts +1 -1
  500. package/src/session/secure-token-file.ts +80 -0
  501. package/src/session/session-manager.ts +757 -57
  502. package/src/session/session-storage.ts +25 -16
  503. package/src/session/startup-auth-config.ts +297 -0
  504. package/src/session/terminal-abort.ts +1253 -0
  505. package/src/session/yield-queue.ts +57 -15
  506. package/src/session-import/claude.ts +334 -0
  507. package/src/session-import/codex.ts +1033 -0
  508. package/src/session-import/command.ts +77 -0
  509. package/src/session-import/detect.ts +133 -0
  510. package/src/session-import/index.ts +7 -0
  511. package/src/session-import/provider-codex.ts +327 -0
  512. package/src/session-import/provider-service.ts +457 -0
  513. package/src/session-import/redact.ts +128 -0
  514. package/src/session-import/service.ts +1264 -0
  515. package/src/session-import/types.ts +148 -0
  516. package/src/setup/paseo/check.ts +213 -0
  517. package/src/setup/paseo/install-saga.ts +293 -0
  518. package/src/setup/paseo/json-publisher.ts +263 -0
  519. package/src/setup/paseo/orchestration-preferences.ts +86 -0
  520. package/src/setup/paseo/paseo-ownership.ts +224 -0
  521. package/src/setup/paseo/paseo-setup.ts +253 -0
  522. package/src/setup/paseo/provider-config.ts +139 -0
  523. package/src/setup/paseo/remove.ts +183 -0
  524. package/src/setup/paseo/result-types.ts +79 -0
  525. package/src/setup/paseo/setup-deps.ts +175 -0
  526. package/src/setup/paseo/skills-bridge.ts +260 -0
  527. package/src/skill-state/active-state.ts +21 -11
  528. package/src/slash-commands/acp-builtins.ts +9 -1
  529. package/src/slash-commands/builtin-registry.ts +117 -19
  530. package/src/slash-commands/helpers/credential-switch.ts +143 -0
  531. package/src/slash-commands/helpers/mcp.ts +9 -3
  532. package/src/slash-commands/helpers/usage-report.ts +61 -56
  533. package/src/slash-commands/types.ts +7 -2
  534. package/src/task/executor.ts +30 -11
  535. package/src/task/index.ts +66 -7
  536. package/src/task/render.ts +23 -9
  537. package/src/tools/ask-answer-registry.ts +23 -7
  538. package/src/tools/ask.ts +8 -4
  539. package/src/tools/bash-sleep-advisory.ts +115 -0
  540. package/src/tools/bash.ts +98 -18
  541. package/src/tools/browser/attach.ts +71 -3
  542. package/src/tools/browser/launch.ts +86 -13
  543. package/src/tools/browser/profile-discovery.ts +57 -7
  544. package/src/tools/browser/render.ts +8 -1
  545. package/src/tools/browser.ts +130 -21
  546. package/src/tools/computer.enforcement.test.ts +1 -1
  547. package/src/tools/computer.ts +3 -2
  548. package/src/tools/cron.ts +12 -1
  549. package/src/tools/image-gen.ts +349 -142
  550. package/src/tools/index.ts +8 -0
  551. package/src/tools/job.ts +33 -6
  552. package/src/tools/monitor.ts +41 -2
  553. package/src/tools/skill-discovery.ts +21 -29
  554. package/src/tools/subagent.ts +142 -24
  555. package/src/tools/todo-contract.ts +55 -3
  556. package/src/tools/todo-write.ts +4 -3
  557. package/src/tools/tool-catalog.generated.ts +6 -6
  558. package/src/tools/ultragoal-ask-guard.ts +10 -3
  559. package/src/utils/herdr-pane.ts +345 -0
  560. package/src/utils/title-generator.ts +2 -0
  561. package/src/web/scrapers/index.ts +6 -4
  562. package/dist/types/cli/malloc-env-guard.test.d.ts +0 -1
  563. package/dist/types/defaults/gjc-defaults.test.d.ts +0 -1
  564. package/dist/types/memory-backend/lazy-loading.test.d.ts +0 -1
  565. package/dist/types/runtime/boot-ordering.test.d.ts +0 -1
  566. package/dist/types/runtime/lazy-service.test.d.ts +0 -1
  567. package/dist/types/runtime/network-prewarm-service.test.d.ts +0 -1
  568. package/dist/types/runtime/workspace-tree-service.test.d.ts +0 -1
  569. package/dist/types/runtime-mcp/config.test.d.ts +0 -1
  570. package/dist/types/runtime-mcp/http.test.d.ts +0 -1
  571. package/dist/types/runtime-mcp/manager-pool.test.d.ts +0 -1
  572. package/dist/types/runtime-mcp/pool-key.test.d.ts +0 -1
  573. package/dist/types/runtime-mcp/pool.test.d.ts +0 -1
  574. package/dist/types/sdk/bus/lifecycle-control-runtime.d.ts +0 -117
  575. package/dist/types/sdk/bus/lifecycle-orchestrator.d.ts +0 -141
  576. package/dist/types/sdk/bus/telegram-cli.d.ts +0 -31
  577. package/dist/types/sdk/elevation/allowlist.d.ts +0 -20
  578. package/dist/types/sdk/elevation/audit.d.ts +0 -93
  579. package/dist/types/sdk/elevation/capability.d.ts +0 -3
  580. package/dist/types/sdk/elevation/digest.d.ts +0 -72
  581. package/dist/types/sdk/elevation/dispatch-receipt.d.ts +0 -85
  582. package/dist/types/sdk/elevation/grant-ledger.d.ts +0 -163
  583. package/dist/types/sdk/elevation/owner.d.ts +0 -48
  584. package/dist/types/sdk/host/import-graph.test.d.ts +0 -1
  585. package/dist/types/sdk/host/sdk-surface-parity.test.d.ts +0 -1
  586. package/dist/types/sdk/host/session-runtime.test.d.ts +0 -1
  587. package/dist/types/sdk/host/websocket-transport.lifecycle.test.d.ts +0 -1
  588. package/dist/types/session/auth-broker-config.d.ts +0 -13
  589. package/dist/types/session/heap-eviction-artifacts.test.d.ts +0 -1
  590. package/dist/types/skills/skills.test.d.ts +0 -1
  591. package/dist/types/slash-commands/notify-command.test.d.ts +0 -1
  592. package/dist/types/task/token-log.test.d.ts +0 -1
  593. package/dist/types/tools/computer.enforcement.test.d.ts +0 -1
  594. package/dist/types/tools/descriptors.test.d.ts +0 -1
  595. package/scripts/g005-daemon-smoke.ts +0 -180
  596. package/scripts/g011-daemon-path-smoke.ts +0 -153
  597. package/src/sdk/bus/lifecycle-control-runtime.ts +0 -1733
  598. package/src/sdk/bus/lifecycle-orchestrator.ts +0 -489
  599. package/src/sdk/bus/telegram-cli.ts +0 -212
  600. package/src/sdk/elevation/allowlist.ts +0 -54
  601. package/src/sdk/elevation/audit.ts +0 -208
  602. package/src/sdk/elevation/capability.ts +0 -41
  603. package/src/sdk/elevation/digest.ts +0 -129
  604. package/src/sdk/elevation/dispatch-receipt.ts +0 -184
  605. package/src/sdk/elevation/grant-ledger.ts +0 -1142
  606. package/src/sdk/elevation/owner.ts +0 -109
  607. package/src/session/auth-broker-config.ts +0 -109
  608. /package/dist/types/{ai-core-import-gate.test.d.ts → discovery/claude-hooks.d.ts} +0 -0
  609. /package/dist/types/{cli/fixture-report.test.d.ts → discovery/codex-hooks.d.ts} +0 -0
  610. /package/dist/types/sdk/{bus/recent-activity.d.ts → lifecycle/recent-sessions.d.ts} +0 -0
@@ -6,25 +6,6 @@ import path from "node:path";
6
6
  import type { NativeDirectoryTreeSnapshot } from "@gajae-code/natives";
7
7
  import { logger } from "@gajae-code/utils";
8
8
  import type { ModelProfileErrorDetails } from "../../config/model-profile-contract";
9
- import { SdkClient, SdkClientError } from "../client";
10
- import { elevationAuthorityPath, signElevationCapability } from "../elevation/capability";
11
- import { type ElevationOperation, elevationRequestDigest, isElevatableOperation } from "../elevation/digest";
12
- import {
13
- type ElevationAnswerAuthority,
14
- type ElevationClaimIdentity,
15
- type ElevationDispatchOutcome,
16
- isElevationRequestId,
17
- } from "../elevation/dispatch-receipt";
18
- import {
19
- type ElevationAnswerValue,
20
- type ElevationGrantRecord,
21
- ElevationLedger,
22
- type ElevationRequester,
23
- type ElevationResult,
24
- type ElevationSessionIdentity,
25
- } from "../elevation/grant-ledger";
26
- import { type BrokerOwnerPrincipal, readBrokerOwnerPrincipal } from "../elevation/owner";
27
- import { findOperation } from "../protocol/operation-registry";
28
9
  import {
29
10
  type DirectoryMigrationPolicy,
30
11
  listManagedSessionCandidates,
@@ -45,16 +26,17 @@ import {
45
26
  readBrokerDiscovery,
46
27
  redactBrokerDiscovery,
47
28
  } from "./discovery";
48
- import { deriveIdempotencyIdentity, deriveLegacyTargetIdentity } from "./identity";
29
+ import { deriveIdempotencyIdentity } from "./identity";
30
+
49
31
  import { canonicalDeleteLocatorPath, executeLifecycle, isCanonicalSessionId } from "./lifecycle";
32
+
50
33
  import {
51
34
  type LifecycleDurableEffectsReceipt,
52
35
  LifecycleLedger,
53
36
  type LifecycleStartupFailureReceipt,
54
37
  type LifecycleState,
55
38
  } from "./lifecycle-ledger";
56
- import { OperationReceiptLedger, operationReceiptDigest, operationReceiptKey } from "./operation-receipt-ledger";
57
- import { type IndexedSession, SessionIndex, type SessionList } from "./session-index";
39
+ import { type IndexedSession, isSessionAuthorityEligible, SessionIndex, type SessionList } from "./session-index";
58
40
  import { BrokerTransport } from "./transport";
59
41
 
60
42
  export interface BrokerSettings {
@@ -90,7 +72,6 @@ export type BrokerErrorCode =
90
72
  | "not_found"
91
73
  | "live_session"
92
74
  | "cleanup_pending"
93
- | "ambiguous_session"
94
75
  | (string & {});
95
76
 
96
77
  export type BrokerCleanupIdentity = {
@@ -190,35 +171,6 @@ export type BrokerResponse =
190
171
  };
191
172
  const error = (code: BrokerErrorCode, message: string): BrokerResponse => ({ ok: false, error: { code, message } });
192
173
 
193
- function elevationResultToBroker<T>(result: ElevationResult<T>): BrokerResponse {
194
- if (result.ok) return { ok: true, result: result.value };
195
- return error(result.error.code as BrokerErrorCode, result.error.message);
196
- }
197
-
198
- function elevationAnswerToBroker(result: ElevationResult<ElevationAnswerValue>): BrokerResponse {
199
- if (!result.ok) return elevationResultToBroker(result);
200
- const { outcome, grant } = result.value;
201
- if (outcome === "granted")
202
- return { ok: true, result: { elevationRequestId: grant.elevationRequestId, state: grant.state, outcome, grant } };
203
- return error(outcome as BrokerErrorCode, `elevation ${outcome}: ${grant.elevationRequestId}`);
204
- }
205
-
206
- /**
207
- * Result of resolving the session behind an elevation grant/request. An
208
- * ambiguous session (C6) can never bind an exact endpoint identity.
209
- */
210
- type ElevationSessionResolution =
211
- | { kind: "identity"; identity: ElevationSessionIdentity }
212
- | { kind: "ambiguous" }
213
- | { kind: "unavailable" };
214
- function sameElevationSessionIdentity(left: ElevationSessionIdentity, right: ElevationSessionIdentity): boolean {
215
- return (
216
- left.sessionId === right.sessionId &&
217
- path.resolve(left.endpointStateRoot) === path.resolve(right.endpointStateRoot) &&
218
- left.endpointGeneration === right.endpointGeneration &&
219
- left.endpointIncarnation === right.endpointIncarnation
220
- );
221
- }
222
174
  function isCleanupPending(response: BrokerResponse): boolean {
223
175
  return !response.ok && response.error.code === "cleanup_pending" && response.error.cleanup !== undefined;
224
176
  }
@@ -231,12 +183,6 @@ function pendingCleanupSessionId(response: BrokerResponse): string | undefined {
231
183
  return typeof response.error.cleanup?.sessionId === "string" ? response.error.cleanup.sessionId : undefined;
232
184
  }
233
185
 
234
- function lifecycleResponseState(response: BrokerResponse): LifecycleState {
235
- if (response.ok) return "terminal_ok";
236
- if (isCleanupPending(response)) return "effect_started";
237
- return response.error.code === "terminal_uncertain" ? "terminal_uncertain" : "terminal_error";
238
- }
239
-
240
186
  const LIFECYCLE_OPERATIONS = new Set([
241
187
  "session.create",
242
188
  "session.fork",
@@ -245,6 +191,15 @@ const LIFECYCLE_OPERATIONS = new Set([
245
191
  "session.delete",
246
192
  ]);
247
193
 
194
+ function lifecycleFingerprint(operation: string, input: unknown): string {
195
+ return createHash("sha256").update(JSON.stringify({ operation, input })).digest("hex");
196
+ }
197
+ function lifecycleResponseState(response: BrokerResponse): LifecycleState {
198
+ if (response.ok) return "terminal_ok";
199
+ if (isCleanupPending(response)) return "effect_started";
200
+ return response.error.code === "terminal_uncertain" ? "terminal_uncertain" : "terminal_error";
201
+ }
202
+
248
203
  type InputNormalization = { input: Record<string, unknown> } | BrokerResponse;
249
204
 
250
205
  type SessionListCursor = {
@@ -380,6 +335,25 @@ function canonicalJson(value: unknown): string {
380
335
  .join(",")}}`;
381
336
  }
382
337
 
338
+ function credentialFreeLifecycleResponse(value: unknown): unknown {
339
+ if (Array.isArray(value)) return value.map(credentialFreeLifecycleResponse);
340
+ if (value === null || typeof value !== "object") return value;
341
+ const output: Record<string, unknown> = {};
342
+ for (const [key, nested] of Object.entries(value as Record<string, unknown>)) {
343
+ if (key === "token" || key === "url" || (key === "endpoint" && nested !== null && typeof nested === "object"))
344
+ continue;
345
+ output[key] = credentialFreeLifecycleResponse(nested);
346
+ }
347
+ return output;
348
+ }
349
+
350
+ type LifecycleReplayEndpoint = {
351
+ endpoint: Record<string, unknown>;
352
+ endpointGeneration: number;
353
+ pid: number;
354
+ endpointMtimeMs: number;
355
+ };
356
+
383
357
  type EndpointAuthority = { endpointGeneration?: number; endpointIncarnation?: string };
384
358
  function endpointIncarnation(
385
359
  record: Pick<IndexedSession, "endpointGeneration" | "endpointMtimeMs" | "pid">,
@@ -433,6 +407,7 @@ function matchesEndpointAuthority(record: IndexedSession, authority: EndpointAut
433
407
  function sameEndpointRecord(expected: IndexedSession, current: IndexedSession): boolean {
434
408
  return (
435
409
  current.live &&
410
+ isSessionAuthorityEligible(current) &&
436
411
  current.endpointGeneration === expected.endpointGeneration &&
437
412
  current.pid === expected.pid &&
438
413
  current.endpointMtimeMs === expected.endpointMtimeMs &&
@@ -465,14 +440,13 @@ function lifecycleTarget(operation: string, input: Record<string, unknown>): unk
465
440
  case "session.resume":
466
441
  case "session.close":
467
442
  case "session.delete":
468
- return { root, sessionId: id };
443
+ return { sessionId: id };
469
444
  default:
470
445
  return { operation, root, sessionId: id };
471
446
  }
472
447
  }
473
448
 
474
449
  const BROKER_LOCK_RECORD = "owner.json";
475
- const BROKER_INDEX_COMPACT_INTERVAL_MS = 6 * 60 * 60 * 1_000;
476
450
  const BROKER_LOCK_STARTUP_WAIT_MS = 1_000;
477
451
  const BROKER_LOCK_RETRY_MS = 10;
478
452
 
@@ -780,28 +754,11 @@ const terminalPersistenceHooksForTest = new WeakMap<Broker, () => void>();
780
754
  const ambiguityGraceOverridesForTest = new WeakMap<Broker, number>();
781
755
  const publicationObservationOverridesForTest = new WeakMap<Broker, BrokerPublicationObservation>();
782
756
  const lockArtifactGraceOverridesForTest = new WeakMap<Broker, number>();
783
- function elevationLedgerEnabledForBroker(): boolean {
784
- const raw = (process.env.GJC_SDK_ELEVATION_ENABLED ?? "").trim().toLowerCase();
785
- return raw === "1" || raw === "true";
786
- }
787
-
788
- /** Recursive reserved-key rejection: the claim selector never travels inside approved input. */
789
- function containsReservedKey(value: unknown, key: string, seen = new Set<object>()): boolean {
790
- if (!value || typeof value !== "object" || seen.has(value)) return false;
791
- seen.add(value);
792
- if (Array.isArray(value)) return value.some(item => containsReservedKey(item, key, seen));
793
- return (
794
- Object.hasOwn(value, key) ||
795
- Object.values(value as Record<string, unknown>).some(nested => containsReservedKey(nested, key, seen))
796
- );
797
- }
798
757
 
799
758
  export class Broker {
800
759
  readonly settings: ResolvedBrokerSettings;
801
760
  readonly index: SessionIndex;
802
761
  readonly ledger: LifecycleLedger;
803
- readonly elevation: ElevationLedger;
804
- readonly operationReceipts: OperationReceiptLedger;
805
762
  discovery: BrokerDiscovery | null = null;
806
763
  #lock: string;
807
764
  #owner = randomBytes(12).toString("hex");
@@ -820,12 +777,6 @@ export class Broker {
820
777
  #completion!: Promise<void>;
821
778
  #resolveCompletion!: () => void;
822
779
  #rejectCompletion!: (error: unknown) => void;
823
- /** Broker tenure counter: increments per start so a restart is a new epoch. */
824
- #epoch = 0;
825
- #incarnation: string | null = null;
826
- #elevationEnforced: boolean;
827
- #compactTimer: NodeJS.Timeout | null = null;
828
- #operatorTimer: NodeJS.Timeout | null = null;
829
780
  constructor(settings: BrokerSettings) {
830
781
  this.settings = {
831
782
  agentDir: settings.agentDir,
@@ -836,10 +787,7 @@ export class Broker {
836
787
  };
837
788
  this.index = new SessionIndex(settings.agentDir);
838
789
  this.ledger = new LifecycleLedger(settings.agentDir);
839
- this.elevation = new ElevationLedger(settings.agentDir);
840
- this.operationReceipts = new OperationReceiptLedger(settings.agentDir);
841
790
  this.#lock = path.join(settings.agentDir, "sdk", "broker.lock");
842
- this.#elevationEnforced = elevationLedgerEnabledForBroker();
843
791
  const completion = Promise.withResolvers<void>();
844
792
  this.#completion = completion.promise;
845
793
  this.#resolveCompletion = completion.resolve;
@@ -992,15 +940,10 @@ export class Broker {
992
940
  this.#startupAdmissions = new StartupAdmissionQueue(sdkHostStartupConcurrency());
993
941
  }
994
942
  this.#stopping = false;
995
- this.#epoch += 1;
996
943
  this.#publicationState = "healthy-owned";
997
944
  this.#lossAt = null;
998
945
  this.#ambiguousAt = null;
999
- await Promise.all([
1000
- this.ledger.assertSupportedStateVersions(),
1001
- this.elevation.assertSupportedStateVersions(),
1002
- readBrokerDiscovery(this.settings.agentDir),
1003
- ]);
946
+ await Promise.all([this.ledger.assertSupportedStateVersions(), readBrokerDiscovery(this.settings.agentDir)]);
1004
947
  await fs.mkdir(path.dirname(this.#lock), { recursive: true, mode: 0o700 });
1005
948
  for (;;) {
1006
949
  try {
@@ -1047,20 +990,9 @@ export class Broker {
1047
990
  try {
1048
991
  await this.index.open();
1049
992
  await this.ledger.open();
1050
- await this.elevation.open();
1051
- await this.operationReceipts.open();
1052
- if (this.#elevationEnforced)
1053
- await fs.mkdir(path.join(this.settings.agentDir, "sdk", "elevation", "operator"), {
1054
- recursive: true,
1055
- mode: 0o700,
1056
- });
1057
- // Broker-scheduled compaction (C3): apply the retention policy once at
1058
- // startup and again every six hours, independently of log rotation.
1059
- await this.#compactSessionIndex();
1060
993
  const now = Date.now();
1061
994
  const incarnation = brokerProcessIncarnation(process.pid);
1062
995
  if (!incarnation) throw new Error("Broker process incarnation is unavailable.");
1063
- this.#incarnation = incarnation;
1064
996
  const token = newBrokerToken();
1065
997
  this.#transport = new BrokerTransport(this, token, this.settings.port);
1066
998
  const port = await this.#transport.start();
@@ -1085,8 +1017,7 @@ export class Broker {
1085
1017
  Math.min(BROKER_PUBLICATION_CADENCE_MS, Math.floor(this.settings.heartbeatTtlMs / 3)),
1086
1018
  );
1087
1019
  this.#heartbeatTimer = setInterval(() => void this.#watchPublication(), cadenceMs);
1088
- this.#compactTimer = setInterval(() => void this.#compactSessionIndex(), BROKER_INDEX_COMPACT_INTERVAL_MS);
1089
- if (this.#elevationEnforced) this.#operatorTimer = setInterval(() => void this.#pollOperatorDirectives(), 250);
1020
+ await this.#checkpointSessionHeartbeats();
1090
1021
  return this.discovery;
1091
1022
  } catch (error) {
1092
1023
  await this.#transport?.stop();
@@ -1153,7 +1084,7 @@ export class Broker {
1153
1084
  this.#lossAt = null;
1154
1085
  this.#ambiguousAt = null;
1155
1086
  if (writeHeartbeat) await this.#writeHeartbeat();
1156
- await this.#checkpointSessionHeartbeats();
1087
+ if (this.#publicationState === "healthy-owned") await this.#checkpointSessionHeartbeats();
1157
1088
  return;
1158
1089
  }
1159
1090
  this.#fence(observation === "ambiguous" ? "observation-ambiguous" : "suspect-unpublished");
@@ -1180,31 +1111,15 @@ export class Broker {
1180
1111
  if (this.#publicationState !== "healthy-owned") return;
1181
1112
  await this.#writeHeartbeat();
1182
1113
  }
1183
-
1184
- /**
1185
- * Production coalesced heartbeat checkpoint seam (C2): delegates to the session
1186
- * index writer, which appends at most one `host_heartbeat` per session per
1187
- * {@link SESSION_HEARTBEAT_INTERVAL_MS} for hosts that are alive and, when recorded,
1188
- * still carry the same OS process incarnation. Returns the number of checkpoints
1189
- * written. Used by the periodic publication watch, by session reads so a fresh
1190
- * registration is observed as live, and by tests.
1191
- */
1114
+ /** Re-observes provably live session hosts and checkpoints their liveness. */
1192
1115
  async heartbeatSessions(now = Date.now()): Promise<number> {
1193
1116
  return await this.index.checkpointLiveHeartbeats(now);
1194
1117
  }
1195
-
1196
1118
  async #checkpointSessionHeartbeats(): Promise<void> {
1197
1119
  try {
1198
1120
  await this.heartbeatSessions();
1199
- } catch {
1200
- // Best-effort observation; a failed checkpoint never fences the broker.
1201
- }
1202
- }
1203
- async #compactSessionIndex(): Promise<void> {
1204
- try {
1205
- await this.index.compact();
1206
- } catch {
1207
- // Compaction is best-effort maintenance; the next scheduled pass retries.
1121
+ } catch (error) {
1122
+ logger.warn(`sdk broker: session heartbeat checkpoint failed: ${String(error)}`);
1208
1123
  }
1209
1124
  }
1210
1125
  async #complete(mode: BrokerStopMode): Promise<void> {
@@ -1220,10 +1135,6 @@ export class Broker {
1220
1135
  if (mode === "lost-root") this.#startupAdmissions.close();
1221
1136
  if (this.#heartbeatTimer) clearInterval(this.#heartbeatTimer);
1222
1137
  this.#heartbeatTimer = null;
1223
- if (this.#compactTimer) clearInterval(this.#compactTimer);
1224
- this.#compactTimer = null;
1225
- if (this.#operatorTimer) clearInterval(this.#operatorTimer);
1226
- this.#operatorTimer = null;
1227
1138
  this.#completionTask = (async () => {
1228
1139
  await this.#transport?.stop();
1229
1140
  this.#transport = null;
@@ -1303,16 +1214,54 @@ export class Broker {
1303
1214
  const authority = expectedEndpointAuthority(input);
1304
1215
  if ("ok" in authority) return authority;
1305
1216
  await this.index.refresh();
1306
- await this.heartbeatSessions();
1307
- const record = this.index.listSessions().sessions.find(session => session.sessionId === sessionId);
1217
+ let record = this.index.listSessions().sessions.find(session => session.sessionId === sessionId);
1218
+ if (
1219
+ record &&
1220
+ !record.live &&
1221
+ !record.terminal &&
1222
+ !record.terminalUncertain &&
1223
+ isSessionAuthorityEligible(record)
1224
+ ) {
1225
+ await this.heartbeatSessions();
1226
+ await this.index.refresh();
1227
+ record = this.index.listSessions().sessions.find(session => session.sessionId === sessionId);
1228
+ }
1308
1229
  if (!record) return error("resource_gone", "session is not indexed");
1309
- // C6: a cross-repo duplicate cannot resolve an endpoint credential; require
1310
- // disambiguation before any credential is minted.
1311
- if (record.ambiguous) return error("ambiguous_session", "session id maps to more than one state root");
1312
- if (!record.live || !matchesEndpointAuthority(record, authority))
1313
- return error("endpoint_stale", "session endpoint is stale");
1230
+ if (!isSessionAuthorityEligible(record)) return error("resource_gone", "session endpoint record is gone");
1231
+ if (!matchesEndpointAuthority(record, authority)) return error("endpoint_stale", "session endpoint is stale");
1232
+ if (!record.live) return error("resource_gone", "session endpoint record is gone");
1314
1233
  return this.#readEndpoint(record, authority);
1315
1234
  }
1235
+ async #readLifecycleReplayEndpoint(sessionId: string): Promise<LifecycleReplayEndpoint | BrokerResponse> {
1236
+ await this.index.refresh();
1237
+ const record = this.index.listSessions().sessions.find(session => session.sessionId === sessionId);
1238
+ if (!record) return error("resource_gone", "session endpoint record is gone");
1239
+ if (record.terminalUncertain)
1240
+ return error("terminal_uncertain", "Session ownership is uncertain and cannot be replayed safely");
1241
+ if (!isSessionAuthorityEligible(record) || !record.live)
1242
+ return error("resource_gone", "session endpoint record is gone");
1243
+ const endpointMtimeMs = record.endpointMtimeMs;
1244
+ if (
1245
+ !Number.isSafeInteger(record.endpointGeneration) ||
1246
+ record.endpointGeneration <= 0 ||
1247
+ !Number.isSafeInteger(record.pid) ||
1248
+ record.pid <= 0 ||
1249
+ endpointMtimeMs === undefined ||
1250
+ !Number.isFinite(endpointMtimeMs) ||
1251
+ endpointMtimeMs <= 0
1252
+ )
1253
+ return error("endpoint_stale", "session endpoint authority is incomplete");
1254
+ const endpoint = await this.#readEndpoint(record, {});
1255
+ if (!endpoint.ok) return endpoint;
1256
+ if (endpoint.result === null || typeof endpoint.result !== "object" || Array.isArray(endpoint.result))
1257
+ return error("endpoint_stale", "session endpoint is malformed");
1258
+ return {
1259
+ endpoint: endpoint.result as Record<string, unknown>,
1260
+ endpointGeneration: record.endpointGeneration,
1261
+ pid: record.pid,
1262
+ endpointMtimeMs,
1263
+ };
1264
+ }
1316
1265
  async #readEndpoint(record: IndexedSession, authority: EndpointAuthority): Promise<BrokerResponse> {
1317
1266
  if (!isCanonicalSessionId(record.sessionId))
1318
1267
  return error("invalid_input", "indexed sessionId is not a canonical safe identifier");
@@ -1326,7 +1275,7 @@ export class Broker {
1326
1275
  endpoint.pid !== record.pid ||
1327
1276
  endpoint.stale === true ||
1328
1277
  record.endpointMtimeMs === undefined ||
1329
- metadata.mtimeMs !== record.endpointMtimeMs
1278
+ Math.abs(metadata.mtimeMs - record.endpointMtimeMs) > 0.001
1330
1279
  )
1331
1280
  return error("endpoint_stale", "session endpoint is stale");
1332
1281
  await this.index.refresh();
@@ -1374,7 +1323,10 @@ export class Broker {
1374
1323
  offset: 0,
1375
1324
  expiresAt: Date.now() + SESSION_LIST_CURSOR_TTL_MS,
1376
1325
  };
1377
- const sessions = snapshot.sessions.slice(snapshot.offset, snapshot.offset + snapshot.limit);
1326
+ const sessions = snapshot.sessions.slice(snapshot.offset, snapshot.offset + snapshot.limit).map(session => {
1327
+ const { lifecycleRequestId: _lifecycleRequestId, ...publicSession } = session;
1328
+ return publicSession;
1329
+ });
1378
1330
  const offset = snapshot.offset + sessions.length;
1379
1331
  if (offset >= snapshot.sessions.length && typeof cursor === "string") this.#sessionListCursors.delete(cursor);
1380
1332
  const continuationCursor =
@@ -1396,18 +1348,13 @@ export class Broker {
1396
1348
  indexSeq: snapshot.indexSeq,
1397
1349
  };
1398
1350
  }
1399
- handleRequest(
1400
- operation: string,
1401
- input: Record<string, unknown>,
1402
- idempotencyKey?: string,
1403
- elevationRequestId?: string,
1404
- ): Promise<BrokerResponse> {
1351
+ handleRequest(operation: string, input: Record<string, unknown>, idempotencyKey?: string): Promise<BrokerResponse> {
1405
1352
  if (this.#stopping || (this.#publication !== null && this.#publicationState !== "healthy-owned"))
1406
1353
  return Promise.resolve(error("unavailable", "broker publication is unavailable"));
1407
1354
  let release!: () => void;
1408
1355
  const admission = new Promise<void>(resolve => (release = resolve));
1409
1356
  this.#admitted.add(admission);
1410
- return this.#handleRequest(operation, input, idempotencyKey, elevationRequestId).finally(() => {
1357
+ return this.#handleRequest(operation, input, idempotencyKey).finally(() => {
1411
1358
  release();
1412
1359
  this.#admitted.delete(admission);
1413
1360
  });
@@ -1416,22 +1363,14 @@ export class Broker {
1416
1363
  operation: string,
1417
1364
  input: Record<string, unknown>,
1418
1365
  idempotencyKey?: string,
1419
- elevationRequestId?: string,
1420
1366
  ): Promise<BrokerResponse> {
1421
1367
  if (this.#stopping) return error("broker_restarting", "broker is stopping");
1422
- const fingerprint = JSON.stringify({ operation, input: JSON.parse(JSON.stringify(input)) });
1368
+ const fingerprint = lifecycleFingerprint(operation, input);
1423
1369
  const normalization = normalizeBrokerInput(operation, input);
1424
1370
  if (isBrokerResponse(normalization)) return normalization;
1425
- const approvedInput = input;
1426
1371
  input = normalization.input;
1427
1372
  if (operation === "session.list") {
1428
- if (input.cursor === undefined) {
1429
- await this.index.refresh();
1430
- // Self-healing heartbeat checkpoint (C2): a host registered since the
1431
- // broker's last pass is observed as live on this read, so a fresh
1432
- // registration never reads as unknown (while a restart still does).
1433
- await this.heartbeatSessions();
1434
- }
1373
+ if (input.cursor === undefined) await this.index.refresh();
1435
1374
  const page = this.#sessionListPage(input, this.index.listSessions());
1436
1375
  if (!page.ok) return page;
1437
1376
  const pageResult = page.result as Record<string, unknown>;
@@ -1446,113 +1385,63 @@ export class Broker {
1446
1385
  ? listed.owned.filter(candidate => candidate.sessionId === resolveSessionId)
1447
1386
  : [];
1448
1387
  const match = matches.length === 1 ? matches[0] : undefined;
1388
+ const savedSession =
1389
+ match &&
1390
+ match.sessionId === resolveSessionId &&
1391
+ match.identity.nlink !== undefined &&
1392
+ match.identity.ctimeNs !== undefined
1393
+ ? {
1394
+ id: match.sessionId,
1395
+ path: match.path,
1396
+ identity: {
1397
+ dev: match.identity.dev.toString(),
1398
+ ino: match.identity.ino.toString(),
1399
+ nlink: match.identity.nlink.toString(),
1400
+ size: match.identity.size,
1401
+ mtimeMs: match.identity.mtimeMs,
1402
+ mtimeNs: match.identity.mtimeNs.toString(),
1403
+ ctimeNs: match.identity.ctimeNs.toString(),
1404
+ sha256: match.identity.sha256,
1405
+ },
1406
+ }
1407
+ : undefined;
1449
1408
  return {
1450
1409
  ...page,
1451
1410
  result: {
1452
1411
  ...pageResult,
1453
- savedSession:
1454
- match && match.sessionId === resolveSessionId
1455
- ? { id: match.sessionId, path: match.path }
1456
- : undefined,
1412
+ ...(savedSession === undefined ? {} : { savedSession }),
1457
1413
  },
1458
1414
  };
1459
1415
  }
1460
1416
  return page;
1461
1417
  }
1462
1418
  if (operation === "session.get_endpoint") return this.#endpoint(input);
1463
- if (operation === "elevation.issue") return this.#elevationIssueRequest(input);
1464
- if (operation === "elevation.status") return this.#elevationStatusRequest(input);
1465
- if (operation === "session.control") return this.#sessionControlRequest(input);
1466
1419
  if (operation === "broker.lookup_lifecycle") {
1467
1420
  const requestedOperation = typeof input.operation === "string" ? input.operation : undefined;
1468
- const fingerprint = typeof input.fingerprint === "string" ? input.fingerprint : undefined;
1469
- if (!idempotencyKey || !requestedOperation || !fingerprint)
1421
+ const requestedFingerprint = typeof input.fingerprint === "string" ? input.fingerprint : undefined;
1422
+ if (!idempotencyKey || !requestedOperation || !requestedFingerprint)
1470
1423
  return error("invalid_input", "operation, idempotencyKey, and fingerprint are required");
1471
1424
  if (!LIFECYCLE_OPERATIONS.has(requestedOperation))
1472
1425
  return error("not_found", "lifecycle operation was not found");
1473
1426
  const identity = await deriveIdempotencyIdentity(this.settings.agentDir, requestedOperation, idempotencyKey);
1474
1427
  const entry = this.ledger.get(identity);
1475
1428
  if (!entry) return error("not_found", "lifecycle operation was not found");
1476
- if (entry.fingerprint !== fingerprint)
1429
+ if (entry.fingerprint !== requestedFingerprint)
1477
1430
  return error("idempotency_conflict", "lifecycle request fingerprint differs");
1478
1431
  if (entry.state === "terminal_uncertain")
1479
- return {
1480
- ok: false,
1481
- error: { code: "terminal_uncertain", message: "lifecycle outcome is still uncertain" },
1482
- };
1483
- // Reconcile to the terminal original response contract: callers expect
1484
- // the BrokerResponse they would have received, not a lookup envelope.
1485
- return (
1486
- (entry.response as BrokerResponse) ?? {
1487
- ok: false,
1488
- error: { code: "terminal_uncertain", message: "lifecycle outcome has no recorded response" },
1489
- }
1490
- );
1432
+ return error("terminal_uncertain", "lifecycle outcome is still uncertain");
1433
+ return isBrokerResponse(entry.response)
1434
+ ? entry.response
1435
+ : error("terminal_uncertain", "lifecycle outcome has no recorded response");
1491
1436
  }
1437
+
1492
1438
  if (!idempotencyKey) return error("invalid_input", "idempotencyKey is required for lifecycle operations");
1493
- // Elevation gate (F1.3): allowlisted raw operations execute only behind a
1494
- // broker-owned single-use grant. Validation is fail-closed and happens
1495
- // before any lifecycle ledger write; the claim itself is consumed right
1496
- // before dispatch and the truthful outcome is recorded after it.
1497
- const elevationGateKind = this.#elevationGateFor(operation);
1498
- let elevationRequestIdForDispatch: string | undefined;
1499
- if (elevationGateKind !== undefined) {
1500
- if (elevationRequestId === undefined)
1501
- return error("elevation_required", "an elevation grant claim is required for this operation");
1502
- if (!isElevationRequestId(elevationRequestId))
1503
- return error("invalid_input", "elevationRequestId must be an RFC4122 lowercase UUID v4");
1504
- const resolved = await this.elevation.get(elevationRequestId);
1505
- if (!resolved.ok) {
1506
- if (resolved.error.code === "not_found")
1507
- return error(
1508
- "elevation_required",
1509
- "elevation grant was not found; a grant is required for this operation",
1510
- );
1511
- return elevationResultToBroker(resolved);
1512
- }
1513
- const grant = resolved.value.grant;
1514
- if (grant.operation.kind !== elevationGateKind || grant.operation.sdkId !== operation)
1515
- return error("elevation_digest_mismatch", "elevation grant was issued for a different operation");
1516
- const digest = elevationRequestDigest({ kind: elevationGateKind, sdkId: operation, input: approvedInput });
1517
- if (!digest.ok || digest.digest !== grant.requestDigest)
1518
- return error("elevation_digest_mismatch", "elevation grant digest does not match the dispatched input");
1519
- // Bind the grant to the actual lifecycle target before any claim: the
1520
- // approved input must resolve to the exact session identity the grant
1521
- // was issued against (C10). session.delete may target a retained
1522
- // stopped session; everything else requires a live endpoint.
1523
- const targetSessionId = typeof input.sessionId === "string" ? input.sessionId : undefined;
1524
- if (targetSessionId !== undefined) {
1525
- const target = await this.#elevationSessionResolution(targetSessionId, {
1526
- allowStopped: operation === "session.delete",
1527
- });
1528
- if (target.kind === "ambiguous")
1529
- return error("ambiguous_session", "session id maps to more than one state root");
1530
- if (target.kind !== "identity")
1531
- return error("target_unavailable", "session endpoint is unreachable or its identity is not provable");
1532
- if (grant.sessionIdentity.sessionId !== target.identity.sessionId)
1533
- return error("elevation_digest_mismatch", "elevation grant was issued for a different session target");
1534
- if (!sameElevationSessionIdentity(grant.sessionIdentity, target.identity))
1535
- return error("endpoint_stale", "session endpoint identity no longer matches the elevation grant");
1536
- }
1537
- elevationRequestIdForDispatch = elevationRequestId;
1538
- }
1539
1439
  const target = createHash("sha256")
1540
1440
  .update(canonicalJson(lifecycleTarget(operation, input)))
1541
1441
  .digest("hex");
1542
- const identity = await deriveIdempotencyIdentity(this.settings.agentDir, operation, idempotencyKey);
1442
+ const identity = await deriveIdempotencyIdentity(this.settings.agentDir, operation, idempotencyKey, target);
1543
1443
  const operationKey = `${operation}\0${idempotencyKey}`;
1544
- const legacyIdentity = await deriveLegacyTargetIdentity(
1545
- this.settings.agentDir,
1546
- operation,
1547
- idempotencyKey,
1548
- target,
1549
- );
1550
- if (!this.ledger.get(identity)) {
1551
- if (await this.ledger.migrateIdentity(legacyIdentity, identity, { operationKey, fingerprint })) {
1552
- // Exact target-derived legacy identity migrated without granting new authority.
1553
- } else if (this.ledger.findByOperationKey(operationKey) || this.ledger.hasLegacyIdentity())
1554
- return error("idempotency_conflict", "legacy lifecycle request has an ambiguous target");
1555
- }
1444
+
1556
1445
  let reconstructedDeleteCleanup: BrokerCleanupEvidence | undefined;
1557
1446
  if (operation === "session.delete" && input.cwd === undefined && input.sessionPath === undefined) {
1558
1447
  const entry = this.ledger.get(identity);
@@ -1618,19 +1507,39 @@ export class Broker {
1618
1507
  await prev;
1619
1508
  try {
1620
1509
  const beforeBegin = this.ledger.get(identity);
1621
- const begun = await this.ledger.begin(identity, requestHash, {
1622
- operationKey,
1623
- fingerprint,
1624
- });
1510
+ const begun = await this.ledger.begin(identity, requestHash, { operationKey, fingerprint });
1625
1511
  if (begun.kind === "replay") {
1626
1512
  const replay = begun.entry.response as BrokerResponse;
1627
1513
  const cleanup = cleanupFromResponse(replay) ?? reconstructedDeleteCleanup;
1628
- if (!cleanup) return replay;
1514
+ if (!cleanup) {
1515
+ if (
1516
+ replay.ok &&
1517
+ (operation === "session.create" || operation === "session.fork" || operation === "session.resume") &&
1518
+ typeof (replay.result as { sessionId?: unknown } | undefined)?.sessionId === "string"
1519
+ ) {
1520
+ const refreshed = await this.#readLifecycleReplayEndpoint(
1521
+ (replay.result as { sessionId: string }).sessionId,
1522
+ );
1523
+ if (isBrokerResponse(refreshed)) return refreshed;
1524
+ return {
1525
+ ok: true,
1526
+ result: {
1527
+ ...(replay.result as Record<string, unknown>),
1528
+ endpointGeneration: refreshed.endpointGeneration,
1529
+ pid: refreshed.pid,
1530
+ endpointMtimeMs: refreshed.endpointMtimeMs,
1531
+ endpoint: refreshed.endpoint,
1532
+ },
1533
+ };
1534
+ }
1535
+ return replay;
1536
+ }
1629
1537
  const outcome = await executeLifecycle(this, operation, input, identity, cleanup);
1630
1538
  const response = outcome.response;
1539
+ const storedResponse = credentialFreeLifecycleResponse(response) as BrokerResponse;
1631
1540
  await this.ledger.transition(identity, lifecycleResponseState(response), {
1632
- response,
1633
- responseDigest: createHash("sha256").update(canonicalJson(response)).digest("hex"),
1541
+ response: storedResponse,
1542
+ responseDigest: createHash("sha256").update(canonicalJson(storedResponse)).digest("hex"),
1634
1543
  ...(outcome.durableEffects ? { durableEffects: outcome.durableEffects } : {}),
1635
1544
  ...(outcome.startupFailure ? { startupFailure: outcome.startupFailure } : {}),
1636
1545
  });
@@ -1645,64 +1554,28 @@ export class Broker {
1645
1554
  return replay ?? error("terminal_uncertain", "prior lifecycle operation outcome is uncertain");
1646
1555
  const outcome = await executeLifecycle(this, operation, input, identity, cleanup);
1647
1556
  const response = outcome.response;
1557
+ const storedResponse = credentialFreeLifecycleResponse(response) as BrokerResponse;
1648
1558
  await this.ledger.transition(identity, lifecycleResponseState(response), {
1649
1559
  ...(pendingCleanupSessionId(response) ? { intendedSessionId: pendingCleanupSessionId(response) } : {}),
1650
- response,
1651
- responseDigest: createHash("sha256").update(canonicalJson(response)).digest("hex"),
1560
+ response: storedResponse,
1561
+ responseDigest: createHash("sha256").update(canonicalJson(storedResponse)).digest("hex"),
1652
1562
  ...(outcome.durableEffects ? { durableEffects: outcome.durableEffects } : {}),
1653
1563
  ...(outcome.startupFailure ? { startupFailure: outcome.startupFailure } : {}),
1654
1564
  });
1655
1565
  return response;
1656
1566
  }
1657
1567
  if (begun.kind === "in_progress") return error("broker_restarting", "lifecycle operation is in progress");
1658
- // Single-use grant claim (F1.3): consume the grant before any dispatch.
1659
- // A claimed grant that crashes before outcome recording is replayed
1660
- // truthfully as consumed/uncertain; retry requires a new grant.
1661
- if (elevationRequestIdForDispatch !== undefined) {
1662
- const claimed = await this.elevationClaim({ elevationRequestId: elevationRequestIdForDispatch });
1663
- if (!claimed.ok) return claimed;
1664
- }
1665
- let outcome: Awaited<ReturnType<typeof executeLifecycle>>;
1666
- try {
1667
- outcome = await executeLifecycle(this, operation, input, identity);
1668
- } catch (cause) {
1669
- if (elevationRequestIdForDispatch !== undefined)
1670
- await this.elevationDispatch({
1671
- elevationRequestId: elevationRequestIdForDispatch,
1672
- outcome: {
1673
- status: "failed",
1674
- code: "unavailable",
1675
- message: cause instanceof Error ? cause.message : String(cause),
1676
- dispatchedAt: Date.now(),
1677
- },
1678
- });
1679
- throw cause;
1680
- }
1568
+ const outcome = await executeLifecycle(this, operation, input, identity);
1681
1569
  const response = outcome.response;
1682
- // Record the truthful dispatch outcome for the claimed grant.
1683
- if (elevationRequestIdForDispatch !== undefined) {
1684
- const dispatchedAt = Date.now();
1685
- const recorded = await this.elevationDispatch({
1686
- elevationRequestId: elevationRequestIdForDispatch,
1687
- outcome: response.ok
1688
- ? { status: "ok", dispatchedAt }
1689
- : {
1690
- status: "failed",
1691
- code: response.error.code,
1692
- message: response.error.message,
1693
- dispatchedAt,
1694
- },
1695
- });
1696
- if (!recorded.ok) return recorded;
1697
- }
1570
+ const storedResponse = credentialFreeLifecycleResponse(response) as BrokerResponse;
1698
1571
  await this.ledger.transition(identity, lifecycleResponseState(response), {
1699
1572
  ...(pendingCleanupSessionId(response) ? { intendedSessionId: pendingCleanupSessionId(response) } : {}),
1700
1573
  resultSessionId:
1701
1574
  response.ok && typeof (response.result as { sessionId?: unknown } | undefined)?.sessionId === "string"
1702
1575
  ? (response.result as { sessionId: string }).sessionId
1703
1576
  : undefined,
1704
- response,
1705
- responseDigest: createHash("sha256").update(canonicalJson(response)).digest("hex"),
1577
+ response: storedResponse,
1578
+ responseDigest: createHash("sha256").update(canonicalJson(storedResponse)).digest("hex"),
1706
1579
  ...(outcome.durableEffects ? { durableEffects: outcome.durableEffects } : {}),
1707
1580
  ...(outcome.startupFailure ? { startupFailure: outcome.startupFailure } : {}),
1708
1581
  });
@@ -1710,7 +1583,7 @@ export class Broker {
1710
1583
  const persisted = await this.ledger.readTerminal(identity, requestHash);
1711
1584
  const persistenceVerified =
1712
1585
  persisted !== undefined &&
1713
- canonicalJson(persisted.response) === canonicalJson(response) &&
1586
+ canonicalJson(persisted.response) === canonicalJson(storedResponse) &&
1714
1587
  canonicalJson(persisted.durableEffects) === canonicalJson(outcome.durableEffects) &&
1715
1588
  canonicalJson(persisted.startupFailure) === canonicalJson(outcome.startupFailure);
1716
1589
  if (!persistenceVerified) {
@@ -1734,460 +1607,6 @@ export class Broker {
1734
1607
  if (this.#chains.get(target) === current) this.#chains.delete(target);
1735
1608
  }
1736
1609
  }
1737
-
1738
- #elevationGateFor(operation: string): "global" | undefined {
1739
- return this.#elevationEnforced && isElevatableOperation("global", operation) ? "global" : undefined;
1740
- }
1741
-
1742
- async #elevationIssueRequest(input: Record<string, unknown>): Promise<BrokerResponse> {
1743
- const sessionId = input.sessionId;
1744
- if (typeof sessionId !== "string" || !isCanonicalSessionId(sessionId))
1745
- return error("invalid_input", "sessionId must be a canonical safe identifier");
1746
- const rawOperation = input.operation;
1747
- if (typeof rawOperation !== "object" || rawOperation === null || Array.isArray(rawOperation))
1748
- return error("invalid_input", "operation must be an object with kind and sdkId");
1749
- const { kind, sdkId } = rawOperation as { kind?: unknown; sdkId?: unknown };
1750
- if (
1751
- typeof kind !== "string" ||
1752
- (kind !== "control" && kind !== "query" && kind !== "global") ||
1753
- typeof sdkId !== "string" ||
1754
- !findOperation(kind, sdkId)
1755
- )
1756
- return error("invalid_request", `unknown elevation operation: ${String(kind)}:${String(sdkId)}`);
1757
- if (!isElevatableOperation(kind, sdkId))
1758
- return error("elevation_not_allowlisted", `${sdkId} does not require elevation`);
1759
- const approved = input.input;
1760
- if (typeof approved !== "object" || approved === null || Array.isArray(approved))
1761
- return error("invalid_input", "input must be the approved operation arguments");
1762
- if (containsReservedKey(approved, "elevationRequestId"))
1763
- return error(
1764
- "invalid_input",
1765
- "elevationRequestId is reserved and cannot appear inside the approved operation input",
1766
- );
1767
- if (input.idempotencyKey !== undefined)
1768
- return error(
1769
- "invalid_input",
1770
- "idempotencyKey is broker-frame metadata and cannot appear inside elevation.issue input",
1771
- );
1772
- const elevationRequestId = input.elevationRequestId;
1773
- if (elevationRequestId !== undefined && !isElevationRequestId(elevationRequestId))
1774
- return error("invalid_input", "elevationRequestId must be an RFC4122 lowercase UUID v4");
1775
- const expiresInMs = input.expiresInMs;
1776
- if (
1777
- expiresInMs !== undefined &&
1778
- (typeof expiresInMs !== "number" || !Number.isSafeInteger(expiresInMs) || expiresInMs <= 0)
1779
- )
1780
- return error("invalid_input", "expiresInMs must be a positive safe integer");
1781
- let requester: ElevationRequester;
1782
- if (input.requester === undefined) {
1783
- requester = { source: "broker_connection", connectionId: `broker:${this.#owner}` };
1784
- } else {
1785
- const candidate = input.requester as { source?: unknown; connectionId?: unknown };
1786
- if (
1787
- candidate?.source !== "broker_connection" ||
1788
- typeof candidate.connectionId !== "string" ||
1789
- candidate.connectionId.length === 0
1790
- )
1791
- return error("invalid_input", "requester must be a broker_connection audit identity");
1792
- requester = { source: "broker_connection", connectionId: candidate.connectionId };
1793
- }
1794
- return await this.elevationIssue({
1795
- sessionId,
1796
- operation: { kind, sdkId },
1797
- input: approved as Record<string, unknown>,
1798
- requester,
1799
- ...(elevationRequestId === undefined ? {} : { elevationRequestId }),
1800
- ...(expiresInMs === undefined ? {} : { expiresInMs }),
1801
- });
1802
- }
1803
-
1804
- async #elevationStatusRequest(input: Record<string, unknown>): Promise<BrokerResponse> {
1805
- const elevationRequestId = input.elevationRequestId;
1806
- if (typeof elevationRequestId !== "string" || !isElevationRequestId(elevationRequestId))
1807
- return error("invalid_input", "elevationRequestId must be an RFC4122 lowercase UUID v4");
1808
- return await this.elevationResolve(elevationRequestId);
1809
- }
1810
-
1811
- #elevationClaimIdentity(): ElevationClaimIdentity {
1812
- const incarnation = this.#incarnation;
1813
- if (incarnation === null) throw new Error("Broker process incarnation is unavailable.");
1814
- return { ownerId: this.#owner, epoch: this.#epoch, pid: process.pid, incarnation };
1815
- }
1816
- async #elevationPrincipal(): Promise<BrokerOwnerPrincipal> {
1817
- const incarnation = this.#incarnation;
1818
- if (incarnation === null) throw new Error("Broker process incarnation is unavailable.");
1819
- return readBrokerOwnerPrincipal(this.settings.agentDir, process.pid, incarnation);
1820
- }
1821
- /**
1822
- * Resolves the exact elevation session identity (C6/C10). A cross-repo
1823
- * duplicate (`ambiguous`) is rejected before any endpoint credential or
1824
- * elevation grant can bind; an unreachable or unprovable session is
1825
- * `unavailable`. The heartbeat checkpoint pass runs first so a fresh
1826
- * registration is observable as live.
1827
- *
1828
- * `allowStopped` is the session.delete-only exemption: a retained stopped
1829
- * session keeps its exact endpoint identity tuple (generation/pid/mtime)
1830
- * in the index row, so a delete grant can still be bound and dispatched
1831
- * against it. Everything else fails closed unless the endpoint is live.
1832
- */
1833
- async #elevationSessionResolution(
1834
- sessionId: string,
1835
- options?: { allowStopped?: boolean },
1836
- ): Promise<ElevationSessionResolution> {
1837
- if (!isCanonicalSessionId(sessionId)) return { kind: "unavailable" };
1838
- await this.index.refresh();
1839
- await this.heartbeatSessions();
1840
- const record = this.index.listSessions().sessions.find(session => session.sessionId === sessionId);
1841
- if (!record) return { kind: "unavailable" };
1842
- if (record.ambiguous) return { kind: "ambiguous" };
1843
- if (!record.live && options?.allowStopped !== true) return { kind: "unavailable" };
1844
- const incarnation = endpointIncarnation(record, sessionId);
1845
- if (incarnation === undefined) return { kind: "unavailable" };
1846
- return {
1847
- kind: "identity",
1848
- identity: {
1849
- sessionId: record.sessionId,
1850
- endpointStateRoot: record.locator.stateRoot,
1851
- endpointGeneration: record.endpointGeneration,
1852
- endpointIncarnation: incarnation,
1853
- },
1854
- };
1855
- }
1856
-
1857
- async #pollOperatorDirectives(): Promise<void> {
1858
- const directory = path.join(this.settings.agentDir, "sdk", "elevation", "operator");
1859
- let names: string[];
1860
- try {
1861
- names = await fs.readdir(directory);
1862
- } catch {
1863
- return;
1864
- }
1865
- for (const name of names) {
1866
- if (!name.endsWith(".json")) continue;
1867
- const file = path.join(directory, name);
1868
- try {
1869
- const stat = await fs.stat(file);
1870
- if ((stat.mode & 0o077) !== 0) throw new Error("operator directive is not private");
1871
- const raw: unknown = JSON.parse(await fs.readFile(file, "utf8"));
1872
- if (!raw || typeof raw !== "object" || Array.isArray(raw)) throw new Error("malformed operator directive");
1873
- const directive = raw as Record<string, unknown>;
1874
- if (
1875
- typeof directive.elevationRequestId !== "string" ||
1876
- (directive.answer !== "approve" && directive.answer !== "deny") ||
1877
- typeof directive.presentedDigest !== "string"
1878
- )
1879
- throw new Error("malformed operator directive");
1880
- const response = await this.elevationAnswer({
1881
- elevationRequestId: directive.elevationRequestId,
1882
- answer: directive.answer,
1883
- presentedDigest: directive.presentedDigest,
1884
- answerer: { source: "local_operator", attestedBy: "gjc-sdk-session-cli" },
1885
- });
1886
- if (!response.ok) throw new Error(response.error.message);
1887
- await fs.rm(file, { force: true });
1888
- } catch {
1889
- await fs.rename(file, `${file}.rejected`).catch(() => undefined);
1890
- }
1891
- }
1892
- }
1893
-
1894
- async #sessionControlRequest(input: Record<string, unknown>): Promise<BrokerResponse> {
1895
- const sessionId = typeof input.sessionId === "string" ? input.sessionId : undefined;
1896
- const operation = typeof input.operation === "string" ? input.operation : undefined;
1897
- const rawInput = input.input;
1898
- const controlInput =
1899
- rawInput && typeof rawInput === "object" && !Array.isArray(rawInput)
1900
- ? (rawInput as Record<string, unknown>)
1901
- : undefined;
1902
- if (!sessionId || !operation || !controlInput || !findOperation("control", operation))
1903
- return error(
1904
- "invalid_input",
1905
- "session.control requires a valid sessionId, control operation, and object input",
1906
- );
1907
- const elevationRequestId = typeof input.elevationRequestId === "string" ? input.elevationRequestId : undefined;
1908
- const elevatable = this.#elevationEnforced && isElevatableOperation("control", operation);
1909
- // Elevation preflight (F1.3) runs before any clientRef reservation so a
1910
- // no-grant attempt can never poison a later granted retry with a stale
1911
- // pending receipt.
1912
- let grant: ElevationGrantRecord | undefined;
1913
- if (elevatable) {
1914
- if (!elevationRequestId) return error("elevation_required", "an elevation grant is required for this control");
1915
- const resolved = await this.elevation.get(elevationRequestId);
1916
- if (!resolved.ok) return elevationResultToBroker(resolved);
1917
- grant = resolved.value.grant;
1918
- const digest = elevationRequestDigest({ kind: "control", sdkId: operation, input: controlInput });
1919
- if (
1920
- grant.operation.kind !== "control" ||
1921
- grant.operation.sdkId !== operation ||
1922
- !digest.ok ||
1923
- digest.digest !== grant.requestDigest
1924
- )
1925
- return error("elevation_digest_mismatch", "elevation grant does not match the requested control");
1926
- }
1927
- await this.index.refresh();
1928
- const matches = this.index.listSessions().sessions.filter(session => session.sessionId === sessionId);
1929
- if (matches.some(session => session.ambiguous) || matches.length > 1)
1930
- return error("ambiguous_session", "session id maps to more than one state root");
1931
- const record = matches[0];
1932
- if (!record?.live) return error("target_unavailable", "session endpoint is unavailable");
1933
- const incarnation = endpointIncarnation(record, sessionId);
1934
- if (!incarnation) return error("endpoint_stale", "session endpoint incarnation is unavailable");
1935
- // Bind the grant to the actual approved target session identity before
1936
- // claim/capability minting: a grant issued for a different session can
1937
- // never authorize this control, even with byte-identical input.
1938
- if (grant !== undefined) {
1939
- if (grant.sessionIdentity.sessionId !== record.sessionId)
1940
- return error("elevation_digest_mismatch", "elevation grant was issued for a different session target");
1941
- const targetIdentity: ElevationSessionIdentity = {
1942
- sessionId: record.sessionId,
1943
- endpointStateRoot: record.locator.stateRoot,
1944
- endpointGeneration: record.endpointGeneration,
1945
- endpointIncarnation: incarnation,
1946
- };
1947
- if (!sameElevationSessionIdentity(grant.sessionIdentity, targetIdentity))
1948
- return error("endpoint_stale", "session endpoint identity no longer matches the elevation grant");
1949
- }
1950
- const clientRef =
1951
- typeof controlInput.clientRef === "string" && controlInput.clientRef.length > 0
1952
- ? controlInput.clientRef
1953
- : undefined;
1954
- // clientRef idempotency is target-session scoped: identical refs and
1955
- // input on different sessions never collide, conflict, or replay.
1956
- const receiptKey = clientRef === undefined ? undefined : operationReceiptKey(sessionId, clientRef);
1957
- const receiptDigest =
1958
- clientRef === undefined ? undefined : operationReceiptDigest(operation, controlInput, sessionId);
1959
- if (receiptKey !== undefined && receiptDigest !== undefined) {
1960
- const reservation = await this.operationReceipts.reserve(receiptKey, receiptDigest);
1961
- if (reservation.status === "replay") return reservation.response;
1962
- if (reservation.status === "in_progress")
1963
- return error("operation_in_progress", "operation receipt is pending");
1964
- if (reservation.status === "conflict")
1965
- return error("idempotency_conflict", "clientRef was reused with different operation input");
1966
- }
1967
- const settle = async (response: BrokerResponse): Promise<BrokerResponse> => {
1968
- if (receiptKey !== undefined && receiptDigest !== undefined)
1969
- await this.operationReceipts.complete(receiptKey, receiptDigest, response);
1970
- return response;
1971
- };
1972
- const endpointResponse = await this.#endpoint({
1973
- sessionId,
1974
- endpointGeneration: record.endpointGeneration,
1975
- endpointIncarnation: incarnation,
1976
- });
1977
- if (!endpointResponse.ok) return settle(endpointResponse);
1978
- const endpoint = endpointResponse.result as { url?: unknown; token?: unknown };
1979
- if (typeof endpoint.url !== "string" || typeof endpoint.token !== "string")
1980
- return settle(error("unavailable", "session endpoint is malformed"));
1981
- let elevationCapability: string | undefined;
1982
- if (elevatable && elevationRequestId) {
1983
- let authorityToken: string | undefined;
1984
- try {
1985
- const authority = JSON.parse(
1986
- await fs.readFile(elevationAuthorityPath(record.locator.stateRoot, sessionId), "utf8"),
1987
- ) as { token?: unknown };
1988
- authorityToken = typeof authority.token === "string" ? authority.token : undefined;
1989
- } catch {
1990
- return settle(error("unavailable", "session elevation authority is unavailable"));
1991
- }
1992
- if (!authorityToken) return settle(error("unavailable", "session elevation authority is malformed"));
1993
- const claimed = await this.elevationClaim({ elevationRequestId });
1994
- if (!claimed.ok) return settle(claimed);
1995
- elevationCapability = signElevationCapability(authorityToken, elevationRequestId, operation, controlInput);
1996
- }
1997
- let client: SdkClient | undefined;
1998
- try {
1999
- client = await SdkClient.connect(endpoint.url, endpoint.token);
2000
- const response = await client.control(operation, controlInput, {
2001
- confirm: input.confirm === true,
2002
- ...(elevationCapability === undefined ? {} : { elevationRequestId: elevationCapability }),
2003
- });
2004
- const ok = (response as { ok?: unknown }).ok === true;
2005
- if (elevatable && elevationRequestId) {
2006
- await this.elevationDispatch({
2007
- elevationRequestId,
2008
- outcome: ok
2009
- ? { status: "ok", dispatchedAt: Date.now() }
2010
- : {
2011
- status: "failed",
2012
- code: String((response as { error?: { code?: unknown } }).error?.code ?? "control_failed"),
2013
- message: String(
2014
- (response as { error?: { message?: unknown } }).error?.message ?? "control failed",
2015
- ),
2016
- dispatchedAt: Date.now(),
2017
- },
2018
- });
2019
- }
2020
- const brokerResponse = ok
2021
- ? { ok: true as const, result: (response as { result?: unknown }).result }
2022
- : error(
2023
- String((response as { error?: { code?: unknown } }).error?.code ?? "unavailable"),
2024
- String((response as { error?: { message?: unknown } }).error?.message ?? "control failed"),
2025
- );
2026
- return settle(brokerResponse);
2027
- } catch (cause) {
2028
- if (elevatable && elevationRequestId)
2029
- await this.elevationDispatch({
2030
- elevationRequestId,
2031
- outcome: {
2032
- status: "failed",
2033
- code: "unavailable",
2034
- message: cause instanceof Error ? cause.message : String(cause),
2035
- dispatchedAt: Date.now(),
2036
- },
2037
- });
2038
- return settle(
2039
- cause instanceof SdkClientError
2040
- ? error(cause.code, cause.message)
2041
- : error("unavailable", cause instanceof Error ? cause.message : String(cause)),
2042
- );
2043
- } finally {
2044
- await client?.close();
2045
- }
2046
- }
2047
- /**
2048
- * Elevation issuance (internal). Binds the exact endpoint identity
2049
- * (endpointStateRoot/generation/incarnation) from the live index record;
2050
- * the request digest is broker-computed from the full `{kind,sdkId,input}`
2051
- * triple with the top-level `elevationRequestId` excluded.
2052
- */
2053
- async elevationIssue(params: {
2054
- sessionId: string;
2055
- operation: ElevationOperation;
2056
- input: Record<string, unknown>;
2057
- requester: ElevationRequester;
2058
- elevationRequestId?: string;
2059
- expiresInMs?: number;
2060
- }): Promise<BrokerResponse> {
2061
- if (!isCanonicalSessionId(params.sessionId))
2062
- return error("invalid_input", "sessionId must be a canonical safe identifier");
2063
- // Retained stopped sessions may only back an elevation request for the
2064
- // exact global lifecycle operation that needs them (session.delete); all
2065
- // other operations still require a live endpoint (fail closed otherwise).
2066
- const allowStopped = params.operation.kind === "global" && params.operation.sdkId === "session.delete";
2067
- const resolution = await this.#elevationSessionResolution(params.sessionId, { allowStopped });
2068
- if (resolution.kind === "ambiguous")
2069
- return error("ambiguous_session", "session id maps to more than one state root");
2070
- if (resolution.kind === "unavailable")
2071
- return error("target_unavailable", "session endpoint is unreachable or its identity is not provable");
2072
- // Bind the grant to the actual approved target session: the approved
2073
- // input for a global lifecycle operation must name the elevation session
2074
- // itself, never a different session (C10).
2075
- if (params.operation.kind === "global") {
2076
- const approvedTarget = typeof params.input.sessionId === "string" ? params.input.sessionId : undefined;
2077
- if (approvedTarget !== undefined && approvedTarget !== params.sessionId)
2078
- return error("invalid_input", "approved input targets a different session than the elevation session");
2079
- }
2080
- const { identity: sessionIdentity } = resolution;
2081
- let principal: BrokerOwnerPrincipal;
2082
- try {
2083
- principal = await this.#elevationPrincipal();
2084
- } catch {
2085
- return error("unavailable", "broker owner principal is unavailable");
2086
- }
2087
- const result = await this.elevation.issue({ ...params, sessionIdentity, principal });
2088
- return elevationResultToBroker(result);
2089
- }
2090
-
2091
- /**
2092
- * Elevation answer (internal, operator-only). There is no public
2093
- * `elevation.answer` operation: this entry point is reserved for the local
2094
- * operator approval surface and the separately authorized gate-answer
2095
- * source, and the ledger enforces the distinct-answer-source boundary.
2096
- */
2097
- async elevationAnswer(params: {
2098
- elevationRequestId: string;
2099
- answer: "approve" | "deny";
2100
- presentedDigest: string;
2101
- answerer: ElevationAnswerAuthority;
2102
- }): Promise<BrokerResponse> {
2103
- const resolved = await this.elevation.get(params.elevationRequestId);
2104
- if (!resolved.ok) return elevationResultToBroker(resolved);
2105
- const sessionId = resolved.value.grant.sessionIdentity.sessionId;
2106
- const grantOperation = resolved.value.grant.operation;
2107
- const resolution = await this.#elevationSessionResolution(sessionId, {
2108
- allowStopped: grantOperation.kind === "global" && grantOperation.sdkId === "session.delete",
2109
- });
2110
- if (resolution.kind === "ambiguous")
2111
- return error("ambiguous_session", "session id maps to more than one state root");
2112
- const currentSessionIdentity = resolution.kind === "identity" ? resolution.identity : undefined;
2113
- let principal: BrokerOwnerPrincipal;
2114
- try {
2115
- principal = await this.#elevationPrincipal();
2116
- } catch {
2117
- return error("unavailable", "broker owner principal is unavailable");
2118
- }
2119
- const result = await this.elevation.answer({ ...params, principal, currentSessionIdentity });
2120
- return elevationAnswerToBroker(result);
2121
- }
2122
-
2123
- /** Single-use grant claim (internal): consumes the grant before dispatch. */
2124
- async elevationClaim(params: { elevationRequestId: string }): Promise<BrokerResponse> {
2125
- const resolved = await this.elevation.get(params.elevationRequestId);
2126
- if (!resolved.ok) return elevationResultToBroker(resolved);
2127
- const sessionId = resolved.value.grant.sessionIdentity.sessionId;
2128
- const grantOperation = resolved.value.grant.operation;
2129
- const resolution = await this.#elevationSessionResolution(sessionId, {
2130
- allowStopped: grantOperation.kind === "global" && grantOperation.sdkId === "session.delete",
2131
- });
2132
- if (resolution.kind === "ambiguous")
2133
- return error("ambiguous_session", "session id maps to more than one state root");
2134
- const currentSessionIdentity = resolution.kind === "identity" ? resolution.identity : undefined;
2135
- let claimIdentity: ElevationClaimIdentity;
2136
- try {
2137
- claimIdentity = this.#elevationClaimIdentity();
2138
- } catch {
2139
- return error("unavailable", "broker process incarnation is unavailable");
2140
- }
2141
- const result = await this.elevation.claim({
2142
- elevationRequestId: params.elevationRequestId,
2143
- claimIdentity,
2144
- currentSessionIdentity,
2145
- });
2146
- return elevationResultToBroker(result);
2147
- }
2148
-
2149
- /** Records the dispatch outcome for a claimed grant (internal). */
2150
- async elevationDispatch(params: {
2151
- elevationRequestId: string;
2152
- outcome: ElevationDispatchOutcome;
2153
- }): Promise<BrokerResponse> {
2154
- let dispatchIdentity: ElevationClaimIdentity;
2155
- try {
2156
- dispatchIdentity = this.#elevationClaimIdentity();
2157
- } catch {
2158
- return error("unavailable", "broker process incarnation is unavailable");
2159
- }
2160
- const result = await this.elevation.dispatch({
2161
- elevationRequestId: params.elevationRequestId,
2162
- dispatchIdentity,
2163
- outcome: params.outcome,
2164
- });
2165
- return elevationResultToBroker(result);
2166
- }
2167
-
2168
- /** Truthful elevation status read (internal; reconciles expiry and crash state). */
2169
- async elevationResolve(elevationRequestId: string): Promise<BrokerResponse> {
2170
- let claimIdentity: ElevationClaimIdentity;
2171
- try {
2172
- claimIdentity = this.#elevationClaimIdentity();
2173
- } catch {
2174
- return error("unavailable", "broker process incarnation is unavailable");
2175
- }
2176
- const result = await this.elevation.resolve(elevationRequestId, claimIdentity);
2177
- return elevationResultToBroker(result);
2178
- }
2179
-
2180
- /** Truthful elevation listing (internal). */
2181
- async elevationList(): Promise<BrokerResponse> {
2182
- let claimIdentity: ElevationClaimIdentity;
2183
- try {
2184
- claimIdentity = this.#elevationClaimIdentity();
2185
- } catch {
2186
- return error("unavailable", "broker process incarnation is unavailable");
2187
- }
2188
- const result = await this.elevation.list(claimIdentity);
2189
- return elevationResultToBroker(result);
2190
- }
2191
1610
  }
2192
1611
 
2193
1612
  /** Test-only hook for simulating a process crash after terminal persistence verification. */