@sayknow-cli/coding-agent 0.4.7 → 0.5.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 (428) hide show
  1. package/CHANGELOG.md +122 -0
  2. package/dist/types/capability/index.d.ts +10 -20
  3. package/dist/types/capability/types.d.ts +3 -0
  4. package/dist/types/cli/args.d.ts +0 -2
  5. package/dist/types/cli/read-cli.d.ts +1 -0
  6. package/dist/types/cli.d.ts +9 -1
  7. package/dist/types/commands/daemon.d.ts +2 -2
  8. package/dist/types/commands/deep-interview.d.ts +9 -0
  9. package/dist/types/commands/harness.d.ts +22 -0
  10. package/dist/types/commands/read.d.ts +6 -0
  11. package/dist/types/commands/sdk.d.ts +20 -2
  12. package/dist/types/config/keybindings.d.ts +34 -4
  13. package/dist/types/config/model-profile-contract.d.ts +40 -0
  14. package/dist/types/config/model-profiles.d.ts +2 -3
  15. package/dist/types/config/model-registry.d.ts +5 -0
  16. package/dist/types/config/provider-auth-health.d.ts +14 -0
  17. package/dist/types/config/provider-ranking.d.ts +59 -0
  18. package/dist/types/config/settings-schema.d.ts +156 -10
  19. package/dist/types/config/settings.d.ts +3 -0
  20. package/dist/types/edit/modes/patch.d.ts +13 -0
  21. package/dist/types/edit/path-mutation-lock.d.ts +15 -0
  22. package/dist/types/eval/py/executor.d.ts +1 -1
  23. package/dist/types/eval/py/runner-artifact.d.ts +8 -0
  24. package/dist/types/eval/py/tool-bridge.d.ts +1 -2
  25. package/dist/types/extensibility/extensions/types.d.ts +43 -2
  26. package/dist/types/extensibility/shared-events.d.ts +6 -0
  27. package/dist/types/internal-urls/local-root-gc.d.ts +2 -0
  28. package/dist/types/main.d.ts +33 -2
  29. package/dist/types/memories/index.d.ts +2 -0
  30. package/dist/types/modes/acp/acp-agent.d.ts +22 -9
  31. package/dist/types/modes/components/assistant-message.d.ts +3 -1
  32. package/dist/types/modes/components/btw-panel.d.ts +1 -0
  33. package/dist/types/modes/components/irc-sidebar.d.ts +41 -0
  34. package/dist/types/modes/components/queue-pane.d.ts +8 -0
  35. package/dist/types/modes/components/queued-message-selector.d.ts +6 -0
  36. package/dist/types/modes/components/read-tool-group.d.ts +4 -4
  37. package/dist/types/modes/components/runtime-mcp-add-wizard.d.ts +2 -1
  38. package/dist/types/modes/components/session-observer-overlay.d.ts +4 -0
  39. package/dist/types/modes/components/settings-selector.d.ts +2 -0
  40. package/dist/types/modes/components/tool-execution.d.ts +8 -0
  41. package/dist/types/modes/components/tool-status-header.d.ts +4 -2
  42. package/dist/types/modes/components/welcome.d.ts +5 -0
  43. package/dist/types/modes/controllers/btw-controller.d.ts +5 -0
  44. package/dist/types/modes/controllers/command-controller.d.ts +2 -0
  45. package/dist/types/modes/controllers/event-controller.d.ts +3 -0
  46. package/dist/types/modes/interactive-mode.d.ts +13 -2
  47. package/dist/types/modes/irc-observation-ledger.d.ts +17 -1
  48. package/dist/types/modes/prompt-action-autocomplete.d.ts +6 -1
  49. package/dist/types/modes/prompt-suggestion-controller.d.ts +35 -0
  50. package/dist/types/modes/shared/agent-wire/workflow-gate-broker.d.ts +2 -2
  51. package/dist/types/modes/types.d.ts +6 -0
  52. package/dist/types/modes/utils/hotkeys-markdown.d.ts +2 -1
  53. package/dist/types/modes/utils/ui-helpers.d.ts +16 -0
  54. package/dist/types/runtime/memory-domain.d.ts +9 -0
  55. package/dist/types/runtime/memory-guard-contract.d.ts +71 -0
  56. package/dist/types/runtime/memory-guard.d.ts +39 -0
  57. package/dist/types/runtime/memory-limit.d.ts +11 -0
  58. package/dist/types/runtime-mcp/content-limits.d.ts +7 -0
  59. package/dist/types/runtime-mcp/discoverable-tool-metadata.d.ts +1 -1
  60. package/dist/types/runtime-mcp/manager.d.ts +9 -1
  61. package/dist/types/runtime-mcp/oauth-flow.d.ts +0 -5
  62. package/dist/types/runtime-mcp/plugin-network-boundary.d.ts +14 -0
  63. package/dist/types/runtime-mcp/redaction.d.ts +2 -0
  64. package/dist/types/runtime-mcp/smithery-auth.d.ts +5 -0
  65. package/dist/types/sdk/acp/final-text.d.ts +19 -0
  66. package/dist/types/sdk/acp/mcp.d.ts +23 -0
  67. package/dist/types/sdk/broker/broker.d.ts +2 -0
  68. package/dist/types/sdk/broker/lifecycle.d.ts +13 -0
  69. package/dist/types/sdk/bus/chat-daemon-control.d.ts +9 -1
  70. package/dist/types/sdk/bus/config-commands.d.ts +2 -2
  71. package/dist/types/sdk/bus/config.d.ts +2 -0
  72. package/dist/types/sdk/bus/control-drain-lease.d.ts +52 -0
  73. package/dist/types/sdk/bus/conversation-store.d.ts +1 -0
  74. package/dist/types/sdk/bus/daemon-paths.d.ts +1 -0
  75. package/dist/types/sdk/bus/index.d.ts +52 -4
  76. package/dist/types/sdk/bus/kind-aware-reconciliation.d.ts +37 -0
  77. package/dist/types/sdk/bus/lifecycle-commands.d.ts +5 -4
  78. package/dist/types/sdk/bus/notification-orchestration.d.ts +1 -0
  79. package/dist/types/sdk/bus/notification-service.d.ts +34 -0
  80. package/dist/types/sdk/bus/operator-runtime.d.ts +1 -0
  81. package/dist/types/sdk/bus/prompt-reconciliation.d.ts +106 -0
  82. package/dist/types/sdk/bus/rate-limit-pool.d.ts +2 -0
  83. package/dist/types/sdk/bus/reconciliation-store.d.ts +65 -0
  84. package/dist/types/sdk/bus/telegram-daemon-contract.d.ts +33 -9
  85. package/dist/types/sdk/bus/telegram-daemon-control.d.ts +5 -1
  86. package/dist/types/sdk/bus/telegram-daemon.d.ts +286 -12
  87. package/dist/types/sdk/bus/telegram-reference.d.ts +2 -0
  88. package/dist/types/sdk/bus/topic-registry.d.ts +88 -2
  89. package/dist/types/sdk/client/discovery.d.ts +2 -0
  90. package/dist/types/sdk/client/liveness.d.ts +7 -0
  91. package/dist/types/sdk/host/control/operations.d.ts +3 -2
  92. package/dist/types/sdk/host/host.d.ts +3 -1
  93. package/dist/types/sdk/host/query/handlers.d.ts +14 -0
  94. package/dist/types/sdk/index.d.ts +2 -0
  95. package/dist/types/sdk/lifecycle-session.d.ts +10 -1
  96. package/dist/types/sdk/prompt-status.d.ts +88 -0
  97. package/dist/types/sdk/session-directory.d.ts +1 -1
  98. package/dist/types/sdk/session.d.ts +7 -0
  99. package/dist/types/sdk/startup-capability.d.ts +9 -0
  100. package/dist/types/sdk/transport/auth-preface.d.ts +9 -0
  101. package/dist/types/sdk/transport/index.d.ts +12 -0
  102. package/dist/types/sdk/transport/relay.d.ts +43 -0
  103. package/dist/types/sdk/transport/serve-cli.d.ts +4 -0
  104. package/dist/types/sdk/transport/socket.d.ts +7 -0
  105. package/dist/types/sdk/transport/stdio.d.ts +3 -0
  106. package/dist/types/session/agent-session.d.ts +83 -24
  107. package/dist/types/session/btw-contract.d.ts +16 -0
  108. package/dist/types/session/fallback-chain-controller.d.ts +18 -2
  109. package/dist/types/session/internal/managed-session-scope.d.ts +70 -10
  110. package/dist/types/session/internal/managed-session-storage.d.ts +34 -12
  111. package/dist/types/session/internal/native-publish-outcome.d.ts +48 -0
  112. package/dist/types/session/memory-guard-checkpoint-participant.d.ts +69 -0
  113. package/dist/types/session/session-manager.d.ts +89 -18
  114. package/dist/types/session/session-storage.d.ts +31 -0
  115. package/dist/types/session/streaming-output.d.ts +60 -5
  116. package/dist/types/setup/credential-import.d.ts +5 -1
  117. package/dist/types/setup/provider-onboarding.d.ts +2 -0
  118. package/dist/types/skc-runtime/deep-interview-runtime.d.ts +1 -1
  119. package/dist/types/skc-runtime/deep-interview-stage.d.ts +37 -0
  120. package/dist/types/skc-runtime/gc-runtime.d.ts +1 -1
  121. package/dist/types/skc-runtime/linux-proc.d.ts +22 -14
  122. package/dist/types/skc-runtime/memory-guard-owner-claims.d.ts +47 -0
  123. package/dist/types/skc-runtime/ralplan-runtime.d.ts +112 -1
  124. package/dist/types/skc-runtime/repository-binding.d.ts +63 -0
  125. package/dist/types/skc-runtime/state-runtime.d.ts +3 -1
  126. package/dist/types/skc-runtime/state-writer.d.ts +5 -1
  127. package/dist/types/skc-runtime/team-launch.d.ts +1 -1
  128. package/dist/types/skc-runtime/team-runtime.d.ts +9 -2
  129. package/dist/types/skc-runtime/team-store.d.ts +12 -0
  130. package/dist/types/skc-runtime/team-worker-memory-guard.d.ts +92 -0
  131. package/dist/types/skc-runtime/tmux-owner-isolation.d.ts +7 -0
  132. package/dist/types/skc-runtime/ultragoal-guard.d.ts +6 -5
  133. package/dist/types/skc-runtime/ultragoal-receipt-freshness.d.ts +28 -0
  134. package/dist/types/skc-runtime/ultragoal-runtime.d.ts +43 -5
  135. package/dist/types/skill-state/workflow-hud.d.ts +3 -0
  136. package/dist/types/ssh/utils.d.ts +1 -0
  137. package/dist/types/task/discovery.d.ts +3 -1
  138. package/dist/types/task/executor.d.ts +6 -0
  139. package/dist/types/task/index.d.ts +3 -1
  140. package/dist/types/task/provider-retry-status.d.ts +13 -0
  141. package/dist/types/task/receipt.d.ts +2 -0
  142. package/dist/types/task/types.d.ts +133 -0
  143. package/dist/types/task/ultragoal-redteam-activation.d.ts +33 -0
  144. package/dist/types/tools/browser/launch.d.ts +7 -0
  145. package/dist/types/tools/fetch.d.ts +22 -9
  146. package/dist/types/tools/image-gen.d.ts +63 -1
  147. package/dist/types/tools/index.d.ts +7 -1
  148. package/dist/types/tools/output-meta.d.ts +21 -1
  149. package/dist/types/tools/read-internals.d.ts +12 -0
  150. package/dist/types/tools/read.d.ts +45 -1
  151. package/dist/types/tools/resource-gc.d.ts +23 -0
  152. package/dist/types/tools/skill-discovery.d.ts +7 -0
  153. package/dist/types/tools/sqlite-reader.d.ts +7 -0
  154. package/dist/types/tools/tool-result.d.ts +7 -1
  155. package/dist/types/utils/prompt-suggestion.d.ts +33 -0
  156. package/dist/types/utils/shell-snapshot.d.ts +12 -0
  157. package/dist/types/web/insane/bridge.d.ts +1 -0
  158. package/dist/types/web/insane/url-guard.d.ts +17 -0
  159. package/package.json +8 -12
  160. package/scripts/benchmark-sticky-viewport-pr1.ts +239 -0
  161. package/scripts/capture-platform-shortcut-labels-showcase.ts +342 -0
  162. package/scripts/capture-sticky-viewport-showcase.ts +292 -0
  163. package/scripts/compile-args.ts +0 -1
  164. package/scripts/dogfood-repository-binding.ts +209 -0
  165. package/scripts/generate-docs-index.ts +4 -1
  166. package/scripts/generate-sdk-operation-inventory.ts +9 -0
  167. package/scripts/run-test-manifest.ts +6 -4
  168. package/scripts/verify-skc-sdk-canonicalization.ts +122 -18
  169. package/scripts/verify-sticky-viewport-showcase.ts +611 -0
  170. package/src/capability/index.ts +58 -42
  171. package/src/capability/types.ts +3 -0
  172. package/src/cli/args.ts +16 -100
  173. package/src/cli/fast-help.ts +1 -1
  174. package/src/cli/mcp-cli.ts +4 -21
  175. package/src/cli/notify-cli.ts +6 -38
  176. package/src/cli/read-cli.ts +2 -1
  177. package/src/cli/setup-cli.ts +10 -4
  178. package/src/cli.ts +133 -11
  179. package/src/commands/daemon.ts +22 -5
  180. package/src/commands/deep-interview.ts +26 -1
  181. package/src/commands/harness.ts +35 -12
  182. package/src/commands/read.ts +11 -2
  183. package/src/commands/sdk.ts +134 -17
  184. package/src/commands/team.ts +1 -1
  185. package/src/commit/agentic/index.ts +3 -3
  186. package/src/commit/map-reduce/index.ts +2 -2
  187. package/src/config/keybindings.ts +158 -25
  188. package/src/config/model-profile-contract.ts +174 -0
  189. package/src/config/model-profiles.ts +51 -10
  190. package/src/config/model-registry.ts +24 -4
  191. package/src/config/model-resolver.ts +2 -2
  192. package/src/config/provider-auth-health.ts +42 -0
  193. package/src/config/provider-ranking.ts +119 -0
  194. package/src/config/settings-schema.ts +157 -10
  195. package/src/config/settings.ts +254 -108
  196. package/src/coordinator-mcp/model-preset.ts +21 -74
  197. package/src/defaults/skc/skills/deep-interview/SKILL.md +147 -24
  198. package/src/defaults/skc/skills/ralplan/SKILL.md +91 -34
  199. package/src/defaults/skc/skills/team/SKILL.md +1 -1
  200. package/src/defaults/skc/skills/ultragoal/SKILL.md +68 -17
  201. package/src/discovery/ssh.ts +12 -1
  202. package/src/edit/index.ts +3 -6
  203. package/src/edit/modes/patch.ts +38 -1
  204. package/src/edit/modes/replace.ts +42 -0
  205. package/src/edit/path-mutation-lock.ts +112 -0
  206. package/src/eval/js/tool-bridge.ts +1 -1
  207. package/src/eval/py/executor.ts +37 -13
  208. package/src/eval/py/kernel.ts +1 -21
  209. package/src/eval/py/prelude.py +2 -2
  210. package/src/eval/py/runner-artifact.ts +84 -0
  211. package/src/eval/py/tool-bridge.ts +34 -18
  212. package/src/extensibility/extensions/runner.ts +4 -0
  213. package/src/extensibility/extensions/types.ts +42 -4
  214. package/src/extensibility/plugins/marketplace/fetcher.ts +191 -7
  215. package/src/extensibility/shared-events.ts +5 -0
  216. package/src/extensibility/skc-plugins/runtime-adapters.ts +2 -1
  217. package/src/hooks/native-skill-hook.ts +17 -4
  218. package/src/internal-urls/docs-index.generated.ts +17 -16
  219. package/src/internal-urls/local-protocol.ts +87 -25
  220. package/src/internal-urls/local-root-gc.ts +127 -0
  221. package/src/internal-urls/mcp-protocol.ts +73 -11
  222. package/src/main.ts +120 -42
  223. package/src/memories/index.ts +10 -0
  224. package/src/modes/DESIGN.md +52 -0
  225. package/src/modes/acp/acp-agent.ts +647 -83
  226. package/src/modes/acp/acp-event-mapper.ts +106 -12
  227. package/src/modes/acp/acp-mode.ts +39 -3
  228. package/src/modes/action-registry.ts +1 -1
  229. package/src/modes/components/assistant-message.ts +123 -22
  230. package/src/modes/components/btw-panel.ts +82 -16
  231. package/src/modes/components/irc-sidebar.ts +176 -62
  232. package/src/modes/components/model-selector.ts +61 -4
  233. package/src/modes/components/notifications-settings-editor.ts +1 -1
  234. package/src/modes/components/oauth-selector.ts +42 -12
  235. package/src/modes/components/plan-preview-overlay.ts +1 -1
  236. package/src/modes/components/queue-pane.ts +55 -9
  237. package/src/modes/components/queued-message-selector.ts +53 -10
  238. package/src/modes/components/read-tool-group.ts +82 -14
  239. package/src/modes/components/runtime-mcp-add-wizard.ts +5 -0
  240. package/src/modes/components/sayknow-pet-widget.ts +20 -8
  241. package/src/modes/components/session-observer-overlay.ts +187 -32
  242. package/src/modes/components/settings-selector.ts +60 -18
  243. package/src/modes/components/tool-execution.ts +138 -27
  244. package/src/modes/components/tool-status-header.ts +26 -8
  245. package/src/modes/components/welcome.ts +50 -23
  246. package/src/modes/controllers/btw-controller.ts +119 -36
  247. package/src/modes/controllers/command-controller.ts +101 -54
  248. package/src/modes/controllers/event-controller.ts +119 -27
  249. package/src/modes/controllers/extension-ui-controller.ts +23 -12
  250. package/src/modes/controllers/input-controller.ts +100 -8
  251. package/src/modes/controllers/runtime-mcp-command-controller.ts +29 -6
  252. package/src/modes/controllers/selector-controller.ts +4 -1
  253. package/src/modes/interactive-mode.ts +201 -60
  254. package/src/modes/irc-observation-ledger.ts +71 -15
  255. package/src/modes/prompt-action-autocomplete.ts +32 -13
  256. package/src/modes/prompt-suggestion-controller.ts +96 -0
  257. package/src/modes/runtime-init.ts +20 -2
  258. package/src/modes/shared/agent-wire/command-dispatch.ts +3 -3
  259. package/src/modes/shared/agent-wire/workflow-gate-broker.ts +2 -2
  260. package/src/modes/types.ts +6 -0
  261. package/src/modes/utils/hotkeys-markdown.ts +64 -36
  262. package/src/modes/utils/ui-helpers.ts +99 -4
  263. package/src/prompts/agent-fragments/restricted-bash.md +1 -1
  264. package/src/prompts/agents/architect.md +17 -3
  265. package/src/prompts/agents/critic.md +17 -2
  266. package/src/prompts/agents/executor.md +1 -1
  267. package/src/prompts/agents/planner.md +8 -1
  268. package/src/prompts/system/btw-user.md +3 -8
  269. package/src/prompts/system/prompt-suggestion-system.md +32 -0
  270. package/src/prompts/system/system-prompt.md +6 -3
  271. package/src/prompts/tools/cron.md +3 -1
  272. package/src/prompts/tools/read.md +4 -2
  273. package/src/prompts/tools/skill-discovery.md +1 -0
  274. package/src/runtime/memory-domain.ts +66 -0
  275. package/src/runtime/memory-guard-contract.ts +73 -0
  276. package/src/runtime/memory-guard.ts +292 -0
  277. package/src/runtime/memory-limit.ts +50 -0
  278. package/src/runtime-mcp/client.ts +41 -55
  279. package/src/runtime-mcp/content-limits.ts +71 -0
  280. package/src/runtime-mcp/discoverable-tool-metadata.ts +1 -1
  281. package/src/runtime-mcp/json-rpc.ts +28 -11
  282. package/src/runtime-mcp/manager.ts +77 -59
  283. package/src/runtime-mcp/oauth-flow.ts +0 -72
  284. package/src/runtime-mcp/plugin-network-boundary.ts +101 -0
  285. package/src/runtime-mcp/redaction.ts +30 -0
  286. package/src/runtime-mcp/smithery-auth.ts +17 -2
  287. package/src/runtime-mcp/smithery-connect.ts +11 -1
  288. package/src/runtime-mcp/transports/http.ts +63 -22
  289. package/src/sdk/acp/adapter.ts +8 -1
  290. package/src/sdk/acp/final-text.ts +40 -0
  291. package/src/sdk/acp/mcp.ts +26 -0
  292. package/src/sdk/broker/broker.ts +34 -20
  293. package/src/sdk/broker/identity.ts +22 -12
  294. package/src/sdk/broker/lifecycle.ts +293 -43
  295. package/src/sdk/broker/session-index.ts +11 -1
  296. package/src/sdk/bus/chat-daemon-cli.ts +2 -2
  297. package/src/sdk/bus/chat-daemon-control.ts +22 -5
  298. package/src/sdk/bus/config-commands.ts +2 -2
  299. package/src/sdk/bus/config.ts +10 -2
  300. package/src/sdk/bus/control-drain-lease.ts +104 -0
  301. package/src/sdk/bus/conversation-store.ts +40 -6
  302. package/src/sdk/bus/daemon-paths.ts +2 -0
  303. package/src/sdk/bus/index.ts +793 -121
  304. package/src/sdk/bus/kind-aware-reconciliation.ts +242 -0
  305. package/src/sdk/bus/lifecycle-commands.ts +63 -10
  306. package/src/sdk/bus/lifecycle-control-runtime.ts +11 -2
  307. package/src/sdk/bus/lifecycle-orchestrator.ts +32 -1
  308. package/src/sdk/bus/notification-orchestration.ts +12 -0
  309. package/src/sdk/bus/notification-service.ts +87 -25
  310. package/src/sdk/bus/operator-runtime.ts +31 -5
  311. package/src/sdk/bus/prompt-reconciliation.ts +240 -0
  312. package/src/sdk/bus/rate-limit-pool.ts +8 -0
  313. package/src/sdk/bus/recent-activity.ts +90 -26
  314. package/src/sdk/bus/reconciliation-store.ts +224 -0
  315. package/src/sdk/bus/telegram-cli.ts +6 -13
  316. package/src/sdk/bus/telegram-daemon-cli.ts +8 -3
  317. package/src/sdk/bus/telegram-daemon-contract.ts +34 -9
  318. package/src/sdk/bus/telegram-daemon-control.ts +161 -40
  319. package/src/sdk/bus/telegram-daemon.ts +3649 -593
  320. package/src/sdk/bus/telegram-reference.ts +20 -2
  321. package/src/sdk/bus/topic-registry.ts +451 -12
  322. package/src/sdk/cli/session-cli.ts +15 -2
  323. package/src/sdk/client/discovery.ts +19 -3
  324. package/src/sdk/client/liveness.ts +49 -0
  325. package/src/sdk/host/control/dispatch.ts +8 -2
  326. package/src/sdk/host/control/operations.ts +3 -2
  327. package/src/sdk/host/host.ts +27 -9
  328. package/src/sdk/host/query/handlers.ts +125 -5
  329. package/src/sdk/index.ts +12 -0
  330. package/src/sdk/lifecycle-session.ts +19 -3
  331. package/src/sdk/mcp/server.ts +46 -18
  332. package/src/sdk/prompt-status.ts +97 -0
  333. package/src/sdk/protocol/operation-inventory.generated.json +145 -0
  334. package/src/sdk/protocol/operation-registry.ts +24 -4
  335. package/src/sdk/session-directory.ts +5 -1
  336. package/src/sdk/session.ts +69 -8
  337. package/src/sdk/startup-capability.ts +15 -1
  338. package/src/sdk/transport/auth-preface.ts +82 -0
  339. package/src/sdk/transport/index.ts +18 -0
  340. package/src/sdk/transport/relay.ts +252 -0
  341. package/src/sdk/transport/serve-cli.ts +105 -0
  342. package/src/sdk/transport/socket.ts +197 -0
  343. package/src/sdk/transport/stdio.ts +17 -0
  344. package/src/session/agent-session.ts +1112 -314
  345. package/src/session/blob-store.ts +25 -7
  346. package/src/session/btw-contract.ts +59 -0
  347. package/src/session/fallback-chain-controller.ts +47 -8
  348. package/src/session/internal/managed-session-scope.ts +1426 -259
  349. package/src/session/internal/managed-session-storage.ts +414 -238
  350. package/src/session/internal/native-publish-outcome.ts +271 -0
  351. package/src/session/memory-guard-checkpoint-participant.ts +131 -0
  352. package/src/session/session-manager.ts +2079 -469
  353. package/src/session/session-storage.ts +151 -11
  354. package/src/session/streaming-output.ts +262 -43
  355. package/src/setup/credential-import.ts +28 -3
  356. package/src/setup/provider-onboarding.ts +49 -5
  357. package/src/setup/provider-presets.json +2 -1
  358. package/src/skc-runtime/deep-interview-runtime.ts +303 -36
  359. package/src/skc-runtime/deep-interview-stage.ts +1016 -0
  360. package/src/skc-runtime/deep-interview-state.ts +8 -5
  361. package/src/skc-runtime/gc-render.ts +1 -0
  362. package/src/skc-runtime/gc-runtime.ts +11 -1
  363. package/src/skc-runtime/linux-proc.ts +70 -36
  364. package/src/skc-runtime/managed-owner-supervisor.ts +83 -4
  365. package/src/skc-runtime/memory-guard-owner-claims.ts +389 -0
  366. package/src/skc-runtime/ralplan-runtime.ts +1057 -75
  367. package/src/skc-runtime/repository-binding.ts +267 -0
  368. package/src/skc-runtime/session-state-sidecar.ts +25 -9
  369. package/src/skc-runtime/state-runtime.ts +50 -12
  370. package/src/skc-runtime/state-writer.ts +68 -56
  371. package/src/skc-runtime/team-launch.ts +34 -5
  372. package/src/skc-runtime/team-runtime.ts +41 -1
  373. package/src/skc-runtime/team-store.ts +32 -0
  374. package/src/skc-runtime/team-worker-memory-guard.ts +363 -0
  375. package/src/skc-runtime/team-workers.ts +1 -0
  376. package/src/skc-runtime/tmux-common.ts +13 -2
  377. package/src/skc-runtime/tmux-owner-isolation.ts +20 -1
  378. package/src/skc-runtime/tmux-sessions.ts +45 -6
  379. package/src/skc-runtime/ultragoal-guard.ts +93 -15
  380. package/src/skc-runtime/ultragoal-receipt-freshness.ts +123 -0
  381. package/src/skc-runtime/ultragoal-runtime.ts +469 -38
  382. package/src/skc-runtime/workflow-manifest.generated.json +195 -5
  383. package/src/skc-runtime/workflow-manifest.ts +67 -3
  384. package/src/skill-state/workflow-hud.ts +25 -2
  385. package/src/skill-state/workflow-mutation-guard.ts +104 -12
  386. package/src/slash-commands/builtin-registry.ts +1 -1
  387. package/src/ssh/utils.ts +14 -0
  388. package/src/task/discovery.ts +8 -2
  389. package/src/task/executor.ts +165 -47
  390. package/src/task/index.ts +123 -15
  391. package/src/task/provider-retry-status.ts +88 -0
  392. package/src/task/receipt.ts +3 -0
  393. package/src/task/render.ts +45 -26
  394. package/src/task/skc-command.ts +2 -2
  395. package/src/task/types.ts +55 -10
  396. package/src/task/ultragoal-redteam-activation.ts +75 -0
  397. package/src/tools/ask.ts +1 -1
  398. package/src/tools/ast-edit.ts +2 -2
  399. package/src/tools/bash-allowed-prefixes.ts +45 -1
  400. package/src/tools/bash-pty-selection.ts +2 -2
  401. package/src/tools/browser/launch.ts +41 -6
  402. package/src/tools/browser/screenshot-format.ts +6 -1
  403. package/src/tools/cron.ts +1 -1
  404. package/src/tools/fetch.ts +127 -154
  405. package/src/tools/image-gen.ts +476 -23
  406. package/src/tools/index.ts +7 -1
  407. package/src/tools/output-meta.ts +176 -9
  408. package/src/tools/path-utils.ts +5 -1
  409. package/src/tools/read-internals.ts +44 -0
  410. package/src/tools/read.ts +1544 -310
  411. package/src/tools/resource-gc.ts +665 -125
  412. package/src/tools/skill-discovery.ts +39 -3
  413. package/src/tools/sqlite-reader.ts +63 -5
  414. package/src/tools/subagent-render.ts +74 -16
  415. package/src/tools/subagent.ts +2 -0
  416. package/src/tools/tool-result.ts +14 -1
  417. package/src/utils/edit-mode.ts +2 -2
  418. package/src/utils/pasted-image-path.ts +15 -6
  419. package/src/utils/prompt-suggestion.ts +265 -0
  420. package/src/utils/shell-snapshot.ts +173 -63
  421. package/src/utils/sixel.ts +3 -3
  422. package/src/web/insane/bridge.ts +5 -1
  423. package/src/web/insane/url-guard.ts +140 -27
  424. package/src/web/scrapers/docs-rs.ts +30 -5
  425. package/src/web/scrapers/types.ts +19 -49
  426. package/src/web/scrapers/utils.ts +12 -26
  427. package/src/web/search/providers/codex.ts +1 -1
  428. package/src/workflow/workflow-intent-diff.ts +4 -1
@@ -16,6 +16,15 @@ import type {
16
16
  MCPTransport,
17
17
  } from "../../runtime-mcp/types";
18
18
  import { MCPExpectedFailure, toJsonRpcError } from "../../runtime-mcp/types";
19
+ import {
20
+ cancelMCPStream,
21
+ MCP_MAX_CONTENT_BYTES,
22
+ MCP_MAX_ERROR_BYTES,
23
+ MCP_MAX_SSE_BATCH_MESSAGES,
24
+ MCP_MAX_SSE_REQUEST_MESSAGES,
25
+ readMCPResponseText,
26
+ } from "../content-limits";
27
+ import { fetchPluginMcpRequest, isPluginMcpPublicNetworkBound } from "../plugin-network-boundary";
19
28
 
20
29
  /**
21
30
  * HTTP transport for MCP servers.
@@ -54,6 +63,12 @@ export class HttpTransport implements MCPTransport {
54
63
  this.#connected = true;
55
64
  }
56
65
 
66
+ #fetch(init: BunFetchRequestInit): Promise<Response> {
67
+ return isPluginMcpPublicNetworkBound(this.config)
68
+ ? fetchPluginMcpRequest(this.config.url, init)
69
+ : fetch(this.config.url, init);
70
+ }
71
+
57
72
  #trackReader(promise: Promise<void>, controller?: AbortController): void {
58
73
  if (controller) this.#streamControllers.add(controller);
59
74
  this.#streamReaders.add(promise);
@@ -73,6 +88,9 @@ export class HttpTransport implements MCPTransport {
73
88
  if (this.#sseConnection) return;
74
89
 
75
90
  const sseConnection = new AbortController();
91
+ const headerController = new AbortController();
92
+ const headerTimeout = this.config.timeout ?? 30000;
93
+ const headerTimeoutId = setTimeout(() => headerController.abort(), headerTimeout);
76
94
  this.#sseConnection = sseConnection;
77
95
  const headers: Record<string, string> = {
78
96
  Accept: "text/event-stream",
@@ -85,21 +103,25 @@ export class HttpTransport implements MCPTransport {
85
103
 
86
104
  let response: Response;
87
105
  try {
88
- response = await fetch(this.config.url, {
106
+ response = await this.#fetch({
89
107
  method: "GET",
90
108
  headers,
91
- signal: sseConnection.signal,
109
+ signal: AbortSignal.any([sseConnection.signal, headerController.signal]),
92
110
  });
93
111
  } catch (error) {
94
112
  this.#sseConnection = this.#sseConnection === sseConnection ? null : this.#sseConnection;
95
- if (error instanceof Error && error.name !== "AbortError") {
113
+ if (headerController.signal.aborted && !sseConnection.signal.aborted) {
114
+ this.onError?.(new Error(`SSE connection timeout after ${headerTimeout}ms`));
115
+ } else if (error instanceof Error && error.name !== "AbortError") {
96
116
  this.onError?.(error);
97
117
  }
98
118
  return;
119
+ } finally {
120
+ clearTimeout(headerTimeoutId);
99
121
  }
100
122
 
101
123
  if (response.status === 405 || !response.ok || !response.body) {
102
- await response.body?.cancel().catch(() => {});
124
+ cancelMCPStream(response.body);
103
125
  this.#sseConnection = this.#sseConnection === sseConnection ? null : this.#sseConnection;
104
126
  return;
105
127
  }
@@ -117,8 +139,13 @@ export class HttpTransport implements MCPTransport {
117
139
  }
118
140
  async #readSSEStream(body: ReadableStream<Uint8Array>, signal: AbortSignal): Promise<void> {
119
141
  try {
120
- for await (const message of readSseJson<JsonRpcMessage>(body, signal)) {
142
+ for await (const message of readSseJson<JsonRpcMessage>(body, signal, undefined, {
143
+ maxEventBytes: MCP_MAX_CONTENT_BYTES,
144
+ })) {
121
145
  if (!this.#connected) break;
146
+ if (Array.isArray(message) && message.length > MCP_MAX_SSE_BATCH_MESSAGES) {
147
+ throw new Error("MCP SSE batch exceeds message limit");
148
+ }
122
149
  this.#dispatchSSEMessage(message);
123
150
  }
124
151
  } catch (error) {
@@ -207,7 +234,7 @@ export class HttpTransport implements MCPTransport {
207
234
  : abortController.signal;
208
235
 
209
236
  try {
210
- const response = await fetch(this.config.url, {
237
+ const response = await this.#fetch({
211
238
  method: "POST",
212
239
  headers,
213
240
  body: JSON.stringify(body),
@@ -221,7 +248,7 @@ export class HttpTransport implements MCPTransport {
221
248
  }
222
249
 
223
250
  if (!response.ok) {
224
- const text = await response.text();
251
+ const text = await readMCPResponseText(response, MCP_MAX_ERROR_BYTES, true, operationSignal);
225
252
  const wwwAuthenticate = response.headers.get("WWW-Authenticate");
226
253
  const mcpAuthServer = response.headers.get("Mcp-Auth-Server");
227
254
  const authHints = [
@@ -245,7 +272,9 @@ export class HttpTransport implements MCPTransport {
245
272
  if (!response.body) {
246
273
  throw new MCPExpectedFailure();
247
274
  }
248
- const parsedResult = await response.json();
275
+ const parsedResult = JSON.parse(
276
+ await readMCPResponseText(response, MCP_MAX_CONTENT_BYTES, false, operationSignal),
277
+ ) as unknown;
249
278
 
250
279
  if (
251
280
  typeof parsedResult !== "object" ||
@@ -293,6 +322,7 @@ export class HttpTransport implements MCPTransport {
293
322
 
294
323
  const { promise, resolve, reject } = Promise.withResolvers<T>();
295
324
  let captured = false;
325
+ let messageCount = 0;
296
326
 
297
327
  // Drain this per-request SSE response from a single iterator. Once the
298
328
  // matching response arrives, resolve/reject and abort the reader so the
@@ -304,8 +334,20 @@ export class HttpTransport implements MCPTransport {
304
334
  this.#streamControllers.add(drainController);
305
335
  const drain = async (): Promise<void> => {
306
336
  try {
307
- for await (const raw of readSseJson<JsonRpcMessage | JsonRpcMessage[]>(response.body!, operationSignal)) {
337
+ for await (const raw of readSseJson<JsonRpcMessage | JsonRpcMessage[]>(
338
+ response.body!,
339
+ operationSignal,
340
+ undefined,
341
+ {
342
+ maxEventBytes: MCP_MAX_CONTENT_BYTES,
343
+ maxTotalBytes: MCP_MAX_CONTENT_BYTES,
344
+ },
345
+ )) {
308
346
  const messages = Array.isArray(raw) ? raw : [raw];
347
+ if (messages.length > MCP_MAX_SSE_BATCH_MESSAGES) throw new Error("MCP SSE batch exceeds message limit");
348
+ messageCount += messages.length;
349
+ if (messageCount > MCP_MAX_SSE_REQUEST_MESSAGES)
350
+ throw new Error("MCP SSE response exceeds message limit");
309
351
  for (const message of messages) {
310
352
  if (
311
353
  !captured &&
@@ -352,7 +394,7 @@ export class HttpTransport implements MCPTransport {
352
394
  } finally {
353
395
  clearTimeout(timeoutId);
354
396
  this.#streamControllers.delete(drainController);
355
- await response.body?.cancel().catch(() => {});
397
+ cancelMCPStream(response.body);
356
398
  }
357
399
  };
358
400
 
@@ -388,7 +430,7 @@ export class HttpTransport implements MCPTransport {
388
430
  headers["Mcp-Session-Id"] = this.#sessionId;
389
431
  }
390
432
  try {
391
- const resp = await fetch(this.config.url, {
433
+ const resp = await this.#fetch({
392
434
  method: "POST",
393
435
  headers,
394
436
  body: JSON.stringify(body),
@@ -396,23 +438,23 @@ export class HttpTransport implements MCPTransport {
396
438
  });
397
439
  // Retry once on auth failure if onAuthError is wired
398
440
  if (this.onAuthError && (resp.status === 401 || resp.status === 403)) {
399
- await resp.body?.cancel();
441
+ cancelMCPStream(resp.body);
400
442
  const newHeaders = await this.onAuthError();
401
443
  if (newHeaders) {
402
444
  this.config.headers ??= {};
403
445
  Object.assign(this.config.headers, newHeaders);
404
446
  Object.assign(headers, newHeaders);
405
- const retry = await fetch(this.config.url, {
447
+ const retry = await this.#fetch({
406
448
  method: "POST",
407
449
  headers,
408
450
  body: JSON.stringify(body),
409
451
  signal: AbortSignal.timeout(this.config.timeout ?? 30000),
410
452
  });
411
- await retry.body?.cancel();
453
+ cancelMCPStream(retry.body);
412
454
  return;
413
455
  }
414
456
  }
415
- await resp.body?.cancel();
457
+ cancelMCPStream(resp.body);
416
458
  } catch {
417
459
  // Best-effort response delivery — server may have disconnected
418
460
  }
@@ -445,18 +487,16 @@ export class HttpTransport implements MCPTransport {
445
487
  const timeoutId = setTimeout(() => abortController.abort(), timeout);
446
488
 
447
489
  try {
448
- const response = await fetch(this.config.url, {
490
+ const response = await this.#fetch({
449
491
  method: "POST",
450
492
  headers,
451
493
  body: JSON.stringify(body),
452
494
  signal: abortController.signal,
453
495
  });
454
496
 
455
- clearTimeout(timeoutId);
456
-
457
497
  // 202 Accepted is success for notifications
458
498
  if (!response.ok && response.status !== 202) {
459
- const text = await response.text();
499
+ const text = await readMCPResponseText(response, MCP_MAX_ERROR_BYTES, true, abortController.signal);
460
500
  throw new Error(`HTTP ${response.status}: ${text}`);
461
501
  }
462
502
 
@@ -472,14 +512,15 @@ export class HttpTransport implements MCPTransport {
472
512
  const reader = this.#readSSEStream(response.body, AbortSignal.any(signals));
473
513
  this.#trackReader(reader, streamController);
474
514
  } else {
475
- await response.body?.cancel();
515
+ cancelMCPStream(response.body);
476
516
  }
477
517
  } catch (error) {
478
- clearTimeout(timeoutId);
479
518
  if (error instanceof Error && error.name === "AbortError") {
480
519
  throw new MCPExpectedFailure(new Error(`Notify timeout after ${timeout}ms`));
481
520
  }
482
521
  throw error instanceof MCPExpectedFailure ? error : new MCPExpectedFailure(error);
522
+ } finally {
523
+ clearTimeout(timeoutId);
483
524
  }
484
525
  }
485
526
 
@@ -508,7 +549,7 @@ export class HttpTransport implements MCPTransport {
508
549
  "Mcp-Session-Id": this.#sessionId,
509
550
  };
510
551
 
511
- await fetch(this.config.url, {
552
+ await this.#fetch({
512
553
  method: "DELETE",
513
554
  headers,
514
555
  signal: AbortSignal.timeout(timeout),
@@ -179,7 +179,14 @@ export class AcpSdkAdapter {
179
179
  this.#assertGenericDisposition("global", operation);
180
180
  if (isLifecycleOperation(operation) && !idempotencyKey)
181
181
  throw new AcpSdkAdapterError("invalid_input", "idempotencyKey is required for lifecycle operations.");
182
- return await this.#client.global(operation, input, { idempotencyKey });
182
+ const readinessTimeoutMs =
183
+ typeof input.readinessTimeoutMs === "number" && Number.isSafeInteger(input.readinessTimeoutMs)
184
+ ? input.readinessTimeoutMs
185
+ : undefined;
186
+ return await this.#client.global(operation, input, {
187
+ idempotencyKey,
188
+ ...(readinessTimeoutMs ? { timeoutMs: readinessTimeoutMs + 1_000 } : {}),
189
+ });
183
190
  }
184
191
 
185
192
  async sdkControl(params: { operation: string; input?: JsonObject }): Promise<unknown> {
@@ -0,0 +1,40 @@
1
+ export const ACP_FINAL_TEXT_LIMIT = 100_000;
2
+
3
+ export interface BoundedAcpFinalText {
4
+ text: string;
5
+ truncated: boolean;
6
+ }
7
+
8
+ export type AcpFinalTextResolution =
9
+ | { kind: "none"; final: BoundedAcpFinalText }
10
+ | { kind: "emit"; final: BoundedAcpFinalText; text: string }
11
+ | { kind: "divergent"; final: BoundedAcpFinalText };
12
+
13
+ export function boundAcpFinalText(value: string): BoundedAcpFinalText {
14
+ if (value.length <= ACP_FINAL_TEXT_LIMIT) return { text: value, truncated: false };
15
+ let text = value.slice(0, ACP_FINAL_TEXT_LIMIT);
16
+ const finalCodeUnit = text.charCodeAt(text.length - 1);
17
+ if (finalCodeUnit >= 0xd800 && finalCodeUnit <= 0xdbff) text = text.slice(0, -1);
18
+ return { text, truncated: true };
19
+ }
20
+
21
+ export function acpFinalTextFromMessage(message: unknown): BoundedAcpFinalText {
22
+ const content = (message as { content?: unknown } | null | undefined)?.content;
23
+ if (typeof content === "string") return boundAcpFinalText(content);
24
+ if (!Array.isArray(content)) return { text: "", truncated: false };
25
+ const parts: string[] = [];
26
+ for (const block of content) {
27
+ if (!block || typeof block !== "object" || (block as { type?: unknown }).type !== "text") continue;
28
+ const text = (block as { text?: unknown }).text;
29
+ if (typeof text === "string") parts.push(text);
30
+ }
31
+ return boundAcpFinalText(parts.join(""));
32
+ }
33
+
34
+ export function resolveAcpFinalText(streamed: string, finalText: string): AcpFinalTextResolution {
35
+ const final = boundAcpFinalText(finalText);
36
+ if (!final.text || streamed === final.text || streamed.includes(final.text)) return { kind: "none", final };
37
+ if (!streamed) return { kind: "emit", final, text: final.text };
38
+ if (final.text.startsWith(streamed)) return { kind: "emit", final, text: final.text.slice(streamed.length) };
39
+ return { kind: "divergent", final };
40
+ }
@@ -0,0 +1,26 @@
1
+ export const ACP_MCP_REQUEST_TIMEOUT_MS = 30_000;
2
+ export const ACP_MCP_LIFECYCLE_TIMEOUT_MS = ACP_MCP_REQUEST_TIMEOUT_MS + 500;
3
+ /**
4
+ * Slice of the readiness budget reserved for the work that follows MCP startup
5
+ * (session wiring, marker publication). The ACP MCP startup ceiling is the
6
+ * remaining time to `semanticReadyDeadlineAt` minus this headroom, so a slow
7
+ * MCP handshake cannot consume the whole readiness window.
8
+ */
9
+ export const ACP_MCP_STARTUP_HEADROOM_MS = 250;
10
+
11
+ export interface SessionLifecycleMcpStdioServer {
12
+ type?: "stdio";
13
+ name: string;
14
+ command: string;
15
+ args: string[];
16
+ env?: Record<string, string>;
17
+ }
18
+
19
+ export interface SessionLifecycleMcpRemoteServer {
20
+ type: "http" | "sse";
21
+ name: string;
22
+ url: string;
23
+ headers?: Record<string, string>;
24
+ }
25
+
26
+ export type SessionLifecycleMcpServer = SessionLifecycleMcpStdioServer | SessionLifecycleMcpRemoteServer;
@@ -1,7 +1,9 @@
1
1
  import { createHash, randomBytes } from "node:crypto";
2
+ import type { BigIntStats } from "node:fs";
2
3
  import * as fs from "node:fs/promises";
3
4
  import path from "node:path";
4
5
  import type { NativeDirectoryTreeSnapshot } from "@sayknow-cli/natives";
6
+ import type { ModelProfileErrorDetails } from "../../config/model-profile-contract";
5
7
  import {
6
8
  type DirectoryMigrationPolicy,
7
9
  listManagedSessionCandidates,
@@ -141,6 +143,7 @@ export type BrokerResponse =
141
143
  error: {
142
144
  code: BrokerErrorCode;
143
145
  message: string;
146
+ details?: ModelProfileErrorDetails;
144
147
  endpoint?: "unavailable";
145
148
  cleanup?: BrokerCleanupEvidence;
146
149
  };
@@ -362,6 +365,7 @@ type BrokerLockSnapshot = {
362
365
  ownerId?: string;
363
366
  pid: number;
364
367
  identity: string;
368
+ lockIdentity: string;
365
369
  };
366
370
 
367
371
  const BROKER_PUBLICATION_CADENCE_MS = 5_000;
@@ -415,7 +419,7 @@ export class Broker {
415
419
  #lockRecordPath(): string {
416
420
  return path.join(this.#lock, BROKER_LOCK_RECORD);
417
421
  }
418
- #lockSnapshot(raw: string): BrokerLockSnapshot {
422
+ async #lockSnapshot(raw: string, lockIdentity: string): Promise<BrokerLockSnapshot> {
419
423
  try {
420
424
  const lock = JSON.parse(raw) as { ownerId?: unknown; pid?: unknown };
421
425
  if (
@@ -425,32 +429,37 @@ export class Broker {
425
429
  Number.isInteger(lock.pid) &&
426
430
  lock.pid > 0
427
431
  )
428
- return { ownerId: lock.ownerId, pid: lock.pid, identity: `owner:${lock.ownerId}` };
432
+ return { ownerId: lock.ownerId, pid: lock.pid, identity: `owner:${lock.ownerId}`, lockIdentity };
429
433
  } catch {}
430
- return { pid: 0, identity: `contents:${createHash("sha256").update(raw).digest("hex")}` };
434
+ return { pid: 0, identity: `contents:${createHash("sha256").update(raw).digest("hex")}`, lockIdentity };
431
435
  }
432
436
  async #readLock(): Promise<BrokerLockSnapshot | null> {
437
+ let lock: BigIntStats;
433
438
  try {
434
- return this.#lockSnapshot(await fs.readFile(this.#lockRecordPath(), "utf8"));
439
+ lock = await fs.lstat(this.#lock, { bigint: true });
435
440
  } catch (e) {
436
- const code = (e as NodeJS.ErrnoException).code;
437
- if (code === "ENOTDIR") {
438
- try {
439
- return this.#lockSnapshot(await fs.readFile(this.#lock, "utf8"));
440
- } catch (legacyError) {
441
- if ((legacyError as NodeJS.ErrnoException).code === "ENOENT") return null;
442
- throw legacyError;
443
- }
444
- }
445
- if (code !== "ENOENT") throw e;
441
+ if ((e as NodeJS.ErrnoException).code === "ENOENT") return null;
442
+ throw e;
446
443
  }
444
+ const lockIdentity = `${lock.dev}:${lock.ino}`;
445
+ let raw: string;
447
446
  try {
448
- const lock = await fs.stat(this.#lock);
449
- return lock.isDirectory() ? { pid: 0, identity: `directory:${lock.dev}:${lock.ino}` } : null;
447
+ raw = lock.isDirectory()
448
+ ? await fs.readFile(path.join(this.#lock, BROKER_LOCK_RECORD), "utf8")
449
+ : await fs.readFile(this.#lock, "utf8");
450
+ } catch (e) {
451
+ if ((e as NodeJS.ErrnoException).code !== "ENOENT") throw e;
452
+ raw = "";
453
+ }
454
+ try {
455
+ const current = await fs.lstat(this.#lock, { bigint: true });
456
+ if (`${current.dev}:${current.ino}` !== lockIdentity || current.isDirectory() !== lock.isDirectory())
457
+ return null;
450
458
  } catch (e) {
451
459
  if ((e as NodeJS.ErrnoException).code === "ENOENT") return null;
452
460
  throw e;
453
461
  }
462
+ return this.#lockSnapshot(raw, lockIdentity);
454
463
  }
455
464
  async #createLock(): Promise<void> {
456
465
  await fs.mkdir(this.#lock, { mode: 0o700 });
@@ -478,13 +487,18 @@ export class Broker {
478
487
  }
479
488
  async #reclaimStaleLock(snapshot: BrokerLockSnapshot): Promise<void> {
480
489
  const current = await this.#readLock();
481
- if (!current || current.identity !== snapshot.identity || (current.pid > 0 && isPidAlive(current.pid))) return;
490
+ if (
491
+ !current ||
492
+ current.identity !== snapshot.identity ||
493
+ current.lockIdentity !== snapshot.lockIdentity ||
494
+ (current.pid > 0 && isPidAlive(current.pid))
495
+ )
496
+ return;
482
497
 
483
- // Keep the tombstone: its immutable-owner-derived pathname prevents a contender
484
- // holding an old snapshot from renaming a newly-created directory lock.
498
+ // Snapshot validation and the deterministic instance suffix protect successor locks.
485
499
  const tombstone = path.join(
486
500
  path.dirname(this.#lock),
487
- `.broker.lock.stale-${createHash("sha256").update(snapshot.identity).digest("hex")}`,
501
+ `.broker.lock.stale-${createHash("sha256").update(snapshot.lockIdentity).digest("hex")}`,
488
502
  );
489
503
  try {
490
504
  await fs.rename(this.#lock, tombstone);
@@ -11,20 +11,30 @@ export function brokerIdentityPath(agentDir: string): string {
11
11
  export async function getBrokerIdentityKey(agentDir: string): Promise<string> {
12
12
  const file = brokerIdentityPath(agentDir);
13
13
  await fs.mkdir(path.dirname(file), { recursive: true, mode: 0o700 });
14
- try {
15
- const key = (await fs.readFile(file, "utf8")).trim();
16
- if (/^[0-9a-f]{64}$/i.test(key)) return key;
17
- throw new Error(`Invalid broker identity key at ${file}`);
18
- } catch (error) {
19
- if ((error as NodeJS.ErrnoException).code !== "ENOENT") throw error;
14
+ while (true) {
15
+ try {
16
+ const key = (await fs.readFile(file, "utf8")).trim();
17
+ if (/^[0-9a-f]{64}$/i.test(key)) return key;
18
+ throw new Error(`Invalid broker identity key at ${file}`);
19
+ } catch (error) {
20
+ if ((error as NodeJS.ErrnoException).code !== "ENOENT") throw error;
21
+ }
22
+
20
23
  const key = randomBytes(32).toString("hex");
24
+ const temporary = path.join(path.dirname(file), `.broker.identity-${randomBytes(16).toString("hex")}`);
21
25
  try {
22
- await fs.writeFile(file, `${key}\n`, { encoding: "utf8", mode: MODE, flag: "wx" });
23
- await fs.chmod(file, MODE);
24
- return key;
25
- } catch (writeError) {
26
- if ((writeError as NodeJS.ErrnoException).code !== "EEXIST") throw writeError;
27
- return getBrokerIdentityKey(agentDir);
26
+ await fs.writeFile(temporary, `${key}\n`, { encoding: "utf8", mode: MODE, flag: "wx" });
27
+ await fs.chmod(temporary, MODE);
28
+ try {
29
+ // Link publishes a fully written key without exposing a partial target.
30
+ // Same-directory hard-link support is required (standard on Linux, macOS, and Windows NTFS).
31
+ await fs.link(temporary, file);
32
+ return key;
33
+ } catch (linkError) {
34
+ if ((linkError as NodeJS.ErrnoException).code !== "EEXIST") throw linkError;
35
+ }
36
+ } finally {
37
+ await fs.rm(temporary, { force: true });
28
38
  }
29
39
  }
30
40
  }