@gajae-code/coding-agent 0.10.2 → 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 (465) hide show
  1. package/CHANGELOG.md +50 -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/model-selector.d.ts +1 -0
  54. package/dist/types/modes/components/notifications-settings-editor.d.ts +135 -0
  55. package/dist/types/modes/components/settings-selector.d.ts +6 -4
  56. package/dist/types/modes/components/welcome.d.ts +1 -1
  57. package/dist/types/modes/controllers/selector-controller.d.ts +35 -1
  58. package/dist/types/modes/index.d.ts +0 -4
  59. package/dist/types/modes/interactive-mode.d.ts +3 -0
  60. package/dist/types/modes/runtime-init.d.ts +2 -2
  61. package/dist/types/modes/shared/agent-wire/approval-gate.d.ts +6 -9
  62. package/dist/types/modes/shared/agent-wire/deep-interview-gate.d.ts +4 -6
  63. package/dist/types/modes/shared/agent-wire/event-contract.d.ts +6 -6
  64. package/dist/types/modes/shared/agent-wire/event-envelope.d.ts +4 -9
  65. package/dist/types/modes/shared/agent-wire/event-observation.d.ts +1 -1
  66. package/dist/types/modes/shared/agent-wire/workflow-gate-broker.d.ts +169 -30
  67. package/dist/types/modes/shared/agent-wire/workflow-gate-schema.d.ts +7 -7
  68. package/dist/types/modes/shared/agent-wire/workflow-gate-types.d.ts +105 -0
  69. package/dist/types/modes/types.d.ts +5 -0
  70. package/dist/types/runtime-mcp/client.d.ts +2 -0
  71. package/dist/types/runtime-mcp/config.d.ts +5 -1
  72. package/dist/types/runtime-mcp/manager.d.ts +8 -1
  73. package/dist/types/runtime-mcp/types.d.ts +4 -0
  74. package/dist/types/sdk/acp/adapter.d.ts +67 -0
  75. package/dist/types/sdk/acp/index.d.ts +2 -0
  76. package/dist/types/sdk/broker/broker.d.ts +53 -0
  77. package/dist/types/sdk/broker/discovery.d.ts +30 -0
  78. package/dist/types/sdk/broker/ensure.d.ts +33 -0
  79. package/dist/types/sdk/broker/identity.d.ts +3 -0
  80. package/dist/types/sdk/broker/index.d.ts +8 -0
  81. package/dist/types/sdk/broker/lifecycle-ledger.d.ts +95 -0
  82. package/dist/types/sdk/broker/lifecycle.d.ts +85 -0
  83. package/dist/types/sdk/broker/process-incarnation.d.ts +14 -0
  84. package/dist/types/sdk/broker/runtime.d.ts +33 -0
  85. package/dist/types/sdk/broker/session-index.d.ts +57 -0
  86. package/dist/types/sdk/broker/state-version.d.ts +8 -0
  87. package/dist/types/sdk/broker/transport.d.ts +9 -0
  88. package/dist/types/sdk/bus/chat-command-policy.d.ts +49 -0
  89. package/dist/types/sdk/bus/chat-daemon-cli.d.ts +21 -0
  90. package/dist/types/sdk/bus/chat-daemon-control.d.ts +111 -0
  91. package/dist/types/sdk/bus/chat-daemon-runtime.d.ts +77 -0
  92. package/dist/types/sdk/bus/chat-effect-journal.d.ts +84 -0
  93. package/dist/types/{notifications → sdk/bus}/config-commands.d.ts +7 -2
  94. package/dist/types/sdk/bus/config.d.ts +274 -0
  95. package/dist/types/sdk/bus/conversation-store.d.ts +72 -0
  96. package/dist/types/sdk/bus/discord-conversation.d.ts +58 -0
  97. package/dist/types/sdk/bus/discord-daemon.d.ts +45 -0
  98. package/dist/types/sdk/bus/discord-live-provider.d.ts +70 -0
  99. package/dist/types/sdk/bus/discord-provider.d.ts +85 -0
  100. package/dist/types/{notifications → sdk/bus}/html-format.d.ts +2 -2
  101. package/dist/types/{notifications → sdk/bus}/index.d.ts +113 -13
  102. package/dist/types/{notifications → sdk/bus}/lifecycle-control-runtime.d.ts +4 -2
  103. package/dist/types/{notifications → sdk/bus}/lifecycle-orchestrator.d.ts +18 -21
  104. package/dist/types/{notifications → sdk/bus}/managed-daemon.d.ts +1 -1
  105. package/dist/types/sdk/bus/native-runtime-compatibility.d.ts +13 -0
  106. package/dist/types/sdk/bus/notification-orchestration.d.ts +170 -0
  107. package/dist/types/{notifications → sdk/bus}/notification-service.d.ts +7 -1
  108. package/dist/types/sdk/bus/session-control.d.ts +83 -0
  109. package/dist/types/sdk/bus/slack-conversation.d.ts +56 -0
  110. package/dist/types/sdk/bus/slack-daemon.d.ts +60 -0
  111. package/dist/types/sdk/bus/slack-live-provider.d.ts +59 -0
  112. package/dist/types/sdk/bus/slack-provider.d.ts +55 -0
  113. package/dist/types/{notifications → sdk/bus}/telegram-cli.d.ts +4 -4
  114. package/dist/types/{notifications → sdk/bus}/telegram-daemon-cli.d.ts +14 -5
  115. package/dist/types/sdk/bus/telegram-daemon-contract.d.ts +12 -0
  116. package/dist/types/{notifications → sdk/bus}/telegram-daemon-control.d.ts +2 -2
  117. package/dist/types/{notifications → sdk/bus}/telegram-daemon.d.ts +55 -17
  118. package/dist/types/{notifications → sdk/bus}/telegram-reference.d.ts +2 -2
  119. package/dist/types/sdk/bus/telegram-setup.d.ts +128 -0
  120. package/dist/types/{notifications → sdk/bus}/topic-registry.d.ts +11 -0
  121. package/dist/types/sdk/cli/index.d.ts +1 -0
  122. package/dist/types/sdk/cli/session-cli.d.ts +19 -0
  123. package/dist/types/sdk/client/client.d.ts +1 -0
  124. package/dist/types/sdk/client/discovery.d.ts +27 -0
  125. package/dist/types/sdk/client/index.d.ts +2 -0
  126. package/dist/types/sdk/host/control/dispatch.d.ts +34 -0
  127. package/dist/types/sdk/host/control/index.d.ts +2 -0
  128. package/dist/types/sdk/host/control/operations.d.ts +65 -0
  129. package/dist/types/sdk/host/events.d.ts +35 -0
  130. package/dist/types/sdk/host/host.d.ts +35 -0
  131. package/dist/types/sdk/host/index.d.ts +4 -0
  132. package/dist/types/sdk/host/query/cursor.d.ts +62 -0
  133. package/dist/types/sdk/host/query/handlers.d.ts +70 -0
  134. package/dist/types/sdk/host/query/index.d.ts +3 -0
  135. package/dist/types/sdk/host/query/revision-store.d.ts +60 -0
  136. package/dist/types/sdk/host/reverse-leases.d.ts +45 -0
  137. package/dist/types/sdk/host/types.d.ts +32 -0
  138. package/dist/types/sdk/index.d.ts +6 -0
  139. package/dist/types/sdk/lifecycle-session.d.ts +14 -0
  140. package/dist/types/sdk/mcp/index.d.ts +1 -0
  141. package/dist/types/sdk/mcp/server.d.ts +37 -0
  142. package/dist/types/sdk/models.d.ts +44 -0
  143. package/dist/types/sdk/protocol/adapter-validation.d.ts +14 -0
  144. package/dist/types/sdk/protocol/operation-registry.d.ts +24 -0
  145. package/dist/types/{sdk.d.ts → sdk/session.d.ts} +44 -38
  146. package/dist/types/sdk/startup-capability.d.ts +50 -0
  147. package/dist/types/session/agent-session.d.ts +114 -21
  148. package/dist/types/session/default-model-selection.d.ts +25 -0
  149. package/dist/types/session/fallback-chain-controller.d.ts +52 -0
  150. package/dist/types/session/session-manager.d.ts +58 -1
  151. package/dist/types/setup/credential-auto-import.d.ts +36 -6
  152. package/dist/types/tools/ask-answer-registry.d.ts +1 -1
  153. package/dist/types/tools/browser/benchmark/detector-report.d.ts +63 -0
  154. package/dist/types/tools/browser/benchmark/run-suite.d.ts +18 -0
  155. package/dist/types/tools/browser/launch.d.ts +22 -0
  156. package/dist/types/tools/browser/profile-discovery.d.ts +31 -0
  157. package/dist/types/tools/browser/profile-posture.d.ts +37 -0
  158. package/dist/types/tools/browser/profile-reuse.d.ts +39 -0
  159. package/dist/types/tools/browser/profile-warmup.d.ts +39 -0
  160. package/dist/types/tools/browser/registry.d.ts +19 -0
  161. package/dist/types/tools/browser/tab-protocol.d.ts +4 -0
  162. package/dist/types/tools/index.d.ts +8 -8
  163. package/package.json +34 -13
  164. package/scripts/build-sdk-package-smoke.ts +94 -0
  165. package/scripts/capture-notifications-settings-showcase.ts +435 -0
  166. package/scripts/compile-args.ts +4 -2
  167. package/scripts/g005-daemon-smoke.ts +4 -5
  168. package/scripts/g011-daemon-path-smoke.ts +5 -5
  169. package/scripts/generate-sdk-adapter-parity-manifest.ts +163 -0
  170. package/scripts/generate-sdk-operation-inventory.ts +867 -0
  171. package/scripts/generate-telegram-baseline-manifest.ts +128 -0
  172. package/scripts/manifest-schema.ts +124 -0
  173. package/scripts/run-test-manifest.ts +153 -0
  174. package/scripts/run-threat-model-check.ts +157 -0
  175. package/scripts/verify-gjc-sdk-canonicalization.ts +3467 -0
  176. package/scripts/verify-gjc-sdk-rename.ts +90 -0
  177. package/src/append-only-mode.ts +1 -1
  178. package/src/cli/args.ts +43 -12
  179. package/src/cli/auth-gateway-cli.ts +2 -2
  180. package/src/cli/config-cli.ts +2 -1
  181. package/src/cli/daemon-cli.ts +63 -14
  182. package/src/cli/list-models.ts +1 -1
  183. package/src/cli/notify-cli.ts +288 -286
  184. package/src/cli.ts +21 -2
  185. package/src/commands/daemon.ts +50 -6
  186. package/src/commands/harness.ts +93 -36
  187. package/src/commands/launch.ts +5 -2
  188. package/src/commands/mcp-serve.ts +19 -12
  189. package/src/commands/notify.ts +36 -3
  190. package/src/commands/sdk.ts +426 -0
  191. package/src/commit/agentic/agent.ts +1 -0
  192. package/src/config/atomic-yaml-patch.ts +423 -0
  193. package/src/config/model-profile-activation.ts +240 -89
  194. package/src/config/model-profiles.ts +18 -15
  195. package/src/config/model-registry.ts +51 -26
  196. package/src/config/model-resolver.ts +138 -51
  197. package/src/config/model-selector-value.ts +50 -0
  198. package/src/config/models-config-schema.ts +14 -21
  199. package/src/config/settings-schema.ts +128 -10
  200. package/src/config/settings.ts +1120 -158
  201. package/src/coordinator-mcp/model-preset.ts +74 -20
  202. package/src/coordinator-mcp/server.ts +1597 -3052
  203. package/src/daemon/builtin.ts +8 -5
  204. package/src/daemon/control-types.ts +1 -1
  205. package/src/defaults/gjc/skills/team/SKILL.md +4 -4
  206. package/src/discovery/mcp-json.ts +327 -20
  207. package/src/extensibility/custom-tools/types.ts +1 -1
  208. package/src/extensibility/extensions/compact-handler.ts +3 -3
  209. package/src/extensibility/extensions/get-commands-handler.ts +2 -2
  210. package/src/extensibility/extensions/loader.ts +69 -5
  211. package/src/extensibility/extensions/runner.ts +112 -4
  212. package/src/extensibility/extensions/types.ts +176 -17
  213. package/src/extensibility/hooks/types.ts +1 -1
  214. package/src/gjc-runtime/launch-tmux.ts +170 -86
  215. package/src/gjc-runtime/psmux-detect.ts +82 -4
  216. package/src/gjc-runtime/session-state-sidecar.ts +41 -31
  217. package/src/gjc-runtime/team-runtime.ts +54 -35
  218. package/src/gjc-runtime/tmux-owner-isolation-cli.ts +53 -17
  219. package/src/gjc-runtime/tmux-owner-isolation.ts +1 -1
  220. package/src/gjc-runtime/tmux-sessions.ts +19 -13
  221. package/src/gjc-runtime/ultragoal-guard.ts +53 -6
  222. package/src/gjc-runtime/ultragoal-runtime.ts +155 -3
  223. package/src/gjc-runtime/windows-powershell-command.ts +58 -0
  224. package/src/harness-control-plane/operate.ts +9 -9
  225. package/src/harness-control-plane/owner.ts +283 -39
  226. package/src/harness-control-plane/receipts.ts +0 -1
  227. package/src/harness-control-plane/sdk-transport.ts +395 -0
  228. package/src/harness-control-plane/session-transport.ts +94 -0
  229. package/src/harness-control-plane/types.ts +1 -1
  230. package/src/internal-urls/docs-index.generated.ts +34 -35
  231. package/src/lsp/client.ts +42 -6
  232. package/src/lsp/index.ts +2 -2
  233. package/src/main.ts +299 -204
  234. package/src/modes/DESIGN.md +258 -0
  235. package/src/modes/acp/acp-agent.ts +1228 -2612
  236. package/src/modes/acp/acp-client-bridge.ts +2 -20
  237. package/src/modes/acp/acp-event-mapper.ts +18 -2
  238. package/src/modes/acp/acp-mode.ts +8 -36
  239. package/src/modes/acp/permission-mode.ts +25 -0
  240. package/src/modes/acp/startup-options.ts +6 -0
  241. package/src/modes/components/agent-dashboard.ts +37 -17
  242. package/src/modes/components/model-selector.ts +93 -35
  243. package/src/modes/components/notifications-settings-editor.ts +1379 -0
  244. package/src/modes/components/settings-selector.ts +60 -11
  245. package/src/modes/components/welcome.ts +4 -4
  246. package/src/modes/controllers/command-controller.ts +1 -2
  247. package/src/modes/controllers/event-controller.ts +11 -16
  248. package/src/modes/controllers/extension-ui-controller.ts +348 -4
  249. package/src/modes/controllers/selector-controller.ts +674 -36
  250. package/src/modes/index.ts +0 -22
  251. package/src/modes/interactive-mode.ts +59 -20
  252. package/src/modes/runtime-init.ts +298 -6
  253. package/src/modes/shared/agent-wire/approval-gate.ts +9 -11
  254. package/src/modes/shared/agent-wire/deep-interview-gate.ts +10 -11
  255. package/src/modes/shared/agent-wire/event-contract.ts +7 -8
  256. package/src/modes/shared/agent-wire/event-envelope.ts +9 -17
  257. package/src/modes/shared/agent-wire/event-observation.ts +16 -7
  258. package/src/modes/shared/agent-wire/workflow-gate-broker.ts +1254 -87
  259. package/src/modes/shared/agent-wire/workflow-gate-schema.ts +15 -15
  260. package/src/modes/shared/agent-wire/workflow-gate-types.ts +113 -0
  261. package/src/modes/types.ts +5 -0
  262. package/src/runtime-mcp/client.ts +94 -19
  263. package/src/runtime-mcp/config.ts +27 -15
  264. package/src/runtime-mcp/manager.ts +343 -105
  265. package/src/runtime-mcp/oauth-flow.ts +42 -12
  266. package/src/runtime-mcp/transports/http.ts +49 -19
  267. package/src/runtime-mcp/transports/stdio.ts +47 -28
  268. package/src/runtime-mcp/types.ts +10 -0
  269. package/src/sdk/acp/adapter.ts +418 -0
  270. package/src/sdk/acp/index.ts +2 -0
  271. package/src/sdk/broker/broker.ts +642 -0
  272. package/src/sdk/broker/discovery.ts +94 -0
  273. package/src/sdk/broker/ensure.ts +249 -0
  274. package/src/sdk/broker/identity.ts +42 -0
  275. package/src/sdk/broker/index.ts +8 -0
  276. package/src/sdk/broker/internal-source-marker-2178.txt +1 -0
  277. package/src/sdk/broker/internal-source.bunfig.toml +1 -0
  278. package/src/sdk/broker/lifecycle-ledger.ts +286 -0
  279. package/src/sdk/broker/lifecycle.ts +2157 -0
  280. package/src/sdk/broker/process-incarnation.ts +159 -0
  281. package/src/sdk/broker/runtime.ts +164 -0
  282. package/src/sdk/broker/session-index.ts +318 -0
  283. package/src/sdk/broker/state-version.ts +23 -0
  284. package/src/sdk/broker/transport.ts +125 -0
  285. package/src/sdk/bus/chat-command-policy.ts +107 -0
  286. package/src/sdk/bus/chat-daemon-cli.ts +214 -0
  287. package/src/sdk/bus/chat-daemon-control.ts +713 -0
  288. package/src/sdk/bus/chat-daemon-runtime.ts +526 -0
  289. package/src/sdk/bus/chat-effect-journal.ts +353 -0
  290. package/src/{notifications → sdk/bus}/config-commands.ts +39 -10
  291. package/src/sdk/bus/config.ts +433 -0
  292. package/src/sdk/bus/conversation-store.ts +391 -0
  293. package/src/sdk/bus/discord-conversation.ts +85 -0
  294. package/src/sdk/bus/discord-daemon.ts +2275 -0
  295. package/src/sdk/bus/discord-live-provider.ts +521 -0
  296. package/src/sdk/bus/discord-provider.ts +56 -0
  297. package/src/{notifications → sdk/bus}/html-format.ts +2 -2
  298. package/src/sdk/bus/index.ts +3943 -0
  299. package/src/{notifications → sdk/bus}/lifecycle-commands.ts +2 -0
  300. package/src/{notifications → sdk/bus}/lifecycle-control-runtime.ts +225 -196
  301. package/src/{notifications → sdk/bus}/lifecycle-orchestrator.ts +161 -74
  302. package/src/{notifications → sdk/bus}/managed-daemon.ts +2 -2
  303. package/src/sdk/bus/native-runtime-compatibility.ts +42 -0
  304. package/src/sdk/bus/natives-augment.d.ts +38 -0
  305. package/src/sdk/bus/notification-orchestration.ts +535 -0
  306. package/src/{notifications → sdk/bus}/notification-service.ts +106 -12
  307. package/src/sdk/bus/session-control.ts +333 -0
  308. package/src/sdk/bus/slack-conversation.ts +74 -0
  309. package/src/sdk/bus/slack-daemon.ts +2144 -0
  310. package/src/sdk/bus/slack-live-provider.ts +380 -0
  311. package/src/sdk/bus/slack-provider.ts +76 -0
  312. package/src/{notifications → sdk/bus}/telegram-cli.ts +8 -8
  313. package/src/sdk/bus/telegram-daemon-cli.ts +330 -0
  314. package/src/sdk/bus/telegram-daemon-contract.ts +14 -0
  315. package/src/{notifications → sdk/bus}/telegram-daemon-control.ts +4 -4
  316. package/src/{notifications → sdk/bus}/telegram-daemon.ts +502 -54
  317. package/src/{notifications → sdk/bus}/telegram-reference.ts +2 -2
  318. package/src/sdk/bus/telegram-setup.ts +663 -0
  319. package/src/{notifications → sdk/bus}/topic-registry.ts +32 -0
  320. package/src/sdk/cli/index.ts +1 -0
  321. package/src/sdk/cli/session-cli.ts +275 -0
  322. package/src/sdk/client/client.ts +1 -0
  323. package/src/sdk/client/discovery.ts +108 -0
  324. package/src/sdk/client/index.ts +2 -0
  325. package/src/sdk/host/control/dispatch.ts +367 -0
  326. package/src/sdk/host/control/index.ts +10 -0
  327. package/src/sdk/host/control/operations.ts +63 -0
  328. package/src/sdk/host/events.ts +91 -0
  329. package/src/sdk/host/host.ts +296 -0
  330. package/src/sdk/host/index.ts +4 -0
  331. package/src/sdk/host/query/cursor.ts +189 -0
  332. package/src/sdk/host/query/handlers.ts +533 -0
  333. package/src/sdk/host/query/index.ts +3 -0
  334. package/src/sdk/host/query/revision-store.ts +1126 -0
  335. package/src/sdk/host/reverse-leases.ts +325 -0
  336. package/src/sdk/host/types.ts +29 -0
  337. package/src/sdk/index.ts +13 -0
  338. package/src/sdk/lifecycle-session.ts +39 -0
  339. package/src/sdk/mcp/index.ts +1 -0
  340. package/src/sdk/mcp/server.ts +332 -0
  341. package/src/sdk/models.ts +168 -0
  342. package/src/sdk/protocol/adapter-validation.ts +48 -0
  343. package/src/sdk/protocol/operation-inventory.generated.json +5654 -0
  344. package/src/sdk/protocol/operation-registry.ts +312 -0
  345. package/src/{sdk.ts → sdk/session.ts} +339 -212
  346. package/src/sdk/startup-capability.ts +189 -0
  347. package/src/session/agent-session.ts +2453 -894
  348. package/src/session/default-model-selection.ts +93 -0
  349. package/src/session/fallback-chain-controller.ts +136 -0
  350. package/src/session/session-manager.ts +438 -11
  351. package/src/session/session-storage.ts +8 -1
  352. package/src/setup/credential-auto-import.ts +290 -35
  353. package/src/setup/hermes/templates/operator-instructions.v1.md +3 -5
  354. package/src/slash-commands/builtin-registry.ts +2 -1
  355. package/src/task/executor.ts +62 -13
  356. package/src/tools/ask-answer-registry.ts +1 -1
  357. package/src/tools/ask.ts +13 -8
  358. package/src/tools/browser/STEALTH-THIRD-PARTY-NOTICES.md +26 -0
  359. package/src/tools/browser/benchmark/detector-report.ts +157 -0
  360. package/src/tools/browser/benchmark/run-suite.ts +39 -0
  361. package/src/tools/browser/launch.ts +124 -19
  362. package/src/tools/browser/profile-discovery.ts +63 -0
  363. package/src/tools/browser/profile-posture.ts +59 -0
  364. package/src/tools/browser/profile-reuse.ts +102 -0
  365. package/src/tools/browser/profile-warmup.ts +109 -0
  366. package/src/tools/browser/registry.ts +87 -11
  367. package/src/tools/browser/tab-protocol.ts +1 -0
  368. package/src/tools/browser/tab-supervisor.ts +1 -0
  369. package/src/tools/browser/tab-worker.ts +1 -1
  370. package/src/tools/browser.ts +6 -0
  371. package/src/tools/index.ts +8 -8
  372. package/src/tools/puppeteer/14_stealth_permissions.txt +57 -0
  373. package/src/tools/puppeteer/15_stealth_webrtc.txt +60 -0
  374. package/src/tools/telegram-send.ts +2 -2
  375. package/vendor/insane-search/MANIFEST.json +2 -1
  376. package/vendor/insane-search/engine/bias_check.py +4 -1
  377. package/vendor/insane-search/engine/phase0.py +26 -1
  378. package/dist/types/harness-control-plane/rpc-adapter.d.ts +0 -69
  379. package/dist/types/modes/bridge/auth.d.ts +0 -12
  380. package/dist/types/modes/bridge/bridge-client-bridge.d.ts +0 -9
  381. package/dist/types/modes/bridge/bridge-mode.d.ts +0 -47
  382. package/dist/types/modes/bridge/bridge-ui-context.d.ts +0 -88
  383. package/dist/types/modes/bridge/event-stream.d.ts +0 -8
  384. package/dist/types/modes/rpc/host-tools.d.ts +0 -1
  385. package/dist/types/modes/rpc/host-uris.d.ts +0 -1
  386. package/dist/types/modes/rpc/rpc-client.d.ts +0 -299
  387. package/dist/types/modes/rpc/rpc-mode.d.ts +0 -86
  388. package/dist/types/modes/rpc/rpc-socket-security.d.ts +0 -10
  389. package/dist/types/modes/rpc/rpc-types.d.ts +0 -810
  390. package/dist/types/modes/shared/agent-wire/command-contract.d.ts +0 -18
  391. package/dist/types/modes/shared/agent-wire/command-dispatch.d.ts +0 -37
  392. package/dist/types/modes/shared/agent-wire/command-validation.d.ts +0 -2
  393. package/dist/types/modes/shared/agent-wire/handshake.d.ts +0 -56
  394. package/dist/types/modes/shared/agent-wire/host-tool-bridge.d.ts +0 -16
  395. package/dist/types/modes/shared/agent-wire/host-uri-bridge.d.ts +0 -17
  396. package/dist/types/modes/shared/agent-wire/protocol.d.ts +0 -25
  397. package/dist/types/modes/shared/agent-wire/responses.d.ts +0 -4
  398. package/dist/types/modes/shared/agent-wire/scopes.d.ts +0 -18
  399. package/dist/types/modes/shared/agent-wire/session-registry.d.ts +0 -25
  400. package/dist/types/modes/shared/agent-wire/ui-request-broker.d.ts +0 -42
  401. package/dist/types/modes/shared/agent-wire/ui-result.d.ts +0 -27
  402. package/dist/types/modes/shared/agent-wire/unattended-action-policy.d.ts +0 -29
  403. package/dist/types/modes/shared/agent-wire/unattended-audit.d.ts +0 -68
  404. package/dist/types/modes/shared/agent-wire/unattended-run-controller.d.ts +0 -166
  405. package/dist/types/modes/shared/agent-wire/unattended-session.d.ts +0 -127
  406. package/dist/types/notifications/config.d.ts +0 -132
  407. package/src/harness-control-plane/rpc-adapter.ts +0 -290
  408. package/src/modes/bridge/auth.ts +0 -41
  409. package/src/modes/bridge/bridge-client-bridge.ts +0 -47
  410. package/src/modes/bridge/bridge-mode.ts +0 -675
  411. package/src/modes/bridge/bridge-ui-context.ts +0 -200
  412. package/src/modes/bridge/event-stream.ts +0 -70
  413. package/src/modes/rpc/host-tools.ts +0 -1
  414. package/src/modes/rpc/host-uris.ts +0 -1
  415. package/src/modes/rpc/rpc-client.ts +0 -1166
  416. package/src/modes/rpc/rpc-mode.ts +0 -1029
  417. package/src/modes/rpc/rpc-socket-security.ts +0 -115
  418. package/src/modes/rpc/rpc-types.ts +0 -637
  419. package/src/modes/shared/agent-wire/command-contract.ts +0 -18
  420. package/src/modes/shared/agent-wire/command-dispatch.ts +0 -498
  421. package/src/modes/shared/agent-wire/command-validation.ts +0 -181
  422. package/src/modes/shared/agent-wire/handshake.ts +0 -157
  423. package/src/modes/shared/agent-wire/host-tool-bridge.ts +0 -197
  424. package/src/modes/shared/agent-wire/host-uri-bridge.ts +0 -236
  425. package/src/modes/shared/agent-wire/protocol.ts +0 -46
  426. package/src/modes/shared/agent-wire/responses.ts +0 -17
  427. package/src/modes/shared/agent-wire/scopes.ts +0 -94
  428. package/src/modes/shared/agent-wire/session-registry.ts +0 -109
  429. package/src/modes/shared/agent-wire/ui-request-broker.ts +0 -150
  430. package/src/modes/shared/agent-wire/ui-result.ts +0 -48
  431. package/src/modes/shared/agent-wire/unattended-action-policy.ts +0 -365
  432. package/src/modes/shared/agent-wire/unattended-audit.ts +0 -176
  433. package/src/modes/shared/agent-wire/unattended-run-controller.ts +0 -426
  434. package/src/modes/shared/agent-wire/unattended-session.ts +0 -528
  435. package/src/notifications/config.ts +0 -219
  436. package/src/notifications/index.ts +0 -1835
  437. package/src/notifications/telegram-daemon-cli.ts +0 -243
  438. package/src/tools/puppeteer/09_stealth_locale.txt +0 -46
  439. /package/dist/types/{notifications → sdk/bus}/attachment-registry.d.ts +0 -0
  440. /package/dist/types/{notifications → sdk/bus}/chat-adapters.d.ts +0 -0
  441. /package/dist/types/{notifications → sdk/bus}/daemon-paths.d.ts +0 -0
  442. /package/dist/types/{notifications → sdk/bus}/engine.d.ts +0 -0
  443. /package/dist/types/{notifications → sdk/bus}/helpers.d.ts +0 -0
  444. /package/dist/types/{notifications → sdk/bus}/lifecycle-commands.d.ts +0 -0
  445. /package/dist/types/{notifications → sdk/bus}/operator-runtime.d.ts +0 -0
  446. /package/dist/types/{notifications → sdk/bus}/rate-limit-pool.d.ts +0 -0
  447. /package/dist/types/{notifications → sdk/bus}/recent-activity.d.ts +0 -0
  448. /package/dist/types/{notifications → sdk/bus}/reply-sent-store.d.ts +0 -0
  449. /package/dist/types/{notifications → sdk/bus}/rich-draft.d.ts +0 -0
  450. /package/dist/types/{notifications → sdk/bus}/rich-render.d.ts +0 -0
  451. /package/dist/types/{notifications → sdk/bus}/threaded-inbound.d.ts +0 -0
  452. /package/dist/types/{notifications → sdk/bus}/threaded-render.d.ts +0 -0
  453. /package/src/{notifications → sdk/bus}/attachment-registry.ts +0 -0
  454. /package/src/{notifications → sdk/bus}/chat-adapters.ts +0 -0
  455. /package/src/{notifications → sdk/bus}/daemon-paths.ts +0 -0
  456. /package/src/{notifications → sdk/bus}/engine.ts +0 -0
  457. /package/src/{notifications → sdk/bus}/helpers.ts +0 -0
  458. /package/src/{notifications → sdk/bus}/operator-runtime.ts +0 -0
  459. /package/src/{notifications → sdk/bus}/rate-limit-pool.ts +0 -0
  460. /package/src/{notifications → sdk/bus}/recent-activity.ts +0 -0
  461. /package/src/{notifications → sdk/bus}/reply-sent-store.ts +0 -0
  462. /package/src/{notifications → sdk/bus}/rich-draft.ts +0 -0
  463. /package/src/{notifications → sdk/bus}/rich-render.ts +0 -0
  464. /package/src/{notifications → sdk/bus}/threaded-inbound.ts +0 -0
  465. /package/src/{notifications → sdk/bus}/threaded-render.ts +0 -0
@@ -0,0 +1,713 @@
1
+ import { spawn as childProcessSpawn, spawnSync } from "node:child_process";
2
+ import * as crypto from "node:crypto";
3
+ import * as fs from "node:fs";
4
+ import * as path from "node:path";
5
+ import type { Settings } from "../../config/settings";
6
+ import type {
7
+ BuiltInDaemonController,
8
+ DaemonHealth,
9
+ DaemonOperationOptions,
10
+ DaemonOperationResult,
11
+ DaemonRuntimeInfo,
12
+ DaemonStatus,
13
+ } from "../../daemon/control-types";
14
+ import { resolveGjcRuntimeSpawnInfo } from "../../daemon/runtime";
15
+ import { getNotificationConfig, isDiscordConfigured, isSlackConfigured } from "./config";
16
+
17
+ export type ChatDaemonKind = "discord" | "slack";
18
+ export type ChatDaemonAction = "stop" | "reload";
19
+
20
+ export interface ChatDaemonState {
21
+ version: 1;
22
+ kind: ChatDaemonKind;
23
+ pid: number;
24
+ ownerId: string;
25
+ identity: string;
26
+ incarnation: string;
27
+ startedAt: number;
28
+ heartbeatAt: number;
29
+ transportHealthy: boolean;
30
+ stoppedAt?: number;
31
+ }
32
+
33
+ export interface ChatDaemonControlRequest {
34
+ version: 1;
35
+ requestId: string;
36
+ action: ChatDaemonAction;
37
+ ownerId: string;
38
+ pid: number;
39
+ createdAt: number;
40
+ incarnation: string;
41
+ }
42
+
43
+ export interface ChatDaemonControlDeps {
44
+ pidAlive?: (pid: number) => boolean;
45
+ sendSignal?: (pid: number, signal: NodeJS.Signals) => void;
46
+ spawn?: (command: string, args: string[], opts: { detached: boolean; stdio: "ignore" }) => { unref?: () => void };
47
+ execPath?: string;
48
+ ownerPid?: number;
49
+ randomId?: () => string;
50
+ pidIncarnation?: (pid: number) => string | undefined;
51
+ sleep?: (ms: number) => Promise<void>;
52
+ }
53
+
54
+ const HEARTBEAT_TTL_MS = 20_000;
55
+ const DEFAULT_GRACEFUL_TIMEOUT_MS = 8_000;
56
+ const DEFAULT_KILL_TIMEOUT_MS = 3_000;
57
+ const UNPUBLISHED_OWNER_LOCK_STALE_MS = HEARTBEAT_TTL_MS;
58
+
59
+ interface ChatDaemonOwnerLock {
60
+ pid: number;
61
+ incarnation: string;
62
+ createdAt: number;
63
+ }
64
+
65
+ interface ChatDaemonOwnershipProbe {
66
+ pidAlive(pid: number): boolean;
67
+ pidIncarnation(pid: number): string | undefined;
68
+ }
69
+
70
+ export function chatDaemonPaths(
71
+ agentDir: string,
72
+ kind: ChatDaemonKind,
73
+ ): { dir: string; lock: string; state: string; control: string } {
74
+ const dir = path.join(agentDir, "sdk", "daemons", kind);
75
+ return {
76
+ dir,
77
+ lock: path.join(dir, "owner.lock"),
78
+ state: path.join(dir, "state.json"),
79
+ control: path.join(dir, "control.json"),
80
+ };
81
+ }
82
+
83
+ function identityFor(settings: Settings, kind: ChatDaemonKind): string | undefined {
84
+ const cfg = getNotificationConfig(settings);
85
+ if (kind === "discord") {
86
+ if (!isDiscordConfigured(cfg)) return undefined;
87
+ return fingerprint([
88
+ cfg.discord.botToken,
89
+ cfg.discord.applicationId,
90
+ cfg.discord.guildId,
91
+ cfg.discord.parentChannelId,
92
+ String(cfg.redact),
93
+ cfg.verbosity,
94
+ ]);
95
+ }
96
+ if (!isSlackConfigured(cfg)) return undefined;
97
+ return fingerprint([
98
+ cfg.slack.botToken,
99
+ cfg.slack.appToken,
100
+ cfg.slack.workspaceId,
101
+ cfg.slack.channelId,
102
+ cfg.slack.authorizedUserId ?? "",
103
+ String(cfg.redact),
104
+ cfg.verbosity,
105
+ ]);
106
+ }
107
+
108
+ function fingerprint(values: string[]): string {
109
+ return crypto.createHash("sha256").update(values.join("\0")).digest("hex").slice(0, 16);
110
+ }
111
+ function defaultPidAlive(pid: number): boolean {
112
+ try {
113
+ process.kill(pid, 0);
114
+ return true;
115
+ } catch {
116
+ return false;
117
+ }
118
+ }
119
+ function defaultSignal(pid: number, signal: NodeJS.Signals): void {
120
+ try {
121
+ process.kill(pid, signal);
122
+ } catch {}
123
+ }
124
+ function defaultPidIncarnation(pid: number): string | undefined {
125
+ if (!Number.isSafeInteger(pid) || pid <= 0) return undefined;
126
+ if (process.platform === "linux") {
127
+ try {
128
+ const stat = fs.readFileSync(`/proc/${pid}/stat`, "utf8");
129
+ return `linux:${
130
+ stat
131
+ .slice(stat.lastIndexOf(")") + 2)
132
+ .trim()
133
+ .split(/\s+/)[19]
134
+ }`;
135
+ } catch {
136
+ return undefined;
137
+ }
138
+ }
139
+ if (process.platform === "darwin") {
140
+ const result = spawnSync("ps", ["-o", "lstart=", "-p", String(pid)], { encoding: "utf8" });
141
+ const startedAt = result.status === 0 ? result.stdout.trim() : "";
142
+ return startedAt ? `darwin:${startedAt}` : undefined;
143
+ }
144
+ return undefined;
145
+ }
146
+ function runtimeInfo(execPath?: string): DaemonRuntimeInfo {
147
+ const rt = resolveGjcRuntimeSpawnInfo(execPath ?? process.execPath);
148
+ return {
149
+ mode: rt.mode,
150
+ execPath: rt.execPath,
151
+ reloadPicksUpSourceEdits: rt.reloadPicksUpSourceEdits,
152
+ warning: rt.warning,
153
+ };
154
+ }
155
+
156
+ const stateWriteTails = new Map<string, Promise<void>>();
157
+
158
+ async function withStateWriteLock<T>(file: string, operation: () => Promise<T>): Promise<T> {
159
+ const previous = stateWriteTails.get(file) ?? Promise.resolve();
160
+ const gate = Promise.withResolvers<void>();
161
+ const tail = previous.then(() => gate.promise);
162
+ stateWriteTails.set(file, tail);
163
+ await previous;
164
+ try {
165
+ return await operation();
166
+ } finally {
167
+ gate.resolve();
168
+ if (stateWriteTails.get(file) === tail) stateWriteTails.delete(file);
169
+ }
170
+ }
171
+
172
+ async function readJson<T>(file: string): Promise<T | undefined> {
173
+ try {
174
+ return JSON.parse(await fs.promises.readFile(file, "utf8")) as T;
175
+ } catch {
176
+ return undefined;
177
+ }
178
+ }
179
+ async function writeJson(file: string, value: unknown): Promise<void> {
180
+ await fs.promises.mkdir(path.dirname(file), { recursive: true, mode: 0o700 });
181
+ const tmp = `${file}.${process.pid}.${crypto.randomUUID()}.tmp`;
182
+ try {
183
+ await fs.promises.writeFile(tmp, `${JSON.stringify(value)}\n`, { mode: 0o600 });
184
+ await fs.promises.rename(tmp, file);
185
+ } catch (error) {
186
+ await fs.promises.unlink(tmp).catch(() => undefined);
187
+ throw error;
188
+ }
189
+ }
190
+
191
+ export async function readChatDaemonState(
192
+ agentDir: string,
193
+ kind: ChatDaemonKind,
194
+ ): Promise<ChatDaemonState | undefined> {
195
+ return await readJson(chatDaemonPaths(agentDir, kind).state);
196
+ }
197
+ export async function readChatDaemonControlRequest(
198
+ agentDir: string,
199
+ kind: ChatDaemonKind,
200
+ ): Promise<ChatDaemonControlRequest | undefined> {
201
+ return await readJson(chatDaemonPaths(agentDir, kind).control);
202
+ }
203
+ export async function writeChatDaemonControlRequest(
204
+ agentDir: string,
205
+ kind: ChatDaemonKind,
206
+ request: ChatDaemonControlRequest,
207
+ ): Promise<void> {
208
+ await writeJson(chatDaemonPaths(agentDir, kind).control, request);
209
+ }
210
+ export async function clearChatDaemonControlRequest(
211
+ agentDir: string,
212
+ kind: ChatDaemonKind,
213
+ requestId?: string,
214
+ ): Promise<void> {
215
+ const paths = chatDaemonPaths(agentDir, kind);
216
+ if (requestId && (await readChatDaemonControlRequest(agentDir, kind))?.requestId !== requestId) return;
217
+ await fs.promises.unlink(paths.control).catch(() => undefined);
218
+ }
219
+
220
+ export function buildChatDaemonSpawnArgs(input: {
221
+ kind: ChatDaemonKind;
222
+ ownerId: string;
223
+ agentDir: string;
224
+ execPath?: string;
225
+ }): { command: string; args: string[]; runtime: DaemonRuntimeInfo } {
226
+ const rt = resolveGjcRuntimeSpawnInfo(input.execPath ?? process.execPath);
227
+ return {
228
+ command: rt.execPath,
229
+ args: [
230
+ ...rt.argsPrefix,
231
+ "daemon",
232
+ `${input.kind}-internal`,
233
+ "--owner-id",
234
+ input.ownerId,
235
+ "--agent-dir",
236
+ input.agentDir,
237
+ ],
238
+ runtime: runtimeInfo(input.execPath),
239
+ };
240
+ }
241
+
242
+ export class ChatDaemonController implements BuiltInDaemonController {
243
+ readonly kind: ChatDaemonKind;
244
+ constructor(
245
+ private readonly settings: Settings,
246
+ kind: ChatDaemonKind,
247
+ private readonly deps: ChatDaemonControlDeps = {},
248
+ ) {
249
+ this.kind = kind;
250
+ }
251
+ private identity(): string | undefined {
252
+ return identityFor(this.settings, this.kind);
253
+ }
254
+ private alive(pid: number): boolean {
255
+ return (this.deps.pidAlive ?? defaultPidAlive)(pid);
256
+ }
257
+ async status(): Promise<DaemonStatus> {
258
+ const runtime = runtimeInfo(this.deps.execPath);
259
+ const identity = this.identity();
260
+ if (!identity) return { kind: this.kind, configured: false, health: "not_configured", runtime };
261
+ const state = await readChatDaemonState(this.settings.getAgentDir(), this.kind);
262
+ const live = Boolean(
263
+ state &&
264
+ state.version === 1 &&
265
+ state.kind === this.kind &&
266
+ state.identity === identity &&
267
+ !state.stoppedAt &&
268
+ state.transportHealthy &&
269
+ Date.now() - state.heartbeatAt <= HEARTBEAT_TTL_MS &&
270
+ this.alive(state.pid) &&
271
+ this.incarnation(state.pid) === state.incarnation,
272
+ );
273
+ const health: DaemonHealth = live ? "running" : state && !state.stoppedAt ? "stale" : "stopped";
274
+ return {
275
+ kind: this.kind,
276
+ configured: true,
277
+ health,
278
+ pid: state?.pid,
279
+ ownerId: state?.ownerId,
280
+ startedAt: state?.startedAt,
281
+ heartbeatAt: state?.heartbeatAt,
282
+ runtime,
283
+ };
284
+ }
285
+ async stop(opts: DaemonOperationOptions = {}): Promise<DaemonOperationResult> {
286
+ return await this.operate("stop", opts);
287
+ }
288
+ async reload(opts: DaemonOperationOptions = {}): Promise<DaemonOperationResult> {
289
+ return await this.operate("reload", opts);
290
+ }
291
+ async ensure(): Promise<EnsureChatDaemonResult> {
292
+ const identity = this.identity();
293
+ if (!identity) return "disabled";
294
+ const existing = await readChatDaemonState(this.settings.getAgentDir(), this.kind);
295
+ if (existing && this.isLiveState(existing)) {
296
+ if (existing.identity === identity) return "attached";
297
+ await this.stopForReplacement(existing);
298
+ }
299
+ const spawned = await this.spawn();
300
+ if (spawned) return "owner_spawned";
301
+ const replacement = await readChatDaemonState(this.settings.getAgentDir(), this.kind);
302
+ if (replacement && replacement.identity === identity && this.isLiveState(replacement)) return "attached";
303
+ throw new Error(`Unable to attach or spawn ${this.kind} daemon owner`);
304
+ }
305
+
306
+ private async operate(action: ChatDaemonAction, opts: DaemonOperationOptions): Promise<DaemonOperationResult> {
307
+ const before = await this.status();
308
+ const warnings = before.runtime.warning ? [before.runtime.warning] : [];
309
+ if (!before.configured)
310
+ return this.result(action, false, `${this.kind} notifications are not configured`, before, before, warnings);
311
+ if (before.health !== "running") {
312
+ if (action === "stop")
313
+ return this.result(action, true, `no running ${this.kind} daemon`, before, before, warnings);
314
+ if (opts.spawnIfStopped === false)
315
+ return this.result(action, true, `no running ${this.kind} daemon to reload`, before, before, warnings);
316
+ const spawned = await this.spawn();
317
+ const after = await this.status();
318
+ return this.result(
319
+ action,
320
+ spawned,
321
+ spawned ? `spawned fresh ${this.kind} daemon` : `a live ${this.kind} owner already exists`,
322
+ before,
323
+ after,
324
+ warnings,
325
+ );
326
+ }
327
+ const state = await readChatDaemonState(this.settings.getAgentDir(), this.kind);
328
+ if (!state || !this.ownsCapturedState(state, before))
329
+ return this.result(
330
+ action,
331
+ false,
332
+ `${this.kind} daemon ownership changed; refusing to signal`,
333
+ before,
334
+ await this.status(),
335
+ warnings,
336
+ );
337
+ const requestId = this.deps.randomId?.() ?? crypto.randomUUID();
338
+ await writeChatDaemonControlRequest(this.settings.getAgentDir(), this.kind, {
339
+ version: 1,
340
+ requestId,
341
+ action,
342
+ ownerId: state.ownerId,
343
+ pid: state.pid,
344
+ incarnation: state.incarnation,
345
+ createdAt: Date.now(),
346
+ });
347
+ if (!(await this.signalIfOwner(state, "SIGTERM"))) return this.ownerChanged(action, requestId, before, warnings);
348
+ let dead = await this.waitForDeath(state.pid, opts.gracefulTimeoutMs ?? DEFAULT_GRACEFUL_TIMEOUT_MS);
349
+ if (!dead && opts.force) {
350
+ if (!(await this.signalIfOwner(state, "SIGKILL")))
351
+ return this.ownerChanged(action, requestId, before, warnings);
352
+ dead = await this.waitForDeath(state.pid, opts.killTimeoutMs ?? DEFAULT_KILL_TIMEOUT_MS);
353
+ }
354
+ if (!dead) {
355
+ await clearChatDaemonControlRequest(this.settings.getAgentDir(), this.kind, requestId);
356
+ const after = await this.status();
357
+ return this.result(
358
+ action,
359
+ false,
360
+ opts.force ? "old daemon did not exit after SIGKILL" : "old daemon did not exit; rerun with --force",
361
+ before,
362
+ after,
363
+ warnings,
364
+ );
365
+ }
366
+ await clearChatDaemonControlRequest(this.settings.getAgentDir(), this.kind, requestId);
367
+ if (action === "stop")
368
+ return this.result(action, true, `stopped ${this.kind} daemon`, before, await this.status(), warnings);
369
+ const spawned = await this.spawn();
370
+ return this.result(
371
+ action,
372
+ spawned,
373
+ spawned ? `reloaded ${this.kind} daemon` : `a live ${this.kind} owner already exists`,
374
+ before,
375
+ await this.status(),
376
+ warnings,
377
+ );
378
+ }
379
+ private incarnation(pid: number): string | undefined {
380
+ return (this.deps.pidIncarnation ?? defaultPidIncarnation)(pid);
381
+ }
382
+ private isLiveState(state: ChatDaemonState): boolean {
383
+ return (
384
+ state.version === 1 &&
385
+ state.kind === this.kind &&
386
+ !state.stoppedAt &&
387
+ state.transportHealthy &&
388
+ Date.now() - state.heartbeatAt <= HEARTBEAT_TTL_MS &&
389
+ this.alive(state.pid) &&
390
+ Boolean(state.incarnation) &&
391
+ this.incarnation(state.pid) === state.incarnation
392
+ );
393
+ }
394
+ private async stopForReplacement(state: ChatDaemonState): Promise<void> {
395
+ if (!this.isLiveState(state)) return;
396
+ const requestId = this.deps.randomId?.() ?? crypto.randomUUID();
397
+ await writeChatDaemonControlRequest(this.settings.getAgentDir(), this.kind, {
398
+ version: 1,
399
+ requestId,
400
+ action: "reload",
401
+ ownerId: state.ownerId,
402
+ pid: state.pid,
403
+ incarnation: state.incarnation,
404
+ createdAt: Date.now(),
405
+ });
406
+ try {
407
+ if (!(await this.signalIfOwner(state, "SIGTERM")))
408
+ throw new Error(`${this.kind} daemon ownership changed; refusing replacement`);
409
+ let dead = await this.waitForDeath(state.pid, DEFAULT_GRACEFUL_TIMEOUT_MS);
410
+ if (!dead) {
411
+ if (!(await this.signalIfOwner(state, "SIGKILL")))
412
+ throw new Error(`${this.kind} daemon ownership changed; refusing replacement`);
413
+ dead = await this.waitForDeath(state.pid, DEFAULT_KILL_TIMEOUT_MS);
414
+ }
415
+ if (!dead) throw new Error(`Old ${this.kind} daemon did not exit before replacement`);
416
+ } finally {
417
+ await clearChatDaemonControlRequest(this.settings.getAgentDir(), this.kind, requestId);
418
+ }
419
+ }
420
+
421
+ private ownsCapturedState(state: ChatDaemonState, before: DaemonStatus): boolean {
422
+ return (
423
+ state.ownerId === before.ownerId &&
424
+ state.pid === before.pid &&
425
+ Boolean(state.incarnation) &&
426
+ this.incarnation(state.pid) === state.incarnation
427
+ );
428
+ }
429
+ private async signalIfOwner(state: ChatDaemonState, signal: NodeJS.Signals): Promise<boolean> {
430
+ const current = await readChatDaemonState(this.settings.getAgentDir(), this.kind);
431
+ if (
432
+ !current ||
433
+ current.ownerId !== state.ownerId ||
434
+ current.pid !== state.pid ||
435
+ current.incarnation !== state.incarnation ||
436
+ this.incarnation(state.pid) !== state.incarnation
437
+ )
438
+ return false;
439
+ (this.deps.sendSignal ?? defaultSignal)(state.pid, signal);
440
+ return true;
441
+ }
442
+ private async ownerChanged(
443
+ action: ChatDaemonAction,
444
+ requestId: string,
445
+ before: DaemonStatus,
446
+ warnings: string[],
447
+ ): Promise<DaemonOperationResult> {
448
+ await clearChatDaemonControlRequest(this.settings.getAgentDir(), this.kind, requestId);
449
+ return this.result(
450
+ action,
451
+ false,
452
+ `${this.kind} daemon ownership changed; refusing to signal`,
453
+ before,
454
+ await this.status(),
455
+ warnings,
456
+ );
457
+ }
458
+ private result(
459
+ action: ChatDaemonAction,
460
+ ok: boolean,
461
+ message: string,
462
+ before: DaemonStatus,
463
+ after: DaemonStatus,
464
+ warnings: string[],
465
+ ): DaemonOperationResult {
466
+ return { kind: this.kind, action, ok, message, before, after, warnings };
467
+ }
468
+ private async waitForDeath(pid: number, timeout: number): Promise<boolean> {
469
+ const until = Date.now() + timeout;
470
+ while (this.alive(pid) && Date.now() < until) await this.sleep(25);
471
+ return !this.alive(pid);
472
+ }
473
+ private sleep(ms: number): Promise<void> {
474
+ return this.deps.sleep ? this.deps.sleep(ms) : new Promise(resolve => setTimeout(resolve, ms));
475
+ }
476
+ private async spawn(): Promise<boolean> {
477
+ const identity = this.identity();
478
+ if (!identity) return false;
479
+ const paths = chatDaemonPaths(this.settings.getAgentDir(), this.kind);
480
+ await fs.promises.mkdir(paths.dir, { recursive: true, mode: 0o700 });
481
+ const existing = await readChatDaemonState(this.settings.getAgentDir(), this.kind);
482
+ if (
483
+ existing &&
484
+ !existing.stoppedAt &&
485
+ existing.identity === identity &&
486
+ Date.now() - existing.heartbeatAt <= HEARTBEAT_TTL_MS &&
487
+ this.alive(existing.pid) &&
488
+ this.incarnation(existing.pid) === existing.incarnation
489
+ )
490
+ return false;
491
+ const ownerId = `${this.deps.ownerPid ?? process.ppid}-${this.deps.randomId?.() ?? crypto.randomUUID()}`;
492
+ const { command, args } = buildChatDaemonSpawnArgs({
493
+ kind: this.kind,
494
+ ownerId,
495
+ agentDir: this.settings.getAgentDir(),
496
+ execPath: this.deps.execPath,
497
+ });
498
+ (this.deps.spawn ?? ((command, args, opts) => childProcessSpawn(command, args, opts)))(command, args, {
499
+ detached: true,
500
+ stdio: "ignore",
501
+ }).unref?.();
502
+ return true;
503
+ }
504
+ }
505
+
506
+ export type EnsureChatDaemonResult = "disabled" | "owner_spawned" | "attached";
507
+
508
+ async function ensureChatDaemon(
509
+ kind: ChatDaemonKind,
510
+ settings: Settings,
511
+ deps: ChatDaemonControlDeps = {},
512
+ ): Promise<EnsureChatDaemonResult> {
513
+ return await new ChatDaemonController(settings, kind, deps).ensure();
514
+ }
515
+
516
+ export async function ensureDiscordDaemon(
517
+ settings: Settings,
518
+ deps: ChatDaemonControlDeps = {},
519
+ ): Promise<EnsureChatDaemonResult> {
520
+ return await ensureChatDaemon("discord", settings, deps);
521
+ }
522
+
523
+ export async function ensureSlackDaemon(
524
+ settings: Settings,
525
+ deps: ChatDaemonControlDeps = {},
526
+ ): Promise<EnsureChatDaemonResult> {
527
+ return await ensureChatDaemon("slack", settings, deps);
528
+ }
529
+
530
+ export async function acquireChatDaemonOwnership(input: {
531
+ agentDir: string;
532
+ kind: ChatDaemonKind;
533
+ ownerId: string;
534
+ pid?: number;
535
+ identity: string;
536
+ incarnation?: string;
537
+ pidAlive?: (pid: number) => boolean;
538
+ pidIncarnation?: (pid: number) => string | undefined;
539
+ }): Promise<boolean> {
540
+ const paths = chatDaemonPaths(input.agentDir, input.kind);
541
+ const pid = input.pid ?? process.pid;
542
+ const probe: ChatDaemonOwnershipProbe = {
543
+ pidAlive: input.pidAlive ?? defaultPidAlive,
544
+ pidIncarnation: input.pidIncarnation ?? defaultPidIncarnation,
545
+ };
546
+ const incarnation = input.incarnation ?? probe.pidIncarnation(pid) ?? "unavailable";
547
+ await fs.promises.mkdir(paths.dir, { recursive: true, mode: 0o700 });
548
+ if (!(await createChatDaemonOwnerLock(paths.lock, { pid, incarnation, createdAt: Date.now() }))) {
549
+ if (!(await reclaimChatDaemonOwnerLock(paths.lock, paths.state, probe))) return false;
550
+ if (!(await createChatDaemonOwnerLock(paths.lock, { pid, incarnation, createdAt: Date.now() }))) return false;
551
+ }
552
+
553
+ await withStateWriteLock(
554
+ paths.state,
555
+ async () =>
556
+ await writeJson(paths.state, {
557
+ version: 1,
558
+ kind: input.kind,
559
+ pid,
560
+ ownerId: input.ownerId,
561
+ identity: input.identity,
562
+ incarnation,
563
+ startedAt: Date.now(),
564
+ heartbeatAt: Date.now(),
565
+ transportHealthy: false,
566
+ } satisfies ChatDaemonState),
567
+ );
568
+ return true;
569
+ }
570
+
571
+ async function createChatDaemonOwnerLock(lock: string, owner: ChatDaemonOwnerLock): Promise<boolean> {
572
+ try {
573
+ const handle = await fs.promises.open(lock, "wx", 0o600);
574
+ try {
575
+ await handle.writeFile(`${JSON.stringify(owner)}\n`, "utf8");
576
+ await handle.sync();
577
+ } finally {
578
+ await handle.close();
579
+ }
580
+ return true;
581
+ } catch (error) {
582
+ if (isAlreadyExists(error)) return false;
583
+ throw error;
584
+ }
585
+ }
586
+
587
+ async function reclaimChatDaemonOwnerLock(
588
+ lock: string,
589
+ stateFile: string,
590
+ probe: ChatDaemonOwnershipProbe,
591
+ ): Promise<boolean> {
592
+ if (!(await canReclaimChatDaemonOwnerLock(lock, stateFile, probe))) return false;
593
+
594
+ const reclaimFile = `${lock}.reclaim`;
595
+ const reclaimLock = await acquireChatDaemonReclaimLock(reclaimFile, probe);
596
+
597
+ if (!reclaimLock) return false;
598
+ try {
599
+ if (!(await canReclaimChatDaemonOwnerLock(lock, stateFile, probe))) return false;
600
+
601
+ await fs.promises.unlink(lock).catch(() => undefined);
602
+ return true;
603
+ } finally {
604
+ await reclaimLock.close().catch(() => undefined);
605
+ await fs.promises.unlink(reclaimFile).catch(() => undefined);
606
+ }
607
+ }
608
+
609
+ async function acquireChatDaemonReclaimLock(
610
+ reclaimFile: string,
611
+ probe: ChatDaemonOwnershipProbe,
612
+ ): Promise<{ close(): Promise<void> } | undefined> {
613
+ const owner: ChatDaemonOwnerLock = {
614
+ pid: process.pid,
615
+ incarnation: probe.pidIncarnation(process.pid) ?? "unavailable",
616
+ createdAt: Date.now(),
617
+ };
618
+ if (await createChatDaemonOwnerLock(reclaimFile, owner)) return await fs.promises.open(reclaimFile, "r+");
619
+ if (!(await isStaleChatDaemonLock(reclaimFile, probe))) return undefined;
620
+ await fs.promises.unlink(reclaimFile).catch(() => undefined);
621
+ if (!(await createChatDaemonOwnerLock(reclaimFile, owner))) return undefined;
622
+ return await fs.promises.open(reclaimFile, "r+");
623
+ }
624
+
625
+ async function isStaleChatDaemonLock(lock: string, probe: ChatDaemonOwnershipProbe): Promise<boolean> {
626
+ let owner: unknown;
627
+ try {
628
+ owner = JSON.parse(await fs.promises.readFile(lock, "utf8"));
629
+ } catch {}
630
+ if (isChatDaemonOwnerLock(owner)) {
631
+ const currentIncarnation = probe.pidIncarnation(owner.pid);
632
+ return (
633
+ !probe.pidAlive(owner.pid) ||
634
+ (currentIncarnation !== undefined &&
635
+ owner.incarnation !== "unavailable" &&
636
+ currentIncarnation !== owner.incarnation)
637
+ );
638
+ }
639
+ const stat = await fs.promises.stat(lock).catch(() => undefined);
640
+ return Boolean(stat && Date.now() - stat.mtimeMs >= UNPUBLISHED_OWNER_LOCK_STALE_MS);
641
+ }
642
+
643
+ async function canReclaimChatDaemonOwnerLock(
644
+ lock: string,
645
+ stateFile: string,
646
+ probe: ChatDaemonOwnershipProbe,
647
+ ): Promise<boolean> {
648
+ const state = await readJson<ChatDaemonState>(stateFile);
649
+ if (state && probe.pidAlive(state.pid) && probe.pidIncarnation(state.pid) === state.incarnation) return false;
650
+ return await isStaleChatDaemonLock(lock, probe);
651
+ }
652
+
653
+ function isChatDaemonOwnerLock(value: unknown): value is ChatDaemonOwnerLock {
654
+ return (
655
+ typeof value === "object" &&
656
+ value !== null &&
657
+ typeof (value as ChatDaemonOwnerLock).pid === "number" &&
658
+ Number.isSafeInteger((value as ChatDaemonOwnerLock).pid) &&
659
+ (value as ChatDaemonOwnerLock).pid > 0 &&
660
+ typeof (value as ChatDaemonOwnerLock).incarnation === "string" &&
661
+ typeof (value as ChatDaemonOwnerLock).createdAt === "number"
662
+ );
663
+ }
664
+
665
+ function isAlreadyExists(error: unknown): error is NodeJS.ErrnoException {
666
+ return typeof error === "object" && error !== null && (error as NodeJS.ErrnoException).code === "EEXIST";
667
+ }
668
+ export async function renewChatDaemonHeartbeat(input: {
669
+ agentDir: string;
670
+ kind: ChatDaemonKind;
671
+ ownerId: string;
672
+ pid?: number;
673
+ incarnation?: string;
674
+ transportHealthy: boolean;
675
+ }): Promise<boolean> {
676
+ const paths = chatDaemonPaths(input.agentDir, input.kind);
677
+ return await withStateWriteLock(paths.state, async () => {
678
+ const state = await readJson<ChatDaemonState>(paths.state);
679
+ const pid = input.pid ?? state?.pid;
680
+ if (
681
+ !state ||
682
+ state.ownerId !== input.ownerId ||
683
+ pid !== state.pid ||
684
+ !input.incarnation ||
685
+ state.incarnation !== input.incarnation
686
+ )
687
+ return false;
688
+ await writeJson(paths.state, { ...state, heartbeatAt: Date.now(), transportHealthy: input.transportHealthy });
689
+ return true;
690
+ });
691
+ }
692
+ export async function releaseChatDaemonOwnership(input: {
693
+ agentDir: string;
694
+ kind: ChatDaemonKind;
695
+ ownerId: string;
696
+ pid?: number;
697
+ incarnation?: string;
698
+ }): Promise<void> {
699
+ const paths = chatDaemonPaths(input.agentDir, input.kind);
700
+ await withStateWriteLock(paths.state, async () => {
701
+ const state = await readJson<ChatDaemonState>(paths.state);
702
+ if (
703
+ state?.ownerId !== input.ownerId ||
704
+ (input.pid !== undefined && state.pid !== input.pid) ||
705
+ (input.incarnation !== undefined && state.incarnation !== input.incarnation)
706
+ )
707
+ return;
708
+ await writeJson(paths.state, { ...state, stoppedAt: Date.now(), transportHealthy: false });
709
+ const lock = await readJson<ChatDaemonOwnerLock>(paths.lock);
710
+ if (lock?.pid === state.pid && lock.incarnation === state.incarnation)
711
+ await fs.promises.unlink(paths.lock).catch(() => undefined);
712
+ });
713
+ }