@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
@@ -12,9 +12,10 @@
12
12
  */
13
13
  import * as fsPromises from "node:fs/promises";
14
14
  import * as path from "node:path";
15
- import type { Settings } from "../config/settings";
15
+ import type { Settings } from "../../config/settings";
16
16
  import {
17
17
  getNotificationConfig,
18
+ isDiscordConfigured,
18
19
  isGloballyConfigured,
19
20
  isTelegramConfigured,
20
21
  maskToken,
@@ -22,7 +23,7 @@ import {
22
23
  tokenFingerprint,
23
24
  } from "./config";
24
25
  import { type DaemonPaths, daemonPaths, HEARTBEAT_TTL_MS } from "./daemon-paths";
25
- import type { DaemonState } from "./telegram-daemon";
26
+ import { DAEMON_GENERATION } from "./telegram-daemon-contract";
26
27
 
27
28
  const DEFAULT_API_BASE = "https://api.telegram.org";
28
29
 
@@ -139,8 +140,8 @@ export function buildNotificationStatusReport(settings: Settings): NotificationS
139
140
  },
140
141
  discord: {
141
142
  botTokenMasked: maskToken(cfg.discord.botToken),
142
- channel: cfg.discord.channelId,
143
- configured: adapterConfigured(cfg.discord.botToken, cfg.discord.channelId),
143
+ channel: cfg.discord.parentChannelId,
144
+ configured: isDiscordConfigured(cfg),
144
145
  },
145
146
  slack: {
146
147
  botTokenMasked: maskToken(cfg.slack.botToken),
@@ -164,7 +165,7 @@ export function formatNotificationStatusReport(report: NotificationStatusReport)
164
165
  ` telegram.fingerprint: ${report.telegram.tokenFingerprint ?? "(unset)"}`,
165
166
  ` telegram.configured: ${yesNo(report.telegram.configured)}`,
166
167
  ` discord.botToken: ${report.discord.botTokenMasked}`,
167
- ` discord.channelId: ${report.discord.channel ?? "(unset)"}`,
168
+ ` discord.parentChannelId: ${report.discord.channel ?? "(unset)"}`,
168
169
  ` slack.botToken: ${report.slack.botTokenMasked}`,
169
170
  ` slack.channelId: ${report.slack.channel ?? "(unset)"}`,
170
171
  ].join("\n");
@@ -215,7 +216,36 @@ async function readEndpointView(fs: NotificationServiceFs, file: string): Promis
215
216
  };
216
217
  }
217
218
 
218
- function parseDaemonState(raw: string): DaemonState | undefined {
219
+ interface NormalizedDaemonState {
220
+ pid: number;
221
+ ownerId: string;
222
+ tokenFingerprint: string | undefined;
223
+ chatId: string | undefined;
224
+ startedAt: number | undefined;
225
+ heartbeatAt: number | undefined;
226
+ stoppedAt: number | undefined;
227
+ roots: string[] | undefined;
228
+ generation: number | undefined;
229
+ generationStatus: "missing" | "valid" | "invalid";
230
+ }
231
+
232
+ function finiteNonNegativeNumber(value: unknown): number | undefined {
233
+ return typeof value === "number" && Number.isFinite(value) && value >= 0 ? value : undefined;
234
+ }
235
+
236
+ function safePositiveInteger(value: unknown): number | undefined {
237
+ return typeof value === "number" && Number.isSafeInteger(value) && value > 0 ? value : undefined;
238
+ }
239
+
240
+ function safeNonNegativeInteger(value: unknown): number | undefined {
241
+ return typeof value === "number" && Number.isSafeInteger(value) && value >= 0 ? value : undefined;
242
+ }
243
+
244
+ function stringArray(value: unknown): string[] | undefined {
245
+ return Array.isArray(value) && value.every(item => typeof item === "string") ? value : undefined;
246
+ }
247
+
248
+ function parseDaemonState(raw: string): NormalizedDaemonState | undefined {
219
249
  let parsed: unknown;
220
250
  try {
221
251
  parsed = JSON.parse(raw);
@@ -224,11 +254,33 @@ function parseDaemonState(raw: string): DaemonState | undefined {
224
254
  }
225
255
  if (!parsed || typeof parsed !== "object") return undefined;
226
256
  const rec = parsed as Record<string, unknown>;
227
- if (typeof rec.pid !== "number" || typeof rec.ownerId !== "string") return undefined;
228
- return parsed as DaemonState;
257
+ const pid = safePositiveInteger(rec.pid);
258
+ if (pid === undefined || typeof rec.ownerId !== "string" || rec.ownerId.length === 0) return undefined;
259
+
260
+ const generation = safeNonNegativeInteger(rec.generation);
261
+ const generationStatus = !Object.hasOwn(rec, "generation")
262
+ ? "missing"
263
+ : generation === undefined
264
+ ? "invalid"
265
+ : "valid";
266
+ return {
267
+ pid,
268
+ ownerId: rec.ownerId,
269
+ tokenFingerprint: typeof rec.tokenFingerprint === "string" ? rec.tokenFingerprint : undefined,
270
+ chatId: typeof rec.chatId === "string" ? rec.chatId : undefined,
271
+ startedAt: finiteNonNegativeNumber(rec.startedAt),
272
+ heartbeatAt: finiteNonNegativeNumber(rec.heartbeatAt),
273
+ stoppedAt: finiteNonNegativeNumber(rec.stoppedAt),
274
+ roots: stringArray(rec.roots),
275
+ generation,
276
+ generationStatus,
277
+ };
229
278
  }
230
279
 
231
- async function readDaemonStateFile(fs: NotificationServiceFs, file: string): Promise<DaemonState | undefined> {
280
+ async function readDaemonStateFile(
281
+ fs: NotificationServiceFs,
282
+ file: string,
283
+ ): Promise<NormalizedDaemonState | undefined> {
232
284
  try {
233
285
  return parseDaemonState(await fs.readFile(file, "utf8"));
234
286
  } catch {
@@ -262,8 +314,28 @@ export interface DaemonHealth {
262
314
  heartbeatFresh: boolean;
263
315
  identityMatches: boolean;
264
316
  stopped: boolean;
317
+ heartbeatAt: number | undefined;
318
+ heartbeatAgeMs: number | undefined;
319
+ generation: number | undefined;
320
+ currentGeneration: number;
321
+ generationRelation: DaemonGenerationRelation;
322
+ }
323
+
324
+ export type DaemonGenerationRelation = "pre_generation" | "older" | "current" | "newer" | "unknown";
325
+
326
+ function daemonGenerationRelation(state: NormalizedDaemonState | undefined): DaemonGenerationRelation {
327
+ if (!state) return "unknown";
328
+ if (state.generationStatus === "missing") return "pre_generation";
329
+ if (state.generation === undefined) return "unknown";
330
+ if (state.generation < DAEMON_GENERATION) return "older";
331
+ if (state.generation === DAEMON_GENERATION) return "current";
332
+ return "newer";
265
333
  }
266
334
 
335
+ function displayHeartbeatAgeMs(now: number, heartbeatAt: number): number | undefined {
336
+ const age = now - heartbeatAt;
337
+ return Number.isFinite(age) ? Math.max(0, age) : undefined;
338
+ }
267
339
  export interface EndpointHealth {
268
340
  total: number;
269
341
  live: number;
@@ -349,18 +421,24 @@ export async function checkNotificationHealth(opts: HealthOptions): Promise<Noti
349
421
  // Daemon ownership state (offline; read the persisted state file directly).
350
422
  const paths = daemonPaths(opts.settings.getAgentDir());
351
423
  const state = await readDaemonStateFile(fs, paths.state);
424
+ const heartbeatAt = state?.heartbeatAt;
352
425
  const daemon: DaemonHealth = {
353
426
  present: Boolean(state),
354
427
  ownerId: state?.ownerId,
355
428
  pid: state?.pid,
356
429
  alive: state ? pidAlive(state.pid) : false,
357
- heartbeatFresh: state ? now - state.heartbeatAt <= HEARTBEAT_TTL_MS : false,
430
+ heartbeatFresh: heartbeatAt !== undefined ? now - heartbeatAt <= HEARTBEAT_TTL_MS : false,
358
431
  identityMatches:
359
432
  Boolean(state) &&
360
433
  telegramConfigured &&
361
434
  state?.tokenFingerprint === tokenFingerprint(cfg.botToken) &&
362
435
  state?.chatId === cfg.chatId,
363
- stopped: typeof state?.stoppedAt === "number",
436
+ stopped: state?.stoppedAt !== undefined,
437
+ heartbeatAt,
438
+ heartbeatAgeMs: heartbeatAt === undefined ? undefined : displayHeartbeatAgeMs(now, heartbeatAt),
439
+ generation: state?.generation,
440
+ currentGeneration: DAEMON_GENERATION,
441
+ generationRelation: daemonGenerationRelation(state),
364
442
  };
365
443
  if (!state) {
366
444
  checks.push({ name: "daemon", level: "ok", detail: "no daemon ownership record (none running)" });
@@ -421,6 +499,22 @@ export async function checkNotificationHealth(opts: HealthOptions): Promise<Noti
421
499
  detail: `${live} live, ${unknownEndpoints} unverified endpoint file(s)`,
422
500
  });
423
501
  }
502
+ if (
503
+ telegramConfigured &&
504
+ daemon.present &&
505
+ daemon.alive &&
506
+ daemon.heartbeatFresh &&
507
+ daemon.identityMatches &&
508
+ !daemon.stopped &&
509
+ endpoints.total === 0
510
+ ) {
511
+ checks.push({
512
+ name: "local_endpoint",
513
+ level: "warn",
514
+ detail:
515
+ "No local notification endpoint for this working directory. In this GJC terminal run /notify on; if it does not report notifications enabled, start a new local GJC session. Do not re-pair Telegram.",
516
+ });
517
+ }
424
518
 
425
519
  // Optional network reachability probe.
426
520
  let reachability = { probed: false, ok: false, detail: "not probed" };
@@ -566,7 +660,7 @@ async function removeDeadOwnerLock(
566
660
  fs: NotificationServiceFs,
567
661
  paths: DaemonPaths,
568
662
  pidAlive: (pid: number) => boolean,
569
- expected: DaemonState,
663
+ expected: NormalizedDaemonState,
570
664
  ): Promise<"cleared" | "contended" | "superseded" | "now-alive" | "unlink-failed"> {
571
665
  if (!(await fs.createExclusive(paths.steal))) return "contended";
572
666
  try {
@@ -0,0 +1,333 @@
1
+ import {
2
+ getCurrentTelegramActivationMarker,
3
+ isSessionNotificationsEnabled,
4
+ isTelegramConfigured,
5
+ type NotificationConfig,
6
+ } from "./config";
7
+
8
+ /** Minimal session-manager surface shared by extension, TUI, and headless hosts. */
9
+ export interface NotificationSessionContext {
10
+ sessionManager: {
11
+ getCwd(): string;
12
+ getSessionId(): string;
13
+ };
14
+ }
15
+
16
+ /** A snapshot of the current session, resolved from the session manager per operation. */
17
+ export interface BoundNotificationSession<Context extends NotificationSessionContext = NotificationSessionContext> {
18
+ readonly context: Context;
19
+ readonly cwd: string;
20
+ readonly sessionId: string;
21
+ unbind(): void;
22
+ }
23
+
24
+ export type NotificationEndpointStartResult = "started" | "already" | "disabled" | "failed";
25
+ export type TelegramDaemonPreflightResult = "ready" | "blocked_identity";
26
+
27
+ /**
28
+ * The endpoint implementation is deliberately injected. The controller owns
29
+ * policy and session-local state while the extension continues to own its
30
+ * concrete NotificationServer resources.
31
+ */
32
+ export interface NotificationSessionRuntime<Context extends NotificationSessionContext = NotificationSessionContext> {
33
+ isRunning(binding: BoundNotificationSession<Context>): boolean;
34
+ start(binding: BoundNotificationSession<Context>): Promise<NotificationEndpointStartResult>;
35
+ stop(binding: BoundNotificationSession<Context>): Promise<boolean>;
36
+ /**
37
+ * Proves the complete Telegram owner identity before a generic endpoint can
38
+ * emit a frame. `blocked_identity` is fail-closed and starts nothing.
39
+ */
40
+ ensureTelegramDaemon?(binding: BoundNotificationSession<Context>): Promise<TelegramDaemonPreflightResult>;
41
+ }
42
+
43
+ export interface NotificationSessionStatus {
44
+ eligible: boolean;
45
+ locallyEnabled: boolean;
46
+ effectiveEnabled: boolean;
47
+ running: boolean;
48
+ environment: "off" | "explicit" | "token" | "default";
49
+ }
50
+
51
+ export interface NotificationSessionReconcileResult {
52
+ outcome: NotificationEndpointStartResult | "stopped";
53
+ status: NotificationSessionStatus;
54
+ }
55
+
56
+ export interface NotificationSessionControllerOptions {
57
+ /** Gate A result, resolved once by the SDK from the canonical host predicate. */
58
+ eligible: boolean;
59
+ /** Reads the global-only, schema-default-resolved notification configuration. */
60
+ getConfig(): NotificationConfig;
61
+ /** Kept as a reference so test and embedding hosts can supply their own environment. */
62
+ env?: NodeJS.ProcessEnv;
63
+ }
64
+
65
+ /**
66
+ * Shared owner of notification session policy.
67
+ *
68
+ * Gate A is captured at creation. Gate B is evaluated for each session
69
+ * operation with `isSessionNotificationsEnabled`. Gate C verifies complete
70
+ * Telegram ownership before a generic endpoint is allowed to start.
71
+ */
72
+ export class NotificationSessionController {
73
+ readonly #eligible: boolean;
74
+ readonly #getConfig: () => NotificationConfig;
75
+ readonly #env: NodeJS.ProcessEnv;
76
+ readonly #disabledSessions = new Set<string>();
77
+ /** Sessions held inactive after a post-commit foreign daemon identity race. */
78
+ readonly #blockedRuntimeSessions = new Set<string>();
79
+ /** Sessions closed during host shutdown; no queued operation may restart them. */
80
+ readonly #shuttingDownSessions = new Set<string>();
81
+ /** Serializes endpoint mutations for each bound session snapshot. */
82
+ readonly #sessionOperations = new Map<string, Promise<void>>();
83
+ #runtime: NotificationSessionRuntime<NotificationSessionContext> | undefined;
84
+
85
+ constructor(options: NotificationSessionControllerOptions) {
86
+ this.#eligible = options.eligible;
87
+ this.#getConfig = options.getConfig;
88
+ this.#env = options.env ?? process.env;
89
+ }
90
+
91
+ /** Attach the concrete generic endpoint implementation used by this host. */
92
+ attachRuntime<Context extends NotificationSessionContext>(runtime: NotificationSessionRuntime<Context>): () => void {
93
+ const attached = runtime as unknown as NotificationSessionRuntime<NotificationSessionContext>;
94
+ this.#runtime = attached;
95
+ return () => {
96
+ if (this.#runtime === attached) this.#runtime = undefined;
97
+ };
98
+ }
99
+
100
+ /**
101
+ * Bind a fresh session snapshot. Callers should not cache it: cwd and session
102
+ * id may change on `/new`, fork, or resume.
103
+ */
104
+ bind<Context extends NotificationSessionContext>(context: Context): BoundNotificationSession<Context> {
105
+ let bound = true;
106
+ const getCwd = context.sessionManager.getCwd;
107
+ const cwd =
108
+ typeof getCwd === "function" ? getCwd.call(context.sessionManager) : (context as { cwd?: unknown }).cwd;
109
+ if (typeof cwd !== "string" || cwd.length === 0) {
110
+ throw new Error("Notification session context does not expose a cwd.");
111
+ }
112
+ return {
113
+ cwd,
114
+ sessionId: context.sessionManager.getSessionId(),
115
+ unbind: () => {
116
+ bound = false;
117
+ },
118
+ get context() {
119
+ if (!bound) throw new Error("Notification session binding has been released.");
120
+ return context;
121
+ },
122
+ };
123
+ }
124
+
125
+ /** Preserve session-local safety state and pending-operation ownership across a session rekey. */
126
+ rekeySession(previousSessionId: string, nextSessionId: string): void {
127
+ if (previousSessionId === nextSessionId) return;
128
+ if (this.#disabledSessions.delete(previousSessionId)) this.#disabledSessions.add(nextSessionId);
129
+ if (this.#blockedRuntimeSessions.delete(previousSessionId)) this.#blockedRuntimeSessions.add(nextSessionId);
130
+ if (this.#shuttingDownSessions.delete(previousSessionId)) this.#shuttingDownSessions.add(nextSessionId);
131
+
132
+ const previousOperation = this.#sessionOperations.get(previousSessionId);
133
+ if (!previousOperation) return;
134
+ const nextOperation = this.#sessionOperations.get(nextSessionId);
135
+ const completion = nextOperation ? previousOperation.then(() => nextOperation) : previousOperation;
136
+ this.#sessionOperations.delete(previousSessionId);
137
+ this.#sessionOperations.set(nextSessionId, completion);
138
+ void completion.then(() => {
139
+ if (this.#sessionOperations.get(nextSessionId) === completion) {
140
+ this.#sessionOperations.delete(nextSessionId);
141
+ }
142
+ });
143
+ }
144
+
145
+ query<Context extends NotificationSessionContext>(context: Context): NotificationSessionStatus {
146
+ const binding = this.bind(context);
147
+ try {
148
+ return this.#query(binding);
149
+ } finally {
150
+ binding.unbind();
151
+ }
152
+ }
153
+
154
+ /** Stop the current endpoint during host shutdown without changing local preference. */
155
+ async stopCurrentSession<Context extends NotificationSessionContext>(context: Context): Promise<boolean> {
156
+ const binding = this.bind(context);
157
+ try {
158
+ // Set this before joining the queue so an in-flight start self-stops before
159
+ // shutdown resolves, and no later operation can restart this session.
160
+ this.#shuttingDownSessions.add(binding.sessionId);
161
+ return await this.#enqueue(binding, async () => {
162
+ const runtime = this.#runtime as NotificationSessionRuntime<Context> | undefined;
163
+ return runtime?.isRunning(binding) ? await runtime.stop(binding) : false;
164
+ });
165
+ } finally {
166
+ binding.unbind();
167
+ }
168
+ }
169
+
170
+ /**
171
+ * Hold this session's endpoint inactive after a foreign-daemon identity race.
172
+ * The block remains until an explicit same-identity reconnect or CAS restore clears it.
173
+ */
174
+ async enterBlockedRuntime<Context extends NotificationSessionContext>(context: Context): Promise<boolean> {
175
+ const binding = this.bind(context);
176
+ try {
177
+ return await this.#enqueue(binding, async () => {
178
+ const runtime = this.#runtime as NotificationSessionRuntime<Context> | undefined;
179
+ const stopped = runtime?.isRunning(binding) ? await runtime.stop(binding) : false;
180
+ this.#blockedRuntimeSessions.add(binding.sessionId);
181
+ return stopped;
182
+ });
183
+ } finally {
184
+ binding.unbind();
185
+ }
186
+ }
187
+
188
+ /** Clear a block only after the caller has verified a safe same-identity reconnect or restore. */
189
+ async clearBlockedRuntime<Context extends NotificationSessionContext>(context: Context): Promise<void> {
190
+ const binding = this.bind(context);
191
+ try {
192
+ await this.#enqueue(binding, async () => {
193
+ this.#blockedRuntimeSessions.delete(binding.sessionId);
194
+ });
195
+ } finally {
196
+ binding.unbind();
197
+ }
198
+ }
199
+
200
+ async setLocalEnabled<Context extends NotificationSessionContext>(
201
+ context: Context,
202
+ enabled: boolean,
203
+ ): Promise<NotificationSessionReconcileResult> {
204
+ const binding = this.bind(context);
205
+ try {
206
+ return await this.#enqueue(binding, async () => {
207
+ if (enabled) {
208
+ this.#disabledSessions.delete(binding.sessionId);
209
+ this.#shuttingDownSessions.delete(binding.sessionId);
210
+ } else {
211
+ this.#disabledSessions.add(binding.sessionId);
212
+ }
213
+ return await this.#reconcile(binding);
214
+ });
215
+ } finally {
216
+ binding.unbind();
217
+ }
218
+ }
219
+
220
+ async reconcileCurrentSession<Context extends NotificationSessionContext>(
221
+ context: Context,
222
+ ): Promise<NotificationSessionReconcileResult> {
223
+ const binding = this.bind(context);
224
+ try {
225
+ return await this.#enqueue(binding, () => this.#reconcile(binding));
226
+ } finally {
227
+ binding.unbind();
228
+ }
229
+ }
230
+
231
+ async #reconcile<Context extends NotificationSessionContext>(
232
+ binding: BoundNotificationSession<Context>,
233
+ ): Promise<NotificationSessionReconcileResult> {
234
+ const cfg = this.#getConfig();
235
+ const runtime = this.#runtime as NotificationSessionRuntime<Context> | undefined;
236
+ const status = this.#status(binding, cfg, runtime);
237
+ if (!status.effectiveEnabled) {
238
+ if (runtime && status.running) await runtime.stop(binding);
239
+ return { outcome: status.running ? "stopped" : "disabled", status: this.#status(binding, cfg, runtime) };
240
+ }
241
+
242
+ if (!runtime) return { outcome: "disabled", status };
243
+ if (getCurrentTelegramActivationMarker(cfg)) {
244
+ if (runtime.isRunning(binding)) await runtime.stop(binding);
245
+ this.#blockedRuntimeSessions.add(binding.sessionId);
246
+ return { outcome: "disabled", status: this.#status(binding, cfg, runtime) };
247
+ }
248
+ if (isTelegramConfigured(cfg)) {
249
+ try {
250
+ const ensured = await runtime.ensureTelegramDaemon?.(binding);
251
+ if (ensured !== "ready") {
252
+ if (runtime.isRunning(binding)) await runtime.stop(binding);
253
+ this.#blockedRuntimeSessions.add(binding.sessionId);
254
+ return { outcome: "disabled", status: this.#status(binding, cfg, runtime) };
255
+ }
256
+ } catch {
257
+ if (runtime.isRunning(binding)) await runtime.stop(binding);
258
+ this.#blockedRuntimeSessions.add(binding.sessionId);
259
+ return { outcome: "failed", status: this.#status(binding, cfg, runtime) };
260
+ }
261
+ }
262
+
263
+ const current = this.#status(binding, cfg, runtime);
264
+ if (!current.effectiveEnabled) {
265
+ if (current.running) await runtime.stop(binding);
266
+ return { outcome: current.running ? "stopped" : "disabled", status: this.#status(binding, cfg, runtime) };
267
+ }
268
+ const outcome = current.running ? "already" : await runtime.start(binding);
269
+ const afterStart = this.#status(binding, cfg, runtime);
270
+ if (!afterStart.effectiveEnabled) {
271
+ if (afterStart.running) await runtime.stop(binding);
272
+ return { outcome: afterStart.running ? "stopped" : "disabled", status: this.#status(binding, cfg, runtime) };
273
+ }
274
+ return { outcome, status: this.#status(binding, cfg, runtime) };
275
+ }
276
+
277
+ #enqueue<Context extends NotificationSessionContext, Result>(
278
+ binding: BoundNotificationSession<Context>,
279
+ operation: () => Promise<Result>,
280
+ ): Promise<Result> {
281
+ const previous = this.#sessionOperations.get(binding.sessionId) ?? Promise.resolve();
282
+ const result = previous.then(operation, operation);
283
+ const completion = result.then(
284
+ () => undefined,
285
+ () => undefined,
286
+ );
287
+ this.#sessionOperations.set(binding.sessionId, completion);
288
+ void completion.then(() => {
289
+ if (this.#sessionOperations.get(binding.sessionId) === completion) {
290
+ this.#sessionOperations.delete(binding.sessionId);
291
+ }
292
+ });
293
+ return result;
294
+ }
295
+
296
+ #query<Context extends NotificationSessionContext>(
297
+ binding: BoundNotificationSession<Context>,
298
+ ): NotificationSessionStatus {
299
+ const cfg = this.#getConfig();
300
+ return this.#status(binding, cfg, this.#runtime as NotificationSessionRuntime<Context> | undefined);
301
+ }
302
+
303
+ #status<Context extends NotificationSessionContext>(
304
+ binding: BoundNotificationSession<Context>,
305
+ cfg: NotificationConfig,
306
+ runtime: NotificationSessionRuntime<Context> | undefined,
307
+ ): NotificationSessionStatus {
308
+ const locallyEnabled = !this.#disabledSessions.has(binding.sessionId);
309
+ const blockedRuntime = this.#blockedRuntimeSessions.has(binding.sessionId);
310
+ const shuttingDown = this.#shuttingDownSessions.has(binding.sessionId);
311
+ const effectiveEnabled =
312
+ !blockedRuntime &&
313
+ !shuttingDown &&
314
+ !getCurrentTelegramActivationMarker(cfg) &&
315
+ this.#eligible &&
316
+ isSessionNotificationsEnabled({ cfg, env: this.#env, sessionDisabled: !locallyEnabled });
317
+ const environment =
318
+ this.#env.GJC_NOTIFICATIONS === "0"
319
+ ? "off"
320
+ : this.#env.GJC_NOTIFICATIONS === "1"
321
+ ? "explicit"
322
+ : this.#env.GJC_NOTIFICATIONS_TOKEN
323
+ ? "token"
324
+ : "default";
325
+ return {
326
+ eligible: this.#eligible,
327
+ locallyEnabled,
328
+ effectiveEnabled,
329
+ running: runtime?.isRunning(binding) ?? false,
330
+ environment,
331
+ };
332
+ }
333
+ }
@@ -0,0 +1,74 @@
1
+ import { boundedDedupe, type ConversationRecord } from "./conversation-store";
2
+
3
+ export interface SlackInboundDispatchReceipt {
4
+ key: string;
5
+ eventId: string;
6
+ interactionId: string;
7
+ retryKey: string;
8
+ eventContext?: string;
9
+ kind: "action" | "command";
10
+ actionId?: string;
11
+ /** The endpoint generation and SDK effect captured before Socket Mode ACK. */
12
+ endpointGeneration: number;
13
+ /** Identifier of the protected journal payload; mappings never retain message bodies. */
14
+ effectId: string;
15
+ idempotencyKey: string;
16
+ }
17
+
18
+ export type SlackConversationState = "absent" | "posting_root" | "active" | "closed_marker" | "resumed_root" | "error";
19
+
20
+ /** Durable Slack mapping. It contains identifiers and receipt metadata only; protected journal effects own message bodies. */
21
+ export interface SlackConversation extends ConversationRecord {
22
+ state: SlackConversationState;
23
+ teamId: string;
24
+ channelId: string;
25
+ rootTs?: string;
26
+ sessionId?: string;
27
+ endpointGeneration?: number;
28
+ clientMsgId?: string;
29
+ /** Exclusive, expiring authority for provider reconciliation/publication of the root intent. */
30
+ rootPublicationOwner?: string;
31
+ rootPublicationLeaseExpiresAt?: number;
32
+ /** Monotonic fencing token; stale root publishers may not commit after takeover. */
33
+ rootPublicationFence?: number;
34
+
35
+ /** Exclusive, expiring authority for provider reconciliation/publication of the outbound action intent. */
36
+ outboundActionOwner?: string;
37
+ outboundActionLeaseExpiresAt?: number;
38
+ /** Monotonic fencing token; stale action publishers may not commit after takeover. */
39
+ outboundActionFence?: number;
40
+ /** Action authority only; action bodies and endpoint credentials are never persisted. */
41
+ pendingActionId?: string;
42
+ /** Durable, unpublished action intent. The action body is supplied only by the caller. */
43
+ outboundActionId?: string;
44
+ outboundActionClientMsgId?: string;
45
+ lastError?: string;
46
+ updatedAt: number;
47
+ seenEventIds: string[];
48
+ seenContextIds: string[];
49
+ seenRetryKeys: string[];
50
+ seenInteractionIds: string[];
51
+ /** Durable inbound effect identifiers. The protected journal owns replay payloads. */
52
+ inboundDispatches?: SlackInboundDispatchReceipt[];
53
+ }
54
+
55
+ export function slackConversationKey(input: { teamId: string; channelId: string; rootTs: string }): string {
56
+ return `${input.teamId}:${input.channelId}:${input.rootTs}`;
57
+ }
58
+
59
+ export function normalizeSlackConversation(record: SlackConversation): SlackConversation {
60
+ const inboundDispatches = record.inboundDispatches ?? [];
61
+ return {
62
+ ...record,
63
+ seenEventIds: boundedDedupe(record.seenEventIds),
64
+ seenContextIds: boundedDedupe(record.seenContextIds),
65
+ seenRetryKeys: boundedDedupe(record.seenRetryKeys),
66
+ seenInteractionIds: boundedDedupe(record.seenInteractionIds),
67
+ // Replay lifecycle and terminal retention are journal-owned; mappings retain identifiers.
68
+ inboundDispatches,
69
+ };
70
+ }
71
+
72
+ export function acceptsSlackInbound(record: SlackConversation, rootTs: string, endpointGeneration: number): boolean {
73
+ return record.state === "active" && record.rootTs === rootTs && record.endpointGeneration === endpointGeneration;
74
+ }