@gajae-code/coding-agent 0.10.2 → 0.11.1

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 (476) hide show
  1. package/CHANGELOG.md +58 -2
  2. package/dist/types/append-only-mode.d.ts +1 -1
  3. package/dist/types/async/job-manager.d.ts +46 -7
  4. package/dist/types/cli/args.d.ts +4 -5
  5. package/dist/types/cli/daemon-cli.d.ts +12 -1
  6. package/dist/types/cli/notify-cli.d.ts +21 -1
  7. package/dist/types/cli.d.ts +3 -0
  8. package/dist/types/commands/daemon.d.ts +39 -0
  9. package/dist/types/commands/launch.d.ts +3 -0
  10. package/dist/types/commands/notify.d.ts +28 -1
  11. package/dist/types/commands/sdk.d.ts +33 -0
  12. package/dist/types/config/atomic-yaml-patch.d.ts +85 -0
  13. package/dist/types/config/model-profile-activation.d.ts +35 -15
  14. package/dist/types/config/model-profiles.d.ts +11 -5
  15. package/dist/types/config/model-registry.d.ts +3 -3
  16. package/dist/types/config/model-resolver.d.ts +39 -6
  17. package/dist/types/config/model-selector-value.d.ts +17 -0
  18. package/dist/types/config/models-config-schema.d.ts +10 -6
  19. package/dist/types/config/settings-schema.d.ts +133 -6
  20. package/dist/types/config/settings.d.ts +63 -35
  21. package/dist/types/coordinator-mcp/model-preset.d.ts +7 -6
  22. package/dist/types/coordinator-mcp/server.d.ts +6 -49
  23. package/dist/types/daemon/builtin.d.ts +6 -5
  24. package/dist/types/daemon/control-types.d.ts +1 -1
  25. package/dist/types/discovery/mcp-json.d.ts +12 -1
  26. package/dist/types/extensibility/custom-tools/types.d.ts +1 -1
  27. package/dist/types/extensibility/extensions/compact-handler.d.ts +4 -2
  28. package/dist/types/extensibility/extensions/get-commands-handler.d.ts +2 -2
  29. package/dist/types/extensibility/extensions/loader.d.ts +10 -1
  30. package/dist/types/extensibility/extensions/runner.d.ts +3 -3
  31. package/dist/types/extensibility/extensions/types.d.ts +141 -16
  32. package/dist/types/extensibility/hooks/types.d.ts +1 -1
  33. package/dist/types/gjc-runtime/launch-tmux.d.ts +3 -2
  34. package/dist/types/gjc-runtime/psmux-detect.d.ts +3 -0
  35. package/dist/types/gjc-runtime/session-state-sidecar.d.ts +3 -0
  36. package/dist/types/gjc-runtime/team-runtime.d.ts +3 -4
  37. package/dist/types/gjc-runtime/tmux-owner-isolation-cli.d.ts +13 -0
  38. package/dist/types/gjc-runtime/tmux-sessions.d.ts +4 -1
  39. package/dist/types/gjc-runtime/ultragoal-receipt-freshness.d.ts +11 -0
  40. package/dist/types/gjc-runtime/windows-powershell-command.d.ts +9 -0
  41. package/dist/types/harness-control-plane/operate.d.ts +5 -5
  42. package/dist/types/harness-control-plane/owner.d.ts +18 -4
  43. package/dist/types/harness-control-plane/receipts.d.ts +0 -1
  44. package/dist/types/harness-control-plane/sdk-transport.d.ts +43 -0
  45. package/dist/types/harness-control-plane/session-transport.d.ts +48 -0
  46. package/dist/types/harness-control-plane/types.d.ts +3 -4
  47. package/dist/types/lsp/index.d.ts +1 -1
  48. package/dist/types/main.d.ts +32 -38
  49. package/dist/types/modes/acp/acp-agent.d.ts +224 -53
  50. package/dist/types/modes/acp/acp-client-bridge.d.ts +1 -0
  51. package/dist/types/modes/acp/acp-mode.d.ts +6 -15
  52. package/dist/types/modes/acp/permission-mode.d.ts +4 -0
  53. package/dist/types/modes/acp/startup-options.d.ts +6 -0
  54. package/dist/types/modes/components/agent-dashboard.d.ts +3 -0
  55. package/dist/types/modes/components/model-selector.d.ts +1 -0
  56. package/dist/types/modes/components/notifications-settings-editor.d.ts +135 -0
  57. package/dist/types/modes/components/settings-selector.d.ts +6 -4
  58. package/dist/types/modes/components/welcome.d.ts +1 -1
  59. package/dist/types/modes/controllers/command-controller.d.ts +2 -2
  60. package/dist/types/modes/controllers/extension-ui-controller.d.ts +1 -0
  61. package/dist/types/modes/controllers/selector-controller.d.ts +35 -1
  62. package/dist/types/modes/index.d.ts +0 -4
  63. package/dist/types/modes/interactive-mode.d.ts +5 -2
  64. package/dist/types/modes/runtime-init.d.ts +2 -2
  65. package/dist/types/modes/shared/agent-wire/approval-gate.d.ts +6 -9
  66. package/dist/types/modes/shared/agent-wire/deep-interview-gate.d.ts +4 -6
  67. package/dist/types/modes/shared/agent-wire/event-contract.d.ts +6 -6
  68. package/dist/types/modes/shared/agent-wire/event-envelope.d.ts +4 -9
  69. package/dist/types/modes/shared/agent-wire/event-observation.d.ts +1 -1
  70. package/dist/types/modes/shared/agent-wire/workflow-gate-broker.d.ts +170 -30
  71. package/dist/types/modes/shared/agent-wire/workflow-gate-schema.d.ts +7 -7
  72. package/dist/types/modes/shared/agent-wire/workflow-gate-types.d.ts +105 -0
  73. package/dist/types/modes/types.d.ts +7 -2
  74. package/dist/types/runtime-mcp/client.d.ts +2 -0
  75. package/dist/types/runtime-mcp/config.d.ts +5 -1
  76. package/dist/types/runtime-mcp/manager.d.ts +8 -1
  77. package/dist/types/runtime-mcp/types.d.ts +4 -0
  78. package/dist/types/sdk/acp/adapter.d.ts +67 -0
  79. package/dist/types/sdk/acp/index.d.ts +2 -0
  80. package/dist/types/sdk/broker/broker.d.ts +53 -0
  81. package/dist/types/sdk/broker/discovery.d.ts +30 -0
  82. package/dist/types/sdk/broker/ensure.d.ts +42 -0
  83. package/dist/types/sdk/broker/identity.d.ts +3 -0
  84. package/dist/types/sdk/broker/index.d.ts +8 -0
  85. package/dist/types/sdk/broker/lifecycle-ledger.d.ts +95 -0
  86. package/dist/types/sdk/broker/lifecycle.d.ts +85 -0
  87. package/dist/types/sdk/broker/process-incarnation.d.ts +14 -0
  88. package/dist/types/sdk/broker/runtime.d.ts +33 -0
  89. package/dist/types/sdk/broker/session-index.d.ts +57 -0
  90. package/dist/types/sdk/broker/state-version.d.ts +8 -0
  91. package/dist/types/sdk/broker/transport.d.ts +9 -0
  92. package/dist/types/sdk/bus/chat-command-policy.d.ts +49 -0
  93. package/dist/types/sdk/bus/chat-daemon-cli.d.ts +21 -0
  94. package/dist/types/sdk/bus/chat-daemon-control.d.ts +111 -0
  95. package/dist/types/sdk/bus/chat-daemon-runtime.d.ts +77 -0
  96. package/dist/types/sdk/bus/chat-effect-journal.d.ts +84 -0
  97. package/dist/types/{notifications → sdk/bus}/config-commands.d.ts +7 -2
  98. package/dist/types/sdk/bus/config.d.ts +274 -0
  99. package/dist/types/sdk/bus/conversation-store.d.ts +72 -0
  100. package/dist/types/sdk/bus/discord-conversation.d.ts +58 -0
  101. package/dist/types/sdk/bus/discord-daemon.d.ts +50 -0
  102. package/dist/types/sdk/bus/discord-live-provider.d.ts +70 -0
  103. package/dist/types/sdk/bus/discord-provider.d.ts +85 -0
  104. package/dist/types/{notifications → sdk/bus}/html-format.d.ts +2 -2
  105. package/dist/types/{notifications → sdk/bus}/index.d.ts +113 -13
  106. package/dist/types/{notifications → sdk/bus}/lifecycle-control-runtime.d.ts +4 -2
  107. package/dist/types/{notifications → sdk/bus}/lifecycle-orchestrator.d.ts +18 -21
  108. package/dist/types/{notifications → sdk/bus}/managed-daemon.d.ts +1 -1
  109. package/dist/types/sdk/bus/native-runtime-compatibility.d.ts +13 -0
  110. package/dist/types/sdk/bus/notification-orchestration.d.ts +170 -0
  111. package/dist/types/{notifications → sdk/bus}/notification-service.d.ts +7 -1
  112. package/dist/types/sdk/bus/session-control.d.ts +83 -0
  113. package/dist/types/sdk/bus/slack-conversation.d.ts +56 -0
  114. package/dist/types/sdk/bus/slack-daemon.d.ts +60 -0
  115. package/dist/types/sdk/bus/slack-live-provider.d.ts +59 -0
  116. package/dist/types/sdk/bus/slack-provider.d.ts +55 -0
  117. package/dist/types/{notifications → sdk/bus}/telegram-cli.d.ts +4 -4
  118. package/dist/types/{notifications → sdk/bus}/telegram-daemon-cli.d.ts +14 -5
  119. package/dist/types/sdk/bus/telegram-daemon-contract.d.ts +12 -0
  120. package/dist/types/{notifications → sdk/bus}/telegram-daemon-control.d.ts +2 -2
  121. package/dist/types/{notifications → sdk/bus}/telegram-daemon.d.ts +78 -17
  122. package/dist/types/{notifications → sdk/bus}/telegram-reference.d.ts +2 -2
  123. package/dist/types/sdk/bus/telegram-setup.d.ts +128 -0
  124. package/dist/types/{notifications → sdk/bus}/topic-registry.d.ts +11 -0
  125. package/dist/types/sdk/cli/index.d.ts +1 -0
  126. package/dist/types/sdk/cli/session-cli.d.ts +19 -0
  127. package/dist/types/sdk/client/client.d.ts +1 -0
  128. package/dist/types/sdk/client/discovery.d.ts +27 -0
  129. package/dist/types/sdk/client/index.d.ts +2 -0
  130. package/dist/types/sdk/host/control/dispatch.d.ts +34 -0
  131. package/dist/types/sdk/host/control/index.d.ts +2 -0
  132. package/dist/types/sdk/host/control/operations.d.ts +65 -0
  133. package/dist/types/sdk/host/events.d.ts +35 -0
  134. package/dist/types/sdk/host/host.d.ts +35 -0
  135. package/dist/types/sdk/host/index.d.ts +4 -0
  136. package/dist/types/sdk/host/query/cursor.d.ts +62 -0
  137. package/dist/types/sdk/host/query/handlers.d.ts +70 -0
  138. package/dist/types/sdk/host/query/index.d.ts +3 -0
  139. package/dist/types/sdk/host/query/revision-store.d.ts +60 -0
  140. package/dist/types/sdk/host/reverse-leases.d.ts +45 -0
  141. package/dist/types/sdk/host/types.d.ts +32 -0
  142. package/dist/types/sdk/index.d.ts +6 -0
  143. package/dist/types/sdk/lifecycle-session.d.ts +14 -0
  144. package/dist/types/sdk/mcp/index.d.ts +1 -0
  145. package/dist/types/sdk/mcp/server.d.ts +37 -0
  146. package/dist/types/sdk/models.d.ts +44 -0
  147. package/dist/types/sdk/protocol/adapter-validation.d.ts +14 -0
  148. package/dist/types/sdk/protocol/operation-registry.d.ts +24 -0
  149. package/dist/types/{sdk.d.ts → sdk/session.d.ts} +44 -38
  150. package/dist/types/sdk/startup-capability.d.ts +50 -0
  151. package/dist/types/session/agent-session.d.ts +114 -21
  152. package/dist/types/session/default-model-selection.d.ts +25 -0
  153. package/dist/types/session/fallback-chain-controller.d.ts +52 -0
  154. package/dist/types/session/session-manager.d.ts +58 -1
  155. package/dist/types/setup/credential-auto-import.d.ts +36 -6
  156. package/dist/types/tools/ask-answer-registry.d.ts +1 -1
  157. package/dist/types/tools/browser/benchmark/detector-report.d.ts +63 -0
  158. package/dist/types/tools/browser/benchmark/run-suite.d.ts +18 -0
  159. package/dist/types/tools/browser/launch.d.ts +22 -0
  160. package/dist/types/tools/browser/profile-discovery.d.ts +31 -0
  161. package/dist/types/tools/browser/profile-posture.d.ts +37 -0
  162. package/dist/types/tools/browser/profile-reuse.d.ts +39 -0
  163. package/dist/types/tools/browser/profile-warmup.d.ts +39 -0
  164. package/dist/types/tools/browser/registry.d.ts +19 -0
  165. package/dist/types/tools/browser/tab-protocol.d.ts +4 -0
  166. package/dist/types/tools/index.d.ts +8 -8
  167. package/dist/types/tools/subagent-render.d.ts +1 -0
  168. package/dist/types/tools/subagent.d.ts +6 -1
  169. package/package.json +34 -13
  170. package/scripts/build-sdk-package-smoke.ts +99 -0
  171. package/scripts/capture-notifications-settings-showcase.ts +435 -0
  172. package/scripts/compile-args.ts +4 -2
  173. package/scripts/g005-daemon-smoke.ts +4 -5
  174. package/scripts/g011-daemon-path-smoke.ts +5 -5
  175. package/scripts/generate-sdk-adapter-parity-manifest.ts +163 -0
  176. package/scripts/generate-sdk-operation-inventory.ts +867 -0
  177. package/scripts/generate-telegram-baseline-manifest.ts +128 -0
  178. package/scripts/manifest-schema.ts +124 -0
  179. package/scripts/run-test-manifest.ts +153 -0
  180. package/scripts/run-threat-model-check.ts +157 -0
  181. package/scripts/verify-gjc-sdk-canonicalization.ts +3467 -0
  182. package/scripts/verify-gjc-sdk-rename.ts +90 -0
  183. package/src/append-only-mode.ts +1 -1
  184. package/src/async/job-manager.ts +406 -63
  185. package/src/cli/args.ts +43 -12
  186. package/src/cli/auth-gateway-cli.ts +2 -2
  187. package/src/cli/config-cli.ts +2 -1
  188. package/src/cli/daemon-cli.ts +63 -14
  189. package/src/cli/list-models.ts +1 -1
  190. package/src/cli/notify-cli.ts +288 -286
  191. package/src/cli.ts +21 -2
  192. package/src/commands/daemon.ts +50 -6
  193. package/src/commands/harness.ts +93 -36
  194. package/src/commands/launch.ts +5 -2
  195. package/src/commands/mcp-serve.ts +19 -12
  196. package/src/commands/notify.ts +36 -3
  197. package/src/commands/sdk.ts +426 -0
  198. package/src/commit/agentic/agent.ts +1 -0
  199. package/src/config/atomic-yaml-patch.ts +423 -0
  200. package/src/config/model-profile-activation.ts +240 -89
  201. package/src/config/model-profiles.ts +18 -15
  202. package/src/config/model-registry.ts +51 -26
  203. package/src/config/model-resolver.ts +138 -51
  204. package/src/config/model-selector-value.ts +50 -0
  205. package/src/config/models-config-schema.ts +14 -21
  206. package/src/config/settings-schema.ts +128 -10
  207. package/src/config/settings.ts +1120 -158
  208. package/src/coordinator-mcp/model-preset.ts +74 -20
  209. package/src/coordinator-mcp/server.ts +1597 -3052
  210. package/src/daemon/builtin.ts +8 -5
  211. package/src/daemon/control-types.ts +1 -1
  212. package/src/defaults/gjc/skills/team/SKILL.md +4 -4
  213. package/src/discovery/mcp-json.ts +327 -20
  214. package/src/extensibility/custom-tools/types.ts +1 -1
  215. package/src/extensibility/extensions/compact-handler.ts +3 -3
  216. package/src/extensibility/extensions/get-commands-handler.ts +2 -2
  217. package/src/extensibility/extensions/loader.ts +69 -5
  218. package/src/extensibility/extensions/runner.ts +112 -4
  219. package/src/extensibility/extensions/types.ts +176 -17
  220. package/src/extensibility/hooks/types.ts +1 -1
  221. package/src/gjc-runtime/launch-tmux.ts +170 -86
  222. package/src/gjc-runtime/psmux-detect.ts +82 -4
  223. package/src/gjc-runtime/session-state-sidecar.ts +41 -31
  224. package/src/gjc-runtime/team-runtime.ts +54 -35
  225. package/src/gjc-runtime/tmux-owner-isolation-cli.ts +53 -17
  226. package/src/gjc-runtime/tmux-owner-isolation.ts +1 -1
  227. package/src/gjc-runtime/tmux-sessions.ts +19 -13
  228. package/src/gjc-runtime/ultragoal-guard.ts +108 -14
  229. package/src/gjc-runtime/ultragoal-receipt-freshness.ts +102 -1
  230. package/src/gjc-runtime/ultragoal-runtime.ts +227 -11
  231. package/src/gjc-runtime/windows-powershell-command.ts +58 -0
  232. package/src/harness-control-plane/operate.ts +9 -9
  233. package/src/harness-control-plane/owner.ts +283 -39
  234. package/src/harness-control-plane/receipts.ts +0 -1
  235. package/src/harness-control-plane/sdk-transport.ts +395 -0
  236. package/src/harness-control-plane/session-transport.ts +94 -0
  237. package/src/harness-control-plane/types.ts +1 -1
  238. package/src/internal-urls/docs-index.generated.ts +34 -35
  239. package/src/lsp/client.ts +42 -6
  240. package/src/lsp/index.ts +2 -2
  241. package/src/main.ts +299 -204
  242. package/src/modes/DESIGN.md +258 -0
  243. package/src/modes/acp/acp-agent.ts +1228 -2612
  244. package/src/modes/acp/acp-client-bridge.ts +2 -20
  245. package/src/modes/acp/acp-event-mapper.ts +18 -2
  246. package/src/modes/acp/acp-mode.ts +8 -36
  247. package/src/modes/acp/permission-mode.ts +25 -0
  248. package/src/modes/acp/startup-options.ts +6 -0
  249. package/src/modes/components/agent-dashboard.ts +37 -17
  250. package/src/modes/components/model-selector.ts +93 -35
  251. package/src/modes/components/notifications-settings-editor.ts +1379 -0
  252. package/src/modes/components/settings-selector.ts +60 -11
  253. package/src/modes/components/welcome.ts +4 -4
  254. package/src/modes/controllers/command-controller.ts +10 -17
  255. package/src/modes/controllers/event-controller.ts +11 -16
  256. package/src/modes/controllers/extension-ui-controller.ts +396 -28
  257. package/src/modes/controllers/selector-controller.ts +674 -36
  258. package/src/modes/index.ts +0 -22
  259. package/src/modes/interactive-mode.ts +87 -36
  260. package/src/modes/runtime-init.ts +298 -6
  261. package/src/modes/shared/agent-wire/approval-gate.ts +9 -11
  262. package/src/modes/shared/agent-wire/deep-interview-gate.ts +10 -11
  263. package/src/modes/shared/agent-wire/event-contract.ts +7 -8
  264. package/src/modes/shared/agent-wire/event-envelope.ts +9 -17
  265. package/src/modes/shared/agent-wire/event-observation.ts +16 -7
  266. package/src/modes/shared/agent-wire/workflow-gate-broker.ts +1258 -87
  267. package/src/modes/shared/agent-wire/workflow-gate-schema.ts +15 -15
  268. package/src/modes/shared/agent-wire/workflow-gate-types.ts +113 -0
  269. package/src/modes/types.ts +7 -2
  270. package/src/runtime-mcp/client.ts +94 -19
  271. package/src/runtime-mcp/config.ts +27 -15
  272. package/src/runtime-mcp/manager.ts +343 -105
  273. package/src/runtime-mcp/oauth-flow.ts +42 -12
  274. package/src/runtime-mcp/transports/http.ts +49 -19
  275. package/src/runtime-mcp/transports/stdio.ts +47 -28
  276. package/src/runtime-mcp/types.ts +10 -0
  277. package/src/sdk/acp/adapter.ts +418 -0
  278. package/src/sdk/acp/index.ts +2 -0
  279. package/src/sdk/broker/broker.ts +642 -0
  280. package/src/sdk/broker/discovery.ts +113 -0
  281. package/src/sdk/broker/ensure.ts +307 -0
  282. package/src/sdk/broker/identity.ts +42 -0
  283. package/src/sdk/broker/index.ts +8 -0
  284. package/src/sdk/broker/internal-source-marker-2178.txt +1 -0
  285. package/src/sdk/broker/internal-source.bunfig.toml +1 -0
  286. package/src/sdk/broker/lifecycle-ledger.ts +286 -0
  287. package/src/sdk/broker/lifecycle.ts +2157 -0
  288. package/src/sdk/broker/process-incarnation.ts +159 -0
  289. package/src/sdk/broker/runtime.ts +164 -0
  290. package/src/sdk/broker/session-index.ts +318 -0
  291. package/src/sdk/broker/state-version.ts +23 -0
  292. package/src/sdk/broker/transport.ts +125 -0
  293. package/src/sdk/bus/chat-command-policy.ts +107 -0
  294. package/src/sdk/bus/chat-daemon-cli.ts +214 -0
  295. package/src/sdk/bus/chat-daemon-control.ts +713 -0
  296. package/src/sdk/bus/chat-daemon-runtime.ts +526 -0
  297. package/src/sdk/bus/chat-effect-journal.ts +355 -0
  298. package/src/{notifications → sdk/bus}/config-commands.ts +39 -10
  299. package/src/sdk/bus/config.ts +433 -0
  300. package/src/sdk/bus/conversation-store.ts +391 -0
  301. package/src/sdk/bus/discord-conversation.ts +85 -0
  302. package/src/sdk/bus/discord-daemon.ts +2292 -0
  303. package/src/sdk/bus/discord-live-provider.ts +521 -0
  304. package/src/sdk/bus/discord-provider.ts +56 -0
  305. package/src/{notifications → sdk/bus}/html-format.ts +2 -2
  306. package/src/sdk/bus/index.ts +3979 -0
  307. package/src/{notifications → sdk/bus}/lifecycle-commands.ts +2 -0
  308. package/src/{notifications → sdk/bus}/lifecycle-control-runtime.ts +225 -196
  309. package/src/{notifications → sdk/bus}/lifecycle-orchestrator.ts +161 -74
  310. package/src/{notifications → sdk/bus}/managed-daemon.ts +2 -2
  311. package/src/sdk/bus/native-runtime-compatibility.ts +42 -0
  312. package/src/sdk/bus/natives-augment.d.ts +38 -0
  313. package/src/sdk/bus/notification-orchestration.ts +535 -0
  314. package/src/{notifications → sdk/bus}/notification-service.ts +106 -12
  315. package/src/sdk/bus/session-control.ts +333 -0
  316. package/src/sdk/bus/slack-conversation.ts +74 -0
  317. package/src/sdk/bus/slack-daemon.ts +2144 -0
  318. package/src/sdk/bus/slack-live-provider.ts +380 -0
  319. package/src/sdk/bus/slack-provider.ts +76 -0
  320. package/src/{notifications → sdk/bus}/telegram-cli.ts +8 -8
  321. package/src/sdk/bus/telegram-daemon-cli.ts +330 -0
  322. package/src/sdk/bus/telegram-daemon-contract.ts +14 -0
  323. package/src/{notifications → sdk/bus}/telegram-daemon-control.ts +4 -4
  324. package/src/{notifications → sdk/bus}/telegram-daemon.ts +603 -63
  325. package/src/{notifications → sdk/bus}/telegram-reference.ts +2 -2
  326. package/src/sdk/bus/telegram-setup.ts +663 -0
  327. package/src/{notifications → sdk/bus}/topic-registry.ts +32 -0
  328. package/src/sdk/cli/index.ts +1 -0
  329. package/src/sdk/cli/session-cli.ts +275 -0
  330. package/src/sdk/client/client.ts +1 -0
  331. package/src/sdk/client/discovery.ts +108 -0
  332. package/src/sdk/client/index.ts +2 -0
  333. package/src/sdk/host/control/dispatch.ts +367 -0
  334. package/src/sdk/host/control/index.ts +10 -0
  335. package/src/sdk/host/control/operations.ts +63 -0
  336. package/src/sdk/host/events.ts +91 -0
  337. package/src/sdk/host/host.ts +296 -0
  338. package/src/sdk/host/index.ts +4 -0
  339. package/src/sdk/host/query/cursor.ts +189 -0
  340. package/src/sdk/host/query/handlers.ts +533 -0
  341. package/src/sdk/host/query/index.ts +3 -0
  342. package/src/sdk/host/query/revision-store.ts +1126 -0
  343. package/src/sdk/host/reverse-leases.ts +325 -0
  344. package/src/sdk/host/types.ts +29 -0
  345. package/src/sdk/index.ts +13 -0
  346. package/src/sdk/lifecycle-session.ts +39 -0
  347. package/src/sdk/mcp/index.ts +1 -0
  348. package/src/sdk/mcp/server.ts +332 -0
  349. package/src/sdk/models.ts +168 -0
  350. package/src/sdk/protocol/adapter-validation.ts +48 -0
  351. package/src/sdk/protocol/operation-inventory.generated.json +5654 -0
  352. package/src/sdk/protocol/operation-registry.ts +312 -0
  353. package/src/{sdk.ts → sdk/session.ts} +339 -212
  354. package/src/sdk/startup-capability.ts +189 -0
  355. package/src/session/agent-session.ts +2650 -944
  356. package/src/session/default-model-selection.ts +93 -0
  357. package/src/session/fallback-chain-controller.ts +136 -0
  358. package/src/session/session-manager.ts +438 -11
  359. package/src/session/session-storage.ts +8 -1
  360. package/src/setup/credential-auto-import.ts +290 -35
  361. package/src/setup/hermes/templates/operator-instructions.v1.md +3 -5
  362. package/src/slash-commands/builtin-registry.ts +2 -1
  363. package/src/task/executor.ts +62 -13
  364. package/src/task/index.ts +26 -16
  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/subagent-render.ts +19 -8
  384. package/src/tools/subagent.ts +84 -32
  385. package/src/tools/telegram-send.ts +2 -2
  386. package/vendor/insane-search/MANIFEST.json +2 -1
  387. package/vendor/insane-search/engine/bias_check.py +4 -1
  388. package/vendor/insane-search/engine/phase0.py +26 -1
  389. package/dist/types/harness-control-plane/rpc-adapter.d.ts +0 -69
  390. package/dist/types/modes/bridge/auth.d.ts +0 -12
  391. package/dist/types/modes/bridge/bridge-client-bridge.d.ts +0 -9
  392. package/dist/types/modes/bridge/bridge-mode.d.ts +0 -47
  393. package/dist/types/modes/bridge/bridge-ui-context.d.ts +0 -88
  394. package/dist/types/modes/bridge/event-stream.d.ts +0 -8
  395. package/dist/types/modes/rpc/host-tools.d.ts +0 -1
  396. package/dist/types/modes/rpc/host-uris.d.ts +0 -1
  397. package/dist/types/modes/rpc/rpc-client.d.ts +0 -299
  398. package/dist/types/modes/rpc/rpc-mode.d.ts +0 -86
  399. package/dist/types/modes/rpc/rpc-socket-security.d.ts +0 -10
  400. package/dist/types/modes/rpc/rpc-types.d.ts +0 -810
  401. package/dist/types/modes/shared/agent-wire/command-contract.d.ts +0 -18
  402. package/dist/types/modes/shared/agent-wire/command-dispatch.d.ts +0 -37
  403. package/dist/types/modes/shared/agent-wire/command-validation.d.ts +0 -2
  404. package/dist/types/modes/shared/agent-wire/handshake.d.ts +0 -56
  405. package/dist/types/modes/shared/agent-wire/host-tool-bridge.d.ts +0 -16
  406. package/dist/types/modes/shared/agent-wire/host-uri-bridge.d.ts +0 -17
  407. package/dist/types/modes/shared/agent-wire/protocol.d.ts +0 -25
  408. package/dist/types/modes/shared/agent-wire/responses.d.ts +0 -4
  409. package/dist/types/modes/shared/agent-wire/scopes.d.ts +0 -18
  410. package/dist/types/modes/shared/agent-wire/session-registry.d.ts +0 -25
  411. package/dist/types/modes/shared/agent-wire/ui-request-broker.d.ts +0 -42
  412. package/dist/types/modes/shared/agent-wire/ui-result.d.ts +0 -27
  413. package/dist/types/modes/shared/agent-wire/unattended-action-policy.d.ts +0 -29
  414. package/dist/types/modes/shared/agent-wire/unattended-audit.d.ts +0 -68
  415. package/dist/types/modes/shared/agent-wire/unattended-run-controller.d.ts +0 -166
  416. package/dist/types/modes/shared/agent-wire/unattended-session.d.ts +0 -127
  417. package/dist/types/notifications/config.d.ts +0 -132
  418. package/src/harness-control-plane/rpc-adapter.ts +0 -290
  419. package/src/modes/bridge/auth.ts +0 -41
  420. package/src/modes/bridge/bridge-client-bridge.ts +0 -47
  421. package/src/modes/bridge/bridge-mode.ts +0 -675
  422. package/src/modes/bridge/bridge-ui-context.ts +0 -200
  423. package/src/modes/bridge/event-stream.ts +0 -70
  424. package/src/modes/rpc/host-tools.ts +0 -1
  425. package/src/modes/rpc/host-uris.ts +0 -1
  426. package/src/modes/rpc/rpc-client.ts +0 -1166
  427. package/src/modes/rpc/rpc-mode.ts +0 -1029
  428. package/src/modes/rpc/rpc-socket-security.ts +0 -115
  429. package/src/modes/rpc/rpc-types.ts +0 -637
  430. package/src/modes/shared/agent-wire/command-contract.ts +0 -18
  431. package/src/modes/shared/agent-wire/command-dispatch.ts +0 -498
  432. package/src/modes/shared/agent-wire/command-validation.ts +0 -181
  433. package/src/modes/shared/agent-wire/handshake.ts +0 -157
  434. package/src/modes/shared/agent-wire/host-tool-bridge.ts +0 -197
  435. package/src/modes/shared/agent-wire/host-uri-bridge.ts +0 -236
  436. package/src/modes/shared/agent-wire/protocol.ts +0 -46
  437. package/src/modes/shared/agent-wire/responses.ts +0 -17
  438. package/src/modes/shared/agent-wire/scopes.ts +0 -94
  439. package/src/modes/shared/agent-wire/session-registry.ts +0 -109
  440. package/src/modes/shared/agent-wire/ui-request-broker.ts +0 -150
  441. package/src/modes/shared/agent-wire/ui-result.ts +0 -48
  442. package/src/modes/shared/agent-wire/unattended-action-policy.ts +0 -365
  443. package/src/modes/shared/agent-wire/unattended-audit.ts +0 -176
  444. package/src/modes/shared/agent-wire/unattended-run-controller.ts +0 -426
  445. package/src/modes/shared/agent-wire/unattended-session.ts +0 -528
  446. package/src/notifications/config.ts +0 -219
  447. package/src/notifications/index.ts +0 -1835
  448. package/src/notifications/telegram-daemon-cli.ts +0 -243
  449. package/src/tools/puppeteer/09_stealth_locale.txt +0 -46
  450. /package/dist/types/{notifications → sdk/bus}/attachment-registry.d.ts +0 -0
  451. /package/dist/types/{notifications → sdk/bus}/chat-adapters.d.ts +0 -0
  452. /package/dist/types/{notifications → sdk/bus}/daemon-paths.d.ts +0 -0
  453. /package/dist/types/{notifications → sdk/bus}/engine.d.ts +0 -0
  454. /package/dist/types/{notifications → sdk/bus}/helpers.d.ts +0 -0
  455. /package/dist/types/{notifications → sdk/bus}/lifecycle-commands.d.ts +0 -0
  456. /package/dist/types/{notifications → sdk/bus}/operator-runtime.d.ts +0 -0
  457. /package/dist/types/{notifications → sdk/bus}/rate-limit-pool.d.ts +0 -0
  458. /package/dist/types/{notifications → sdk/bus}/recent-activity.d.ts +0 -0
  459. /package/dist/types/{notifications → sdk/bus}/reply-sent-store.d.ts +0 -0
  460. /package/dist/types/{notifications → sdk/bus}/rich-draft.d.ts +0 -0
  461. /package/dist/types/{notifications → sdk/bus}/rich-render.d.ts +0 -0
  462. /package/dist/types/{notifications → sdk/bus}/threaded-inbound.d.ts +0 -0
  463. /package/dist/types/{notifications → sdk/bus}/threaded-render.d.ts +0 -0
  464. /package/src/{notifications → sdk/bus}/attachment-registry.ts +0 -0
  465. /package/src/{notifications → sdk/bus}/chat-adapters.ts +0 -0
  466. /package/src/{notifications → sdk/bus}/daemon-paths.ts +0 -0
  467. /package/src/{notifications → sdk/bus}/engine.ts +0 -0
  468. /package/src/{notifications → sdk/bus}/helpers.ts +0 -0
  469. /package/src/{notifications → sdk/bus}/operator-runtime.ts +0 -0
  470. /package/src/{notifications → sdk/bus}/rate-limit-pool.ts +0 -0
  471. /package/src/{notifications → sdk/bus}/recent-activity.ts +0 -0
  472. /package/src/{notifications → sdk/bus}/reply-sent-store.ts +0 -0
  473. /package/src/{notifications → sdk/bus}/rich-draft.ts +0 -0
  474. /package/src/{notifications → sdk/bus}/rich-render.ts +0 -0
  475. /package/src/{notifications → sdk/bus}/threaded-inbound.ts +0 -0
  476. /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
+ }