@gajae-code/coding-agent 0.10.1 → 0.11.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 (474) hide show
  1. package/CHANGELOG.md +70 -2
  2. package/dist/types/append-only-mode.d.ts +1 -1
  3. package/dist/types/cli/args.d.ts +4 -5
  4. package/dist/types/cli/daemon-cli.d.ts +12 -1
  5. package/dist/types/cli/notify-cli.d.ts +21 -1
  6. package/dist/types/cli.d.ts +3 -0
  7. package/dist/types/commands/daemon.d.ts +39 -0
  8. package/dist/types/commands/launch.d.ts +3 -0
  9. package/dist/types/commands/notify.d.ts +28 -1
  10. package/dist/types/commands/sdk.d.ts +33 -0
  11. package/dist/types/config/atomic-yaml-patch.d.ts +85 -0
  12. package/dist/types/config/model-profile-activation.d.ts +35 -15
  13. package/dist/types/config/model-profiles.d.ts +11 -5
  14. package/dist/types/config/model-registry.d.ts +3 -3
  15. package/dist/types/config/model-resolver.d.ts +39 -6
  16. package/dist/types/config/model-selector-value.d.ts +17 -0
  17. package/dist/types/config/models-config-schema.d.ts +10 -6
  18. package/dist/types/config/settings-schema.d.ts +133 -6
  19. package/dist/types/config/settings.d.ts +63 -35
  20. package/dist/types/coordinator-mcp/model-preset.d.ts +7 -6
  21. package/dist/types/coordinator-mcp/server.d.ts +6 -49
  22. package/dist/types/daemon/builtin.d.ts +6 -5
  23. package/dist/types/daemon/control-types.d.ts +1 -1
  24. package/dist/types/discovery/mcp-json.d.ts +12 -1
  25. package/dist/types/extensibility/custom-tools/types.d.ts +1 -1
  26. package/dist/types/extensibility/extensions/compact-handler.d.ts +4 -2
  27. package/dist/types/extensibility/extensions/get-commands-handler.d.ts +2 -2
  28. package/dist/types/extensibility/extensions/loader.d.ts +10 -1
  29. package/dist/types/extensibility/extensions/runner.d.ts +3 -3
  30. package/dist/types/extensibility/extensions/types.d.ts +141 -16
  31. package/dist/types/extensibility/hooks/types.d.ts +1 -1
  32. package/dist/types/gjc-runtime/launch-tmux.d.ts +3 -2
  33. package/dist/types/gjc-runtime/psmux-detect.d.ts +3 -0
  34. package/dist/types/gjc-runtime/session-state-sidecar.d.ts +3 -0
  35. package/dist/types/gjc-runtime/team-runtime.d.ts +3 -4
  36. package/dist/types/gjc-runtime/tmux-owner-isolation-cli.d.ts +13 -0
  37. package/dist/types/gjc-runtime/tmux-sessions.d.ts +4 -1
  38. package/dist/types/gjc-runtime/windows-powershell-command.d.ts +9 -0
  39. package/dist/types/harness-control-plane/operate.d.ts +5 -5
  40. package/dist/types/harness-control-plane/owner.d.ts +18 -4
  41. package/dist/types/harness-control-plane/receipts.d.ts +0 -1
  42. package/dist/types/harness-control-plane/sdk-transport.d.ts +43 -0
  43. package/dist/types/harness-control-plane/session-transport.d.ts +48 -0
  44. package/dist/types/harness-control-plane/types.d.ts +3 -4
  45. package/dist/types/lsp/index.d.ts +1 -1
  46. package/dist/types/main.d.ts +32 -38
  47. package/dist/types/modes/acp/acp-agent.d.ts +224 -53
  48. package/dist/types/modes/acp/acp-client-bridge.d.ts +1 -0
  49. package/dist/types/modes/acp/acp-mode.d.ts +6 -15
  50. package/dist/types/modes/acp/permission-mode.d.ts +4 -0
  51. package/dist/types/modes/acp/startup-options.d.ts +6 -0
  52. package/dist/types/modes/components/agent-dashboard.d.ts +3 -0
  53. package/dist/types/modes/components/gajae-pet-widget.d.ts +1 -0
  54. package/dist/types/modes/components/model-selector.d.ts +1 -0
  55. package/dist/types/modes/components/notifications-settings-editor.d.ts +135 -0
  56. package/dist/types/modes/components/pet-capability.d.ts +37 -0
  57. package/dist/types/modes/components/pet-selector.d.ts +1 -1
  58. package/dist/types/modes/components/settings-selector.d.ts +15 -4
  59. package/dist/types/modes/components/welcome.d.ts +1 -1
  60. package/dist/types/modes/controllers/selector-controller.d.ts +35 -1
  61. package/dist/types/modes/index.d.ts +0 -4
  62. package/dist/types/modes/interactive-mode.d.ts +5 -1
  63. package/dist/types/modes/runtime-init.d.ts +2 -2
  64. package/dist/types/modes/shared/agent-wire/approval-gate.d.ts +6 -9
  65. package/dist/types/modes/shared/agent-wire/deep-interview-gate.d.ts +4 -6
  66. package/dist/types/modes/shared/agent-wire/event-contract.d.ts +6 -6
  67. package/dist/types/modes/shared/agent-wire/event-envelope.d.ts +4 -9
  68. package/dist/types/modes/shared/agent-wire/event-observation.d.ts +1 -1
  69. package/dist/types/modes/shared/agent-wire/workflow-gate-broker.d.ts +169 -30
  70. package/dist/types/modes/shared/agent-wire/workflow-gate-schema.d.ts +7 -7
  71. package/dist/types/modes/shared/agent-wire/workflow-gate-types.d.ts +105 -0
  72. package/dist/types/modes/types.d.ts +16 -1
  73. package/dist/types/runtime-mcp/client.d.ts +2 -0
  74. package/dist/types/runtime-mcp/config.d.ts +5 -1
  75. package/dist/types/runtime-mcp/manager.d.ts +8 -1
  76. package/dist/types/runtime-mcp/types.d.ts +4 -0
  77. package/dist/types/sdk/acp/adapter.d.ts +67 -0
  78. package/dist/types/sdk/acp/index.d.ts +2 -0
  79. package/dist/types/sdk/broker/broker.d.ts +53 -0
  80. package/dist/types/sdk/broker/discovery.d.ts +30 -0
  81. package/dist/types/sdk/broker/ensure.d.ts +33 -0
  82. package/dist/types/sdk/broker/identity.d.ts +3 -0
  83. package/dist/types/sdk/broker/index.d.ts +8 -0
  84. package/dist/types/sdk/broker/lifecycle-ledger.d.ts +95 -0
  85. package/dist/types/sdk/broker/lifecycle.d.ts +85 -0
  86. package/dist/types/sdk/broker/process-incarnation.d.ts +14 -0
  87. package/dist/types/sdk/broker/runtime.d.ts +33 -0
  88. package/dist/types/sdk/broker/session-index.d.ts +57 -0
  89. package/dist/types/sdk/broker/state-version.d.ts +8 -0
  90. package/dist/types/sdk/broker/transport.d.ts +9 -0
  91. package/dist/types/sdk/bus/chat-command-policy.d.ts +49 -0
  92. package/dist/types/sdk/bus/chat-daemon-cli.d.ts +21 -0
  93. package/dist/types/sdk/bus/chat-daemon-control.d.ts +111 -0
  94. package/dist/types/sdk/bus/chat-daemon-runtime.d.ts +77 -0
  95. package/dist/types/sdk/bus/chat-effect-journal.d.ts +84 -0
  96. package/dist/types/{notifications → sdk/bus}/config-commands.d.ts +7 -2
  97. package/dist/types/sdk/bus/config.d.ts +274 -0
  98. package/dist/types/sdk/bus/conversation-store.d.ts +72 -0
  99. package/dist/types/sdk/bus/discord-conversation.d.ts +58 -0
  100. package/dist/types/sdk/bus/discord-daemon.d.ts +45 -0
  101. package/dist/types/sdk/bus/discord-live-provider.d.ts +70 -0
  102. package/dist/types/sdk/bus/discord-provider.d.ts +85 -0
  103. package/dist/types/{notifications → sdk/bus}/html-format.d.ts +2 -2
  104. package/dist/types/{notifications → sdk/bus}/index.d.ts +113 -13
  105. package/dist/types/{notifications → sdk/bus}/lifecycle-control-runtime.d.ts +4 -2
  106. package/dist/types/{notifications → sdk/bus}/lifecycle-orchestrator.d.ts +18 -21
  107. package/dist/types/{notifications → sdk/bus}/managed-daemon.d.ts +1 -1
  108. package/dist/types/sdk/bus/native-runtime-compatibility.d.ts +13 -0
  109. package/dist/types/sdk/bus/notification-orchestration.d.ts +170 -0
  110. package/dist/types/{notifications → sdk/bus}/notification-service.d.ts +7 -1
  111. package/dist/types/sdk/bus/session-control.d.ts +83 -0
  112. package/dist/types/sdk/bus/slack-conversation.d.ts +56 -0
  113. package/dist/types/sdk/bus/slack-daemon.d.ts +60 -0
  114. package/dist/types/sdk/bus/slack-live-provider.d.ts +59 -0
  115. package/dist/types/sdk/bus/slack-provider.d.ts +55 -0
  116. package/dist/types/{notifications → sdk/bus}/telegram-cli.d.ts +4 -4
  117. package/dist/types/{notifications → sdk/bus}/telegram-daemon-cli.d.ts +14 -5
  118. package/dist/types/sdk/bus/telegram-daemon-contract.d.ts +12 -0
  119. package/dist/types/{notifications → sdk/bus}/telegram-daemon-control.d.ts +2 -2
  120. package/dist/types/{notifications → sdk/bus}/telegram-daemon.d.ts +55 -17
  121. package/dist/types/{notifications → sdk/bus}/telegram-reference.d.ts +2 -2
  122. package/dist/types/sdk/bus/telegram-setup.d.ts +128 -0
  123. package/dist/types/{notifications → sdk/bus}/topic-registry.d.ts +11 -0
  124. package/dist/types/sdk/cli/index.d.ts +1 -0
  125. package/dist/types/sdk/cli/session-cli.d.ts +19 -0
  126. package/dist/types/sdk/client/client.d.ts +1 -0
  127. package/dist/types/sdk/client/discovery.d.ts +27 -0
  128. package/dist/types/sdk/client/index.d.ts +2 -0
  129. package/dist/types/sdk/host/control/dispatch.d.ts +34 -0
  130. package/dist/types/sdk/host/control/index.d.ts +2 -0
  131. package/dist/types/sdk/host/control/operations.d.ts +65 -0
  132. package/dist/types/sdk/host/events.d.ts +35 -0
  133. package/dist/types/sdk/host/host.d.ts +35 -0
  134. package/dist/types/sdk/host/index.d.ts +4 -0
  135. package/dist/types/sdk/host/query/cursor.d.ts +62 -0
  136. package/dist/types/sdk/host/query/handlers.d.ts +70 -0
  137. package/dist/types/sdk/host/query/index.d.ts +3 -0
  138. package/dist/types/sdk/host/query/revision-store.d.ts +60 -0
  139. package/dist/types/sdk/host/reverse-leases.d.ts +45 -0
  140. package/dist/types/sdk/host/types.d.ts +32 -0
  141. package/dist/types/sdk/index.d.ts +6 -0
  142. package/dist/types/sdk/lifecycle-session.d.ts +14 -0
  143. package/dist/types/sdk/mcp/index.d.ts +1 -0
  144. package/dist/types/sdk/mcp/server.d.ts +37 -0
  145. package/dist/types/sdk/models.d.ts +44 -0
  146. package/dist/types/sdk/protocol/adapter-validation.d.ts +14 -0
  147. package/dist/types/sdk/protocol/operation-registry.d.ts +24 -0
  148. package/dist/types/{sdk.d.ts → sdk/session.d.ts} +44 -38
  149. package/dist/types/sdk/startup-capability.d.ts +50 -0
  150. package/dist/types/session/agent-session.d.ts +114 -21
  151. package/dist/types/session/default-model-selection.d.ts +25 -0
  152. package/dist/types/session/fallback-chain-controller.d.ts +52 -0
  153. package/dist/types/session/session-manager.d.ts +58 -1
  154. package/dist/types/setup/credential-auto-import.d.ts +36 -6
  155. package/dist/types/tools/ask-answer-registry.d.ts +1 -1
  156. package/dist/types/tools/browser/benchmark/detector-report.d.ts +63 -0
  157. package/dist/types/tools/browser/benchmark/run-suite.d.ts +18 -0
  158. package/dist/types/tools/browser/launch.d.ts +22 -0
  159. package/dist/types/tools/browser/profile-discovery.d.ts +31 -0
  160. package/dist/types/tools/browser/profile-posture.d.ts +37 -0
  161. package/dist/types/tools/browser/profile-reuse.d.ts +39 -0
  162. package/dist/types/tools/browser/profile-warmup.d.ts +39 -0
  163. package/dist/types/tools/browser/registry.d.ts +19 -0
  164. package/dist/types/tools/browser/tab-protocol.d.ts +4 -0
  165. package/dist/types/tools/index.d.ts +8 -8
  166. package/package.json +34 -13
  167. package/scripts/build-sdk-package-smoke.ts +94 -0
  168. package/scripts/capture-notifications-settings-showcase.ts +435 -0
  169. package/scripts/compile-args.ts +4 -2
  170. package/scripts/g005-daemon-smoke.ts +4 -5
  171. package/scripts/g011-daemon-path-smoke.ts +5 -5
  172. package/scripts/generate-sdk-adapter-parity-manifest.ts +163 -0
  173. package/scripts/generate-sdk-operation-inventory.ts +867 -0
  174. package/scripts/generate-telegram-baseline-manifest.ts +128 -0
  175. package/scripts/manifest-schema.ts +124 -0
  176. package/scripts/run-test-manifest.ts +153 -0
  177. package/scripts/run-threat-model-check.ts +157 -0
  178. package/scripts/verify-gjc-sdk-canonicalization.ts +3467 -0
  179. package/scripts/verify-gjc-sdk-rename.ts +90 -0
  180. package/src/append-only-mode.ts +1 -1
  181. package/src/cli/args.ts +43 -12
  182. package/src/cli/auth-gateway-cli.ts +2 -2
  183. package/src/cli/config-cli.ts +2 -1
  184. package/src/cli/daemon-cli.ts +63 -14
  185. package/src/cli/list-models.ts +1 -1
  186. package/src/cli/notify-cli.ts +288 -286
  187. package/src/cli.ts +21 -2
  188. package/src/commands/daemon.ts +50 -6
  189. package/src/commands/harness.ts +93 -36
  190. package/src/commands/launch.ts +5 -2
  191. package/src/commands/mcp-serve.ts +19 -12
  192. package/src/commands/notify.ts +36 -3
  193. package/src/commands/sdk.ts +426 -0
  194. package/src/commit/agentic/agent.ts +1 -0
  195. package/src/config/atomic-yaml-patch.ts +423 -0
  196. package/src/config/model-profile-activation.ts +240 -89
  197. package/src/config/model-profiles.ts +37 -34
  198. package/src/config/model-registry.ts +51 -26
  199. package/src/config/model-resolver.ts +138 -51
  200. package/src/config/model-selector-value.ts +50 -0
  201. package/src/config/models-config-schema.ts +14 -21
  202. package/src/config/settings-schema.ts +128 -10
  203. package/src/config/settings.ts +1120 -158
  204. package/src/coordinator-mcp/model-preset.ts +74 -20
  205. package/src/coordinator-mcp/server.ts +1597 -3052
  206. package/src/daemon/builtin.ts +8 -5
  207. package/src/daemon/control-types.ts +1 -1
  208. package/src/defaults/gjc/skills/team/SKILL.md +4 -4
  209. package/src/discovery/mcp-json.ts +327 -20
  210. package/src/extensibility/custom-tools/types.ts +1 -1
  211. package/src/extensibility/extensions/compact-handler.ts +3 -3
  212. package/src/extensibility/extensions/get-commands-handler.ts +2 -2
  213. package/src/extensibility/extensions/loader.ts +69 -5
  214. package/src/extensibility/extensions/runner.ts +112 -4
  215. package/src/extensibility/extensions/types.ts +176 -17
  216. package/src/extensibility/hooks/types.ts +1 -1
  217. package/src/extensibility/slash-commands.ts +3 -3
  218. package/src/gjc-runtime/launch-tmux.ts +170 -86
  219. package/src/gjc-runtime/psmux-detect.ts +82 -4
  220. package/src/gjc-runtime/session-state-sidecar.ts +41 -31
  221. package/src/gjc-runtime/team-runtime.ts +54 -35
  222. package/src/gjc-runtime/tmux-owner-isolation-cli.ts +53 -17
  223. package/src/gjc-runtime/tmux-owner-isolation.ts +5 -2
  224. package/src/gjc-runtime/tmux-sessions.ts +19 -13
  225. package/src/gjc-runtime/ultragoal-guard.ts +53 -6
  226. package/src/gjc-runtime/ultragoal-runtime.ts +155 -3
  227. package/src/gjc-runtime/windows-powershell-command.ts +58 -0
  228. package/src/harness-control-plane/operate.ts +9 -9
  229. package/src/harness-control-plane/owner.ts +283 -39
  230. package/src/harness-control-plane/receipts.ts +0 -1
  231. package/src/harness-control-plane/sdk-transport.ts +395 -0
  232. package/src/harness-control-plane/session-transport.ts +94 -0
  233. package/src/harness-control-plane/types.ts +1 -1
  234. package/src/internal-urls/docs-index.generated.ts +36 -37
  235. package/src/lsp/client.ts +42 -6
  236. package/src/lsp/index.ts +2 -2
  237. package/src/main.ts +299 -204
  238. package/src/modes/DESIGN.md +258 -0
  239. package/src/modes/acp/acp-agent.ts +1228 -2612
  240. package/src/modes/acp/acp-client-bridge.ts +2 -20
  241. package/src/modes/acp/acp-event-mapper.ts +18 -2
  242. package/src/modes/acp/acp-mode.ts +8 -36
  243. package/src/modes/acp/permission-mode.ts +25 -0
  244. package/src/modes/acp/startup-options.ts +6 -0
  245. package/src/modes/components/agent-dashboard.ts +37 -17
  246. package/src/modes/components/custom-editor.ts +1 -0
  247. package/src/modes/components/gajae-pet-widget.ts +169 -20
  248. package/src/modes/components/model-selector.ts +93 -35
  249. package/src/modes/components/notifications-settings-editor.ts +1379 -0
  250. package/src/modes/components/pet-capability.ts +115 -0
  251. package/src/modes/components/pet-selector.ts +4 -6
  252. package/src/modes/components/settings-selector.ts +86 -11
  253. package/src/modes/components/welcome.ts +4 -4
  254. package/src/modes/controllers/command-controller.ts +1 -2
  255. package/src/modes/controllers/event-controller.ts +11 -16
  256. package/src/modes/controllers/extension-ui-controller.ts +357 -12
  257. package/src/modes/controllers/input-controller.ts +43 -13
  258. package/src/modes/controllers/selector-controller.ts +671 -44
  259. package/src/modes/index.ts +0 -22
  260. package/src/modes/interactive-mode.ts +108 -24
  261. package/src/modes/runtime-init.ts +298 -6
  262. package/src/modes/shared/agent-wire/approval-gate.ts +9 -11
  263. package/src/modes/shared/agent-wire/deep-interview-gate.ts +10 -11
  264. package/src/modes/shared/agent-wire/event-contract.ts +7 -8
  265. package/src/modes/shared/agent-wire/event-envelope.ts +9 -17
  266. package/src/modes/shared/agent-wire/event-observation.ts +16 -7
  267. package/src/modes/shared/agent-wire/workflow-gate-broker.ts +1254 -87
  268. package/src/modes/shared/agent-wire/workflow-gate-schema.ts +15 -15
  269. package/src/modes/shared/agent-wire/workflow-gate-types.ts +113 -0
  270. package/src/modes/types.ts +16 -1
  271. package/src/runtime-mcp/client.ts +94 -19
  272. package/src/runtime-mcp/config.ts +27 -15
  273. package/src/runtime-mcp/manager.ts +343 -105
  274. package/src/runtime-mcp/oauth-flow.ts +42 -12
  275. package/src/runtime-mcp/transports/http.ts +49 -19
  276. package/src/runtime-mcp/transports/stdio.ts +47 -28
  277. package/src/runtime-mcp/types.ts +10 -0
  278. package/src/sdk/acp/adapter.ts +418 -0
  279. package/src/sdk/acp/index.ts +2 -0
  280. package/src/sdk/broker/broker.ts +642 -0
  281. package/src/sdk/broker/discovery.ts +94 -0
  282. package/src/sdk/broker/ensure.ts +249 -0
  283. package/src/sdk/broker/identity.ts +42 -0
  284. package/src/sdk/broker/index.ts +8 -0
  285. package/src/sdk/broker/internal-source-marker-2178.txt +1 -0
  286. package/src/sdk/broker/internal-source.bunfig.toml +1 -0
  287. package/src/sdk/broker/lifecycle-ledger.ts +286 -0
  288. package/src/sdk/broker/lifecycle.ts +2157 -0
  289. package/src/sdk/broker/process-incarnation.ts +159 -0
  290. package/src/sdk/broker/runtime.ts +164 -0
  291. package/src/sdk/broker/session-index.ts +318 -0
  292. package/src/sdk/broker/state-version.ts +23 -0
  293. package/src/sdk/broker/transport.ts +125 -0
  294. package/src/sdk/bus/chat-command-policy.ts +107 -0
  295. package/src/sdk/bus/chat-daemon-cli.ts +214 -0
  296. package/src/sdk/bus/chat-daemon-control.ts +713 -0
  297. package/src/sdk/bus/chat-daemon-runtime.ts +526 -0
  298. package/src/sdk/bus/chat-effect-journal.ts +353 -0
  299. package/src/{notifications → sdk/bus}/config-commands.ts +39 -10
  300. package/src/sdk/bus/config.ts +433 -0
  301. package/src/sdk/bus/conversation-store.ts +391 -0
  302. package/src/sdk/bus/discord-conversation.ts +85 -0
  303. package/src/sdk/bus/discord-daemon.ts +2275 -0
  304. package/src/sdk/bus/discord-live-provider.ts +521 -0
  305. package/src/sdk/bus/discord-provider.ts +56 -0
  306. package/src/{notifications → sdk/bus}/html-format.ts +2 -2
  307. package/src/sdk/bus/index.ts +3943 -0
  308. package/src/{notifications → sdk/bus}/lifecycle-commands.ts +2 -0
  309. package/src/{notifications → sdk/bus}/lifecycle-control-runtime.ts +225 -196
  310. package/src/{notifications → sdk/bus}/lifecycle-orchestrator.ts +161 -74
  311. package/src/{notifications → sdk/bus}/managed-daemon.ts +2 -2
  312. package/src/sdk/bus/native-runtime-compatibility.ts +42 -0
  313. package/src/sdk/bus/natives-augment.d.ts +38 -0
  314. package/src/sdk/bus/notification-orchestration.ts +535 -0
  315. package/src/{notifications → sdk/bus}/notification-service.ts +106 -12
  316. package/src/sdk/bus/session-control.ts +333 -0
  317. package/src/sdk/bus/slack-conversation.ts +74 -0
  318. package/src/sdk/bus/slack-daemon.ts +2144 -0
  319. package/src/sdk/bus/slack-live-provider.ts +380 -0
  320. package/src/sdk/bus/slack-provider.ts +76 -0
  321. package/src/{notifications → sdk/bus}/telegram-cli.ts +8 -8
  322. package/src/sdk/bus/telegram-daemon-cli.ts +330 -0
  323. package/src/sdk/bus/telegram-daemon-contract.ts +14 -0
  324. package/src/{notifications → sdk/bus}/telegram-daemon-control.ts +4 -4
  325. package/src/{notifications → sdk/bus}/telegram-daemon.ts +502 -54
  326. package/src/{notifications → sdk/bus}/telegram-reference.ts +2 -2
  327. package/src/sdk/bus/telegram-setup.ts +663 -0
  328. package/src/{notifications → sdk/bus}/topic-registry.ts +32 -0
  329. package/src/sdk/cli/index.ts +1 -0
  330. package/src/sdk/cli/session-cli.ts +275 -0
  331. package/src/sdk/client/client.ts +1 -0
  332. package/src/sdk/client/discovery.ts +108 -0
  333. package/src/sdk/client/index.ts +2 -0
  334. package/src/sdk/host/control/dispatch.ts +367 -0
  335. package/src/sdk/host/control/index.ts +10 -0
  336. package/src/sdk/host/control/operations.ts +63 -0
  337. package/src/sdk/host/events.ts +91 -0
  338. package/src/sdk/host/host.ts +296 -0
  339. package/src/sdk/host/index.ts +4 -0
  340. package/src/sdk/host/query/cursor.ts +189 -0
  341. package/src/sdk/host/query/handlers.ts +533 -0
  342. package/src/sdk/host/query/index.ts +3 -0
  343. package/src/sdk/host/query/revision-store.ts +1126 -0
  344. package/src/sdk/host/reverse-leases.ts +325 -0
  345. package/src/sdk/host/types.ts +29 -0
  346. package/src/sdk/index.ts +13 -0
  347. package/src/sdk/lifecycle-session.ts +39 -0
  348. package/src/sdk/mcp/index.ts +1 -0
  349. package/src/sdk/mcp/server.ts +332 -0
  350. package/src/sdk/models.ts +168 -0
  351. package/src/sdk/protocol/adapter-validation.ts +48 -0
  352. package/src/sdk/protocol/operation-inventory.generated.json +5654 -0
  353. package/src/sdk/protocol/operation-registry.ts +312 -0
  354. package/src/{sdk.ts → sdk/session.ts} +339 -212
  355. package/src/sdk/startup-capability.ts +189 -0
  356. package/src/session/agent-session.ts +2470 -866
  357. package/src/session/default-model-selection.ts +93 -0
  358. package/src/session/fallback-chain-controller.ts +136 -0
  359. package/src/session/session-manager.ts +447 -13
  360. package/src/session/session-storage.ts +8 -1
  361. package/src/setup/credential-auto-import.ts +290 -35
  362. package/src/setup/hermes/templates/operator-instructions.v1.md +3 -5
  363. package/src/slash-commands/builtin-registry.ts +33 -30
  364. package/src/task/executor.ts +62 -13
  365. package/src/tools/ask-answer-registry.ts +1 -1
  366. package/src/tools/ask.ts +13 -8
  367. package/src/tools/browser/STEALTH-THIRD-PARTY-NOTICES.md +26 -0
  368. package/src/tools/browser/benchmark/detector-report.ts +157 -0
  369. package/src/tools/browser/benchmark/run-suite.ts +39 -0
  370. package/src/tools/browser/launch.ts +124 -19
  371. package/src/tools/browser/profile-discovery.ts +63 -0
  372. package/src/tools/browser/profile-posture.ts +59 -0
  373. package/src/tools/browser/profile-reuse.ts +102 -0
  374. package/src/tools/browser/profile-warmup.ts +109 -0
  375. package/src/tools/browser/registry.ts +87 -11
  376. package/src/tools/browser/tab-protocol.ts +1 -0
  377. package/src/tools/browser/tab-supervisor.ts +1 -0
  378. package/src/tools/browser/tab-worker.ts +1 -1
  379. package/src/tools/browser.ts +6 -0
  380. package/src/tools/index.ts +8 -8
  381. package/src/tools/puppeteer/14_stealth_permissions.txt +57 -0
  382. package/src/tools/puppeteer/15_stealth_webrtc.txt +60 -0
  383. package/src/tools/telegram-send.ts +2 -2
  384. package/vendor/insane-search/MANIFEST.json +2 -1
  385. package/vendor/insane-search/engine/bias_check.py +4 -1
  386. package/vendor/insane-search/engine/phase0.py +26 -1
  387. package/dist/types/harness-control-plane/rpc-adapter.d.ts +0 -69
  388. package/dist/types/modes/bridge/auth.d.ts +0 -12
  389. package/dist/types/modes/bridge/bridge-client-bridge.d.ts +0 -9
  390. package/dist/types/modes/bridge/bridge-mode.d.ts +0 -47
  391. package/dist/types/modes/bridge/bridge-ui-context.d.ts +0 -88
  392. package/dist/types/modes/bridge/event-stream.d.ts +0 -8
  393. package/dist/types/modes/rpc/host-tools.d.ts +0 -1
  394. package/dist/types/modes/rpc/host-uris.d.ts +0 -1
  395. package/dist/types/modes/rpc/rpc-client.d.ts +0 -299
  396. package/dist/types/modes/rpc/rpc-mode.d.ts +0 -86
  397. package/dist/types/modes/rpc/rpc-socket-security.d.ts +0 -10
  398. package/dist/types/modes/rpc/rpc-types.d.ts +0 -810
  399. package/dist/types/modes/shared/agent-wire/command-contract.d.ts +0 -18
  400. package/dist/types/modes/shared/agent-wire/command-dispatch.d.ts +0 -37
  401. package/dist/types/modes/shared/agent-wire/command-validation.d.ts +0 -2
  402. package/dist/types/modes/shared/agent-wire/handshake.d.ts +0 -56
  403. package/dist/types/modes/shared/agent-wire/host-tool-bridge.d.ts +0 -16
  404. package/dist/types/modes/shared/agent-wire/host-uri-bridge.d.ts +0 -17
  405. package/dist/types/modes/shared/agent-wire/protocol.d.ts +0 -25
  406. package/dist/types/modes/shared/agent-wire/responses.d.ts +0 -4
  407. package/dist/types/modes/shared/agent-wire/scopes.d.ts +0 -18
  408. package/dist/types/modes/shared/agent-wire/session-registry.d.ts +0 -25
  409. package/dist/types/modes/shared/agent-wire/ui-request-broker.d.ts +0 -42
  410. package/dist/types/modes/shared/agent-wire/ui-result.d.ts +0 -27
  411. package/dist/types/modes/shared/agent-wire/unattended-action-policy.d.ts +0 -29
  412. package/dist/types/modes/shared/agent-wire/unattended-audit.d.ts +0 -68
  413. package/dist/types/modes/shared/agent-wire/unattended-run-controller.d.ts +0 -166
  414. package/dist/types/modes/shared/agent-wire/unattended-session.d.ts +0 -127
  415. package/dist/types/notifications/config.d.ts +0 -132
  416. package/src/harness-control-plane/rpc-adapter.ts +0 -290
  417. package/src/modes/bridge/auth.ts +0 -41
  418. package/src/modes/bridge/bridge-client-bridge.ts +0 -47
  419. package/src/modes/bridge/bridge-mode.ts +0 -675
  420. package/src/modes/bridge/bridge-ui-context.ts +0 -200
  421. package/src/modes/bridge/event-stream.ts +0 -70
  422. package/src/modes/rpc/host-tools.ts +0 -1
  423. package/src/modes/rpc/host-uris.ts +0 -1
  424. package/src/modes/rpc/rpc-client.ts +0 -1166
  425. package/src/modes/rpc/rpc-mode.ts +0 -1029
  426. package/src/modes/rpc/rpc-socket-security.ts +0 -115
  427. package/src/modes/rpc/rpc-types.ts +0 -637
  428. package/src/modes/shared/agent-wire/command-contract.ts +0 -18
  429. package/src/modes/shared/agent-wire/command-dispatch.ts +0 -498
  430. package/src/modes/shared/agent-wire/command-validation.ts +0 -181
  431. package/src/modes/shared/agent-wire/handshake.ts +0 -157
  432. package/src/modes/shared/agent-wire/host-tool-bridge.ts +0 -197
  433. package/src/modes/shared/agent-wire/host-uri-bridge.ts +0 -236
  434. package/src/modes/shared/agent-wire/protocol.ts +0 -46
  435. package/src/modes/shared/agent-wire/responses.ts +0 -17
  436. package/src/modes/shared/agent-wire/scopes.ts +0 -94
  437. package/src/modes/shared/agent-wire/session-registry.ts +0 -109
  438. package/src/modes/shared/agent-wire/ui-request-broker.ts +0 -150
  439. package/src/modes/shared/agent-wire/ui-result.ts +0 -48
  440. package/src/modes/shared/agent-wire/unattended-action-policy.ts +0 -365
  441. package/src/modes/shared/agent-wire/unattended-audit.ts +0 -176
  442. package/src/modes/shared/agent-wire/unattended-run-controller.ts +0 -426
  443. package/src/modes/shared/agent-wire/unattended-session.ts +0 -528
  444. package/src/notifications/config.ts +0 -219
  445. package/src/notifications/index.ts +0 -1835
  446. package/src/notifications/telegram-daemon-cli.ts +0 -243
  447. package/src/tools/puppeteer/09_stealth_locale.txt +0 -46
  448. /package/dist/types/{notifications → sdk/bus}/attachment-registry.d.ts +0 -0
  449. /package/dist/types/{notifications → sdk/bus}/chat-adapters.d.ts +0 -0
  450. /package/dist/types/{notifications → sdk/bus}/daemon-paths.d.ts +0 -0
  451. /package/dist/types/{notifications → sdk/bus}/engine.d.ts +0 -0
  452. /package/dist/types/{notifications → sdk/bus}/helpers.d.ts +0 -0
  453. /package/dist/types/{notifications → sdk/bus}/lifecycle-commands.d.ts +0 -0
  454. /package/dist/types/{notifications → sdk/bus}/operator-runtime.d.ts +0 -0
  455. /package/dist/types/{notifications → sdk/bus}/rate-limit-pool.d.ts +0 -0
  456. /package/dist/types/{notifications → sdk/bus}/recent-activity.d.ts +0 -0
  457. /package/dist/types/{notifications → sdk/bus}/reply-sent-store.d.ts +0 -0
  458. /package/dist/types/{notifications → sdk/bus}/rich-draft.d.ts +0 -0
  459. /package/dist/types/{notifications → sdk/bus}/rich-render.d.ts +0 -0
  460. /package/dist/types/{notifications → sdk/bus}/threaded-inbound.d.ts +0 -0
  461. /package/dist/types/{notifications → sdk/bus}/threaded-render.d.ts +0 -0
  462. /package/src/{notifications → sdk/bus}/attachment-registry.ts +0 -0
  463. /package/src/{notifications → sdk/bus}/chat-adapters.ts +0 -0
  464. /package/src/{notifications → sdk/bus}/daemon-paths.ts +0 -0
  465. /package/src/{notifications → sdk/bus}/engine.ts +0 -0
  466. /package/src/{notifications → sdk/bus}/helpers.ts +0 -0
  467. /package/src/{notifications → sdk/bus}/operator-runtime.ts +0 -0
  468. /package/src/{notifications → sdk/bus}/rate-limit-pool.ts +0 -0
  469. /package/src/{notifications → sdk/bus}/recent-activity.ts +0 -0
  470. /package/src/{notifications → sdk/bus}/reply-sent-store.ts +0 -0
  471. /package/src/{notifications → sdk/bus}/rich-draft.ts +0 -0
  472. /package/src/{notifications → sdk/bus}/rich-render.ts +0 -0
  473. /package/src/{notifications → sdk/bus}/threaded-inbound.ts +0 -0
  474. /package/src/{notifications → sdk/bus}/threaded-render.ts +0 -0
@@ -0,0 +1,642 @@
1
+ import { createHash, randomBytes } from "node:crypto";
2
+ import * as fs from "node:fs/promises";
3
+ import path from "node:path";
4
+ import { resolveResumableSession, SessionManager } from "../../session/session-manager";
5
+ import {
6
+ BROKER_HEARTBEAT_TTL_MS,
7
+ type BrokerDiscovery,
8
+ brokerDiscoveryPath,
9
+ brokerProcessIncarnation,
10
+ isPidAlive,
11
+ newBrokerToken,
12
+ readBrokerDiscovery,
13
+ redactBrokerDiscovery,
14
+ writeBrokerDiscovery,
15
+ } from "./discovery";
16
+ import { deriveIdempotencyIdentity } from "./identity";
17
+ import { executeLifecycle, isCanonicalSessionId } from "./lifecycle";
18
+
19
+ import {
20
+ type LifecycleDurableEffectsReceipt,
21
+ LifecycleLedger,
22
+ type LifecycleStartupFailureReceipt,
23
+ } from "./lifecycle-ledger";
24
+ import { type IndexedSession, SessionIndex } from "./session-index";
25
+ import { BrokerTransport } from "./transport";
26
+
27
+ export interface BrokerSettings {
28
+ agentDir: string;
29
+ packageGeneration?: string;
30
+ port?: number;
31
+ heartbeatTtlMs?: number;
32
+ }
33
+ export type BrokerErrorCode =
34
+ | "idempotency_conflict"
35
+ | "terminal_uncertain"
36
+ | "broker_restarting"
37
+ | "unavailable"
38
+ | "endpoint_stale"
39
+ | "resource_gone"
40
+ | "invalid_input"
41
+ | "spawn_failed"
42
+ | "readiness_timeout"
43
+ | "close_refused"
44
+ | "not_found"
45
+ | "live_session"
46
+ | "cleanup_pending"
47
+ | (string & {});
48
+
49
+ export type BrokerCleanupEvidence = {
50
+ phase: "artifacts" | "transcript" | "metadata";
51
+ artifactsIdentity?: { dev: string; ino: string };
52
+ transcriptIdentity?: { dev: string; ino: string };
53
+ };
54
+ export type BrokerResponse =
55
+ | { ok: true; result?: unknown; indexSeq?: number }
56
+ | {
57
+ ok: false;
58
+ error: {
59
+ code: BrokerErrorCode;
60
+ message: string;
61
+ endpoint?: "unavailable";
62
+ cleanup?: BrokerCleanupEvidence;
63
+ };
64
+ indexSeq?: number;
65
+ durableEffects?: LifecycleDurableEffectsReceipt;
66
+ startupFailure?: LifecycleStartupFailureReceipt;
67
+ };
68
+ const error = (code: BrokerErrorCode, message: string): BrokerResponse => ({ ok: false, error: { code, message } });
69
+
70
+ type InputNormalization = { input: Record<string, unknown> } | BrokerResponse;
71
+
72
+ function isBrokerResponse(value: InputNormalization): value is BrokerResponse {
73
+ return "ok" in value;
74
+ }
75
+
76
+ function normalizeAliasedString(
77
+ input: Record<string, unknown>,
78
+ canonical: string,
79
+ aliases: readonly string[],
80
+ normalize = (value: string) => value,
81
+ ): { value: string | undefined; error?: string } {
82
+ const supplied = [canonical, ...aliases].filter(name => input[name] !== undefined).map(name => input[name]);
83
+ if (supplied.length === 0) return { value: undefined };
84
+ if (supplied.some(value => typeof value !== "string" || value.length === 0))
85
+ return { value: undefined, error: `${canonical} must be a non-empty string` };
86
+ const values = supplied.map(value => normalize(value as string));
87
+ if (values.some(value => value !== values[0])) return { value: undefined, error: `${canonical} aliases conflict` };
88
+ return { value: values[0] };
89
+ }
90
+
91
+ function normalizeBrokerInput(operation: string, input: Record<string, unknown>): InputNormalization {
92
+ const normalized: Record<string, unknown> = { ...input };
93
+ const session = normalizeAliasedString(input, "sessionId", ["id"]);
94
+ if (session.error) return error("invalid_input", session.error);
95
+ if (session.value !== undefined) {
96
+ if (!isCanonicalSessionId(session.value))
97
+ return error("invalid_input", "sessionId must be a canonical safe identifier");
98
+ normalized.sessionId = session.value;
99
+ delete normalized.id;
100
+ }
101
+ const source = normalizeAliasedString(input, "sourceSessionId", ["sourceId"]);
102
+ if (source.error) return error("invalid_input", source.error);
103
+ if (source.value !== undefined) {
104
+ if (!isCanonicalSessionId(source.value))
105
+ return error("invalid_input", "sourceSessionId must be a canonical safe identifier");
106
+ normalized.sourceSessionId = source.value;
107
+ delete normalized.sourceId;
108
+ }
109
+
110
+ if (operation === "session.list") {
111
+ const resolved = input.resolveSessionId;
112
+ if (resolved !== undefined && (typeof resolved !== "string" || !isCanonicalSessionId(resolved)))
113
+ return error("invalid_input", "resolveSessionId must be a canonical safe identifier");
114
+ return { input: normalized };
115
+ }
116
+ if (
117
+ operation !== "session.create" &&
118
+ operation !== "session.fork" &&
119
+ operation !== "session.resume" &&
120
+ operation !== "session.close" &&
121
+ operation !== "session.delete"
122
+ )
123
+ return { input: normalized };
124
+
125
+ const target =
126
+ typeof input.target === "object" && input.target !== null && !Array.isArray(input.target)
127
+ ? (input.target as Record<string, unknown>)
128
+ : undefined;
129
+ const cwd = normalizeAliasedString(
130
+ { cwd: input.cwd, path: input.path, targetPath: target?.path },
131
+ "cwd",
132
+ ["path", "targetPath"],
133
+ value => path.resolve(value),
134
+ );
135
+ if (cwd.error) return error("invalid_input", cwd.error);
136
+ if (cwd.value !== undefined) {
137
+ normalized.cwd = cwd.value;
138
+ delete normalized.path;
139
+ }
140
+ const stateRoot = normalizeAliasedString(
141
+ { stateRoot: input.stateRoot, targetStateRoot: target?.stateRoot },
142
+ "stateRoot",
143
+ ["targetStateRoot"],
144
+ value => path.resolve(value),
145
+ );
146
+ if (stateRoot.error) return error("invalid_input", stateRoot.error);
147
+ if (stateRoot.value !== undefined && (!cwd.value || stateRoot.value !== path.join(cwd.value, ".gjc", "state")))
148
+ return error("invalid_input", "stateRoot must be the default .gjc/state for cwd.");
149
+ if (cwd.value !== undefined) normalized.stateRoot = path.join(cwd.value, ".gjc", "state");
150
+ else if (stateRoot.value !== undefined) return error("invalid_input", "stateRoot requires cwd.");
151
+
152
+ if (target) {
153
+ const normalizedTarget = { ...target };
154
+ delete normalizedTarget.path;
155
+ delete normalizedTarget.stateRoot;
156
+ if (Object.keys(normalizedTarget).length > 0) normalized.target = normalizedTarget;
157
+ else delete normalized.target;
158
+ }
159
+ return { input: normalized };
160
+ }
161
+ function canonicalJson(value: unknown): string {
162
+ if (value === null || typeof value !== "object") return JSON.stringify(value);
163
+ if (Array.isArray(value)) return `[${value.map(canonicalJson).join(",")}]`;
164
+ const record = value as Record<string, unknown>;
165
+ return `{${Object.keys(record)
166
+ .sort()
167
+ .map(key => `${JSON.stringify(key)}:${canonicalJson(record[key])}`)
168
+ .join(",")}}`;
169
+ }
170
+
171
+ type EndpointAuthority = { endpointGeneration?: number; endpointIncarnation?: string };
172
+ function endpointIncarnation(
173
+ record: Pick<IndexedSession, "endpointGeneration" | "endpointMtimeMs" | "pid">,
174
+ sessionId: string,
175
+ ): string | undefined {
176
+ if (
177
+ !Number.isSafeInteger(record.endpointGeneration) ||
178
+ record.endpointGeneration <= 0 ||
179
+ !Number.isSafeInteger(record.pid) ||
180
+ record.pid <= 0 ||
181
+ typeof record.endpointMtimeMs !== "number" ||
182
+ !Number.isFinite(record.endpointMtimeMs) ||
183
+ record.endpointMtimeMs <= 0
184
+ )
185
+ return undefined;
186
+ return createHash("sha256")
187
+ .update(
188
+ canonicalJson({
189
+ endpointGeneration: record.endpointGeneration,
190
+ endpointMtimeMs: record.endpointMtimeMs,
191
+ pid: record.pid,
192
+ sessionId,
193
+ }),
194
+ )
195
+ .digest("hex");
196
+ }
197
+ function expectedEndpointAuthority(input: Record<string, unknown>): EndpointAuthority | BrokerResponse {
198
+ const endpointGeneration = input.endpointGeneration;
199
+ const endpointIncarnation = input.endpointIncarnation;
200
+ if (
201
+ endpointGeneration !== undefined &&
202
+ (typeof endpointGeneration !== "number" || !Number.isSafeInteger(endpointGeneration) || endpointGeneration <= 0)
203
+ )
204
+ return error("invalid_input", "endpointGeneration must be a positive safe integer");
205
+ if (
206
+ endpointIncarnation !== undefined &&
207
+ (typeof endpointIncarnation !== "string" || !/^[a-f0-9]{64}$/.test(endpointIncarnation))
208
+ )
209
+ return error("invalid_input", "endpointIncarnation must be a SHA-256 hash");
210
+ if (endpointIncarnation !== undefined && endpointGeneration === undefined)
211
+ return error("invalid_input", "endpointIncarnation requires endpointGeneration");
212
+ return { endpointGeneration, endpointIncarnation };
213
+ }
214
+ function matchesEndpointAuthority(record: IndexedSession, authority: EndpointAuthority): boolean {
215
+ return (
216
+ (authority.endpointGeneration === undefined || authority.endpointGeneration === record.endpointGeneration) &&
217
+ (authority.endpointIncarnation === undefined ||
218
+ authority.endpointIncarnation === endpointIncarnation(record, record.sessionId))
219
+ );
220
+ }
221
+ function sameEndpointRecord(expected: IndexedSession, current: IndexedSession): boolean {
222
+ return (
223
+ current.live &&
224
+ current.endpointGeneration === expected.endpointGeneration &&
225
+ current.pid === expected.pid &&
226
+ current.endpointMtimeMs === expected.endpointMtimeMs &&
227
+ path.resolve(current.locator.repo) === path.resolve(expected.locator.repo) &&
228
+ path.resolve(current.locator.stateRoot) === path.resolve(expected.locator.stateRoot)
229
+ );
230
+ }
231
+
232
+ function lifecycleTarget(operation: string, input: Record<string, unknown>): unknown {
233
+ const target = input.target as Record<string, unknown> | undefined;
234
+ const string = (...values: unknown[]): string | undefined =>
235
+ values.find((value): value is string => typeof value === "string" && value.length > 0);
236
+ const explicitRoot = string(input.stateRoot, target?.stateRoot);
237
+ const root =
238
+ explicitRoot ??
239
+ (() => {
240
+ const cwd = string(input.cwd, input.path, target?.path);
241
+ return cwd ? path.join(cwd, ".gjc", "state") : undefined;
242
+ })();
243
+ const id = string(input.sessionId, input.id);
244
+ switch (operation) {
245
+ case "session.create":
246
+ return { root };
247
+ case "session.fork":
248
+ return {
249
+ root,
250
+ sourceSessionId: string(input.sourceSessionId, input.sourceId),
251
+ sourceSessionPath: string(input.sourceSessionPath, input.sourcePath, input.sessionPath),
252
+ };
253
+ case "session.resume":
254
+ case "session.close":
255
+ case "session.delete":
256
+ return { sessionId: id };
257
+ default:
258
+ return { operation, root, sessionId: id };
259
+ }
260
+ }
261
+
262
+ const BROKER_LOCK_RECORD = "owner.json";
263
+ const BROKER_LOCK_STARTUP_WAIT_MS = 1_000;
264
+ const BROKER_LOCK_RETRY_MS = 10;
265
+
266
+ type BrokerLockSnapshot = {
267
+ ownerId?: string;
268
+ pid: number;
269
+ identity: string;
270
+ };
271
+
272
+ const terminalPersistenceHooksForTest = new WeakMap<Broker, () => void>();
273
+
274
+ export class Broker {
275
+ readonly settings: Required<BrokerSettings>;
276
+ readonly index: SessionIndex;
277
+ readonly ledger: LifecycleLedger;
278
+ discovery: BrokerDiscovery | null = null;
279
+ #lock: string;
280
+ #owner = randomBytes(12).toString("hex");
281
+ #chains = new Map<string, Promise<void>>();
282
+ #stopping = false;
283
+ #transport: BrokerTransport | null = null;
284
+ #heartbeatTimer: ReturnType<typeof setInterval> | null = null;
285
+ #heartbeatWrite: Promise<void> = Promise.resolve();
286
+ constructor(settings: BrokerSettings) {
287
+ this.settings = { packageGeneration: "unknown", port: 0, heartbeatTtlMs: BROKER_HEARTBEAT_TTL_MS, ...settings };
288
+ this.index = new SessionIndex(settings.agentDir);
289
+ this.ledger = new LifecycleLedger(settings.agentDir);
290
+ this.#lock = path.join(settings.agentDir, "sdk", "broker.lock");
291
+ }
292
+ #lockRecordPath(): string {
293
+ return path.join(this.#lock, BROKER_LOCK_RECORD);
294
+ }
295
+ #lockSnapshot(raw: string): BrokerLockSnapshot {
296
+ try {
297
+ const lock = JSON.parse(raw) as { ownerId?: unknown; pid?: unknown };
298
+ if (
299
+ typeof lock.ownerId === "string" &&
300
+ lock.ownerId.length > 0 &&
301
+ typeof lock.pid === "number" &&
302
+ Number.isInteger(lock.pid) &&
303
+ lock.pid > 0
304
+ )
305
+ return { ownerId: lock.ownerId, pid: lock.pid, identity: `owner:${lock.ownerId}` };
306
+ } catch {}
307
+ return { pid: 0, identity: `contents:${createHash("sha256").update(raw).digest("hex")}` };
308
+ }
309
+ async #readLock(): Promise<BrokerLockSnapshot | null> {
310
+ try {
311
+ return this.#lockSnapshot(await fs.readFile(this.#lockRecordPath(), "utf8"));
312
+ } catch (e) {
313
+ const code = (e as NodeJS.ErrnoException).code;
314
+ if (code === "ENOTDIR") {
315
+ try {
316
+ return this.#lockSnapshot(await fs.readFile(this.#lock, "utf8"));
317
+ } catch (legacyError) {
318
+ if ((legacyError as NodeJS.ErrnoException).code === "ENOENT") return null;
319
+ throw legacyError;
320
+ }
321
+ }
322
+ if (code !== "ENOENT") throw e;
323
+ }
324
+ try {
325
+ const lock = await fs.stat(this.#lock);
326
+ return lock.isDirectory() ? { pid: 0, identity: `directory:${lock.dev}:${lock.ino}` } : null;
327
+ } catch (e) {
328
+ if ((e as NodeJS.ErrnoException).code === "ENOENT") return null;
329
+ throw e;
330
+ }
331
+ }
332
+ async #createLock(): Promise<void> {
333
+ await fs.mkdir(this.#lock, { mode: 0o700 });
334
+ try {
335
+ await fs.writeFile(
336
+ this.#lockRecordPath(),
337
+ JSON.stringify({ version: 1, ownerId: this.#owner, pid: process.pid, acquiredAt: Date.now() }),
338
+ { flag: "wx", mode: 0o600 },
339
+ );
340
+ } catch (e) {
341
+ try {
342
+ await fs.rmdir(this.#lock);
343
+ } catch {}
344
+ throw e;
345
+ }
346
+ }
347
+ async #waitForBrokerDiscovery(): Promise<BrokerDiscovery | null> {
348
+ const deadline = Date.now() + BROKER_LOCK_STARTUP_WAIT_MS;
349
+ while (Date.now() < deadline) {
350
+ const live = await readBrokerDiscovery(this.settings.agentDir, this.settings.heartbeatTtlMs);
351
+ if (live) return live;
352
+ await Bun.sleep(BROKER_LOCK_RETRY_MS);
353
+ }
354
+ return readBrokerDiscovery(this.settings.agentDir, this.settings.heartbeatTtlMs);
355
+ }
356
+ async #reclaimStaleLock(snapshot: BrokerLockSnapshot): Promise<void> {
357
+ const current = await this.#readLock();
358
+ if (!current || current.identity !== snapshot.identity || (current.pid > 0 && isPidAlive(current.pid))) return;
359
+
360
+ // Keep the tombstone: its immutable-owner-derived pathname prevents a contender
361
+ // holding an old snapshot from renaming a newly-created directory lock.
362
+ const tombstone = path.join(
363
+ path.dirname(this.#lock),
364
+ `.broker.lock.stale-${createHash("sha256").update(snapshot.identity).digest("hex")}`,
365
+ );
366
+ try {
367
+ await fs.rename(this.#lock, tombstone);
368
+ } catch (e) {
369
+ const code = (e as NodeJS.ErrnoException).code;
370
+ if (["ENOENT", "EEXIST", "ENOTEMPTY", "EISDIR", "ENOTDIR"].includes(code ?? "")) return;
371
+ if (code === "EPERM") {
372
+ try {
373
+ await fs.lstat(tombstone);
374
+ return;
375
+ } catch (statError) {
376
+ if ((statError as NodeJS.ErrnoException).code !== "ENOENT") throw statError;
377
+ }
378
+ }
379
+ throw e;
380
+ }
381
+ }
382
+ async start(): Promise<BrokerDiscovery> {
383
+ this.#stopping = false;
384
+ await Promise.all([
385
+ this.index.assertSupportedStateVersions(),
386
+ this.ledger.assertSupportedStateVersions(),
387
+ readBrokerDiscovery(this.settings.agentDir),
388
+ ]);
389
+ await fs.mkdir(path.dirname(this.#lock), { recursive: true, mode: 0o700 });
390
+ for (;;) {
391
+ try {
392
+ await this.#createLock();
393
+ break;
394
+ } catch (e) {
395
+ if ((e as NodeJS.ErrnoException).code !== "EEXIST") throw e;
396
+ }
397
+
398
+ const live = await readBrokerDiscovery(this.settings.agentDir, this.settings.heartbeatTtlMs);
399
+ if (live) {
400
+ this.discovery = live;
401
+ return live;
402
+ }
403
+ const snapshot = await this.#readLock();
404
+ if (!snapshot) continue;
405
+ if (snapshot.pid > 0 && isPidAlive(snapshot.pid)) {
406
+ const starting = await this.#waitForBrokerDiscovery();
407
+ if (starting) {
408
+ this.discovery = starting;
409
+ return starting;
410
+ }
411
+ const current = await this.#readLock();
412
+ if (current && current.identity === snapshot.identity && current.pid > 0 && isPidAlive(current.pid))
413
+ throw new Error("Broker lock is held by a live owner");
414
+ continue;
415
+ }
416
+ await this.#reclaimStaleLock(snapshot);
417
+ }
418
+ await this.index.open();
419
+ await this.ledger.open();
420
+ const now = Date.now();
421
+ const incarnation = brokerProcessIncarnation(process.pid);
422
+ if (!incarnation) throw new Error("Broker process incarnation is unavailable.");
423
+ const token = newBrokerToken();
424
+ this.#transport = new BrokerTransport(this, token, this.settings.port);
425
+ const port = await this.#transport.start();
426
+ this.discovery = {
427
+ version: 1,
428
+ protocolVersion: 3,
429
+ packageGeneration: this.settings.packageGeneration,
430
+ ownerId: this.#owner,
431
+ pid: process.pid,
432
+ incarnation,
433
+ host: "127.0.0.1",
434
+ port,
435
+ url: `ws://127.0.0.1:${port}`,
436
+ token,
437
+ startedAt: now,
438
+ heartbeatAt: now,
439
+ };
440
+ await writeBrokerDiscovery(this.settings.agentDir, this.discovery);
441
+ this.#heartbeatTimer = setInterval(
442
+ () => void this.heartbeat(),
443
+ Math.max(1, Math.floor(this.settings.heartbeatTtlMs / 3)),
444
+ );
445
+ return this.discovery;
446
+ }
447
+ get ownsDiscovery(): boolean {
448
+ return this.discovery?.ownerId === this.#owner;
449
+ }
450
+ status(): ReturnType<typeof redactBrokerDiscovery> | null {
451
+ return this.discovery ? redactBrokerDiscovery(this.discovery) : null;
452
+ }
453
+ async heartbeat(): Promise<void> {
454
+ if (!this.discovery || this.discovery.ownerId !== this.#owner) return;
455
+ this.discovery = { ...this.discovery, heartbeatAt: Date.now() };
456
+ const discovery = this.discovery;
457
+ this.#heartbeatWrite = this.#heartbeatWrite.then(() => writeBrokerDiscovery(this.settings.agentDir, discovery));
458
+ await this.#heartbeatWrite;
459
+ }
460
+ async stop(): Promise<void> {
461
+ this.#stopping = true;
462
+ if (this.#heartbeatTimer) {
463
+ clearInterval(this.#heartbeatTimer);
464
+ this.#heartbeatTimer = null;
465
+ }
466
+ await this.#heartbeatWrite;
467
+ await Promise.allSettled(this.#chains.values());
468
+ await this.#transport?.stop();
469
+ this.#transport = null;
470
+ if (this.discovery?.ownerId === this.#owner) {
471
+ try {
472
+ const disk = JSON.parse(await fs.readFile(brokerDiscoveryPath(this.settings.agentDir), "utf8")) as {
473
+ ownerId?: string;
474
+ };
475
+ if (disk.ownerId === this.#owner) await fs.unlink(brokerDiscoveryPath(this.settings.agentDir));
476
+ } catch (e) {
477
+ if ((e as NodeJS.ErrnoException).code !== "ENOENT") throw e;
478
+ }
479
+ try {
480
+ const lock = await this.#readLock();
481
+ if (lock?.ownerId === this.#owner) {
482
+ await fs.unlink(this.#lockRecordPath());
483
+ await fs.rmdir(this.#lock);
484
+ }
485
+ } catch (e) {
486
+ if ((e as NodeJS.ErrnoException).code !== "ENOENT") throw e;
487
+ }
488
+ }
489
+ this.discovery = null;
490
+ }
491
+ async #endpoint(input: Record<string, unknown>): Promise<BrokerResponse> {
492
+ const sessionId = input.sessionId;
493
+ if (typeof sessionId !== "string" || !isCanonicalSessionId(sessionId))
494
+ return error("invalid_input", "sessionId must be a canonical safe identifier");
495
+ const authority = expectedEndpointAuthority(input);
496
+ if ("ok" in authority) return authority;
497
+ await this.index.refresh();
498
+ const record = this.index.listSessions().sessions.find(session => session.sessionId === sessionId);
499
+ if (!record) return error("resource_gone", "session is not indexed");
500
+ if (!record.live || !matchesEndpointAuthority(record, authority))
501
+ return error("endpoint_stale", "session endpoint is stale");
502
+ return this.#readEndpoint(record, authority);
503
+ }
504
+ async #readEndpoint(record: IndexedSession, authority: EndpointAuthority): Promise<BrokerResponse> {
505
+ if (!isCanonicalSessionId(record.sessionId))
506
+ return error("invalid_input", "indexed sessionId is not a canonical safe identifier");
507
+
508
+ try {
509
+ const endpointPath = path.join(record.locator.stateRoot, "sdk", `${record.sessionId}.json`);
510
+ const [source, metadata] = await Promise.all([fs.readFile(endpointPath, "utf8"), fs.stat(endpointPath)]);
511
+ const endpoint = JSON.parse(source) as Record<string, unknown>;
512
+ if (
513
+ endpoint.sessionId !== record.sessionId ||
514
+ endpoint.pid !== record.pid ||
515
+ endpoint.stale === true ||
516
+ record.endpointMtimeMs === undefined ||
517
+ metadata.mtimeMs !== record.endpointMtimeMs
518
+ )
519
+ return error("endpoint_stale", "session endpoint is stale");
520
+ await this.index.refresh();
521
+ const current = this.index.listSessions().sessions.find(session => session.sessionId === record.sessionId);
522
+ if (!current || !sameEndpointRecord(record, current) || !matchesEndpointAuthority(current, authority))
523
+ return error("endpoint_stale", "session endpoint is stale");
524
+ return { ok: true, result: endpoint };
525
+ } catch (e) {
526
+ if ((e as NodeJS.ErrnoException).code === "ENOENT")
527
+ return error("resource_gone", "session endpoint record is gone");
528
+ throw e;
529
+ }
530
+ }
531
+ async handleRequest(
532
+ operation: string,
533
+ input: Record<string, unknown>,
534
+ idempotencyKey?: string,
535
+ ): Promise<BrokerResponse> {
536
+ if (this.#stopping) return error("broker_restarting", "broker is stopping");
537
+ const normalization = normalizeBrokerInput(operation, input);
538
+ if (isBrokerResponse(normalization)) return normalization;
539
+ input = normalization.input;
540
+ if (operation === "session.list") {
541
+ await this.index.refresh();
542
+ const result = this.index.listSessions();
543
+ const resolveSessionId = typeof input.resolveSessionId === "string" ? input.resolveSessionId : undefined;
544
+ const cwd = typeof input.cwd === "string" ? input.cwd : undefined;
545
+ if (resolveSessionId && cwd) {
546
+ const sessionDir = SessionManager.getDefaultSessionDir(cwd, this.settings.agentDir);
547
+ const match = await resolveResumableSession(resolveSessionId, cwd, sessionDir);
548
+ return {
549
+ ok: true,
550
+ result: {
551
+ ...result,
552
+ savedSession:
553
+ match && match.session.id === resolveSessionId
554
+ ? { id: match.session.id, path: match.session.path }
555
+ : undefined,
556
+ },
557
+ indexSeq: result.indexSeq,
558
+ };
559
+ }
560
+ return { ok: true, result, indexSeq: result.indexSeq };
561
+ }
562
+ if (operation === "session.get_endpoint") return this.#endpoint(input);
563
+ if (!idempotencyKey) return error("invalid_input", "idempotencyKey is required for lifecycle operations");
564
+ const target = createHash("sha256")
565
+ .update(canonicalJson(lifecycleTarget(operation, input)))
566
+ .digest("hex");
567
+ const identity = await deriveIdempotencyIdentity(this.settings.agentDir, operation, idempotencyKey, target);
568
+ const requestHash = createHash("sha256").update(canonicalJson({ operation, input })).digest("hex");
569
+ const prev = this.#chains.get(target) ?? Promise.resolve();
570
+ let release!: () => void;
571
+ const current = new Promise<void>(resolve => (release = resolve));
572
+ this.#chains.set(
573
+ target,
574
+ prev.then(() => current),
575
+ );
576
+ await prev;
577
+ try {
578
+ const begun = await this.ledger.begin(identity, requestHash);
579
+ if (begun.kind === "replay") return begun.entry.response as BrokerResponse;
580
+ if (begun.kind === "idempotency_conflict")
581
+ return error("idempotency_conflict", "idempotency key was used with a different request");
582
+ if (begun.kind === "terminal_uncertain")
583
+ return begun.entry.response
584
+ ? (begun.entry.response as BrokerResponse)
585
+ : error("terminal_uncertain", "prior lifecycle operation outcome is uncertain");
586
+ if (begun.kind === "in_progress") return error("broker_restarting", "lifecycle operation is in progress");
587
+ const outcome = await executeLifecycle(this, operation, input, identity);
588
+ const response = outcome.response;
589
+ await this.ledger.transition(
590
+ identity,
591
+ response.ok
592
+ ? "terminal_ok"
593
+ : response.error.code === "terminal_uncertain"
594
+ ? "terminal_uncertain"
595
+ : "terminal_error",
596
+ {
597
+ resultSessionId:
598
+ response.ok && typeof (response.result as { sessionId?: unknown } | undefined)?.sessionId === "string"
599
+ ? (response.result as { sessionId: string }).sessionId
600
+ : undefined,
601
+ response,
602
+ responseDigest: createHash("sha256").update(canonicalJson(response)).digest("hex"),
603
+ ...(outcome.durableEffects ? { durableEffects: outcome.durableEffects } : {}),
604
+ ...(outcome.startupFailure ? { startupFailure: outcome.startupFailure } : {}),
605
+ },
606
+ );
607
+ const reopenedLedger = await new LifecycleLedger(this.settings.agentDir).open();
608
+ const persisted = reopenedLedger.get(identity);
609
+ const expectedResponseDigest = createHash("sha256").update(canonicalJson(response)).digest("hex");
610
+ const persistenceVerified =
611
+ persisted?.responseDigest === expectedResponseDigest &&
612
+ canonicalJson(persisted.response) === canonicalJson(response) &&
613
+ canonicalJson(persisted.durableEffects) === canonicalJson(outcome.durableEffects) &&
614
+ canonicalJson(persisted.startupFailure) === canonicalJson(outcome.startupFailure);
615
+ if (!persistenceVerified) {
616
+ const uncertain = error(
617
+ "terminal_uncertain",
618
+ "Lifecycle terminal evidence could not be verified after persistence; retained artifacts require reconciliation.",
619
+ );
620
+ await this.ledger.transition(identity, "terminal_uncertain", {
621
+ response: uncertain,
622
+ responseDigest: createHash("sha256").update(canonicalJson(uncertain)).digest("hex"),
623
+ ...(outcome.durableEffects ? { durableEffects: outcome.durableEffects } : {}),
624
+ ...(outcome.startupFailure ? { startupFailure: outcome.startupFailure } : {}),
625
+ });
626
+ return uncertain;
627
+ }
628
+ terminalPersistenceHooksForTest.get(this)?.();
629
+ await outcome.deferredArtifactCleanup?.();
630
+ return response;
631
+ } finally {
632
+ release();
633
+ if (this.#chains.get(target) === current) this.#chains.delete(target);
634
+ }
635
+ }
636
+ }
637
+
638
+ /** Test-only hook for simulating a process crash after terminal persistence verification. */
639
+ export function setTerminalPersistenceHookForTest(broker: Broker, hook: (() => void) | undefined): void {
640
+ if (hook) terminalPersistenceHooksForTest.set(broker, hook);
641
+ else terminalPersistenceHooksForTest.delete(broker);
642
+ }