@sayknow-cli/coding-agent 0.3.12 → 0.3.13

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 (1122) hide show
  1. package/package.json +116 -117
  2. package/scripts/g004-tmux-smoke.ts +196 -36
  3. package/scripts/generate-docs-index.ts +2 -0
  4. package/src/cli/daemon-cli.ts +19 -29
  5. package/src/cli/malloc-env-guard.test.ts +104 -0
  6. package/src/cli/malloc-env-guard.ts +149 -0
  7. package/src/cli/notify-cli.ts +78 -14
  8. package/src/cli/session-picker.ts +23 -40
  9. package/src/cli/update-cli.ts +56 -37
  10. package/src/cli.ts +50 -10
  11. package/src/commands/daemon.ts +19 -5
  12. package/src/commands/harness.ts +515 -47
  13. package/src/commands/launch.ts +11 -3
  14. package/src/commands/notify.ts +5 -1
  15. package/src/commands/session.ts +6 -1
  16. package/src/config/keybindings.ts +5 -0
  17. package/src/config/model-profile-activation.ts +1 -1
  18. package/src/config/model-profiles.ts +14 -14
  19. package/src/config/settings-schema.ts +41 -1
  20. package/src/coordinator-mcp/model-preset.ts +100 -0
  21. package/src/coordinator-mcp/server.ts +2735 -435
  22. package/src/daemon/control-types.ts +11 -0
  23. package/src/daemon/operator-contract.ts +121 -0
  24. package/src/defaults/skc/extensions/grok-cli-vendor/src/models/catalog.ts +57 -17
  25. package/src/defaults/skc/extensions/grok-cli-vendor/src/payload/sanitize.ts +15 -2
  26. package/src/defaults/skc/extensions/grok-cli-vendor/src/provider/register.ts +5 -1
  27. package/src/internal-urls/docs-index.generated.ts +7 -7
  28. package/src/internal-urls/local-protocol.ts +29 -10
  29. package/src/main.ts +298 -75
  30. package/src/modes/acp/acp-agent.ts +728 -98
  31. package/src/modes/acp/acp-mode.ts +29 -2
  32. package/src/modes/components/assistant-message.ts +32 -3
  33. package/src/modes/components/bash-execution.ts +43 -13
  34. package/src/modes/components/custom-editor.ts +5 -0
  35. package/src/modes/components/hook-selector.ts +12 -0
  36. package/src/modes/components/irc-sidebar.ts +206 -0
  37. package/src/modes/components/pet-selector.ts +55 -0
  38. package/src/modes/components/sayknow-pet-widget.ts +360 -0
  39. package/src/modes/components/session-selector.ts +222 -221
  40. package/src/modes/components/settings-selector.ts +10 -0
  41. package/src/modes/components/tool-execution.ts +47 -6
  42. package/src/modes/components/user-message.ts +26 -9
  43. package/src/modes/controllers/command-controller.ts +28 -2
  44. package/src/modes/controllers/event-controller.ts +159 -36
  45. package/src/modes/controllers/extension-ui-controller.ts +36 -17
  46. package/src/modes/controllers/input-controller.ts +8 -2
  47. package/src/modes/controllers/selector-controller.ts +62 -15
  48. package/src/modes/interactive-mode.ts +183 -7
  49. package/src/modes/irc-observation-ledger.ts +127 -0
  50. package/src/modes/shared/agent-wire/deep-interview-gate.ts +45 -2
  51. package/src/modes/shared/agent-wire/unattended-session.ts +288 -1
  52. package/src/modes/shared/agent-wire/workflow-gate-broker.ts +130 -20
  53. package/src/modes/types.ts +34 -2
  54. package/src/modes/utils/hotkeys-markdown.ts +1 -0
  55. package/src/modes/utils/irc-message.ts +138 -0
  56. package/src/modes/utils/ui-helpers.ts +216 -50
  57. package/src/notifications/config.ts +44 -0
  58. package/src/notifications/daemon-paths.ts +8 -0
  59. package/src/notifications/index.ts +535 -75
  60. package/src/notifications/lifecycle-control-runtime.ts +930 -77
  61. package/src/notifications/notification-service.ts +710 -0
  62. package/src/notifications/rate-limit-pool.ts +46 -12
  63. package/src/notifications/telegram-daemon-control.ts +27 -2
  64. package/src/notifications/telegram-daemon.ts +635 -94
  65. package/src/notifications/telegram-reference.ts +73 -13
  66. package/src/notifications/topic-registry.ts +86 -9
  67. package/src/prompts/system/irc-peer-roster.md +1 -0
  68. package/src/prompts/system/subagent-system-prompt.md +1 -0
  69. package/src/prompts/system/system-prompt.md +1 -0
  70. package/src/prompts/tools/irc.md +1 -1
  71. package/src/registry/agent-registry.ts +3 -0
  72. package/src/sdk.ts +52 -1
  73. package/src/secrets/obfuscator.ts +4 -2
  74. package/src/session/agent-session.ts +396 -145
  75. package/src/session/cache-economics.ts +275 -0
  76. package/src/session/provider-safety-stop.ts +9 -0
  77. package/src/session/session-dump-format.ts +6 -0
  78. package/src/session/session-manager.ts +809 -39
  79. package/src/session/session-storage.ts +582 -38
  80. package/src/skc-runtime/session-state-sidecar.ts +756 -135
  81. package/src/skc-runtime/tmux-common.ts +26 -7
  82. package/src/skc-runtime/tmux-gc.ts +52 -9
  83. package/src/skc-runtime/tmux-owner-isolation-cli.ts +185 -0
  84. package/src/skc-runtime/tmux-owner-isolation.ts +2060 -0
  85. package/src/skc-runtime/tmux-sessions.ts +871 -68
  86. package/src/slash-commands/builtin-registry.ts +143 -9
  87. package/src/task/executor.ts +12 -3
  88. package/src/task/index.ts +8 -0
  89. package/src/task/receipt.ts +7 -2
  90. package/src/task/types.ts +26 -0
  91. package/src/tools/ask-answer-registry.ts +25 -0
  92. package/src/tools/browser/tab-supervisor.ts +63 -6
  93. package/src/tools/browser.ts +8 -3
  94. package/src/tools/index.ts +81 -6
  95. package/src/tools/sqlite-reader.ts +8 -4
  96. package/src/tui/output-block.ts +18 -6
  97. package/src/utils/external-editor.ts +6 -1
  98. package/dist/types/append-only-mode.d.ts +0 -31
  99. package/dist/types/async/index.d.ts +0 -2
  100. package/dist/types/async/job-manager.d.ts +0 -328
  101. package/dist/types/async/support.d.ts +0 -2
  102. package/dist/types/autoresearch/dashboard.d.ts +0 -4
  103. package/dist/types/autoresearch/git.d.ts +0 -36
  104. package/dist/types/autoresearch/helpers.d.ts +0 -24
  105. package/dist/types/autoresearch/index.d.ts +0 -2
  106. package/dist/types/autoresearch/state.d.ts +0 -17
  107. package/dist/types/autoresearch/storage.d.ts +0 -142
  108. package/dist/types/autoresearch/tools/init-experiment.d.ts +0 -31
  109. package/dist/types/autoresearch/tools/log-experiment.d.ts +0 -23
  110. package/dist/types/autoresearch/tools/run-experiment.d.ts +0 -8
  111. package/dist/types/autoresearch/tools/update-notes.d.ts +0 -12
  112. package/dist/types/autoresearch/types.d.ts +0 -154
  113. package/dist/types/build-metadata.d.ts +0 -7
  114. package/dist/types/capability/context-file.d.ts +0 -17
  115. package/dist/types/capability/extension-module.d.ts +0 -15
  116. package/dist/types/capability/extension.d.ts +0 -28
  117. package/dist/types/capability/fs.d.ts +0 -20
  118. package/dist/types/capability/hook.d.ts +0 -19
  119. package/dist/types/capability/index.d.ts +0 -80
  120. package/dist/types/capability/instruction.d.ts +0 -17
  121. package/dist/types/capability/mcp.d.ts +0 -49
  122. package/dist/types/capability/prompt.d.ts +0 -15
  123. package/dist/types/capability/rule.d.ts +0 -69
  124. package/dist/types/capability/settings.d.ts +0 -15
  125. package/dist/types/capability/skill.d.ts +0 -36
  126. package/dist/types/capability/slash-command.d.ts +0 -17
  127. package/dist/types/capability/ssh.d.ts +0 -23
  128. package/dist/types/capability/system-prompt.d.ts +0 -15
  129. package/dist/types/capability/tool.d.ts +0 -19
  130. package/dist/types/capability/types.d.ts +0 -154
  131. package/dist/types/cli/agents-cli.d.ts +0 -12
  132. package/dist/types/cli/args.d.ts +0 -57
  133. package/dist/types/cli/auth-broker-cli.d.ts +0 -25
  134. package/dist/types/cli/auth-gateway-cli.d.ts +0 -18
  135. package/dist/types/cli/classify-install-target.d.ts +0 -18
  136. package/dist/types/cli/commands/init-xdg.d.ts +0 -1
  137. package/dist/types/cli/completion-cli.d.ts +0 -41
  138. package/dist/types/cli/config-cli.d.ts +0 -23
  139. package/dist/types/cli/daemon-cli.d.ts +0 -25
  140. package/dist/types/cli/fast-help.d.ts +0 -1
  141. package/dist/types/cli/file-processor.d.ts +0 -11
  142. package/dist/types/cli/fixture-report.d.ts +0 -20
  143. package/dist/types/cli/fixture-report.test.d.ts +0 -1
  144. package/dist/types/cli/grep-cli.d.ts +0 -17
  145. package/dist/types/cli/initial-message.d.ts +0 -17
  146. package/dist/types/cli/list-models.d.ts +0 -36
  147. package/dist/types/cli/local-provider-smoke.d.ts +0 -59
  148. package/dist/types/cli/mcp-cli.d.ts +0 -25
  149. package/dist/types/cli/migrate-cli.d.ts +0 -20
  150. package/dist/types/cli/nofile-limit.d.ts +0 -11
  151. package/dist/types/cli/notify-cli.d.ts +0 -37
  152. package/dist/types/cli/plugin-cli.d.ts +0 -34
  153. package/dist/types/cli/read-cli.d.ts +0 -4
  154. package/dist/types/cli/session-picker.d.ts +0 -3
  155. package/dist/types/cli/setup-cli.d.ts +0 -73
  156. package/dist/types/cli/shell-cli.d.ts +0 -8
  157. package/dist/types/cli/skills-cli.d.ts +0 -9
  158. package/dist/types/cli/ssh-cli.d.ts +0 -21
  159. package/dist/types/cli/stats-cli.d.ts +0 -17
  160. package/dist/types/cli/telegram-cli.d.ts +0 -13
  161. package/dist/types/cli/update-cli.d.ts +0 -89
  162. package/dist/types/cli/web-search-cli.d.ts +0 -32
  163. package/dist/types/cli/worktree-cli.d.ts +0 -26
  164. package/dist/types/cli.d.ts +0 -135
  165. package/dist/types/commands/acp.d.ts +0 -12
  166. package/dist/types/commands/agents.d.ts +0 -34
  167. package/dist/types/commands/auth-broker.d.ts +0 -54
  168. package/dist/types/commands/auth-gateway.d.ts +0 -32
  169. package/dist/types/commands/codex-native-hook.d.ts +0 -6
  170. package/dist/types/commands/commit.d.ts +0 -27
  171. package/dist/types/commands/completion.d.ts +0 -29
  172. package/dist/types/commands/config.d.ts +0 -33
  173. package/dist/types/commands/contribution-prep.d.ts +0 -18
  174. package/dist/types/commands/coordinator.d.ts +0 -19
  175. package/dist/types/commands/daemon.d.ts +0 -41
  176. package/dist/types/commands/deep-interview.d.ts +0 -54
  177. package/dist/types/commands/gc.d.ts +0 -26
  178. package/dist/types/commands/grep.d.ts +0 -42
  179. package/dist/types/commands/harness.d.ts +0 -43
  180. package/dist/types/commands/launch.d.ts +0 -130
  181. package/dist/types/commands/local-provider.d.ts +0 -34
  182. package/dist/types/commands/mcp-serve.d.ts +0 -24
  183. package/dist/types/commands/mcp.d.ts +0 -70
  184. package/dist/types/commands/migrate.d.ts +0 -33
  185. package/dist/types/commands/notify.d.ts +0 -41
  186. package/dist/types/commands/plugin.d.ts +0 -58
  187. package/dist/types/commands/ralplan.d.ts +0 -7
  188. package/dist/types/commands/read.d.ts +0 -15
  189. package/dist/types/commands/rlm.d.ts +0 -10
  190. package/dist/types/commands/session.d.ts +0 -30
  191. package/dist/types/commands/setup.d.ts +0 -105
  192. package/dist/types/commands/shell.d.ts +0 -21
  193. package/dist/types/commands/skills.d.ts +0 -26
  194. package/dist/types/commands/ssh.d.ts +0 -48
  195. package/dist/types/commands/state.d.ts +0 -7
  196. package/dist/types/commands/stats.d.ts +0 -25
  197. package/dist/types/commands/team.d.ts +0 -24
  198. package/dist/types/commands/telegram.d.ts +0 -22
  199. package/dist/types/commands/ultragoal.d.ts +0 -8
  200. package/dist/types/commands/update.d.ts +0 -20
  201. package/dist/types/commands/web-search.d.ts +0 -87
  202. package/dist/types/commands/worktree.d.ts +0 -34
  203. package/dist/types/commit/agentic/agent.d.ts +0 -31
  204. package/dist/types/commit/agentic/fallback.d.ts +0 -5
  205. package/dist/types/commit/agentic/index.d.ts +0 -2
  206. package/dist/types/commit/agentic/state.d.ts +0 -58
  207. package/dist/types/commit/agentic/tools/analyze-file.d.ts +0 -19
  208. package/dist/types/commit/agentic/tools/git-file-diff.d.ts +0 -10
  209. package/dist/types/commit/agentic/tools/git-hunk.d.ts +0 -9
  210. package/dist/types/commit/agentic/tools/git-overview.d.ts +0 -9
  211. package/dist/types/commit/agentic/tools/index.d.ts +0 -16
  212. package/dist/types/commit/agentic/tools/propose-changelog.d.ts +0 -28
  213. package/dist/types/commit/agentic/tools/propose-commit.d.ts +0 -36
  214. package/dist/types/commit/agentic/tools/recent-commits.d.ts +0 -7
  215. package/dist/types/commit/agentic/tools/schemas.d.ts +0 -27
  216. package/dist/types/commit/agentic/tools/split-commit.d.ts +0 -53
  217. package/dist/types/commit/agentic/topo-sort.d.ts +0 -4
  218. package/dist/types/commit/agentic/trivial.d.ts +0 -7
  219. package/dist/types/commit/agentic/validation.d.ts +0 -20
  220. package/dist/types/commit/analysis/conventional.d.ts +0 -22
  221. package/dist/types/commit/analysis/index.d.ts +0 -4
  222. package/dist/types/commit/analysis/scope.d.ts +0 -6
  223. package/dist/types/commit/analysis/summary.d.ts +0 -19
  224. package/dist/types/commit/analysis/validation.d.ts +0 -8
  225. package/dist/types/commit/changelog/detect.d.ts +0 -2
  226. package/dist/types/commit/changelog/generate.d.ts +0 -30
  227. package/dist/types/commit/changelog/index.d.ts +0 -30
  228. package/dist/types/commit/changelog/parse.d.ts +0 -2
  229. package/dist/types/commit/cli.d.ts +0 -3
  230. package/dist/types/commit/git/diff.d.ts +0 -5
  231. package/dist/types/commit/index.d.ts +0 -4
  232. package/dist/types/commit/map-reduce/index.d.ts +0 -28
  233. package/dist/types/commit/map-reduce/map-phase.d.ts +0 -17
  234. package/dist/types/commit/map-reduce/reduce-phase.d.ts +0 -13
  235. package/dist/types/commit/map-reduce/utils.d.ts +0 -2
  236. package/dist/types/commit/message.d.ts +0 -2
  237. package/dist/types/commit/model-selection.d.ts +0 -15
  238. package/dist/types/commit/pipeline.d.ts +0 -5
  239. package/dist/types/commit/shared-llm.d.ts +0 -54
  240. package/dist/types/commit/types.d.ts +0 -78
  241. package/dist/types/commit/utils/exclusions.d.ts +0 -4
  242. package/dist/types/commit/utils.d.ts +0 -20
  243. package/dist/types/config/config-file.d.ts +0 -58
  244. package/dist/types/config/file-lock-gc.d.ts +0 -5
  245. package/dist/types/config/file-lock.d.ts +0 -36
  246. package/dist/types/config/keybindings.d.ts +0 -359
  247. package/dist/types/config/model-equivalence.d.ts +0 -24
  248. package/dist/types/config/model-profile-activation.d.ts +0 -64
  249. package/dist/types/config/model-profiles.d.ts +0 -39
  250. package/dist/types/config/model-registry.d.ts +0 -473
  251. package/dist/types/config/model-resolver.d.ts +0 -217
  252. package/dist/types/config/models-config-schema.d.ts +0 -700
  253. package/dist/types/config/prompt-templates.d.ts +0 -32
  254. package/dist/types/config/resolve-config-value.d.ts +0 -17
  255. package/dist/types/config/settings-schema.d.ts +0 -4183
  256. package/dist/types/config/settings.d.ts +0 -156
  257. package/dist/types/config/skill-settings-defaults.d.ts +0 -14
  258. package/dist/types/config/telegram-autostart.d.ts +0 -14
  259. package/dist/types/config/telegram-env-bridge.d.ts +0 -18
  260. package/dist/types/config.d.ts +0 -66
  261. package/dist/types/coordinator/contract.d.ts +0 -4
  262. package/dist/types/coordinator-mcp/policy.d.ts +0 -24
  263. package/dist/types/coordinator-mcp/safety.d.ts +0 -26
  264. package/dist/types/coordinator-mcp/server.d.ts +0 -66
  265. package/dist/types/cursor.d.ts +0 -24
  266. package/dist/types/daemon/builtin.d.ts +0 -20
  267. package/dist/types/daemon/control-types.d.ts +0 -57
  268. package/dist/types/daemon/runtime.d.ts +0 -25
  269. package/dist/types/dap/client.d.ts +0 -39
  270. package/dist/types/dap/config.d.ts +0 -6
  271. package/dist/types/dap/index.d.ts +0 -4
  272. package/dist/types/dap/session.d.ts +0 -108
  273. package/dist/types/dap/types.d.ts +0 -524
  274. package/dist/types/debug/crash-diagnostics.d.ts +0 -45
  275. package/dist/types/debug/index.d.ts +0 -15
  276. package/dist/types/debug/log-formatting.d.ts +0 -4
  277. package/dist/types/debug/log-viewer.d.ts +0 -68
  278. package/dist/types/debug/profiler.d.ts +0 -24
  279. package/dist/types/debug/raw-sse-buffer.d.ts +0 -44
  280. package/dist/types/debug/raw-sse.d.ts +0 -16
  281. package/dist/types/debug/report-bundle.d.ts +0 -55
  282. package/dist/types/debug/runtime-gauges.d.ts +0 -6
  283. package/dist/types/debug/system-info.d.ts +0 -26
  284. package/dist/types/deep-interview/plaintext-gate-guard.d.ts +0 -11
  285. package/dist/types/deep-interview/render-middleware.d.ts +0 -6
  286. package/dist/types/defaults/skc/extensions/grok-build/index.d.ts +0 -1
  287. package/dist/types/defaults/skc/extensions/grok-cli-vendor/src/index.d.ts +0 -1
  288. package/dist/types/defaults/skc/extensions/grok-cli-vendor/src/models/catalog.d.ts +0 -25
  289. package/dist/types/defaults/skc/extensions/grok-cli-vendor/src/payload/sanitize.d.ts +0 -27
  290. package/dist/types/defaults/skc/extensions/grok-cli-vendor/src/provider/billing.d.ts +0 -8
  291. package/dist/types/defaults/skc/extensions/grok-cli-vendor/src/provider/register.d.ts +0 -5
  292. package/dist/types/defaults/skc/extensions/grok-cli-vendor/src/provider/stream.d.ts +0 -10
  293. package/dist/types/defaults/skc/extensions/grok-cli-vendor/src/provider/usage.d.ts +0 -2
  294. package/dist/types/defaults/skc/extensions/grok-cli-vendor/src/shared/base-url.d.ts +0 -2
  295. package/dist/types/defaults/skc/extensions/grok-cli-vendor/src/shared/errors.d.ts +0 -38
  296. package/dist/types/defaults/skc-defaults.d.ts +0 -64
  297. package/dist/types/defaults/skc-grok-cli.d.ts +0 -5
  298. package/dist/types/discovery/agents-md.d.ts +0 -1
  299. package/dist/types/discovery/agents.d.ts +0 -12
  300. package/dist/types/discovery/builtin.d.ts +0 -1
  301. package/dist/types/discovery/claude-plugins.d.ts +0 -1
  302. package/dist/types/discovery/claude.d.ts +0 -1
  303. package/dist/types/discovery/cline.d.ts +0 -1
  304. package/dist/types/discovery/codex.d.ts +0 -1
  305. package/dist/types/discovery/cursor.d.ts +0 -19
  306. package/dist/types/discovery/gemini.d.ts +0 -1
  307. package/dist/types/discovery/github.d.ts +0 -1
  308. package/dist/types/discovery/helpers.d.ts +0 -271
  309. package/dist/types/discovery/index.d.ts +0 -48
  310. package/dist/types/discovery/mcp-json.d.ts +0 -1
  311. package/dist/types/discovery/opencode.d.ts +0 -1
  312. package/dist/types/discovery/plugin-dir-roots.d.ts +0 -15
  313. package/dist/types/discovery/ssh.d.ts +0 -1
  314. package/dist/types/discovery/substitute-plugin-root.d.ts +0 -5
  315. package/dist/types/discovery/vscode.d.ts +0 -1
  316. package/dist/types/discovery/windsurf.d.ts +0 -15
  317. package/dist/types/edit/apply-patch/index.d.ts +0 -35
  318. package/dist/types/edit/apply-patch/parser.d.ts +0 -34
  319. package/dist/types/edit/diff.d.ts +0 -75
  320. package/dist/types/edit/file-read-cache.d.ts +0 -25
  321. package/dist/types/edit/index.d.ts +0 -58
  322. package/dist/types/edit/modes/apply-patch.d.ts +0 -24
  323. package/dist/types/edit/modes/patch.d.ts +0 -99
  324. package/dist/types/edit/modes/replace.d.ts +0 -149
  325. package/dist/types/edit/normalize.d.ts +0 -43
  326. package/dist/types/edit/notebook.d.ts +0 -26
  327. package/dist/types/edit/read-file.d.ts +0 -8
  328. package/dist/types/edit/renderer.d.ts +0 -110
  329. package/dist/types/edit/streaming.d.ts +0 -66
  330. package/dist/types/eval/backend.d.ts +0 -40
  331. package/dist/types/eval/index.d.ts +0 -4
  332. package/dist/types/eval/js/context-manager.d.ts +0 -27
  333. package/dist/types/eval/js/executor.d.ts +0 -29
  334. package/dist/types/eval/js/index.d.ts +0 -10
  335. package/dist/types/eval/js/shared/helpers.d.ts +0 -38
  336. package/dist/types/eval/js/shared/indirect-eval.d.ts +0 -14
  337. package/dist/types/eval/js/shared/prelude.d.ts +0 -1
  338. package/dist/types/eval/js/shared/rewrite-imports.d.ts +0 -6
  339. package/dist/types/eval/js/shared/runtime.d.ts +0 -47
  340. package/dist/types/eval/js/shared/types.d.ts +0 -24
  341. package/dist/types/eval/js/tool-bridge.d.ts +0 -18
  342. package/dist/types/eval/js/worker-core.d.ts +0 -5
  343. package/dist/types/eval/js/worker-entry.d.ts +0 -1
  344. package/dist/types/eval/js/worker-protocol.d.ts +0 -77
  345. package/dist/types/eval/py/display.d.ts +0 -25
  346. package/dist/types/eval/py/executor.d.ts +0 -88
  347. package/dist/types/eval/py/index.d.ts +0 -10
  348. package/dist/types/eval/py/kernel.d.ts +0 -65
  349. package/dist/types/eval/py/prelude.d.ts +0 -1
  350. package/dist/types/eval/py/runtime.d.ts +0 -29
  351. package/dist/types/eval/py/tool-bridge.d.ts +0 -20
  352. package/dist/types/eval/types.d.ts +0 -52
  353. package/dist/types/exa/factory.d.ts +0 -13
  354. package/dist/types/exa/index.d.ts +0 -20
  355. package/dist/types/exa/mcp-client.d.ts +0 -45
  356. package/dist/types/exa/render.d.ts +0 -19
  357. package/dist/types/exa/researcher.d.ts +0 -9
  358. package/dist/types/exa/search.d.ts +0 -9
  359. package/dist/types/exa/types.d.ts +0 -155
  360. package/dist/types/exa/websets.d.ts +0 -9
  361. package/dist/types/exec/bash-executor.d.ts +0 -64
  362. package/dist/types/exec/exec.d.ts +0 -25
  363. package/dist/types/exec/idle-timeout-watchdog.d.ts +0 -18
  364. package/dist/types/exec/non-interactive-env.d.ts +0 -1
  365. package/dist/types/export/custom-share.d.ts +0 -20
  366. package/dist/types/export/html/index.d.ts +0 -10
  367. package/dist/types/export/html/template.generated.d.ts +0 -1
  368. package/dist/types/export/html/template.macro.d.ts +0 -5
  369. package/dist/types/export/ttsr.d.ts +0 -55
  370. package/dist/types/extensibility/custom-commands/bundled/ci-green/index.d.ts +0 -9
  371. package/dist/types/extensibility/custom-commands/index.d.ts +0 -2
  372. package/dist/types/extensibility/custom-commands/loader.d.ts +0 -29
  373. package/dist/types/extensibility/custom-commands/types.d.ts +0 -106
  374. package/dist/types/extensibility/custom-tools/index.d.ts +0 -6
  375. package/dist/types/extensibility/custom-tools/loader.d.ts +0 -69
  376. package/dist/types/extensibility/custom-tools/types.d.ts +0 -229
  377. package/dist/types/extensibility/custom-tools/wrapper.d.ts +0 -24
  378. package/dist/types/extensibility/extensions/compact-handler.d.ts +0 -26
  379. package/dist/types/extensibility/extensions/get-commands-handler.d.ts +0 -29
  380. package/dist/types/extensibility/extensions/index.d.ts +0 -9
  381. package/dist/types/extensibility/extensions/loader.d.ts +0 -43
  382. package/dist/types/extensibility/extensions/prefix-command-bridge.d.ts +0 -35
  383. package/dist/types/extensibility/extensions/runner.d.ts +0 -135
  384. package/dist/types/extensibility/extensions/types.d.ts +0 -918
  385. package/dist/types/extensibility/extensions/wrapper.d.ts +0 -55
  386. package/dist/types/extensibility/hooks/index.d.ts +0 -5
  387. package/dist/types/extensibility/hooks/loader.d.ts +0 -89
  388. package/dist/types/extensibility/hooks/runner.d.ts +0 -126
  389. package/dist/types/extensibility/hooks/tool-wrapper.d.ts +0 -25
  390. package/dist/types/extensibility/hooks/types.d.ts +0 -431
  391. package/dist/types/extensibility/plugins/doctor.d.ts +0 -3
  392. package/dist/types/extensibility/plugins/git-url.d.ts +0 -34
  393. package/dist/types/extensibility/plugins/index.d.ts +0 -8
  394. package/dist/types/extensibility/plugins/installer.d.ts +0 -5
  395. package/dist/types/extensibility/plugins/legacy-pi-compat.d.ts +0 -2
  396. package/dist/types/extensibility/plugins/loader.d.ts +0 -31
  397. package/dist/types/extensibility/plugins/manager.d.ts +0 -65
  398. package/dist/types/extensibility/plugins/marketplace/cache.d.ts +0 -41
  399. package/dist/types/extensibility/plugins/marketplace/fetcher.d.ts +0 -48
  400. package/dist/types/extensibility/plugins/marketplace/index.d.ts +0 -6
  401. package/dist/types/extensibility/plugins/marketplace/manager.d.ts +0 -57
  402. package/dist/types/extensibility/plugins/marketplace/registry.d.ts +0 -34
  403. package/dist/types/extensibility/plugins/marketplace/source-resolver.d.ts +0 -28
  404. package/dist/types/extensibility/plugins/marketplace/types.d.ts +0 -130
  405. package/dist/types/extensibility/plugins/parser.d.ts +0 -52
  406. package/dist/types/extensibility/plugins/security-scanner.d.ts +0 -31
  407. package/dist/types/extensibility/plugins/types.d.ts +0 -149
  408. package/dist/types/extensibility/runtime-skill-discovery.d.ts +0 -20
  409. package/dist/types/extensibility/shared-events.d.ts +0 -281
  410. package/dist/types/extensibility/skc-plugins/activation.d.ts +0 -14
  411. package/dist/types/extensibility/skc-plugins/compiler.d.ts +0 -19
  412. package/dist/types/extensibility/skc-plugins/constrained-hooks.d.ts +0 -29
  413. package/dist/types/extensibility/skc-plugins/index.d.ts +0 -18
  414. package/dist/types/extensibility/skc-plugins/injection.d.ts +0 -40
  415. package/dist/types/extensibility/skc-plugins/installer.d.ts +0 -13
  416. package/dist/types/extensibility/skc-plugins/loader.d.ts +0 -3
  417. package/dist/types/extensibility/skc-plugins/mcp-policy.d.ts +0 -26
  418. package/dist/types/extensibility/skc-plugins/observability.d.ts +0 -27
  419. package/dist/types/extensibility/skc-plugins/paths.d.ts +0 -8
  420. package/dist/types/extensibility/skc-plugins/prompt-appendix.d.ts +0 -16
  421. package/dist/types/extensibility/skc-plugins/registry.d.ts +0 -32
  422. package/dist/types/extensibility/skc-plugins/runtime-adapters.d.ts +0 -64
  423. package/dist/types/extensibility/skc-plugins/schema.d.ts +0 -3
  424. package/dist/types/extensibility/skc-plugins/session-validation.d.ts +0 -42
  425. package/dist/types/extensibility/skc-plugins/state.d.ts +0 -9
  426. package/dist/types/extensibility/skc-plugins/tools.d.ts +0 -8
  427. package/dist/types/extensibility/skc-plugins/types.d.ts +0 -220
  428. package/dist/types/extensibility/skc-plugins/validation.d.ts +0 -11
  429. package/dist/types/extensibility/skills.d.ts +0 -82
  430. package/dist/types/extensibility/slash-commands.d.ts +0 -48
  431. package/dist/types/extensibility/tool-proxy.d.ts +0 -4
  432. package/dist/types/extensibility/typebox.d.ts +0 -155
  433. package/dist/types/extensibility/utils.d.ts +0 -12
  434. package/dist/types/goals/index.d.ts +0 -3
  435. package/dist/types/goals/runtime.d.ts +0 -63
  436. package/dist/types/goals/state.d.ts +0 -32
  437. package/dist/types/goals/tools/goal-tool.d.ts +0 -62
  438. package/dist/types/harness-control-plane/classifier.d.ts +0 -13
  439. package/dist/types/harness-control-plane/control-endpoint.d.ts +0 -31
  440. package/dist/types/harness-control-plane/finalize.d.ts +0 -60
  441. package/dist/types/harness-control-plane/gc-adapter.d.ts +0 -3
  442. package/dist/types/harness-control-plane/operate.d.ts +0 -35
  443. package/dist/types/harness-control-plane/owner.d.ts +0 -53
  444. package/dist/types/harness-control-plane/phase-rollup.d.ts +0 -23
  445. package/dist/types/harness-control-plane/preserve.d.ts +0 -19
  446. package/dist/types/harness-control-plane/receipt-ingest.d.ts +0 -19
  447. package/dist/types/harness-control-plane/receipt-spool.d.ts +0 -19
  448. package/dist/types/harness-control-plane/receipts.d.ts +0 -149
  449. package/dist/types/harness-control-plane/rpc-adapter.d.ts +0 -69
  450. package/dist/types/harness-control-plane/seams.d.ts +0 -21
  451. package/dist/types/harness-control-plane/session-lease.d.ts +0 -65
  452. package/dist/types/harness-control-plane/state-machine.d.ts +0 -24
  453. package/dist/types/harness-control-plane/storage.d.ts +0 -81
  454. package/dist/types/harness-control-plane/types.d.ts +0 -187
  455. package/dist/types/hashline/anchors.d.ts +0 -20
  456. package/dist/types/hashline/apply.d.ts +0 -14
  457. package/dist/types/hashline/constants.d.ts +0 -19
  458. package/dist/types/hashline/diff-preview.d.ts +0 -2
  459. package/dist/types/hashline/diff.d.ts +0 -17
  460. package/dist/types/hashline/execute.d.ts +0 -4
  461. package/dist/types/hashline/hash.d.ts +0 -123
  462. package/dist/types/hashline/index.d.ts +0 -13
  463. package/dist/types/hashline/input.d.ts +0 -11
  464. package/dist/types/hashline/parser.d.ts +0 -7
  465. package/dist/types/hashline/prefixes.d.ts +0 -7
  466. package/dist/types/hashline/recovery.d.ts +0 -27
  467. package/dist/types/hashline/stream.d.ts +0 -2
  468. package/dist/types/hashline/types.d.ts +0 -77
  469. package/dist/types/hindsight/backend.d.ts +0 -13
  470. package/dist/types/hindsight/bank.d.ts +0 -54
  471. package/dist/types/hindsight/client.d.ts +0 -224
  472. package/dist/types/hindsight/config.d.ts +0 -51
  473. package/dist/types/hindsight/content.d.ts +0 -70
  474. package/dist/types/hindsight/index.d.ts +0 -8
  475. package/dist/types/hindsight/mental-models.d.ts +0 -125
  476. package/dist/types/hindsight/state.d.ts +0 -99
  477. package/dist/types/hindsight/transcript.d.ts +0 -28
  478. package/dist/types/hooks/codex-native-hooks-config.d.ts +0 -29
  479. package/dist/types/hooks/native-skill-hook.d.ts +0 -28
  480. package/dist/types/hooks/skill-keywords.d.ts +0 -18
  481. package/dist/types/hooks/skill-state.d.ts +0 -93
  482. package/dist/types/i18n/index.d.ts +0 -38
  483. package/dist/types/i18n/messages/de.d.ts +0 -2
  484. package/dist/types/i18n/messages/de.messages.d.ts +0 -1
  485. package/dist/types/i18n/messages/de.settings.d.ts +0 -1
  486. package/dist/types/i18n/messages/en.d.ts +0 -81
  487. package/dist/types/i18n/messages/es.d.ts +0 -2
  488. package/dist/types/i18n/messages/es.messages.d.ts +0 -1
  489. package/dist/types/i18n/messages/es.settings.d.ts +0 -1
  490. package/dist/types/i18n/messages/fr.d.ts +0 -2
  491. package/dist/types/i18n/messages/fr.messages.d.ts +0 -1
  492. package/dist/types/i18n/messages/fr.settings.d.ts +0 -1
  493. package/dist/types/i18n/messages/ja.d.ts +0 -2
  494. package/dist/types/i18n/messages/ja.messages.d.ts +0 -1
  495. package/dist/types/i18n/messages/ja.settings.d.ts +0 -1
  496. package/dist/types/i18n/messages/ko.d.ts +0 -2
  497. package/dist/types/i18n/messages/ko.messages.d.ts +0 -1
  498. package/dist/types/i18n/messages/ko.settings.d.ts +0 -1
  499. package/dist/types/i18n/messages/zh.d.ts +0 -2
  500. package/dist/types/i18n/messages/zh.messages.d.ts +0 -1
  501. package/dist/types/i18n/messages/zh.settings.d.ts +0 -1
  502. package/dist/types/index.d.ts +0 -33
  503. package/dist/types/internal-urls/agent-protocol.d.ts +0 -12
  504. package/dist/types/internal-urls/artifact-protocol.d.ts +0 -6
  505. package/dist/types/internal-urls/docs-index.generated.d.ts +0 -2
  506. package/dist/types/internal-urls/index.d.ts +0 -20
  507. package/dist/types/internal-urls/issue-pr-protocol.d.ts +0 -17
  508. package/dist/types/internal-urls/json-query.d.ts +0 -30
  509. package/dist/types/internal-urls/local-protocol.d.ts +0 -39
  510. package/dist/types/internal-urls/mcp-protocol.d.ts +0 -12
  511. package/dist/types/internal-urls/memory-protocol.d.ts +0 -23
  512. package/dist/types/internal-urls/parse.d.ts +0 -19
  513. package/dist/types/internal-urls/registry-helpers.d.ts +0 -11
  514. package/dist/types/internal-urls/router.d.ts +0 -14
  515. package/dist/types/internal-urls/rule-protocol.d.ts +0 -6
  516. package/dist/types/internal-urls/skc-protocol.d.ts +0 -12
  517. package/dist/types/internal-urls/skill-protocol.d.ts +0 -13
  518. package/dist/types/internal-urls/types.d.ts +0 -109
  519. package/dist/types/lsp/client.d.ts +0 -66
  520. package/dist/types/lsp/clients/biome-client.d.ts +0 -16
  521. package/dist/types/lsp/clients/index.d.ts +0 -19
  522. package/dist/types/lsp/clients/lsp-linter-client.d.ts +0 -16
  523. package/dist/types/lsp/clients/swiftlint-client.d.ts +0 -20
  524. package/dist/types/lsp/config.d.ts +0 -65
  525. package/dist/types/lsp/edits.d.ts +0 -23
  526. package/dist/types/lsp/index.d.ts +0 -130
  527. package/dist/types/lsp/lspmux.d.ts +0 -58
  528. package/dist/types/lsp/render.d.ts +0 -32
  529. package/dist/types/lsp/startup-events.d.ts +0 -12
  530. package/dist/types/lsp/types.d.ts +0 -306
  531. package/dist/types/lsp/utils.d.ts +0 -108
  532. package/dist/types/main.d.ts +0 -75
  533. package/dist/types/memories/index.d.ts +0 -28
  534. package/dist/types/memories/storage.d.ts +0 -110
  535. package/dist/types/memory-backend/index.d.ts +0 -4
  536. package/dist/types/memory-backend/local-backend.d.ts +0 -9
  537. package/dist/types/memory-backend/off-backend.d.ts +0 -7
  538. package/dist/types/memory-backend/resolve.d.ts +0 -15
  539. package/dist/types/memory-backend/types.d.ts +0 -61
  540. package/dist/types/migrate/action-planner.d.ts +0 -11
  541. package/dist/types/migrate/adapters/claude-code.d.ts +0 -2
  542. package/dist/types/migrate/adapters/codex.d.ts +0 -5
  543. package/dist/types/migrate/adapters/index.d.ts +0 -45
  544. package/dist/types/migrate/adapters/opencode.d.ts +0 -2
  545. package/dist/types/migrate/executor.d.ts +0 -2
  546. package/dist/types/migrate/mcp-mapper.d.ts +0 -20
  547. package/dist/types/migrate/report.d.ts +0 -18
  548. package/dist/types/migrate/skill-normalizer.d.ts +0 -27
  549. package/dist/types/migrate/types.d.ts +0 -126
  550. package/dist/types/modes/acp/acp-agent.d.ts +0 -61
  551. package/dist/types/modes/acp/acp-client-bridge.d.ts +0 -9
  552. package/dist/types/modes/acp/acp-event-mapper.d.ts +0 -36
  553. package/dist/types/modes/acp/acp-mode.d.ts +0 -5
  554. package/dist/types/modes/acp/index.d.ts +0 -2
  555. package/dist/types/modes/acp/terminal-auth.d.ts +0 -6
  556. package/dist/types/modes/bridge/auth.d.ts +0 -12
  557. package/dist/types/modes/bridge/bridge-client-bridge.d.ts +0 -9
  558. package/dist/types/modes/bridge/bridge-mode.d.ts +0 -47
  559. package/dist/types/modes/bridge/bridge-ui-context.d.ts +0 -88
  560. package/dist/types/modes/bridge/event-stream.d.ts +0 -8
  561. package/dist/types/modes/components/agent-dashboard.d.ts +0 -21
  562. package/dist/types/modes/components/assistant-message.d.ts +0 -20
  563. package/dist/types/modes/components/bash-execution.d.ts +0 -29
  564. package/dist/types/modes/components/bordered-loader.d.ts +0 -11
  565. package/dist/types/modes/components/branch-summary-message.d.ts +0 -13
  566. package/dist/types/modes/components/btw-panel.d.ts +0 -16
  567. package/dist/types/modes/components/compaction-summary-message.d.ts +0 -13
  568. package/dist/types/modes/components/countdown-timer.d.ts +0 -14
  569. package/dist/types/modes/components/custom-editor.d.ts +0 -67
  570. package/dist/types/modes/components/custom-message.d.ts +0 -15
  571. package/dist/types/modes/components/custom-model-preset-wizard.d.ts +0 -12
  572. package/dist/types/modes/components/custom-provider-wizard.d.ts +0 -10
  573. package/dist/types/modes/components/diff.d.ts +0 -11
  574. package/dist/types/modes/components/dynamic-border.d.ts +0 -14
  575. package/dist/types/modes/components/eval-execution.d.ts +0 -23
  576. package/dist/types/modes/components/execution-shared.d.ts +0 -46
  577. package/dist/types/modes/components/extensions/extension-dashboard.d.ts +0 -26
  578. package/dist/types/modes/components/extensions/extension-list.d.ts +0 -39
  579. package/dist/types/modes/components/extensions/index.d.ts +0 -8
  580. package/dist/types/modes/components/extensions/inspector-panel.d.ts +0 -8
  581. package/dist/types/modes/components/extensions/state-manager.d.ts +0 -50
  582. package/dist/types/modes/components/extensions/types.d.ts +0 -151
  583. package/dist/types/modes/components/footer.d.ts +0 -30
  584. package/dist/types/modes/components/history-search.d.ts +0 -7
  585. package/dist/types/modes/components/hook-editor.d.ts +0 -18
  586. package/dist/types/modes/components/hook-input.d.ts +0 -15
  587. package/dist/types/modes/components/hook-message.d.ts +0 -15
  588. package/dist/types/modes/components/hook-selector.d.ts +0 -54
  589. package/dist/types/modes/components/index.d.ts +0 -35
  590. package/dist/types/modes/components/jobs-overlay-model.d.ts +0 -31
  591. package/dist/types/modes/components/jobs-overlay.d.ts +0 -30
  592. package/dist/types/modes/components/keybinding-hints.d.ts +0 -40
  593. package/dist/types/modes/components/login-dialog.d.ts +0 -32
  594. package/dist/types/modes/components/message-frame.d.ts +0 -42
  595. package/dist/types/modes/components/model-selector.d.ts +0 -60
  596. package/dist/types/modes/components/oauth-selector.d.ts +0 -17
  597. package/dist/types/modes/components/plugin-selector.d.ts +0 -26
  598. package/dist/types/modes/components/plugin-settings.d.ts +0 -66
  599. package/dist/types/modes/components/provider-onboarding-selector.d.ts +0 -7
  600. package/dist/types/modes/components/queue-mode-selector.d.ts +0 -9
  601. package/dist/types/modes/components/queued-message-selector.d.ts +0 -12
  602. package/dist/types/modes/components/read-tool-group.d.ts +0 -30
  603. package/dist/types/modes/components/runtime-mcp-add-wizard.d.ts +0 -26
  604. package/dist/types/modes/components/session-observer-overlay.d.ts +0 -11
  605. package/dist/types/modes/components/session-selector.d.ts +0 -30
  606. package/dist/types/modes/components/settings-defs.d.ts +0 -50
  607. package/dist/types/modes/components/settings-selector.d.ts +0 -62
  608. package/dist/types/modes/components/show-images-selector.d.ts +0 -9
  609. package/dist/types/modes/components/skill-hud/render.d.ts +0 -2
  610. package/dist/types/modes/components/skill-message.d.ts +0 -10
  611. package/dist/types/modes/components/status-line/context-thresholds.d.ts +0 -4
  612. package/dist/types/modes/components/status-line/git-utils.d.ts +0 -28
  613. package/dist/types/modes/components/status-line/index.d.ts +0 -4
  614. package/dist/types/modes/components/status-line/presets.d.ts +0 -3
  615. package/dist/types/modes/components/status-line/segments.d.ts +0 -5
  616. package/dist/types/modes/components/status-line/separators.d.ts +0 -3
  617. package/dist/types/modes/components/status-line/token-rate.d.ts +0 -10
  618. package/dist/types/modes/components/status-line/types.d.ts +0 -83
  619. package/dist/types/modes/components/status-line.d.ts +0 -104
  620. package/dist/types/modes/components/theme-selector.d.ts +0 -10
  621. package/dist/types/modes/components/thinking-selector.d.ts +0 -15
  622. package/dist/types/modes/components/todo-reminder.d.ts +0 -13
  623. package/dist/types/modes/components/tool-execution.d.ts +0 -54
  624. package/dist/types/modes/components/tree-selector.d.ts +0 -31
  625. package/dist/types/modes/components/ttsr-notification.d.ts +0 -13
  626. package/dist/types/modes/components/two-column-body.d.ts +0 -9
  627. package/dist/types/modes/components/user-message-selector.d.ts +0 -28
  628. package/dist/types/modes/components/user-message.d.ts +0 -7
  629. package/dist/types/modes/components/visual-truncate.d.ts +0 -19
  630. package/dist/types/modes/components/welcome.d.ts +0 -46
  631. package/dist/types/modes/controllers/btw-controller.d.ts +0 -10
  632. package/dist/types/modes/controllers/command-controller-shared.d.ts +0 -47
  633. package/dist/types/modes/controllers/command-controller.d.ts +0 -40
  634. package/dist/types/modes/controllers/event-controller.d.ts +0 -17
  635. package/dist/types/modes/controllers/extension-ui-controller.d.ts +0 -80
  636. package/dist/types/modes/controllers/input-controller.d.ts +0 -42
  637. package/dist/types/modes/controllers/runtime-mcp-command-controller.d.ts +0 -10
  638. package/dist/types/modes/controllers/selector-controller.d.ts +0 -67
  639. package/dist/types/modes/controllers/ssh-command-controller.d.ts +0 -10
  640. package/dist/types/modes/controllers/todo-command-controller.d.ts +0 -7
  641. package/dist/types/modes/emoji-autocomplete.d.ts +0 -16
  642. package/dist/types/modes/index.d.ts +0 -10
  643. package/dist/types/modes/interactive-mode.d.ts +0 -275
  644. package/dist/types/modes/jobs-observer.d.ts +0 -57
  645. package/dist/types/modes/oauth-manual-input.d.ts +0 -8
  646. package/dist/types/modes/print-mode.d.ts +0 -45
  647. package/dist/types/modes/prompt-action-autocomplete.d.ts +0 -50
  648. package/dist/types/modes/rpc/host-tools.d.ts +0 -1
  649. package/dist/types/modes/rpc/host-uris.d.ts +0 -1
  650. package/dist/types/modes/rpc/rpc-client.d.ts +0 -293
  651. package/dist/types/modes/rpc/rpc-mode.d.ts +0 -85
  652. package/dist/types/modes/rpc/rpc-socket-security.d.ts +0 -10
  653. package/dist/types/modes/rpc/rpc-types.d.ts +0 -794
  654. package/dist/types/modes/runtime-init.d.ts +0 -21
  655. package/dist/types/modes/session-observer-registry.d.ts +0 -26
  656. package/dist/types/modes/shared/agent-wire/approval-gate.d.ts +0 -57
  657. package/dist/types/modes/shared/agent-wire/command-contract.d.ts +0 -18
  658. package/dist/types/modes/shared/agent-wire/command-dispatch.d.ts +0 -37
  659. package/dist/types/modes/shared/agent-wire/command-validation.d.ts +0 -2
  660. package/dist/types/modes/shared/agent-wire/deep-interview-gate.d.ts +0 -94
  661. package/dist/types/modes/shared/agent-wire/event-contract.d.ts +0 -103
  662. package/dist/types/modes/shared/agent-wire/event-envelope.d.ts +0 -60
  663. package/dist/types/modes/shared/agent-wire/event-observation.d.ts +0 -37
  664. package/dist/types/modes/shared/agent-wire/handshake.d.ts +0 -56
  665. package/dist/types/modes/shared/agent-wire/host-tool-bridge.d.ts +0 -16
  666. package/dist/types/modes/shared/agent-wire/host-uri-bridge.d.ts +0 -17
  667. package/dist/types/modes/shared/agent-wire/protocol.d.ts +0 -25
  668. package/dist/types/modes/shared/agent-wire/responses.d.ts +0 -4
  669. package/dist/types/modes/shared/agent-wire/scopes.d.ts +0 -18
  670. package/dist/types/modes/shared/agent-wire/session-registry.d.ts +0 -25
  671. package/dist/types/modes/shared/agent-wire/ui-request-broker.d.ts +0 -42
  672. package/dist/types/modes/shared/agent-wire/ui-result.d.ts +0 -27
  673. package/dist/types/modes/shared/agent-wire/unattended-action-policy.d.ts +0 -29
  674. package/dist/types/modes/shared/agent-wire/unattended-audit.d.ts +0 -68
  675. package/dist/types/modes/shared/agent-wire/unattended-run-controller.d.ts +0 -161
  676. package/dist/types/modes/shared/agent-wire/unattended-session.d.ts +0 -82
  677. package/dist/types/modes/shared/agent-wire/workflow-gate-broker.d.ts +0 -114
  678. package/dist/types/modes/shared/agent-wire/workflow-gate-schema.d.ts +0 -39
  679. package/dist/types/modes/shared.d.ts +0 -15
  680. package/dist/types/modes/slash-command-visibility.d.ts +0 -1
  681. package/dist/types/modes/theme/defaults/index.d.ts +0 -652
  682. package/dist/types/modes/theme/mermaid-cache.d.ts +0 -9
  683. package/dist/types/modes/theme/shimmer.d.ts +0 -38
  684. package/dist/types/modes/theme/theme.d.ts +0 -275
  685. package/dist/types/modes/tmux-scroll.d.ts +0 -18
  686. package/dist/types/modes/types.d.ts +0 -292
  687. package/dist/types/modes/utils/abort-message.d.ts +0 -4
  688. package/dist/types/modes/utils/context-usage.d.ts +0 -51
  689. package/dist/types/modes/utils/hotkeys-markdown.d.ts +0 -5
  690. package/dist/types/modes/utils/injected-user-submission.d.ts +0 -52
  691. package/dist/types/modes/utils/keybinding-matchers.d.ts +0 -10
  692. package/dist/types/modes/utils/terminal-bell.d.ts +0 -3
  693. package/dist/types/modes/utils/tools-markdown.d.ts +0 -5
  694. package/dist/types/modes/utils/ui-helpers.d.ts +0 -66
  695. package/dist/types/notifications/attachment-registry.d.ts +0 -17
  696. package/dist/types/notifications/chat-adapters.d.ts +0 -9
  697. package/dist/types/notifications/config-commands.d.ts +0 -64
  698. package/dist/types/notifications/config.d.ts +0 -98
  699. package/dist/types/notifications/daemon-paths.d.ts +0 -10
  700. package/dist/types/notifications/engine.d.ts +0 -59
  701. package/dist/types/notifications/helpers.d.ts +0 -55
  702. package/dist/types/notifications/html-format.d.ts +0 -75
  703. package/dist/types/notifications/index.d.ts +0 -192
  704. package/dist/types/notifications/lifecycle-commands.d.ts +0 -69
  705. package/dist/types/notifications/lifecycle-control-runtime.d.ts +0 -99
  706. package/dist/types/notifications/lifecycle-orchestrator.d.ts +0 -144
  707. package/dist/types/notifications/managed-daemon.d.ts +0 -48
  708. package/dist/types/notifications/operator-runtime.d.ts +0 -52
  709. package/dist/types/notifications/rate-limit-pool.d.ts +0 -95
  710. package/dist/types/notifications/recent-activity.d.ts +0 -39
  711. package/dist/types/notifications/reply-sent-store.d.ts +0 -53
  712. package/dist/types/notifications/rich-draft.d.ts +0 -68
  713. package/dist/types/notifications/rich-render.d.ts +0 -90
  714. package/dist/types/notifications/telegram-cli.d.ts +0 -19
  715. package/dist/types/notifications/telegram-daemon-cli.d.ts +0 -28
  716. package/dist/types/notifications/telegram-daemon-control.d.ts +0 -64
  717. package/dist/types/notifications/telegram-daemon.d.ts +0 -431
  718. package/dist/types/notifications/telegram-reference.d.ts +0 -119
  719. package/dist/types/notifications/threaded-inbound.d.ts +0 -77
  720. package/dist/types/notifications/threaded-render.d.ts +0 -88
  721. package/dist/types/notifications/topic-registry.d.ts +0 -69
  722. package/dist/types/plan-mode/approved-plan.d.ts +0 -49
  723. package/dist/types/plan-mode/state.d.ts +0 -6
  724. package/dist/types/registry/agent-registry.d.ts +0 -62
  725. package/dist/types/reminders/star-reminder.d.ts +0 -115
  726. package/dist/types/research-plan/index.d.ts +0 -1
  727. package/dist/types/research-plan/ledger.d.ts +0 -33
  728. package/dist/types/rlm/artifacts.d.ts +0 -9
  729. package/dist/types/rlm/complete-research-tool.d.ts +0 -35
  730. package/dist/types/rlm/data-context.d.ts +0 -6
  731. package/dist/types/rlm/index.d.ts +0 -47
  732. package/dist/types/rlm/notebook.d.ts +0 -12
  733. package/dist/types/rlm/preset.d.ts +0 -23
  734. package/dist/types/rlm/python-tool.d.ts +0 -16
  735. package/dist/types/rlm/report.d.ts +0 -14
  736. package/dist/types/rlm/types.d.ts +0 -37
  737. package/dist/types/runtime/process-lifecycle.d.ts +0 -108
  738. package/dist/types/runtime-api-key.d.ts +0 -7
  739. package/dist/types/runtime-credential-selector.d.ts +0 -7
  740. package/dist/types/runtime-mcp/client.d.ts +0 -74
  741. package/dist/types/runtime-mcp/config-writer.d.ts +0 -89
  742. package/dist/types/runtime-mcp/config.d.ts +0 -77
  743. package/dist/types/runtime-mcp/discoverable-tool-metadata.d.ts +0 -7
  744. package/dist/types/runtime-mcp/index.d.ts +0 -18
  745. package/dist/types/runtime-mcp/json-rpc.d.ts +0 -22
  746. package/dist/types/runtime-mcp/loader.d.ts +0 -45
  747. package/dist/types/runtime-mcp/manager.d.ts +0 -195
  748. package/dist/types/runtime-mcp/oauth-discovery.d.ts +0 -40
  749. package/dist/types/runtime-mcp/oauth-flow.d.ts +0 -59
  750. package/dist/types/runtime-mcp/render.d.ts +0 -25
  751. package/dist/types/runtime-mcp/smithery-auth.d.ts +0 -16
  752. package/dist/types/runtime-mcp/smithery-connect.d.ts +0 -38
  753. package/dist/types/runtime-mcp/smithery-registry.d.ts +0 -51
  754. package/dist/types/runtime-mcp/tool-bridge.d.ts +0 -86
  755. package/dist/types/runtime-mcp/tool-cache.d.ts +0 -8
  756. package/dist/types/runtime-mcp/transports/http.d.ts +0 -36
  757. package/dist/types/runtime-mcp/transports/index.d.ts +0 -5
  758. package/dist/types/runtime-mcp/transports/stdio.d.ts +0 -29
  759. package/dist/types/runtime-mcp/types.d.ts +0 -349
  760. package/dist/types/sdk.d.ts +0 -254
  761. package/dist/types/secrets/index.d.ts +0 -9
  762. package/dist/types/secrets/obfuscator.d.ts +0 -23
  763. package/dist/types/secrets/regex.d.ts +0 -2
  764. package/dist/types/session/agent-session.d.ts +0 -1201
  765. package/dist/types/session/agent-storage.d.ts +0 -101
  766. package/dist/types/session/artifacts.d.ts +0 -64
  767. package/dist/types/session/auth-broker-config.d.ts +0 -13
  768. package/dist/types/session/auth-storage.d.ts +0 -6
  769. package/dist/types/session/blob-store.d.ts +0 -160
  770. package/dist/types/session/client-bridge.d.ts +0 -88
  771. package/dist/types/session/contribution-prep.d.ts +0 -47
  772. package/dist/types/session/file-mention-pruning.d.ts +0 -8
  773. package/dist/types/session/history-storage.d.ts +0 -16
  774. package/dist/types/session/messages.d.ts +0 -169
  775. package/dist/types/session/session-dump-format.d.ts +0 -22
  776. package/dist/types/session/session-manager.d.ts +0 -661
  777. package/dist/types/session/session-storage.d.ts +0 -88
  778. package/dist/types/session/streaming-output.d.ts +0 -209
  779. package/dist/types/session/tool-choice-queue.d.ts +0 -84
  780. package/dist/types/session/yield-queue.d.ts +0 -24
  781. package/dist/types/setup/credential-auto-import.d.ts +0 -63
  782. package/dist/types/setup/credential-import.d.ts +0 -82
  783. package/dist/types/setup/hermes-setup.d.ts +0 -78
  784. package/dist/types/setup/host-plugin-setup.d.ts +0 -39
  785. package/dist/types/setup/model-onboarding-guidance.d.ts +0 -9
  786. package/dist/types/setup/provider-onboarding.d.ts +0 -52
  787. package/dist/types/skc-runtime/cli-write-receipt.d.ts +0 -24
  788. package/dist/types/skc-runtime/deep-interview-ambiguity.d.ts +0 -39
  789. package/dist/types/skc-runtime/deep-interview-recorder.d.ts +0 -114
  790. package/dist/types/skc-runtime/deep-interview-runtime.d.ts +0 -36
  791. package/dist/types/skc-runtime/deep-interview-state.d.ts +0 -127
  792. package/dist/types/skc-runtime/gc-render.d.ts +0 -6
  793. package/dist/types/skc-runtime/gc-runtime.d.ts +0 -134
  794. package/dist/types/skc-runtime/goal-mode-request.d.ts +0 -53
  795. package/dist/types/skc-runtime/launch-tmux.d.ts +0 -78
  796. package/dist/types/skc-runtime/launch-worktree.d.ts +0 -46
  797. package/dist/types/skc-runtime/ledger-event-renderer.d.ts +0 -68
  798. package/dist/types/skc-runtime/psmux-detect.d.ts +0 -78
  799. package/dist/types/skc-runtime/ralplan-runtime.d.ts +0 -26
  800. package/dist/types/skc-runtime/restricted-role-agent-bash.d.ts +0 -3
  801. package/dist/types/skc-runtime/session-layout.d.ts +0 -59
  802. package/dist/types/skc-runtime/session-resolution.d.ts +0 -47
  803. package/dist/types/skc-runtime/session-state-sidecar.d.ts +0 -39
  804. package/dist/types/skc-runtime/state-graph.d.ts +0 -4
  805. package/dist/types/skc-runtime/state-migrations.d.ts +0 -33
  806. package/dist/types/skc-runtime/state-renderer.d.ts +0 -70
  807. package/dist/types/skc-runtime/state-runtime.d.ts +0 -38
  808. package/dist/types/skc-runtime/state-schema.d.ts +0 -319
  809. package/dist/types/skc-runtime/state-validation.d.ts +0 -6
  810. package/dist/types/skc-runtime/state-writer.d.ts +0 -244
  811. package/dist/types/skc-runtime/team-gc.d.ts +0 -7
  812. package/dist/types/skc-runtime/team-runtime.d.ts +0 -355
  813. package/dist/types/skc-runtime/tmux-common.d.ts +0 -105
  814. package/dist/types/skc-runtime/tmux-gc.d.ts +0 -7
  815. package/dist/types/skc-runtime/tmux-sessions.d.ts +0 -62
  816. package/dist/types/skc-runtime/ultragoal-guard.d.ts +0 -93
  817. package/dist/types/skc-runtime/ultragoal-receipt-freshness.d.ts +0 -42
  818. package/dist/types/skc-runtime/ultragoal-runtime.d.ts +0 -410
  819. package/dist/types/skc-runtime/workflow-command-ref.d.ts +0 -43
  820. package/dist/types/skc-runtime/workflow-manifest.d.ts +0 -54
  821. package/dist/types/skill-state/active-state.d.ts +0 -126
  822. package/dist/types/skill-state/canonical-skills.d.ts +0 -3
  823. package/dist/types/skill-state/deep-interview-mutation-guard.d.ts +0 -38
  824. package/dist/types/skill-state/initial-phase.d.ts +0 -12
  825. package/dist/types/skill-state/workflow-hud.d.ts +0 -83
  826. package/dist/types/skill-state/workflow-state-contract.d.ts +0 -57
  827. package/dist/types/skill-state/workflow-state-version.d.ts +0 -3
  828. package/dist/types/slash-commands/acp-builtins.d.ts +0 -18
  829. package/dist/types/slash-commands/builtin-registry.d.ts +0 -24
  830. package/dist/types/slash-commands/helpers/context-report.d.ts +0 -7
  831. package/dist/types/slash-commands/helpers/fast-status-report.d.ts +0 -82
  832. package/dist/types/slash-commands/helpers/format.d.ts +0 -10
  833. package/dist/types/slash-commands/helpers/mcp.d.ts +0 -3
  834. package/dist/types/slash-commands/helpers/parse.d.ts +0 -33
  835. package/dist/types/slash-commands/helpers/ssh.d.ts +0 -3
  836. package/dist/types/slash-commands/helpers/todo.d.ts +0 -3
  837. package/dist/types/slash-commands/helpers/usage-report.d.ts +0 -7
  838. package/dist/types/slash-commands/types.d.ts +0 -120
  839. package/dist/types/ssh/config-writer.d.ts +0 -49
  840. package/dist/types/ssh/connection-manager.d.ts +0 -33
  841. package/dist/types/ssh/ssh-executor.d.ts +0 -37
  842. package/dist/types/ssh/sshfs-mount.d.ts +0 -6
  843. package/dist/types/ssh/utils.d.ts +0 -2
  844. package/dist/types/stt/downloader.d.ts +0 -9
  845. package/dist/types/stt/index.d.ts +0 -3
  846. package/dist/types/stt/recorder.d.ts +0 -13
  847. package/dist/types/stt/setup.d.ts +0 -18
  848. package/dist/types/stt/stt-controller.d.ts +0 -16
  849. package/dist/types/stt/transcriber.d.ts +0 -16
  850. package/dist/types/system-prompt.d.ts +0 -118
  851. package/dist/types/task/agents.d.ts +0 -28
  852. package/dist/types/task/commands.d.ts +0 -35
  853. package/dist/types/task/discovery.d.ts +0 -19
  854. package/dist/types/task/executor.d.ts +0 -124
  855. package/dist/types/task/fork-context-advisory.d.ts +0 -13
  856. package/dist/types/task/fork-context-budget.d.ts +0 -7
  857. package/dist/types/task/id.d.ts +0 -7
  858. package/dist/types/task/index.d.ts +0 -43
  859. package/dist/types/task/name-generator.d.ts +0 -16
  860. package/dist/types/task/output-manager.d.ts +0 -36
  861. package/dist/types/task/parallel.d.ts +0 -34
  862. package/dist/types/task/receipt.d.ts +0 -87
  863. package/dist/types/task/render.d.ts +0 -28
  864. package/dist/types/task/roi-reconciliation.d.ts +0 -27
  865. package/dist/types/task/simple-mode.d.ts +0 -8
  866. package/dist/types/task/skc-command.d.ts +0 -7
  867. package/dist/types/task/spawn-gate.d.ts +0 -29
  868. package/dist/types/task/subprocess-tool-registry.d.ts +0 -72
  869. package/dist/types/task/token-log.d.ts +0 -30
  870. package/dist/types/task/token-log.test.d.ts +0 -1
  871. package/dist/types/task/types.d.ts +0 -508
  872. package/dist/types/task/worktree.d.ts +0 -94
  873. package/dist/types/thinking-metadata.d.ts +0 -16
  874. package/dist/types/thinking.d.ts +0 -22
  875. package/dist/types/tool-discovery/tool-index.d.ts +0 -112
  876. package/dist/types/tools/archive-reader.d.ts +0 -40
  877. package/dist/types/tools/ask-answer-registry.d.ts +0 -13
  878. package/dist/types/tools/ask.d.ts +0 -123
  879. package/dist/types/tools/ast-edit.d.ts +0 -77
  880. package/dist/types/tools/ast-grep.d.ts +0 -69
  881. package/dist/types/tools/auto-generated-guard.d.ts +0 -17
  882. package/dist/types/tools/bash-allowed-prefixes.d.ts +0 -10
  883. package/dist/types/tools/bash-command-fixup.d.ts +0 -11
  884. package/dist/types/tools/bash-interactive.d.ts +0 -16
  885. package/dist/types/tools/bash-interceptor.d.ts +0 -24
  886. package/dist/types/tools/bash-pty-selection.d.ts +0 -7
  887. package/dist/types/tools/bash-skill-urls.d.ts +0 -31
  888. package/dist/types/tools/bash.d.ts +0 -157
  889. package/dist/types/tools/bisect.d.ts +0 -81
  890. package/dist/types/tools/browser/actions.d.ts +0 -54
  891. package/dist/types/tools/browser/attach.d.ts +0 -50
  892. package/dist/types/tools/browser/launch.d.ts +0 -62
  893. package/dist/types/tools/browser/readable.d.ts +0 -17
  894. package/dist/types/tools/browser/registry.d.ts +0 -57
  895. package/dist/types/tools/browser/render.d.ts +0 -53
  896. package/dist/types/tools/browser/tab-protocol.d.ts +0 -144
  897. package/dist/types/tools/browser/tab-supervisor.d.ts +0 -103
  898. package/dist/types/tools/browser/tab-worker-entry.d.ts +0 -1
  899. package/dist/types/tools/browser/tab-worker.d.ts +0 -19
  900. package/dist/types/tools/browser.d.ts +0 -211
  901. package/dist/types/tools/calculator.d.ts +0 -76
  902. package/dist/types/tools/checkpoint.d.ts +0 -63
  903. package/dist/types/tools/composer-bash-policy.d.ts +0 -14
  904. package/dist/types/tools/computer/render.d.ts +0 -17
  905. package/dist/types/tools/computer-gc.d.ts +0 -23
  906. package/dist/types/tools/computer.d.ts +0 -465
  907. package/dist/types/tools/conflict-detect.d.ts +0 -205
  908. package/dist/types/tools/context.d.ts +0 -19
  909. package/dist/types/tools/cron.d.ts +0 -91
  910. package/dist/types/tools/debug.d.ts +0 -209
  911. package/dist/types/tools/eval.d.ts +0 -108
  912. package/dist/types/tools/fetch.d.ts +0 -84
  913. package/dist/types/tools/file-recorder.d.ts +0 -13
  914. package/dist/types/tools/find.d.ts +0 -95
  915. package/dist/types/tools/fs-cache-invalidation.d.ts +0 -15
  916. package/dist/types/tools/gh-format.d.ts +0 -6
  917. package/dist/types/tools/gh-renderer.d.ts +0 -25
  918. package/dist/types/tools/gh.d.ts +0 -356
  919. package/dist/types/tools/github-cache.d.ts +0 -103
  920. package/dist/types/tools/grouped-file-output.d.ts +0 -36
  921. package/dist/types/tools/hindsight-recall.d.ts +0 -21
  922. package/dist/types/tools/hindsight-reflect.d.ts +0 -23
  923. package/dist/types/tools/hindsight-retain.d.ts +0 -27
  924. package/dist/types/tools/image-gen.d.ts +0 -79
  925. package/dist/types/tools/index.d.ts +0 -319
  926. package/dist/types/tools/irc.d.ts +0 -79
  927. package/dist/types/tools/job.d.ts +0 -78
  928. package/dist/types/tools/json-tree.d.ts +0 -23
  929. package/dist/types/tools/jtd-to-json-schema.d.ts +0 -29
  930. package/dist/types/tools/jtd-to-typescript.d.ts +0 -26
  931. package/dist/types/tools/jtd-utils.d.ts +0 -42
  932. package/dist/types/tools/list-limit.d.ts +0 -12
  933. package/dist/types/tools/match-line-format.d.ts +0 -11
  934. package/dist/types/tools/monitor.d.ts +0 -54
  935. package/dist/types/tools/output-meta.d.ts +0 -204
  936. package/dist/types/tools/path-utils.d.ts +0 -147
  937. package/dist/types/tools/plan-mode-guard.d.ts +0 -18
  938. package/dist/types/tools/read.d.ts +0 -85
  939. package/dist/types/tools/recipe/index.d.ts +0 -45
  940. package/dist/types/tools/recipe/render.d.ts +0 -36
  941. package/dist/types/tools/recipe/runner.d.ts +0 -60
  942. package/dist/types/tools/recipe/runners/cargo.d.ts +0 -16
  943. package/dist/types/tools/recipe/runners/index.d.ts +0 -2
  944. package/dist/types/tools/recipe/runners/just.d.ts +0 -2
  945. package/dist/types/tools/recipe/runners/make.d.ts +0 -2
  946. package/dist/types/tools/recipe/runners/pkg.d.ts +0 -2
  947. package/dist/types/tools/recipe/runners/task.d.ts +0 -2
  948. package/dist/types/tools/render-mermaid.d.ts +0 -37
  949. package/dist/types/tools/render-utils.d.ts +0 -158
  950. package/dist/types/tools/renderers.d.ts +0 -26
  951. package/dist/types/tools/resolve.d.ts +0 -80
  952. package/dist/types/tools/resource-gc.d.ts +0 -54
  953. package/dist/types/tools/review.d.ts +0 -63
  954. package/dist/types/tools/search-tool-bm25.d.ts +0 -65
  955. package/dist/types/tools/search.d.ts +0 -93
  956. package/dist/types/tools/skill-discovery.d.ts +0 -40
  957. package/dist/types/tools/skill.d.ts +0 -48
  958. package/dist/types/tools/sqlite-reader.d.ts +0 -94
  959. package/dist/types/tools/ssh.d.ts +0 -67
  960. package/dist/types/tools/subagent-render.d.ts +0 -31
  961. package/dist/types/tools/subagent.d.ts +0 -117
  962. package/dist/types/tools/telegram-send.d.ts +0 -32
  963. package/dist/types/tools/todo-write.d.ts +0 -120
  964. package/dist/types/tools/tool-errors.d.ts +0 -33
  965. package/dist/types/tools/tool-result.d.ts +0 -30
  966. package/dist/types/tools/tool-timeouts.d.ts +0 -56
  967. package/dist/types/tools/ultragoal-ask-guard.d.ts +0 -12
  968. package/dist/types/tools/vim.d.ts +0 -58
  969. package/dist/types/tools/write.d.ts +0 -59
  970. package/dist/types/tools/yield.d.ts +0 -25
  971. package/dist/types/tui/code-cell.d.ts +0 -32
  972. package/dist/types/tui/file-list.d.ts +0 -22
  973. package/dist/types/tui/hyperlink.d.ts +0 -42
  974. package/dist/types/tui/index.d.ts +0 -11
  975. package/dist/types/tui/output-block.d.ts +0 -28
  976. package/dist/types/tui/status-line.d.ts +0 -18
  977. package/dist/types/tui/tree-list.d.ts +0 -19
  978. package/dist/types/tui/types.d.ts +0 -13
  979. package/dist/types/tui/utils.d.ts +0 -36
  980. package/dist/types/utils/changelog.d.ts +0 -37
  981. package/dist/types/utils/clipboard.d.ts +0 -22
  982. package/dist/types/utils/cmux-workspace.d.ts +0 -52
  983. package/dist/types/utils/command-args.d.ts +0 -9
  984. package/dist/types/utils/commit-message-generator.d.ts +0 -7
  985. package/dist/types/utils/edit-mode.d.ts +0 -13
  986. package/dist/types/utils/event-bus.d.ts +0 -6
  987. package/dist/types/utils/external-editor.d.ts +0 -17
  988. package/dist/types/utils/file-display-mode.d.ts +0 -27
  989. package/dist/types/utils/file-mentions.d.ts +0 -25
  990. package/dist/types/utils/git.d.ts +0 -393
  991. package/dist/types/utils/image-loading.d.ts +0 -26
  992. package/dist/types/utils/image-resize.d.ts +0 -39
  993. package/dist/types/utils/lang-from-path.d.ts +0 -8
  994. package/dist/types/utils/linkedom.d.ts +0 -4
  995. package/dist/types/utils/markit.d.ts +0 -7
  996. package/dist/types/utils/open.d.ts +0 -2
  997. package/dist/types/utils/pasted-image-path.d.ts +0 -32
  998. package/dist/types/utils/session-color.d.ts +0 -10
  999. package/dist/types/utils/shell-snapshot.d.ts +0 -5
  1000. package/dist/types/utils/sixel.d.ts +0 -21
  1001. package/dist/types/utils/title-generator.d.ts +0 -31
  1002. package/dist/types/utils/tool-choice.d.ts +0 -20
  1003. package/dist/types/utils/tools-manager.d.ts +0 -9
  1004. package/dist/types/vim/buffer.d.ts +0 -41
  1005. package/dist/types/vim/commands.d.ts +0 -6
  1006. package/dist/types/vim/engine.d.ts +0 -47
  1007. package/dist/types/vim/parser.d.ts +0 -3
  1008. package/dist/types/vim/render.d.ts +0 -25
  1009. package/dist/types/vim/types.d.ts +0 -182
  1010. package/dist/types/web/insane/bridge.d.ts +0 -103
  1011. package/dist/types/web/insane/url-guard.d.ts +0 -25
  1012. package/dist/types/web/kagi.d.ts +0 -23
  1013. package/dist/types/web/parallel.d.ts +0 -58
  1014. package/dist/types/web/scrapers/artifacthub.d.ts +0 -6
  1015. package/dist/types/web/scrapers/arxiv.d.ts +0 -5
  1016. package/dist/types/web/scrapers/aur.d.ts +0 -5
  1017. package/dist/types/web/scrapers/biorxiv.d.ts +0 -5
  1018. package/dist/types/web/scrapers/bluesky.d.ts +0 -5
  1019. package/dist/types/web/scrapers/brew.d.ts +0 -5
  1020. package/dist/types/web/scrapers/cheatsh.d.ts +0 -8
  1021. package/dist/types/web/scrapers/chocolatey.d.ts +0 -5
  1022. package/dist/types/web/scrapers/choosealicense.d.ts +0 -2
  1023. package/dist/types/web/scrapers/cisa-kev.d.ts +0 -5
  1024. package/dist/types/web/scrapers/clojars.d.ts +0 -5
  1025. package/dist/types/web/scrapers/coingecko.d.ts +0 -5
  1026. package/dist/types/web/scrapers/crates-io.d.ts +0 -5
  1027. package/dist/types/web/scrapers/crossref.d.ts +0 -2
  1028. package/dist/types/web/scrapers/devto.d.ts +0 -5
  1029. package/dist/types/web/scrapers/discogs.d.ts +0 -8
  1030. package/dist/types/web/scrapers/discourse.d.ts +0 -5
  1031. package/dist/types/web/scrapers/dockerhub.d.ts +0 -5
  1032. package/dist/types/web/scrapers/docs-rs.d.ts +0 -2
  1033. package/dist/types/web/scrapers/fdroid.d.ts +0 -5
  1034. package/dist/types/web/scrapers/firefox-addons.d.ts +0 -2
  1035. package/dist/types/web/scrapers/flathub.d.ts +0 -2
  1036. package/dist/types/web/scrapers/github-gist.d.ts +0 -5
  1037. package/dist/types/web/scrapers/github.d.ts +0 -12
  1038. package/dist/types/web/scrapers/gitlab.d.ts +0 -5
  1039. package/dist/types/web/scrapers/go-pkg.d.ts +0 -5
  1040. package/dist/types/web/scrapers/hackage.d.ts +0 -5
  1041. package/dist/types/web/scrapers/hackernews.d.ts +0 -2
  1042. package/dist/types/web/scrapers/hex.d.ts +0 -5
  1043. package/dist/types/web/scrapers/huggingface.d.ts +0 -2
  1044. package/dist/types/web/scrapers/iacr.d.ts +0 -5
  1045. package/dist/types/web/scrapers/index.d.ts +0 -84
  1046. package/dist/types/web/scrapers/jetbrains-marketplace.d.ts +0 -2
  1047. package/dist/types/web/scrapers/lemmy.d.ts +0 -2
  1048. package/dist/types/web/scrapers/lobsters.d.ts +0 -5
  1049. package/dist/types/web/scrapers/mastodon.d.ts +0 -5
  1050. package/dist/types/web/scrapers/maven.d.ts +0 -6
  1051. package/dist/types/web/scrapers/mdn.d.ts +0 -2
  1052. package/dist/types/web/scrapers/metacpan.d.ts +0 -5
  1053. package/dist/types/web/scrapers/musicbrainz.d.ts +0 -5
  1054. package/dist/types/web/scrapers/npm.d.ts +0 -5
  1055. package/dist/types/web/scrapers/nuget.d.ts +0 -5
  1056. package/dist/types/web/scrapers/nvd.d.ts +0 -5
  1057. package/dist/types/web/scrapers/ollama.d.ts +0 -2
  1058. package/dist/types/web/scrapers/open-vsx.d.ts +0 -5
  1059. package/dist/types/web/scrapers/opencorporates.d.ts +0 -5
  1060. package/dist/types/web/scrapers/openlibrary.d.ts +0 -5
  1061. package/dist/types/web/scrapers/orcid.d.ts +0 -5
  1062. package/dist/types/web/scrapers/osv.d.ts +0 -5
  1063. package/dist/types/web/scrapers/packagist.d.ts +0 -5
  1064. package/dist/types/web/scrapers/pub-dev.d.ts +0 -5
  1065. package/dist/types/web/scrapers/pubmed.d.ts +0 -5
  1066. package/dist/types/web/scrapers/pypi.d.ts +0 -5
  1067. package/dist/types/web/scrapers/rawg.d.ts +0 -2
  1068. package/dist/types/web/scrapers/readthedocs.d.ts +0 -2
  1069. package/dist/types/web/scrapers/reddit.d.ts +0 -5
  1070. package/dist/types/web/scrapers/repology.d.ts +0 -5
  1071. package/dist/types/web/scrapers/rfc.d.ts +0 -5
  1072. package/dist/types/web/scrapers/rubygems.d.ts +0 -5
  1073. package/dist/types/web/scrapers/searchcode.d.ts +0 -2
  1074. package/dist/types/web/scrapers/sec-edgar.d.ts +0 -5
  1075. package/dist/types/web/scrapers/semantic-scholar.d.ts +0 -2
  1076. package/dist/types/web/scrapers/snapcraft.d.ts +0 -2
  1077. package/dist/types/web/scrapers/sourcegraph.d.ts +0 -2
  1078. package/dist/types/web/scrapers/spdx.d.ts +0 -5
  1079. package/dist/types/web/scrapers/spotify.d.ts +0 -8
  1080. package/dist/types/web/scrapers/stackoverflow.d.ts +0 -6
  1081. package/dist/types/web/scrapers/terraform.d.ts +0 -5
  1082. package/dist/types/web/scrapers/tldr.d.ts +0 -7
  1083. package/dist/types/web/scrapers/twitter.d.ts +0 -5
  1084. package/dist/types/web/scrapers/types.d.ts +0 -83
  1085. package/dist/types/web/scrapers/utils.d.ts +0 -32
  1086. package/dist/types/web/scrapers/vimeo.d.ts +0 -5
  1087. package/dist/types/web/scrapers/vscode-marketplace.d.ts +0 -5
  1088. package/dist/types/web/scrapers/w3c.d.ts +0 -2
  1089. package/dist/types/web/scrapers/wikidata.d.ts +0 -5
  1090. package/dist/types/web/scrapers/wikipedia.d.ts +0 -5
  1091. package/dist/types/web/scrapers/youtube.d.ts +0 -13
  1092. package/dist/types/web/search/index.d.ts +0 -118
  1093. package/dist/types/web/search/provider.d.ts +0 -55
  1094. package/dist/types/web/search/providers/anthropic.d.ts +0 -32
  1095. package/dist/types/web/search/providers/base.d.ts +0 -90
  1096. package/dist/types/web/search/providers/brave.d.ts +0 -27
  1097. package/dist/types/web/search/providers/codex.d.ts +0 -35
  1098. package/dist/types/web/search/providers/duckduckgo.d.ts +0 -57
  1099. package/dist/types/web/search/providers/exa.d.ts +0 -52
  1100. package/dist/types/web/search/providers/gemini.d.ts +0 -57
  1101. package/dist/types/web/search/providers/insane.d.ts +0 -53
  1102. package/dist/types/web/search/providers/jina.d.ts +0 -26
  1103. package/dist/types/web/search/providers/kagi.d.ts +0 -24
  1104. package/dist/types/web/search/providers/kimi.d.ts +0 -27
  1105. package/dist/types/web/search/providers/openai-compatible.d.ts +0 -9
  1106. package/dist/types/web/search/providers/parallel.d.ts +0 -15
  1107. package/dist/types/web/search/providers/perplexity.d.ts +0 -38
  1108. package/dist/types/web/search/providers/searxng.d.ts +0 -44
  1109. package/dist/types/web/search/providers/synthetic.d.ts +0 -21
  1110. package/dist/types/web/search/providers/tavily.d.ts +0 -29
  1111. package/dist/types/web/search/providers/text-citations.d.ts +0 -23
  1112. package/dist/types/web/search/providers/utils.d.ts +0 -88
  1113. package/dist/types/web/search/providers/xai.d.ts +0 -64
  1114. package/dist/types/web/search/providers/zai.d.ts +0 -28
  1115. package/dist/types/web/search/render.d.ts +0 -36
  1116. package/dist/types/web/search/types.d.ts +0 -372
  1117. package/dist/types/web/search/utils.d.ts +0 -4
  1118. package/dist/types/workflow/workflow-intent-diff.d.ts +0 -25
  1119. package/dist/types/workflow/workflow-intent-report-consumer.d.ts +0 -50
  1120. package/dist/types/workflow/workflow-intent-report-schema.d.ts +0 -2
  1121. package/dist/types/workflow/workflow-intent-report.d.ts +0 -48
  1122. package/dist/types/workspace-tree.d.ts +0 -42
@@ -11,8 +11,31 @@ import {
11
11
  } from "../coordinator/contract";
12
12
  import {
13
13
  SKC_COORDINATOR_SESSION_ID_ENV,
14
+ SKC_COORDINATOR_SESSION_LAUNCH_ID_ENV,
15
+ SKC_COORDINATOR_SESSION_READINESS_FILE_ENV,
14
16
  SKC_COORDINATOR_SESSION_STATE_FILE_ENV,
17
+ SKC_TMUX_OWNER_GENERATION_ENV,
18
+ SKC_TMUX_OWNER_SERVER_KEY_ENV,
19
+ SKC_TMUX_OWNER_STATE_DIR_ENV,
15
20
  } from "../skc-runtime/session-state-sidecar";
21
+
22
+ import {
23
+ captureOwnerGenerationBaselineSync,
24
+ classifyCgroup,
25
+ isExactScopedBootstrapSuccessReceipt,
26
+ type OwnerIsolationProbe,
27
+ ownerProcessStartTime,
28
+ planTmuxOwnerIsolation,
29
+ replaceOwnerGenerationSync,
30
+ type TmuxServerProof,
31
+ } from "../skc-runtime/tmux-owner-isolation";
32
+
33
+ import {
34
+ type CoordinatorModelProfileLoader,
35
+ loadCoordinatorModelProfiles,
36
+ resolveCoordinatorMpreset,
37
+ } from "./model-preset";
38
+
16
39
  import {
17
40
  assertCoordinatorArtifactPath,
18
41
  assertCoordinatorWorkdir,
@@ -44,8 +67,15 @@ interface JsonRpcResponse {
44
67
  interface SessionStartInput {
45
68
  cwd: string;
46
69
  prompt?: string;
70
+ mpreset?: string | null;
47
71
  namespace: { profile: string | null; repo: string | null };
48
72
  worktree: true;
73
+ sessionId: string;
74
+ launchId: string;
75
+ /** Canonical, coordinator-owned immutable readiness marker path. */
76
+ readinessMarkerFile: string;
77
+ /** @deprecated Compatibility alias for injected launch providers. */
78
+ readinessFile: string;
49
79
  }
50
80
 
51
81
  interface SessionRegisterInput {
@@ -79,10 +109,20 @@ interface RuntimeSessionStatePayload extends CoordinatorSessionState {
79
109
  error?: { code: string; message: string; recoverable: boolean } | null;
80
110
  }
81
111
 
112
+ interface CommandResult {
113
+ exitCode: number;
114
+ stdout: string;
115
+ stderr: string;
116
+ }
117
+
118
+ type CommandRunner = (command: string[], stdinLine?: string) => Promise<CommandResult>;
119
+
82
120
  interface CoordinatorServices {
83
121
  listSessions?: () => unknown[] | Promise<unknown[]>;
84
122
  startSession?: (input: SessionStartInput) => unknown | Promise<unknown>;
85
- commandRunner?: (command: string[]) => Promise<{ exitCode: number; stdout: string; stderr: string }>;
123
+ commandRunner?: CommandRunner;
124
+ ownerIsolationProbe?: OwnerIsolationProbe;
125
+ resolveModelProfiles?: CoordinatorModelProfileLoader;
86
126
  }
87
127
 
88
128
  interface CoordinatorMcpServerOptions {
@@ -92,7 +132,7 @@ interface CoordinatorMcpServerOptions {
92
132
 
93
133
  interface LegacyHandlerOptions {
94
134
  env?: NodeJS.ProcessEnv;
95
- createSession?: () => unknown;
135
+ createSession?: (input: SessionStartInput) => unknown;
96
136
  }
97
137
 
98
138
  type TurnStatus =
@@ -112,16 +152,28 @@ interface TurnRecord {
112
152
  session_id: string;
113
153
  namespace: { profile: string | null; repo: string | null };
114
154
  status: TurnStatus;
115
- prompt: { text: string; created_at: string; source: "mcp" | "question_answer" };
155
+ prompt: {
156
+ text: string;
157
+ created_at: string;
158
+ source: "mcp" | "question_answer";
159
+ };
116
160
  delivery: {
117
161
  delivered: boolean;
118
162
  queued: boolean;
119
163
  target: string | null;
120
164
  tmux_keys_sent?: boolean;
121
165
  prompt_acknowledged?: boolean;
122
- state?: "queued" | "tmux_keys_sent" | "acknowledged" | "unavailable" | "unacknowledged";
166
+ state?:
167
+ | "queued"
168
+ | "awaiting_runtime_readiness"
169
+ | "delivering"
170
+ | "tmux_keys_sent"
171
+ | "acknowledged"
172
+ | "unavailable"
173
+ | "unacknowledged";
123
174
  attempts: Array<{
124
175
  delivered: boolean;
176
+ attempt_id?: string;
125
177
  created_at: string;
126
178
  reason: string | null;
127
179
  channel?: "tmux_keys" | "runtime_ack";
@@ -138,7 +190,11 @@ interface TurnRecord {
138
190
  };
139
191
  evidence: Array<Record<string, unknown>>;
140
192
  error: { code: string; message: string; recoverable: boolean } | null;
141
- liveness: { checked_at: string | null; live: boolean | null; reason: string | null };
193
+ liveness: {
194
+ checked_at: string | null;
195
+ live: boolean | null;
196
+ reason: string | null;
197
+ };
142
198
  created_at: string;
143
199
  updated_at: string;
144
200
  started_at: string | null;
@@ -163,9 +219,22 @@ interface CoordinatorSessionState {
163
219
  current_turn_id: string | null;
164
220
  last_turn_id: string | null;
165
221
  updated_at: string;
166
- source: "coordinator" | "agent_session_event";
222
+ source: "coordinator" | "agent_session_event" | "process_postmortem";
223
+
167
224
  live: boolean | null;
168
225
  reason: string | null;
226
+ cwd?: string;
227
+ workdir?: string;
228
+ session_file?: string | null;
229
+ owner_generation?: string;
230
+ event?: string;
231
+ owner_terminal?: {
232
+ generation: string;
233
+ socket_key: string;
234
+ classification: string;
235
+ observer: string;
236
+ observed_at: string;
237
+ };
169
238
  }
170
239
 
171
240
  type CoordinatorEventKind =
@@ -175,6 +244,7 @@ type CoordinatorEventKind =
175
244
  | "turn.queued"
176
245
  | "turn.delivering"
177
246
  | "turn.active"
247
+ | "turn.acknowledged"
178
248
  | "turn.waiting_for_answer"
179
249
  | "turn.completed"
180
250
  | "turn.failed"
@@ -217,6 +287,10 @@ const MISSING_FINAL_RESPONSE_ADVISORY = "completion_missing_final_response";
217
287
  const PROMPT_ACK_TIMEOUT_REASON = "runtime_prompt_ack_timeout";
218
288
  const DEFAULT_RUNTIME_PROMPT_ACK_TIMEOUT_MS = 10_000;
219
289
  const MAX_RUNTIME_PROMPT_ACK_TIMEOUT_MS = 5 * 60 * 1000;
290
+ const RUNTIME_READINESS_TIMEOUT_REASON = "runtime_readiness_timeout";
291
+ const DEFAULT_RUNTIME_READINESS_TIMEOUT_MS = 10_000;
292
+ const MAX_RUNTIME_READINESS_TIMEOUT_MS = 5 * 60 * 1000;
293
+
220
294
  const ACTIVE_TURN_STATUSES = new Set<TurnStatus>(["delivering", "active", "waiting_for_answer", "completing"]);
221
295
  const TERMINAL_TURN_STATUSES = new Set<TurnStatus>(["completed", "failed", "cancelled", "superseded"]);
222
296
  const TURN_ID_PATTERN = /^turn-[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
@@ -232,7 +306,12 @@ function textResult(
232
306
  isError = false,
233
307
  ): { content: Array<{ type: "text"; text: string }>; isError: boolean } {
234
308
  return {
235
- content: [{ type: "text", text: typeof payload === "string" ? payload : JSON.stringify(payload) }],
309
+ content: [
310
+ {
311
+ type: "text",
312
+ text: typeof payload === "string" ? payload : JSON.stringify(payload),
313
+ },
314
+ ],
236
315
  isError,
237
316
  };
238
317
  }
@@ -242,13 +321,27 @@ function toolSchema(name: CoordinatorToolName): {
242
321
  description: string;
243
322
  inputSchema: Record<string, unknown>;
244
323
  } {
245
- const allowMutation = { type: "boolean", description: "Required and must be true for mutating tools." };
324
+ const allowMutation = {
325
+ type: "boolean",
326
+ description: "Required and must be true for mutating tools.",
327
+ };
246
328
  const cwd = {
247
329
  type: "string",
248
330
  description: "Canonicalized SKC worktree or project directory inside configured roots.",
249
331
  };
250
- const sessionId = { type: "string", description: "SKC coordinator bridge session id." };
251
- const pathField = { type: "string", description: "Artifact path inside configured safe roots." };
332
+ const sessionId = {
333
+ type: "string",
334
+ description: "SKC coordinator bridge session id.",
335
+ };
336
+ const pathField = {
337
+ type: "string",
338
+ description: "Artifact path inside configured safe roots.",
339
+ };
340
+ const mpreset = {
341
+ type: "string",
342
+ description:
343
+ "Optional SKC model profile (`skc --mpreset <profile>`) to authoritatively activate for a fresh session; resolved through the merged built-in/custom profile registry and applied from the first turn. Unknown names are rejected with the available-profile listing.",
344
+ };
252
345
  const common = { type: "object", properties: {} as Record<string, unknown> };
253
346
  if (name === "skc_coordinator_register_session") {
254
347
  return {
@@ -277,7 +370,12 @@ function toolSchema(name: CoordinatorToolName): {
277
370
  description: "Start a SKC worktree/tmux oriented session through the coordinator bridge.",
278
371
  inputSchema: {
279
372
  type: "object",
280
- properties: { cwd, prompt: { type: "string" }, allow_mutation: allowMutation },
373
+ properties: {
374
+ cwd,
375
+ prompt: { type: "string" },
376
+ mpreset,
377
+ allow_mutation: allowMutation,
378
+ },
281
379
  required: ["cwd", "allow_mutation"],
282
380
  },
283
381
  };
@@ -306,7 +404,11 @@ function toolSchema(name: CoordinatorToolName): {
306
404
  description: "Read authoritative durable turn state plus bounded advisory tmux status.",
307
405
  inputSchema: {
308
406
  type: "object",
309
- properties: { session_id: sessionId, turn_id: { type: "string" }, lines: { type: "number" } },
407
+ properties: {
408
+ session_id: sessionId,
409
+ turn_id: { type: "string" },
410
+ lines: { type: "number" },
411
+ },
310
412
  required: ["turn_id"],
311
413
  },
312
414
  };
@@ -375,7 +477,11 @@ function toolSchema(name: CoordinatorToolName): {
375
477
  return {
376
478
  name,
377
479
  description: "Read one bounded artifact from configured safe roots.",
378
- inputSchema: { type: "object", properties: { path: pathField }, required: ["path"] },
480
+ inputSchema: {
481
+ type: "object",
482
+ properties: { path: pathField },
483
+ required: ["path"],
484
+ },
379
485
  };
380
486
  }
381
487
  if (name === "skc_coordinator_read_status") {
@@ -389,21 +495,35 @@ function toolSchema(name: CoordinatorToolName): {
389
495
  return {
390
496
  name,
391
497
  description: "Read a bounded structured session tail, not tmux scrollback.",
392
- inputSchema: { type: "object", properties: { session_id: sessionId, lines: { type: "number" } } },
498
+ inputSchema: {
499
+ type: "object",
500
+ properties: { session_id: sessionId, lines: { type: "number" } },
501
+ },
393
502
  };
394
503
  }
395
504
  if (name === "skc_coordinator_list_questions") {
396
505
  return {
397
506
  name,
398
507
  description: "List bounded structured questions for coordinator coordination.",
399
- inputSchema: { type: "object", properties: { session_id: sessionId, status: { type: "string" } } },
508
+ inputSchema: {
509
+ type: "object",
510
+ properties: { session_id: sessionId, status: { type: "string" } },
511
+ },
400
512
  };
401
513
  }
402
514
  if (name === "skc_coordinator_list_artifacts") {
403
- return { name, description: "List known safe artifact roots for coordinator coordination.", inputSchema: common };
515
+ return {
516
+ name,
517
+ description: "List known safe artifact roots for coordinator coordination.",
518
+ inputSchema: common,
519
+ };
404
520
  }
405
521
  if (name === "skc_coordinator_read_coordination_status") {
406
- return { name, description: "Read coordinator coordination reports.", inputSchema: common };
522
+ return {
523
+ name,
524
+ description: "Read coordinator coordination reports.",
525
+ inputSchema: common,
526
+ };
407
527
  }
408
528
  if (name === "skc_coordinator_watch_events") {
409
529
  return {
@@ -437,7 +557,10 @@ function toolSchema(name: CoordinatorToolName): {
437
557
  type: "string",
438
558
  description: "Delegated task or objective to run through the selected SKC workflow.",
439
559
  },
440
- prompt: { type: "string", description: "Alias for task; accepted when task is absent." },
560
+ prompt: {
561
+ type: "string",
562
+ description: "Alias for task; accepted when task is absent.",
563
+ },
441
564
  allow_mutation: allowMutation,
442
565
  session_id: {
443
566
  type: "string",
@@ -452,24 +575,38 @@ function toolSchema(name: CoordinatorToolName): {
452
575
  type: "boolean",
453
576
  description: "When reusing a session with an active turn, supersede it before sending.",
454
577
  },
578
+ mpreset,
455
579
  model: {
456
580
  type: "string",
457
581
  description: "Optional model hint passed in prompt metadata; no provider default is implied.",
458
582
  },
459
- await_completion: { type: "boolean", description: "If true, poll the turn until terminal or timeout." },
583
+ await_completion: {
584
+ type: "boolean",
585
+ description: "If true, poll the turn until terminal or timeout.",
586
+ },
460
587
  timeout_ms: {
461
588
  type: "number",
462
589
  description:
463
590
  "Bounded await timeout in milliseconds, capped at 30 minutes like skc_coordinator_await_turn.",
464
591
  },
465
- poll_interval_ms: { type: "number", description: "Bounded await polling interval." },
466
- lines: { type: "number", description: "Bounded advisory tail lines returned with await/read payloads." },
592
+ poll_interval_ms: {
593
+ type: "number",
594
+ description: "Bounded await polling interval.",
595
+ },
596
+ lines: {
597
+ type: "number",
598
+ description: "Bounded advisory tail lines returned with await/read payloads.",
599
+ },
467
600
  },
468
601
  required: ["cwd", "allow_mutation"],
469
602
  },
470
603
  };
471
604
  }
472
- return { name, description: "List known scoped SKC coordinator bridge sessions.", inputSchema: common };
605
+ return {
606
+ name,
607
+ description: "List known scoped SKC coordinator bridge sessions.",
608
+ inputSchema: common,
609
+ };
473
610
  }
474
611
 
475
612
  type DelegateWorkflow = "plan" | "execute" | "team";
@@ -535,13 +672,80 @@ function workflowPrompt(
535
672
  ].join("\n");
536
673
  }
537
674
 
675
+ const PRIVATE_SESSION_CONTROL_FIELDS = new Set([
676
+ "tmux_socket_key",
677
+ "tmuxSocketKey",
678
+ "tmux_owner_generation",
679
+ "tmuxOwnerGeneration",
680
+ "tmux_owner_state_dir",
681
+ "tmuxOwnerStateDir",
682
+ "tmux_owner_server_key",
683
+ "tmuxOwnerServerKey",
684
+ "tmux_owner_server_pid",
685
+ "tmuxOwnerServerPid",
686
+ "tmux_owner_server_start_time",
687
+ "tmuxOwnerServerStartTime",
688
+ "tmux_native_session_id",
689
+ "tmuxNativeSessionId",
690
+ "pane_id",
691
+ "paneId",
692
+ "socket_key",
693
+ "socketKey",
694
+ "owner_generation",
695
+ "ownerGeneration",
696
+ "state_dir",
697
+ "ownerStateDir",
698
+ "owner_server_key",
699
+ "ownerServerKey",
700
+ "owner_server_pid",
701
+ "ownerServerPid",
702
+ "owner_server_start_time",
703
+ "ownerServerStartTime",
704
+ "owner_terminal",
705
+ "ownerTerminal",
706
+ "generation",
707
+ "server_key",
708
+ "intent_id",
709
+ "dedupe_key",
710
+ "__coordinatorOwnerTransaction",
711
+ ]);
712
+
713
+ function publicSessionProjection(session: Record<string, unknown>): Record<string, unknown> {
714
+ return Object.fromEntries(Object.entries(session).filter(([key]) => !PRIVATE_SESSION_CONTROL_FIELDS.has(key)));
715
+ }
716
+
717
+ function publicCoordinatorResponse(value: unknown): unknown {
718
+ if (Array.isArray(value)) return value.map(publicCoordinatorResponse);
719
+ const record = asRecord(value);
720
+ if (!record) return value;
721
+ return Object.fromEntries(
722
+ Object.entries(publicSessionProjection(record)).map(([key, item]) => [key, publicCoordinatorResponse(item)]),
723
+ );
724
+ }
725
+
538
726
  function normalizeSession(session: Record<string, unknown>): Record<string, unknown> {
727
+ const suppliedIds = [session.session_id, session.sessionId, session.name].filter(
728
+ (value): value is string => typeof value === "string",
729
+ );
730
+ if (suppliedIds.length === 0) throw new Error("invalid_session_id");
731
+ const sessionId = safeExternalId("session", suppliedIds[0]);
732
+ if (suppliedIds.some(value => value !== sessionId)) throw new Error("session_id_conflict");
539
733
  return {
540
- session_id: session.sessionId ?? session.session_id ?? session.name ?? "unknown",
541
734
  ...(session.tmuxSession ? { tmux_session: session.tmuxSession } : {}),
735
+ ...(session.tmuxTarget ? { tmux_target: session.tmuxTarget } : {}),
736
+ ...(session.tmuxSocketKey ? { tmux_socket_key: session.tmuxSocketKey } : {}),
737
+ ...(session.tmuxOwnerGeneration ? { tmux_owner_generation: session.tmuxOwnerGeneration } : {}),
738
+ ...(session.tmuxOwnerStateDir ? { tmux_owner_state_dir: session.tmuxOwnerStateDir } : {}),
739
+ ...(session.tmuxOwnerServerKey ? { tmux_owner_server_key: session.tmuxOwnerServerKey } : {}),
740
+ ...(session.tmuxOwnerServerPid ? { tmux_owner_server_pid: session.tmuxOwnerServerPid } : {}),
741
+ ...(session.tmuxOwnerServerStartTime ? { tmux_owner_server_start_time: session.tmuxOwnerServerStartTime } : {}),
742
+ ...(session.tmuxNativeSessionId ? { tmux_native_session_id: session.tmuxNativeSessionId } : {}),
743
+ ...(session.paneId ? { pane_id: session.paneId } : {}),
542
744
  ...(session.cwd ? { cwd: session.cwd } : {}),
543
745
  ...(session.createdAt ? { created_at: session.createdAt } : {}),
544
746
  ...session,
747
+ session_id: sessionId,
748
+ sessionId,
545
749
  };
546
750
  }
547
751
 
@@ -553,15 +757,379 @@ async function canonicalizePath(value: string): Promise<string> {
553
757
  }
554
758
  }
555
759
 
760
+ interface RuntimeReadinessMarker {
761
+ schema_version: 1;
762
+ session_id: string;
763
+ launch_id: string;
764
+ state: "ready_for_input";
765
+ event: "interactive_input_ready";
766
+ source: "skc_interactive_runtime";
767
+ ready_for_input: true;
768
+ created_at: string;
769
+ }
770
+
771
+ async function readRuntimeReadinessMarker(
772
+ markerFile: string,
773
+ sessionId: string,
774
+ launchId: string,
775
+ readinessRoot?: string,
776
+ ): Promise<RuntimeReadinessMarker | null> {
777
+ try {
778
+ const expectedFile = path.resolve(markerFile);
779
+ if (readinessRoot) {
780
+ const canonicalRoot = await fs.realpath(readinessRoot);
781
+ const canonicalParent = await fs.realpath(path.dirname(expectedFile));
782
+ if (
783
+ !expectedFile.startsWith(`${canonicalRoot}${path.sep}`) ||
784
+ !canonicalParent.startsWith(`${canonicalRoot}${path.sep}`)
785
+ )
786
+ return null;
787
+ }
788
+ const stat = await fs.lstat(expectedFile);
789
+ if (!stat.isFile() || stat.isSymbolicLink()) return null;
790
+ const marker = JSON.parse(await fs.readFile(expectedFile, "utf8")) as RuntimeReadinessMarker;
791
+ return marker.schema_version === 1 &&
792
+ marker.session_id === sessionId &&
793
+ marker.launch_id === launchId &&
794
+ marker.state === "ready_for_input" &&
795
+ marker.event === "interactive_input_ready" &&
796
+ marker.source === "skc_interactive_runtime" &&
797
+ marker.ready_for_input === true &&
798
+ typeof marker.created_at === "string" &&
799
+ marker.created_at.length > 0 &&
800
+ Number.isFinite(Date.parse(marker.created_at))
801
+ ? marker
802
+ : null;
803
+ } catch {
804
+ return null;
805
+ }
806
+ }
807
+
808
+ async function waitForRuntimeReadinessMarker(
809
+ markerFile: string,
810
+ sessionId: string,
811
+ launchId: string,
812
+ readinessRoot: string,
813
+ timeoutMs: number,
814
+ ): Promise<boolean> {
815
+ const ready = async (): Promise<boolean> =>
816
+ (await readRuntimeReadinessMarker(markerFile, sessionId, launchId, readinessRoot)) !== null;
817
+ if (await ready()) return true;
818
+
819
+ const markerDir = path.dirname(markerFile);
820
+ await ensureDir(markerDir);
821
+ const deadline = Date.now() + Math.max(timeoutMs, 0);
822
+ let wake = Promise.withResolvers<void>();
823
+ let watcher: nodeFs.FSWatcher | null = null;
824
+ try {
825
+ watcher = nodeFs.watch(markerDir, (_eventType, filename) => {
826
+ if (filename === path.basename(markerFile)) wake.resolve();
827
+ });
828
+ // A marker may be installed between the first read and watch creation.
829
+ if (await ready()) return true;
830
+ while (Date.now() < deadline) {
831
+ const remainingMs = Math.max(1, deadline - Date.now());
832
+ let timer: NodeJS.Timeout | null = null;
833
+ const fallback = new Promise<void>(resolve => {
834
+ timer = setTimeout(resolve, Math.min(50, remainingMs));
835
+ timer.unref?.();
836
+ });
837
+ await Promise.race([wake.promise, fallback]);
838
+ if (timer) clearTimeout(timer);
839
+ wake = Promise.withResolvers<void>();
840
+ if (await ready()) return true;
841
+ }
842
+ return false;
843
+ } finally {
844
+ watcher?.close();
845
+ }
846
+ }
847
+
556
848
  async function ensureDir(dir: string): Promise<void> {
557
849
  await fs.mkdir(dir, { recursive: true });
558
850
  }
559
851
 
852
+ class CoordinatorStateError extends Error {
853
+ constructor(readonly reason: "invalid" | "unreadable") {
854
+ super(`coordinator_state_${reason}`);
855
+ }
856
+ }
857
+
858
+ const COORDINATOR_SESSION_STATES = new Set<CoordinatorSessionStateValue>([
859
+ "booting",
860
+ "ready_for_input",
861
+ "running",
862
+ "needs_user_input",
863
+ "completed",
864
+ "errored",
865
+ "stale",
866
+ "unknown",
867
+ ]);
868
+ const COORDINATOR_EVENT_KINDS = new Set<CoordinatorEventKind>([
869
+ "session.registered",
870
+ "session.started",
871
+ "session.state_changed",
872
+ "turn.queued",
873
+ "turn.delivering",
874
+ "turn.active",
875
+ "turn.acknowledged",
876
+ "turn.waiting_for_answer",
877
+ "turn.completed",
878
+ "turn.failed",
879
+ "turn.cancelled",
880
+ "turn.superseded",
881
+ "question.opened",
882
+ "question.answered",
883
+ "report.written",
884
+ "tmux.delivery_succeeded",
885
+ "tmux.delivery_failed",
886
+ "delegation.started",
887
+ ]);
888
+
889
+ function coordinatorStateError(reason: "invalid" | "unreadable"): CoordinatorStateError {
890
+ return new CoordinatorStateError(reason);
891
+ }
892
+
893
+ function isValidCoordinatorSessionState(value: unknown, sessionId: string): value is CoordinatorSessionState {
894
+ const record = asRecord(value);
895
+ return (
896
+ record?.schema_version === 1 &&
897
+ record.session_id === sessionId &&
898
+ typeof record.state === "string" &&
899
+ COORDINATOR_SESSION_STATES.has(record.state as CoordinatorSessionStateValue) &&
900
+ typeof record.ready_for_input === "boolean" &&
901
+ record.ready_for_input === (record.state === "ready_for_input" || record.state === "completed") &&
902
+ (record.current_turn_id === null ||
903
+ (typeof record.current_turn_id === "string" && TURN_ID_PATTERN.test(record.current_turn_id))) &&
904
+ (record.last_turn_id === null ||
905
+ (typeof record.last_turn_id === "string" && TURN_ID_PATTERN.test(record.last_turn_id))) &&
906
+ typeof record.updated_at === "string" &&
907
+ Number.isFinite(Date.parse(record.updated_at)) &&
908
+ (record.source === "coordinator" ||
909
+ record.source === "agent_session_event" ||
910
+ record.source === "process_postmortem") &&
911
+ (record.live === null || typeof record.live === "boolean") &&
912
+ (record.reason === null || typeof record.reason === "string")
913
+ );
914
+ }
915
+
916
+ const MAX_RUNTIME_FINAL_RESPONSE_TEXT_LENGTH = 16_384;
917
+ const MAX_RUNTIME_FINAL_RESPONSE_SOURCE_LENGTH = 256;
918
+ const MAX_RUNTIME_FINAL_RESPONSE_ARTIFACT_PATH_LENGTH = 1_024;
919
+ const MAX_RUNTIME_ERROR_CODE_LENGTH = 128;
920
+ const MAX_RUNTIME_ERROR_MESSAGE_LENGTH = 4_096;
921
+
922
+ function isBoundedNullableString(value: unknown, maxLength: number): value is string | null {
923
+ return value === null || (typeof value === "string" && value.length <= maxLength);
924
+ }
925
+
926
+ function isValidRuntimeFinalResponse(value: unknown): value is CoordinatorFinalResponse {
927
+ const record = asRecord(value);
928
+ return (
929
+ record !== null &&
930
+ Object.keys(record).every(key => ["text", "format", "source", "artifact_path", "truncated"].includes(key)) &&
931
+ isBoundedNullableString(record.text, MAX_RUNTIME_FINAL_RESPONSE_TEXT_LENGTH) &&
932
+ record.format === "markdown" &&
933
+ (record.source === null ||
934
+ (typeof record.source === "string" &&
935
+ record.source.length > 0 &&
936
+ record.source.length <= MAX_RUNTIME_FINAL_RESPONSE_SOURCE_LENGTH)) &&
937
+ (record.artifact_path === null ||
938
+ (typeof record.artifact_path === "string" &&
939
+ record.artifact_path.length > 0 &&
940
+ record.artifact_path.length <= MAX_RUNTIME_FINAL_RESPONSE_ARTIFACT_PATH_LENGTH &&
941
+ !path.isAbsolute(record.artifact_path) &&
942
+ !record.artifact_path.split(/[\\/]/).includes(".."))) &&
943
+ typeof record.truncated === "boolean"
944
+ );
945
+ }
946
+
947
+ function isValidRuntimeError(value: unknown): value is NonNullable<RuntimeSessionStatePayload["error"]> {
948
+ const record = asRecord(value);
949
+ return (
950
+ record !== null &&
951
+ Object.keys(record).every(key => ["code", "message", "recoverable"].includes(key)) &&
952
+ typeof record.code === "string" &&
953
+ record.code.length > 0 &&
954
+ record.code.length <= MAX_RUNTIME_ERROR_CODE_LENGTH &&
955
+ typeof record.message === "string" &&
956
+ record.message.length > 0 &&
957
+ record.message.length <= MAX_RUNTIME_ERROR_MESSAGE_LENGTH &&
958
+ typeof record.recoverable === "boolean"
959
+ );
960
+ }
961
+
962
+ function isValidOwnerTerminalRuntimeReceipt(value: unknown, sessionId: string): boolean {
963
+ const record = asRecord(value);
964
+ if (!record) return false;
965
+ const baseKeys = [
966
+ "generation",
967
+ "socket_key",
968
+ "signal",
969
+ "result",
970
+ "classification",
971
+ "observer",
972
+ "observed_at",
973
+ "dedupe_key",
974
+ ];
975
+ const keys = Object.keys(record);
976
+ if (!keys.every(key => [...baseKeys, "intent_id"].includes(key)) || baseKeys.some(key => !keys.includes(key)))
977
+ return false;
978
+ if (
979
+ !validRuntimeOwnerGeneration(record.generation) ||
980
+ !validRuntimeOwnerGeneration(record.socket_key) ||
981
+ typeof record.signal !== "string" ||
982
+ !["SIGTERM", "SIGHUP", "SIGINT", "SIGKILL", "EXIT", "MANUAL", "UNKNOWN"].includes(record.signal) ||
983
+ typeof record.result !== "string" ||
984
+ !record.result ||
985
+ record.result.length > 64 ||
986
+ !["expected_operator_shutdown", "unexpected_owner_loss", "non_operator_cleanup"].includes(
987
+ String(record.classification),
988
+ ) ||
989
+ !["sidecar", "raw_monitor"].includes(String(record.observer)) ||
990
+ typeof record.observed_at !== "string" ||
991
+ !Number.isFinite(Date.parse(record.observed_at)) ||
992
+ record.dedupe_key !== `owner-loss:${sessionId}:${record.generation}`
993
+ )
994
+ return false;
995
+ if (record.classification === "expected_operator_shutdown")
996
+ return (
997
+ record.signal === "SIGTERM" &&
998
+ record.result === "owner_term_then_session_cleanup" &&
999
+ typeof record.intent_id === "string" &&
1000
+ record.intent_id.length > 0
1001
+ );
1002
+ if (record.intent_id !== undefined) return false;
1003
+ if (record.classification === "non_operator_cleanup") return record.result === "cleanup";
1004
+ return ["owner_lost", "cleanup", "process_postmortem", "exit", "unknown_terminal"].includes(record.result);
1005
+ }
1006
+
1007
+ function isValidRuntimeSessionState(value: unknown, sessionId: string): value is RuntimeSessionStatePayload {
1008
+ if (!isValidCoordinatorSessionState(value, sessionId)) return false;
1009
+ const record = asRecord(value);
1010
+ if (!record) return false;
1011
+ const finalResponse = record.final_response;
1012
+ const error = record.error;
1013
+ if (finalResponse !== undefined && !isValidRuntimeFinalResponse(finalResponse)) return false;
1014
+ if (record.source !== "coordinator" && record.live !== (record.state === "running")) return false;
1015
+ if (error !== undefined && error !== null && !isValidRuntimeError(error)) return false;
1016
+ if (record.owner_generation !== undefined && !validRuntimeOwnerGeneration(record.owner_generation)) return false;
1017
+ if (record.source === "coordinator") return finalResponse === undefined && (error === undefined || error === null);
1018
+ if (record.source === "process_postmortem") {
1019
+ if (record.state !== "completed" && record.state !== "errored") return false;
1020
+ if (record.event === "owner_terminal") {
1021
+ if (!isValidOwnerTerminalRuntimeReceipt(record.owner_terminal, sessionId)) return false;
1022
+ const ownerTerminal = asRecord(record.owner_terminal)!;
1023
+ const expected = ownerTerminal.classification === "expected_operator_shutdown";
1024
+ if (
1025
+ record.reason !== ownerTerminal.classification ||
1026
+ record.state !== (expected ? "completed" : "errored") ||
1027
+ (expected
1028
+ ? error !== undefined && error !== null
1029
+ : error === undefined ||
1030
+ error === null ||
1031
+ error.code !== ownerTerminal.classification ||
1032
+ error.recoverable !== true)
1033
+ )
1034
+ return false;
1035
+ } else if (record.event === "process_exit") {
1036
+ if (
1037
+ record.owner_terminal !== undefined ||
1038
+ typeof record.exit_kind !== "string" ||
1039
+ record.exit_kind.length === 0 ||
1040
+ record.exit_kind.length > 128 ||
1041
+ (record.exit_code !== null &&
1042
+ (typeof record.exit_code !== "number" || !Number.isSafeInteger(record.exit_code))) ||
1043
+ (record.signal !== null &&
1044
+ record.signal !== "SIGINT" &&
1045
+ record.signal !== "SIGTERM" &&
1046
+ record.signal !== "SIGHUP") ||
1047
+ typeof record.prompt_accepted !== "boolean" ||
1048
+ typeof record.observed_recoverable_worktree_changes !== "boolean" ||
1049
+ (record.worktree_baseline_dirty !== null && typeof record.worktree_baseline_dirty !== "boolean") ||
1050
+ typeof record.worktree_changed_since_baseline !== "boolean"
1051
+ )
1052
+ return false;
1053
+ } else {
1054
+ return false;
1055
+ }
1056
+ }
1057
+ if (record.state === "completed") return error === undefined || error === null;
1058
+ if (record.state === "errored") return error !== undefined && error !== null;
1059
+ return finalResponse === undefined && (error === undefined || error === null);
1060
+ }
1061
+
1062
+ const PUBLIC_COORDINATOR_ERRORS = new Set([
1063
+ "coordinator_workdir_required",
1064
+ "coordinator_workdir_roots_required",
1065
+ "coordinator_workdir_outside_allowed_roots",
1066
+ "coordinator_artifact_path_required",
1067
+ "coordinator_artifact_roots_required",
1068
+ "coordinator_artifact_outside_allowed_roots",
1069
+ "coordinator_mutation_class_disabled",
1070
+ "coordinator_mutation_call_not_allowed",
1071
+ "invalid_session_id",
1072
+ "invalid_tmux_session",
1073
+ "invalid_tmux_target",
1074
+ "invalid_turn_id",
1075
+ "invalid_question_id",
1076
+ "session_id_conflict",
1077
+ "tmux_session_unavailable",
1078
+ "tmux_target_unavailable",
1079
+ "coordinator_session_command_required",
1080
+ "coordinator_launch_attestation_missing",
1081
+ "coordinator_launch_attestation_mismatch",
1082
+ "coordinator_launch_metadata_missing",
1083
+ "coordinator_readiness_path_invalid",
1084
+ "session_origin_unverified",
1085
+ "coordinator_prompt_buffer_privacy_unverified",
1086
+ "coordinator_tmux_start_failed",
1087
+ "coordinator_tmux_owner_server_unsafe",
1088
+ "coordinator_tmux_owner_server_unverifiable",
1089
+ "coordinator_tmux_owner_server_race",
1090
+ "coordinator_evidence_paths_must_be_array",
1091
+ ]);
1092
+
1093
+ class CoordinatorStartError extends Error {
1094
+ constructor(
1095
+ message: string,
1096
+ readonly cleanupStatus: "cleaned" | "failed" | "unverifiable",
1097
+ ) {
1098
+ super(message);
1099
+ }
1100
+ }
1101
+
1102
+ function publicCleanupStatus(error: unknown): "cleaned" | "failed" | "unverifiable" | undefined {
1103
+ return error instanceof CoordinatorStartError ? error.cleanupStatus : undefined;
1104
+ }
1105
+
1106
+ function publicCoordinatorError(error: unknown): string {
1107
+ if (error instanceof CoordinatorStateError) return `coordinator_state_${error.reason}`;
1108
+ const message = error instanceof Error ? error.message : "";
1109
+ const normalized = message
1110
+ .replace(/[\u0000-\u001f\u007f]/g, " ")
1111
+ .replace(/\s+/g, " ")
1112
+ .trim();
1113
+ const code = normalized.split(":", 1)[0];
1114
+ return PUBLIC_COORDINATOR_ERRORS.has(code) ? code : "coordinator_request_failed";
1115
+ }
1116
+
1117
+ function isExpectedStateAbsence(error: unknown): boolean {
1118
+ return (error as NodeJS.ErrnoException | undefined)?.code === "ENOENT";
1119
+ }
1120
+
560
1121
  async function readJsonFile(file: string): Promise<unknown | null> {
1122
+ let content: string;
1123
+ try {
1124
+ content = await fs.readFile(file, "utf8");
1125
+ } catch (error) {
1126
+ if (isExpectedStateAbsence(error)) return null;
1127
+ throw coordinatorStateError("unreadable");
1128
+ }
561
1129
  try {
562
- return JSON.parse(await fs.readFile(file, "utf8"));
1130
+ return JSON.parse(content);
563
1131
  } catch {
564
- return null;
1132
+ throw coordinatorStateError("invalid");
565
1133
  }
566
1134
  }
567
1135
 
@@ -571,15 +1139,16 @@ async function writeJsonFile(file: string, value: unknown): Promise<void> {
571
1139
  }
572
1140
 
573
1141
  async function listJsonFiles(dir: string): Promise<unknown[]> {
1142
+ let entries: string[];
574
1143
  try {
575
- const entries = await fs.readdir(dir);
576
- const values = await Promise.all(
577
- entries.filter(entry => entry.endsWith(".json")).map(entry => readJsonFile(path.join(dir, entry))),
578
- );
579
- return values.filter(value => value !== null);
580
- } catch {
581
- return [];
1144
+ entries = await fs.readdir(dir);
1145
+ } catch (error) {
1146
+ if (isExpectedStateAbsence(error)) return [];
1147
+ throw coordinatorStateError("unreadable");
582
1148
  }
1149
+ return await Promise.all(
1150
+ entries.filter(entry => entry.endsWith(".json")).map(entry => readJsonFile(path.join(dir, entry))),
1151
+ );
583
1152
  }
584
1153
 
585
1154
  const COORDINATOR_STATUS_EVENT_LIMIT = 100;
@@ -667,22 +1236,39 @@ function boundSummary(value: string): string {
667
1236
  }
668
1237
 
669
1238
  async function readLatestEventSeq(namespaceDir: string): Promise<number> {
670
- const sequence = asRecord(await readJsonFile(eventSequenceFile(namespaceDir)));
671
- const seq = sequence?.seq;
672
- if (typeof seq === "number" && Number.isInteger(seq) && seq >= 0) return seq;
673
- let latestSeq = 0;
674
- for (const event of await readCoordinatorEvents(namespaceDir)) latestSeq = Math.max(latestSeq, event.seq);
675
- return latestSeq;
1239
+ const rawSequence = await readJsonFile(eventSequenceFile(namespaceDir));
1240
+ const events = await readCoordinatorEvents(namespaceDir);
1241
+ const journalLatestSeq = events.at(-1)?.seq ?? 0;
1242
+ if (rawSequence === null) return journalLatestSeq;
1243
+ const sequence = asRecord(rawSequence);
1244
+ if (
1245
+ !sequence ||
1246
+ Object.keys(sequence).some(key => key !== "seq" && key !== "updated_at") ||
1247
+ typeof sequence.seq !== "number" ||
1248
+ !Number.isSafeInteger(sequence.seq) ||
1249
+ sequence.seq < 0 ||
1250
+ typeof sequence.updated_at !== "string" ||
1251
+ !Number.isFinite(Date.parse(sequence.updated_at)) ||
1252
+ sequence.seq > journalLatestSeq
1253
+ )
1254
+ throw coordinatorStateError("invalid");
1255
+ return journalLatestSeq;
676
1256
  }
677
1257
 
678
1258
  const eventAppendQueues = new Map<string, Promise<unknown>>();
679
1259
 
1260
+ /** Lock order: namespace transaction lock, then event journal lock. */
1261
+ function coordinatorTransactionLockFile(namespaceDir: string): string {
1262
+ return path.join(namespaceDir, "locks", "mutation.lock");
1263
+ }
1264
+
1265
+ async function withCoordinatorTransaction<T>(namespaceDir: string, operation: () => Promise<T>): Promise<T> {
1266
+ return await withSessionStateLock(coordinatorTransactionLockFile(namespaceDir), operation);
1267
+ }
1268
+
680
1269
  async function appendCoordinatorEvent(namespaceDir: string, input: CoordinatorEventInput): Promise<CoordinatorEvent> {
681
1270
  const previous = eventAppendQueues.get(namespaceDir) ?? Promise.resolve();
682
- let release!: () => void;
683
- const current = new Promise<void>(resolve => {
684
- release = resolve;
685
- });
1271
+ const { promise: current, resolve: release } = Promise.withResolvers<void>();
686
1272
  const queued = previous.then(
687
1273
  () => current,
688
1274
  () => current,
@@ -691,57 +1277,148 @@ async function appendCoordinatorEvent(namespaceDir: string, input: CoordinatorEv
691
1277
 
692
1278
  await previous.catch(() => undefined);
693
1279
  try {
694
- const latestSeq = await readLatestEventSeq(namespaceDir);
695
- const seq = latestSeq + 1;
696
- const timestamp = new Date().toISOString();
697
- const event: CoordinatorEvent = {
698
- schema_version: 1,
699
- seq,
700
- id: `event-${seq.toString().padStart(12, "0")}`,
701
- timestamp,
702
- kind: input.kind,
703
- summary: boundSummary(input.summary),
704
- ...(input.sessionId ? { session_id: input.sessionId } : {}),
705
- ...(input.turnId ? { turn_id: input.turnId } : {}),
706
- ...(input.questionId ? { question_id: input.questionId } : {}),
707
- ...(input.reportId ? { report_id: input.reportId } : {}),
708
- ...(input.payloadRef ? { payload_ref: input.payloadRef } : {}),
709
- ...(input.metadata ? { metadata: input.metadata } : {}),
710
- };
711
- await ensureDir(eventsDir(namespaceDir));
712
- await fs.appendFile(eventJournalFile(namespaceDir), `${JSON.stringify(event)}\n`);
713
- await writeJsonFile(eventSequenceFile(namespaceDir), { seq, updated_at: timestamp });
714
- return event;
1280
+ return await withSessionStateLock(eventJournalFile(namespaceDir), async () => {
1281
+ const latestSeq = await readLatestEventSeq(namespaceDir);
1282
+ const seq = latestSeq + 1;
1283
+ const timestamp = new Date().toISOString();
1284
+ const event: CoordinatorEvent = {
1285
+ schema_version: 1,
1286
+ seq,
1287
+ id: `event-${seq.toString().padStart(12, "0")}`,
1288
+ timestamp,
1289
+ kind: input.kind,
1290
+ summary: boundSummary(input.summary),
1291
+ ...(input.sessionId ? { session_id: input.sessionId } : {}),
1292
+ ...(input.turnId ? { turn_id: input.turnId } : {}),
1293
+ ...(input.questionId ? { question_id: input.questionId } : {}),
1294
+ ...(input.reportId ? { report_id: input.reportId } : {}),
1295
+ ...(input.payloadRef ? { payload_ref: input.payloadRef } : {}),
1296
+ ...(input.metadata ? { metadata: input.metadata } : {}),
1297
+ };
1298
+ await ensureDir(eventsDir(namespaceDir));
1299
+ await fs.appendFile(eventJournalFile(namespaceDir), `${JSON.stringify(event)}\n`);
1300
+ await writeJsonFile(eventSequenceFile(namespaceDir), {
1301
+ seq,
1302
+ updated_at: timestamp,
1303
+ });
1304
+ return event;
1305
+ });
715
1306
  } finally {
716
1307
  release();
717
1308
  if (eventAppendQueues.get(namespaceDir) === queued) eventAppendQueues.delete(namespaceDir);
718
1309
  }
719
1310
  }
720
1311
 
721
- function parseCoordinatorEvent(line: string): CoordinatorEvent | null {
1312
+ // Per-session mutation lock. Concurrent stdio dispatch (see pumpCoordinatorMcpStream) makes
1313
+ // same-session read-modify-write tool calls (notably send_prompt: read active turn → decide →
1314
+ // write new turn) interleave, which could persist two "active" turns while only one active-turn
1315
+ // pointer survives. Serializing per session — same promise-chain shape as eventAppendQueues —
1316
+ // restores the atomicity the former serial read loop provided, without serializing across
1317
+ // sessions or blocking long read-only polls (await_turn/watch_events). This is an in-process
1318
+ // lock distinct from the file-based withSessionStateLock/withCoordinatorTransaction, so it can
1319
+ // never deadlock by nesting inside them.
1320
+ const sessionMutationQueues = new Map<string, Promise<unknown>>();
1321
+
1322
+ async function withSessionMutation<T>(key: string, fn: () => Promise<T>): Promise<T> {
1323
+ const previous = sessionMutationQueues.get(key) ?? Promise.resolve();
1324
+ const { promise: current, resolve: release } = Promise.withResolvers<void>();
1325
+ const queued = previous.then(
1326
+ () => current,
1327
+ () => current,
1328
+ );
1329
+ sessionMutationQueues.set(key, queued);
1330
+ await previous.catch(() => undefined);
1331
+ try {
1332
+ return await fn();
1333
+ } finally {
1334
+ release();
1335
+ if (sessionMutationQueues.get(key) === queued) sessionMutationQueues.delete(key);
1336
+ }
1337
+ }
1338
+
1339
+ function parseCoordinatorEvent(line: string): CoordinatorEvent {
1340
+ let event: unknown;
722
1341
  try {
723
- const event = JSON.parse(line) as CoordinatorEvent;
724
- if (typeof event.seq !== "number" || typeof event.kind !== "string") return null;
725
- return event;
1342
+ event = JSON.parse(line);
726
1343
  } catch {
727
- return null;
1344
+ throw coordinatorStateError("invalid");
1345
+ }
1346
+ const record = asRecord(event);
1347
+ const allowed = new Set([
1348
+ "schema_version",
1349
+ "seq",
1350
+ "id",
1351
+ "timestamp",
1352
+ "kind",
1353
+ "session_id",
1354
+ "turn_id",
1355
+ "question_id",
1356
+ "report_id",
1357
+ "summary",
1358
+ "payload_ref",
1359
+ "metadata",
1360
+ ]);
1361
+ const metadata = asRecord(record?.metadata);
1362
+ const validMetadata =
1363
+ record?.metadata === undefined && metadata === null
1364
+ ? true
1365
+ : metadata !== null &&
1366
+ Object.values(metadata).every(
1367
+ value => value === null || ["string", "number", "boolean"].includes(typeof value),
1368
+ );
1369
+ const validOptionalExternalId = (value: unknown) =>
1370
+ value === undefined || (typeof value === "string" && SAFE_EXTERNAL_ID_PATTERN.test(value));
1371
+ const validPayloadRef =
1372
+ record?.payload_ref === undefined ||
1373
+ (typeof record.payload_ref === "string" &&
1374
+ record.payload_ref.length > 0 &&
1375
+ !path.isAbsolute(record.payload_ref) &&
1376
+ !record.payload_ref.split(/[\\/]/).includes(".."));
1377
+ if (
1378
+ !record ||
1379
+ Object.keys(record).some(key => !allowed.has(key)) ||
1380
+ record.schema_version !== 1 ||
1381
+ typeof record.seq !== "number" ||
1382
+ !Number.isSafeInteger(record.seq) ||
1383
+ record.seq <= 0 ||
1384
+ record.id !== `event-${record.seq.toString().padStart(12, "0")}` ||
1385
+ typeof record.timestamp !== "string" ||
1386
+ !Number.isFinite(Date.parse(record.timestamp)) ||
1387
+ typeof record.kind !== "string" ||
1388
+ !COORDINATOR_EVENT_KINDS.has(record.kind as CoordinatorEventKind) ||
1389
+ typeof record.summary !== "string" ||
1390
+ record.summary.length > 240 ||
1391
+ !validOptionalExternalId(record.session_id) ||
1392
+ (record.turn_id !== undefined && (typeof record.turn_id !== "string" || !TURN_ID_PATTERN.test(record.turn_id))) ||
1393
+ !validOptionalExternalId(record.question_id) ||
1394
+ !validOptionalExternalId(record.report_id) ||
1395
+ !validPayloadRef ||
1396
+ !validMetadata
1397
+ ) {
1398
+ throw coordinatorStateError("invalid");
728
1399
  }
1400
+ return record as unknown as CoordinatorEvent;
729
1401
  }
730
1402
 
731
1403
  async function readCoordinatorEvents(namespaceDir: string): Promise<CoordinatorEvent[]> {
1404
+ const journalFile = eventJournalFile(namespaceDir);
1405
+ let content: string;
732
1406
  try {
733
- const content = await fs.readFile(eventJournalFile(namespaceDir), "utf8");
734
- return content
735
- .split("\n")
736
- .map(line => line.trim())
737
- .filter(Boolean)
738
- .map(parseCoordinatorEvent)
739
- .filter((event): event is CoordinatorEvent => event !== null)
740
- .sort((left, right) => left.seq - right.seq);
1407
+ content = await fs.readFile(journalFile, "utf8");
741
1408
  } catch (error) {
742
- if ((error as NodeJS.ErrnoException).code === "ENOENT") return [];
743
- throw error;
1409
+ if (isExpectedStateAbsence(error)) return [];
1410
+ throw coordinatorStateError("unreadable");
1411
+ }
1412
+ const events = content
1413
+ .split("\n")
1414
+ .map(line => line.trim())
1415
+ .filter(Boolean)
1416
+ .map(parseCoordinatorEvent);
1417
+ for (let index = 1; index < events.length; index++) {
1418
+ if (events[index].seq !== events[index - 1].seq + 1) throw coordinatorStateError("invalid");
744
1419
  }
1420
+
1421
+ return events;
745
1422
  }
746
1423
 
747
1424
  function boundedEventLimit(value: unknown): number {
@@ -818,6 +1495,33 @@ function safeTmuxTarget(value: unknown): string {
818
1495
  return value;
819
1496
  }
820
1497
 
1498
+ function safeTmuxRuntimeIdentity(value: string): boolean {
1499
+ return value.length > 0 && value.length <= 160 && !/[\s\u0000-\u001f\u007f]/.test(value);
1500
+ }
1501
+
1502
+ function coordinatorCleanupPredicate(expectedPid: number, nativeSessionId: string, attemptSession: string): string {
1503
+ return `#{&&:#{==:#{pid},${expectedPid}},#{&&:#{==:#{session_id},${nativeSessionId}},#{==:#{session_name},${attemptSession}}}}`;
1504
+ }
1505
+
1506
+ function parseTmuxNewSessionRecord(
1507
+ stdout: string,
1508
+ sessionName: string,
1509
+ ): { tmuxTarget: string; paneId: string; nativeSessionId: string } | undefined {
1510
+ const match = /^(\S+) (%\d+) (\$\d+)\n?$/.exec(stdout);
1511
+ if (!match) return undefined;
1512
+ const [tmuxTarget, paneId, nativeSessionId] = match.slice(1);
1513
+ const targetSuffix = tmuxTarget!.slice(`${sessionName}:`.length);
1514
+ if (
1515
+ !tmuxTarget!.startsWith(`${sessionName}:`) ||
1516
+ !/^\d+\.\d+$/.test(targetSuffix) ||
1517
+ !safeTmuxRuntimeIdentity(tmuxTarget!) ||
1518
+ !safeTmuxRuntimeIdentity(paneId!) ||
1519
+ !safeTmuxRuntimeIdentity(nativeSessionId!)
1520
+ )
1521
+ return undefined;
1522
+ return { tmuxTarget: tmuxTarget!, paneId: paneId!, nativeSessionId: nativeSessionId! };
1523
+ }
1524
+
821
1525
  function optionalString(value: unknown): string | null {
822
1526
  return typeof value === "string" && value.trim().length > 0 ? value.trim() : null;
823
1527
  }
@@ -846,8 +1550,150 @@ function sessionStateFile(namespaceDir: string, sessionId: string): string {
846
1550
  return path.join(namespaceDir, "session-states", `${safeExternalId("session", sessionId)}.json`);
847
1551
  }
848
1552
 
1553
+ function isTimestamp(value: unknown): value is string {
1554
+ return typeof value === "string" && Number.isFinite(Date.parse(value));
1555
+ }
1556
+
1557
+ function isValidTurnRecord(value: unknown): value is TurnRecord {
1558
+ const turn = asRecord(value);
1559
+ const delivery = asRecord(turn?.delivery);
1560
+ const prompt = asRecord(turn?.prompt);
1561
+ const response = asRecord(turn?.final_response);
1562
+ const liveness = asRecord(turn?.liveness);
1563
+ const namespace = asRecord(turn?.namespace);
1564
+ const statuses: readonly TurnStatus[] = [
1565
+ "queued",
1566
+ "delivering",
1567
+ "active",
1568
+ "waiting_for_answer",
1569
+ "completing",
1570
+ "completed",
1571
+ "failed",
1572
+ "cancelled",
1573
+ "superseded",
1574
+ ];
1575
+ if (
1576
+ turn?.schema_version !== 1 ||
1577
+ typeof turn.turn_id !== "string" ||
1578
+ !TURN_ID_PATTERN.test(turn.turn_id) ||
1579
+ typeof turn.session_id !== "string" ||
1580
+ !SAFE_EXTERNAL_ID_PATTERN.test(turn.session_id) ||
1581
+ !namespace ||
1582
+ (namespace.profile !== null && typeof namespace.profile !== "string") ||
1583
+ (namespace.repo !== null && typeof namespace.repo !== "string") ||
1584
+ !statuses.includes(turn.status as TurnStatus)
1585
+ )
1586
+ return false;
1587
+ if (
1588
+ !prompt ||
1589
+ typeof prompt.text !== "string" ||
1590
+ !isTimestamp(prompt.created_at) ||
1591
+ (prompt.source !== "mcp" && prompt.source !== "question_answer")
1592
+ )
1593
+ return false;
1594
+ if (
1595
+ !delivery ||
1596
+ typeof delivery.delivered !== "boolean" ||
1597
+ typeof delivery.queued !== "boolean" ||
1598
+ (delivery.target !== null && typeof delivery.target !== "string") ||
1599
+ (delivery.tmux_keys_sent !== undefined && typeof delivery.tmux_keys_sent !== "boolean") ||
1600
+ (delivery.prompt_acknowledged !== undefined && typeof delivery.prompt_acknowledged !== "boolean") ||
1601
+ (delivery.state !== undefined &&
1602
+ ![
1603
+ "queued",
1604
+ "awaiting_runtime_readiness",
1605
+ "delivering",
1606
+ "tmux_keys_sent",
1607
+ "acknowledged",
1608
+ "unavailable",
1609
+ "unacknowledged",
1610
+ ].includes(delivery.state as string)) ||
1611
+ !Array.isArray(delivery.attempts) ||
1612
+ !delivery.attempts.every(attempt => {
1613
+ const item = asRecord(attempt);
1614
+ return (
1615
+ item !== null &&
1616
+ typeof item.delivered === "boolean" &&
1617
+ isTimestamp(item.created_at) &&
1618
+ (item.reason === null || typeof item.reason === "string") &&
1619
+ (item.channel === undefined || item.channel === "tmux_keys" || item.channel === "runtime_ack") &&
1620
+ (item.tmux_keys_sent === undefined || typeof item.tmux_keys_sent === "boolean")
1621
+ );
1622
+ })
1623
+ )
1624
+ return false;
1625
+ if (
1626
+ !Array.isArray(turn.question_ids) ||
1627
+ !turn.question_ids.every(id => typeof id === "string" && SAFE_EXTERNAL_ID_PATTERN.test(id)) ||
1628
+ !isValidRuntimeFinalResponse(response) ||
1629
+ !Array.isArray(turn.evidence) ||
1630
+ !turn.evidence.every(item => asRecord(item) !== null) ||
1631
+ (turn.error !== null && !isValidRuntimeError(turn.error))
1632
+ )
1633
+ return false;
1634
+ if (
1635
+ !liveness ||
1636
+ (liveness.checked_at !== null && !isTimestamp(liveness.checked_at)) ||
1637
+ (liveness.live !== null && typeof liveness.live !== "boolean") ||
1638
+ (liveness.reason !== null && typeof liveness.reason !== "string") ||
1639
+ !isTimestamp(turn.created_at) ||
1640
+ !isTimestamp(turn.updated_at) ||
1641
+ (turn.started_at !== null && !isTimestamp(turn.started_at)) ||
1642
+ (turn.completed_at !== null && !isTimestamp(turn.completed_at))
1643
+ )
1644
+ return false;
1645
+ return TERMINAL_TURN_STATUSES.has(turn.status as TurnStatus)
1646
+ ? turn.completed_at !== null && (turn.status === "failed" ? turn.error !== null : turn.error === null)
1647
+ : turn.completed_at === null && turn.error === null;
1648
+ }
1649
+
1650
+ function isValidActiveTurnRecord(
1651
+ value: unknown,
1652
+ sessionId: string,
1653
+ ): value is {
1654
+ session_id: string;
1655
+ turn_id: string;
1656
+ status: TurnStatus;
1657
+ updated_at: string;
1658
+ } {
1659
+ const active = asRecord(value);
1660
+ return (
1661
+ active !== null &&
1662
+ Object.keys(active).every(key => ["session_id", "turn_id", "status", "updated_at"].includes(key)) &&
1663
+ active.session_id === sessionId &&
1664
+ typeof active.turn_id === "string" &&
1665
+ TURN_ID_PATTERN.test(active.turn_id) &&
1666
+ ACTIVE_TURN_STATUSES.has(active.status as TurnStatus) &&
1667
+ isTimestamp(active.updated_at)
1668
+ );
1669
+ }
1670
+
849
1671
  async function readTurnRecord(namespaceDir: string, turnId: unknown): Promise<TurnRecord | null> {
850
- return (await readJsonFile(turnFile(namespaceDir, safeTurnId(turnId)))) as TurnRecord | null;
1672
+ const requestedTurnId = safeTurnId(turnId);
1673
+ const value = await readJsonFile(turnFile(namespaceDir, requestedTurnId));
1674
+ if (value === null) return null;
1675
+ if (!isValidTurnRecord(value) || value.turn_id !== requestedTurnId) throw coordinatorStateError("invalid");
1676
+ return value;
1677
+ }
1678
+
1679
+ async function listTurnRecords(namespaceDir: string): Promise<TurnRecord[]> {
1680
+ let entries: string[];
1681
+ try {
1682
+ entries = await fs.readdir(turnsDir(namespaceDir));
1683
+ } catch (error) {
1684
+ if (isExpectedStateAbsence(error)) return [];
1685
+ throw coordinatorStateError("unreadable");
1686
+ }
1687
+ const records = await Promise.all(
1688
+ entries
1689
+ .filter(entry => entry.endsWith(".json"))
1690
+ .map(async entry => {
1691
+ const turnId = entry.slice(0, -".json".length);
1692
+ if (!TURN_ID_PATTERN.test(turnId)) throw coordinatorStateError("invalid");
1693
+ return await readTurnRecord(namespaceDir, turnId);
1694
+ }),
1695
+ );
1696
+ return records.filter((record): record is TurnRecord => record !== null);
851
1697
  }
852
1698
 
853
1699
  function turnEventKind(status: TurnStatus): CoordinatorEventKind | null {
@@ -863,7 +1709,8 @@ function turnEventKind(status: TurnStatus): CoordinatorEventKind | null {
863
1709
  }
864
1710
 
865
1711
  async function writeTurnRecord(namespaceDir: string, turn: TurnRecord): Promise<void> {
866
- const previous = (await readJsonFile(turnFile(namespaceDir, turn.turn_id))) as TurnRecord | null;
1712
+ if (!isValidTurnRecord(turn)) throw coordinatorStateError("invalid");
1713
+ const previous = await readTurnRecord(namespaceDir, turn.turn_id);
867
1714
  await writeJsonFile(turnFile(namespaceDir, turn.turn_id), turn);
868
1715
  const kind = previous?.status === turn.status ? null : turnEventKind(turn.status);
869
1716
  if (kind) {
@@ -883,14 +1730,17 @@ async function writeTurnRecord(namespaceDir: string, turn: TurnRecord): Promise<
883
1730
  }
884
1731
 
885
1732
  async function readActiveTurn(namespaceDir: string, sessionId: string): Promise<TurnRecord | null> {
886
- const active = asRecord(await readJsonFile(activeTurnFile(namespaceDir, sessionId)));
887
- if (!active || typeof active.turn_id !== "string") return null;
888
- const turn = await readTurnRecord(namespaceDir, active.turn_id);
889
- if (!turn || turn.session_id !== sessionId || !ACTIVE_TURN_STATUSES.has(turn.status)) return null;
1733
+ const value = await readJsonFile(activeTurnFile(namespaceDir, sessionId));
1734
+ if (value === null) return null;
1735
+ if (!isValidActiveTurnRecord(value, sessionId)) throw coordinatorStateError("invalid");
1736
+ const turn = await readTurnRecord(namespaceDir, value.turn_id);
1737
+ if (!turn || turn.session_id !== sessionId || turn.status !== value.status || !ACTIVE_TURN_STATUSES.has(turn.status))
1738
+ throw coordinatorStateError("invalid");
890
1739
  return turn;
891
1740
  }
892
1741
 
893
1742
  async function writeActiveTurn(namespaceDir: string, turn: TurnRecord): Promise<void> {
1743
+ if (!ACTIVE_TURN_STATUSES.has(turn.status)) throw coordinatorStateError("invalid");
894
1744
  await writeJsonFile(activeTurnFile(namespaceDir, turn.session_id), {
895
1745
  session_id: turn.session_id,
896
1746
  turn_id: turn.turn_id,
@@ -900,15 +1750,84 @@ async function writeActiveTurn(namespaceDir: string, turn: TurnRecord): Promise<
900
1750
  }
901
1751
 
902
1752
  async function clearActiveTurn(namespaceDir: string, turn: TurnRecord): Promise<void> {
903
- const active = asRecord(await readJsonFile(activeTurnFile(namespaceDir, turn.session_id)));
904
- if (active?.turn_id === turn.turn_id) await fs.rm(activeTurnFile(namespaceDir, turn.session_id), { force: true });
1753
+ const value = await readJsonFile(activeTurnFile(namespaceDir, turn.session_id));
1754
+ if (value === null) return;
1755
+ if (!isValidActiveTurnRecord(value, turn.session_id)) throw coordinatorStateError("invalid");
1756
+ if (value.turn_id === turn.turn_id) await fs.rm(activeTurnFile(namespaceDir, turn.session_id), { force: true });
905
1757
  }
906
1758
 
907
1759
  async function readSessionState(namespaceDir: string, sessionId: string): Promise<CoordinatorSessionState | null> {
908
- return (await readJsonFile(sessionStateFile(namespaceDir, sessionId))) as CoordinatorSessionState | null;
1760
+ const value = await readJsonFile(sessionStateFile(namespaceDir, sessionId));
1761
+ if (value === null) return null;
1762
+ if (!isValidRuntimeSessionState(value, sessionId)) throw coordinatorStateError("invalid");
1763
+ return value;
909
1764
  }
910
1765
 
911
- async function writeSessionState(
1766
+ function validRuntimeOwnerGeneration(value: unknown): value is string {
1767
+ return (
1768
+ typeof value === "string" && value.length > 0 && value.length <= 256 && !/[\s\u0000-\u001f\u007f]/.test(value)
1769
+ );
1770
+ }
1771
+
1772
+ function runtimeStateMatchesPrivateOwnerGeneration(
1773
+ sessionState: CoordinatorSessionState,
1774
+ session: Record<string, unknown> | null,
1775
+ ): boolean {
1776
+ const expectedGeneration = session?.tmux_owner_generation ?? session?.tmuxOwnerGeneration;
1777
+ if (expectedGeneration === undefined)
1778
+ return !(sessionState.source === "process_postmortem" && sessionState.event === "owner_terminal");
1779
+ if (!validRuntimeOwnerGeneration(expectedGeneration) || sessionState.owner_generation !== expectedGeneration)
1780
+ return false;
1781
+ if (sessionState.source !== "process_postmortem") return true;
1782
+ if (sessionState.event !== "owner_terminal") return false;
1783
+ const expectedSocketKey = session?.tmux_owner_server_key ?? session?.tmuxOwnerServerKey;
1784
+ return (
1785
+ typeof expectedSocketKey === "string" &&
1786
+ sessionState.owner_terminal?.generation === expectedGeneration &&
1787
+ sessionState.owner_terminal.socket_key === expectedSocketKey
1788
+ );
1789
+ }
1790
+
1791
+ async function reconcileTerminalRuntimeEvidence(namespaceDir: string, sessionId: string): Promise<boolean> {
1792
+ const sessionState = await readSessionState(namespaceDir, sessionId);
1793
+ if (
1794
+ !sessionState ||
1795
+ sessionState.source === "coordinator" ||
1796
+ (sessionState.state !== "completed" && sessionState.state !== "errored")
1797
+ )
1798
+ return false;
1799
+ const session = asRecord(await readJsonFile(path.join(namespaceDir, "sessions", `${sessionId}.json`)));
1800
+ if (!runtimeStateMatchesPrivateOwnerGeneration(sessionState, session)) return false;
1801
+
1802
+ const activeTurn = await readActiveTurn(namespaceDir, sessionId);
1803
+ if (activeTurn && (sessionState.current_turn_id === null || sessionState.current_turn_id === activeTurn.turn_id)) {
1804
+ await markTurnTerminalFromSessionState(namespaceDir, activeTurn, sessionState);
1805
+ }
1806
+ return true;
1807
+ }
1808
+
1809
+ async function preflightCoordinatorMutation(
1810
+ namespaceDir: string,
1811
+ sessionId?: string,
1812
+ options: { rejectTerminalRuntimeEvidence?: boolean } = {},
1813
+ ): Promise<void> {
1814
+ await readLatestEventSeq(namespaceDir);
1815
+ if (!sessionId) return;
1816
+ const [sessionState, session] = await Promise.all([
1817
+ readSessionState(namespaceDir, sessionId),
1818
+ readJsonFile(path.join(namespaceDir, "sessions", `${sessionId}.json`)),
1819
+ ]);
1820
+ const sessionRecord = asRecord(session);
1821
+ if (sessionRecord && normalizeSession(sessionRecord).session_id !== sessionId)
1822
+ throw coordinatorStateError("invalid");
1823
+ if (sessionState && sessionState.session_id !== sessionId) throw coordinatorStateError("invalid");
1824
+ await readActiveTurn(namespaceDir, sessionId);
1825
+ const terminalRuntimeEvidence = await reconcileTerminalRuntimeEvidence(namespaceDir, sessionId);
1826
+ if (terminalRuntimeEvidence && options.rejectTerminalRuntimeEvidence !== false)
1827
+ throw coordinatorStateError("invalid");
1828
+ }
1829
+
1830
+ async function writeSessionStateUnlocked(
912
1831
  namespaceDir: string,
913
1832
  sessionId: string,
914
1833
  state: CoordinatorSessionStateValue,
@@ -921,6 +1840,20 @@ async function writeSessionState(
921
1840
  } = {},
922
1841
  ): Promise<CoordinatorSessionState> {
923
1842
  const previous = await readSessionState(namespaceDir, sessionId);
1843
+ if (previous && previous.source !== "coordinator") return previous;
1844
+ const session = asRecord(await readJsonFile(path.join(namespaceDir, "sessions", `${sessionId}.json`)));
1845
+ const previousCwd = typeof previous?.cwd === "string" && previous.cwd.trim() ? previous.cwd : null;
1846
+ const cwd =
1847
+ typeof session?.cwd === "string" && session.cwd.trim()
1848
+ ? path.resolve(session.cwd)
1849
+ : previousCwd
1850
+ ? path.resolve(previousCwd)
1851
+ : null;
1852
+ if (!cwd) throw coordinatorStateError("invalid");
1853
+ const sessionFile =
1854
+ typeof session?.session_file === "string" && session.session_file.trim()
1855
+ ? path.resolve(session.session_file)
1856
+ : null;
924
1857
  const payload: CoordinatorSessionState = {
925
1858
  schema_version: 1,
926
1859
  session_id: sessionId,
@@ -932,6 +1865,9 @@ async function writeSessionState(
932
1865
  source: options.source ?? "coordinator",
933
1866
  live: options.live ?? previous?.live ?? null,
934
1867
  reason: options.reason ?? null,
1868
+ cwd,
1869
+ workdir: cwd,
1870
+ session_file: sessionFile,
935
1871
  };
936
1872
  await writeJsonFile(sessionStateFile(namespaceDir, sessionId), payload);
937
1873
  if (
@@ -959,31 +1895,196 @@ async function writeSessionState(
959
1895
  return payload;
960
1896
  }
961
1897
 
962
- function hasTmuxIdentity(session: Record<string, unknown>): boolean {
1898
+ interface SessionStateLockOwner {
1899
+ pid: number;
1900
+ start_time: string;
1901
+ token: string;
1902
+ }
1903
+
1904
+ function processStartTime(pid: number): string | null {
1905
+ try {
1906
+ const stat = nodeFs.readFileSync(`/proc/${pid}/stat`, "utf8");
1907
+ const close = stat.lastIndexOf(")");
1908
+ const fields = stat
1909
+ .slice(close + 1)
1910
+ .trim()
1911
+ .split(/\s+/);
1912
+ return fields[19] ?? null;
1913
+ } catch {
1914
+ return null;
1915
+ }
1916
+ }
1917
+
1918
+ function validLockOwner(value: unknown): value is SessionStateLockOwner {
1919
+ if (!value || typeof value !== "object") return false;
1920
+ const owner = value as Partial<SessionStateLockOwner>;
963
1921
  return (
964
- (typeof session.tmux_session === "string" && session.tmux_session.length > 0) ||
965
- (typeof session.tmuxSession === "string" && session.tmuxSession.length > 0)
1922
+ typeof owner.pid === "number" &&
1923
+ Number.isSafeInteger(owner.pid) &&
1924
+ owner.pid > 0 &&
1925
+ typeof owner.start_time === "string" &&
1926
+ typeof owner.token === "string" &&
1927
+ owner.token.length > 0
966
1928
  );
967
1929
  }
968
1930
 
969
- async function markTurnFailedForUnavailableSession(
970
- namespaceDir: string,
971
- turn: TurnRecord,
972
- reason: string,
973
- ): Promise<TurnRecord> {
974
- const timestamp = new Date().toISOString();
975
- const failed: TurnRecord = {
976
- ...turn,
977
- status: "failed",
978
- final_response: {
979
- text: `Coordinator session unavailable: ${reason}`,
980
- format: "markdown",
1931
+ function lockOwnerIsAlive(value: unknown): boolean {
1932
+ if (!validLockOwner(value)) return false;
1933
+ const owner = value;
1934
+ try {
1935
+ process.kill(owner.pid, 0);
1936
+ } catch (error) {
1937
+ if ((error as NodeJS.ErrnoException).code === "ESRCH") return false;
1938
+ return true;
1939
+ }
1940
+ const currentStartTime = processStartTime(owner.pid);
1941
+ return currentStartTime === null || currentStartTime === owner.start_time;
1942
+ }
1943
+
1944
+ async function reclaimStaleSessionStateLock(lockFile: string): Promise<void> {
1945
+ let raw: string;
1946
+ try {
1947
+ raw = await fs.readFile(lockFile, "utf8");
1948
+ } catch (error) {
1949
+ if ((error as NodeJS.ErrnoException).code === "ENOENT") return;
1950
+ throw error;
1951
+ }
1952
+ let owner: unknown;
1953
+ try {
1954
+ owner = JSON.parse(raw);
1955
+ } catch {
1956
+ owner = null;
1957
+ }
1958
+ if (!validLockOwner(owner)) {
1959
+ const stat = await fs.stat(lockFile);
1960
+ if (Date.now() - stat.mtimeMs < 30_000) return;
1961
+ } else if (lockOwnerIsAlive(owner)) return;
1962
+ try {
1963
+ if ((await fs.readFile(lockFile, "utf8")) === raw) await fs.rm(lockFile);
1964
+ } catch (error) {
1965
+ if ((error as NodeJS.ErrnoException).code !== "ENOENT") throw error;
1966
+ }
1967
+ }
1968
+
1969
+ async function withSessionStateLock<T>(stateFile: string, operation: () => Promise<T>): Promise<T> {
1970
+ const lockFile = `${stateFile}.lock`;
1971
+ const owner: SessionStateLockOwner = {
1972
+ pid: process.pid,
1973
+ start_time: processStartTime(process.pid) ?? "unknown",
1974
+ token: randomUUID(),
1975
+ };
1976
+ await ensureDir(path.dirname(stateFile));
1977
+ for (let attempt = 0; attempt < 12_000; attempt++) {
1978
+ let handle: fs.FileHandle | undefined;
1979
+ try {
1980
+ handle = await fs.open(lockFile, "wx");
1981
+ try {
1982
+ await handle.writeFile(JSON.stringify(owner));
1983
+ } catch (error) {
1984
+ await handle.close().catch(() => undefined);
1985
+ handle = undefined;
1986
+ await fs.rm(lockFile, { force: true }).catch(() => undefined);
1987
+ throw error;
1988
+ }
1989
+ const outcome = await operation().then(
1990
+ value => ({ ok: true as const, value }),
1991
+ error => ({ ok: false as const, error }),
1992
+ );
1993
+ await handle.close();
1994
+ try {
1995
+ if ((await fs.readFile(lockFile, "utf8")) === JSON.stringify(owner)) await fs.rm(lockFile);
1996
+ } catch (error) {
1997
+ if ((error as NodeJS.ErrnoException).code !== "ENOENT") throw error;
1998
+ }
1999
+ if (!outcome.ok) throw outcome.error;
2000
+ return outcome.value;
2001
+ } catch (error) {
2002
+ if (handle) throw error;
2003
+ if ((error as NodeJS.ErrnoException).code !== "EEXIST") throw coordinatorStateError("unreadable");
2004
+ await reclaimStaleSessionStateLock(lockFile);
2005
+ await Bun.sleep(5);
2006
+ }
2007
+ }
2008
+ throw coordinatorStateError("unreadable");
2009
+ }
2010
+
2011
+ async function writeSessionState(
2012
+ namespaceDir: string,
2013
+ sessionId: string,
2014
+ state: CoordinatorSessionStateValue,
2015
+ options: {
2016
+ currentTurnId?: string | null;
2017
+ lastTurnId?: string | null;
2018
+ live?: boolean | null;
2019
+ reason?: string | null;
2020
+ source?: CoordinatorSessionState["source"];
2021
+ } = {},
2022
+ ): Promise<CoordinatorSessionState> {
2023
+ const file = sessionStateFile(namespaceDir, sessionId);
2024
+ return await withSessionStateLock(
2025
+ file,
2026
+ async () => await writeSessionStateUnlocked(namespaceDir, sessionId, state, options),
2027
+ );
2028
+ }
2029
+
2030
+ function hasTmuxIdentity(session: Record<string, unknown>): boolean {
2031
+ return (
2032
+ (typeof session.tmux_session === "string" && session.tmux_session.length > 0) ||
2033
+ (typeof session.tmuxSession === "string" && session.tmuxSession.length > 0)
2034
+ );
2035
+ }
2036
+
2037
+ function unavailableSessionReason(turn: TurnRecord, reason: string): string {
2038
+ if (
2039
+ reason === "tmux_session_missing" &&
2040
+ turn.delivery.tmux_keys_sent === true &&
2041
+ turn.delivery.prompt_acknowledged === true
2042
+ ) {
2043
+ return "tmux_session_missing_after_prompt_acknowledgement";
2044
+ }
2045
+ return reason;
2046
+ }
2047
+
2048
+ function unavailableSessionEvidence(turn: TurnRecord, reason: string, timestamp: string): Record<string, unknown>[] {
2049
+ if (reason !== "tmux_session_missing_after_prompt_acknowledgement") return turn.evidence;
2050
+ return [
2051
+ ...turn.evidence,
2052
+ {
2053
+ type: reason,
2054
+ message:
2055
+ "The tmux session disappeared after SKC runtime acknowledged the prompt, before any terminal final_response or error was recorded. Treat this as an in-flight vanished session and inspect/restart with recovery evidence rather than resubmitting blindly.",
2056
+ tmux_keys_sent: true,
2057
+ prompt_acknowledged: true,
2058
+ prior_status: turn.status,
2059
+ created_at: timestamp,
2060
+ },
2061
+ ];
2062
+ }
2063
+
2064
+ async function markTurnFailedForUnavailableSession(
2065
+ namespaceDir: string,
2066
+ turn: TurnRecord,
2067
+ reason: string,
2068
+ ): Promise<TurnRecord> {
2069
+ const timestamp = new Date().toISOString();
2070
+ const durableReason = unavailableSessionReason(turn, reason);
2071
+ const failed: TurnRecord = {
2072
+ ...turn,
2073
+ status: "failed",
2074
+ final_response: {
2075
+ text: `Coordinator session unavailable: ${durableReason}`,
2076
+ format: "markdown",
981
2077
  source: "coordinator_liveness",
982
2078
  artifact_path: null,
983
2079
  truncated: false,
984
2080
  },
985
- error: { code: "session_unavailable", message: reason, recoverable: true },
986
- liveness: { checked_at: timestamp, live: false, reason },
2081
+ evidence: unavailableSessionEvidence(turn, durableReason, timestamp),
2082
+ error: {
2083
+ code: "session_unavailable",
2084
+ message: durableReason,
2085
+ recoverable: true,
2086
+ },
2087
+ liveness: { checked_at: timestamp, live: false, reason: durableReason },
987
2088
  updated_at: timestamp,
988
2089
  completed_at: timestamp,
989
2090
  };
@@ -992,7 +2093,7 @@ async function markTurnFailedForUnavailableSession(
992
2093
  await writeSessionState(namespaceDir, failed.session_id, "stale", {
993
2094
  lastTurnId: failed.turn_id,
994
2095
  live: false,
995
- reason,
2096
+ reason: durableReason,
996
2097
  });
997
2098
  return failed;
998
2099
  }
@@ -1092,6 +2193,18 @@ async function markTurnAcknowledgedFromRuntimeState(
1092
2193
  };
1093
2194
  await writeTurnRecord(namespaceDir, acknowledged);
1094
2195
  await writeActiveTurn(namespaceDir, acknowledged);
2196
+ await appendCoordinatorEvent(namespaceDir, {
2197
+ kind: "turn.acknowledged",
2198
+ sessionId: acknowledged.session_id,
2199
+ turnId: acknowledged.turn_id,
2200
+ summary: `Turn ${acknowledged.turn_id} was acknowledged by the SKC runtime`,
2201
+ payloadRef: path.relative(namespaceDir, turnFile(namespaceDir, acknowledged.turn_id)),
2202
+ metadata: {
2203
+ status: acknowledged.status,
2204
+ tmux_keys_sent: acknowledged.delivery.tmux_keys_sent ?? null,
2205
+ prompt_acknowledged: true,
2206
+ },
2207
+ });
1095
2208
  return acknowledged;
1096
2209
  }
1097
2210
 
@@ -1151,7 +2264,11 @@ async function markTurnFailedForUnacknowledgedDelivery(
1151
2264
  created_at: timestamp,
1152
2265
  },
1153
2266
  ],
1154
- liveness: { checked_at: timestamp, live: turn.liveness.live, reason: PROMPT_ACK_TIMEOUT_REASON },
2267
+ liveness: {
2268
+ checked_at: timestamp,
2269
+ live: turn.liveness.live,
2270
+ reason: PROMPT_ACK_TIMEOUT_REASON,
2271
+ },
1155
2272
  updated_at: timestamp,
1156
2273
  completed_at: timestamp,
1157
2274
  };
@@ -1172,6 +2289,16 @@ async function reconcileRuntimeAcknowledgement(
1172
2289
  ackTimeoutMs: number,
1173
2290
  options: { failOnTimeout: boolean } = { failOnTimeout: true },
1174
2291
  ): Promise<TurnRecord> {
2292
+ const session = asRecord(await readJsonFile(path.join(namespaceDir, "sessions", `${turn.session_id}.json`)));
2293
+ if (
2294
+ sessionState &&
2295
+ runtimeStateMatchesPrivateOwnerGeneration(sessionState, session) &&
2296
+ sessionState.source !== "coordinator" &&
2297
+ (sessionState.state === "completed" || sessionState.state === "errored") &&
2298
+ (sessionState.current_turn_id === null || sessionState.current_turn_id === turn.turn_id)
2299
+ ) {
2300
+ return await markTurnTerminalFromSessionState(namespaceDir, turn, sessionState);
2301
+ }
1175
2302
  if (sessionState && runtimeStateAcknowledgesTurn(turn, sessionState)) {
1176
2303
  return await markTurnAcknowledgedFromRuntimeState(namespaceDir, turn, sessionState);
1177
2304
  }
@@ -1184,6 +2311,27 @@ async function reconcileRuntimeAcknowledgement(
1184
2311
  function shellQuote(value: string): string {
1185
2312
  return `'${value.replaceAll("'", "'\\''")}'`;
1186
2313
  }
2314
+
2315
+ /**
2316
+ * Append an authoritative `--mpreset <profile>` to the coordinator child launch
2317
+ * command so the spawned SKC session activates the selected profile from its
2318
+ * first turn. The profile name is validated against the merged registry before
2319
+ * reaching here; it is still shell-quoted as defense in depth.
2320
+ *
2321
+ * Precedence note: this matches `skc --mpreset <profile>` semantics exactly, so
2322
+ * an explicit model-affecting flag already present in the operator-configured
2323
+ * `sessionCommand` (e.g. a `--model`/`--thinking` baked into the launch command)
2324
+ * follows the same CLI precedence as a direct `skc --mpreset ... --model ...`
2325
+ * invocation. The effective profile recorded on the session is the
2326
+ * coordinator-resolved request, not a child-attested activation receipt; a
2327
+ * cross-process activation receipt is a possible future enhancement and is out
2328
+ * of scope for the per-session selection contract here.
2329
+ */
2330
+ export function buildCoordinatorSessionCommand(sessionCommand: string, mpreset: string | null | undefined): string {
2331
+ if (!mpreset) return sessionCommand;
2332
+ return `${sessionCommand} --mpreset ${shellQuote(mpreset)}`;
2333
+ }
2334
+
1187
2335
  function makeTurnRecord(
1188
2336
  config: CoordinatorMcpConfig,
1189
2337
  sessionId: string,
@@ -1208,7 +2356,13 @@ function makeTurnRecord(
1208
2356
  attempts: [],
1209
2357
  },
1210
2358
  question_ids: [],
1211
- final_response: { text: null, format: "markdown", source: null, artifact_path: null, truncated: false },
2359
+ final_response: {
2360
+ text: null,
2361
+ format: "markdown",
2362
+ source: null,
2363
+ artifact_path: null,
2364
+ truncated: false,
2365
+ },
1212
2366
  evidence: [],
1213
2367
  error: null,
1214
2368
  liveness: { checked_at: null, live: null, reason: null },
@@ -1230,6 +2384,8 @@ function asTerminalTurnStatus(status: unknown): TurnStatus | null {
1230
2384
 
1231
2385
  export const COORDINATOR_AWAIT_TURN_TIMEOUT_MAX_MS = 30 * 60 * 1000;
1232
2386
  export const COORDINATOR_RUNTIME_PROMPT_ACK_TIMEOUT_MAX_MS = MAX_RUNTIME_PROMPT_ACK_TIMEOUT_MS;
2387
+ export const COORDINATOR_RUNTIME_READINESS_TIMEOUT_MAX_MS = MAX_RUNTIME_READINESS_TIMEOUT_MS;
2388
+
1233
2389
  export const COORDINATOR_EVENT_WATCH_TIMEOUT_MAX_MS = 30_000;
1234
2390
  export const COORDINATOR_POLL_INTERVAL_MAX_MS = 10_000;
1235
2391
 
@@ -1248,6 +2404,12 @@ export function boundedRuntimePromptAckTimeoutMs(value: unknown): number {
1248
2404
  COORDINATOR_RUNTIME_PROMPT_ACK_TIMEOUT_MAX_MS,
1249
2405
  );
1250
2406
  }
2407
+ export function boundedRuntimeReadinessTimeoutMs(value: unknown): number {
2408
+ return Math.min(
2409
+ parsePositiveIntegerMs(value, DEFAULT_RUNTIME_READINESS_TIMEOUT_MS),
2410
+ COORDINATOR_RUNTIME_READINESS_TIMEOUT_MAX_MS,
2411
+ );
2412
+ }
1251
2413
 
1252
2414
  export function boundedEventWatchTimeoutMs(value: unknown): number {
1253
2415
  return Math.min(parsePositiveIntegerMs(value, 1000), COORDINATOR_EVENT_WATCH_TIMEOUT_MAX_MS);
@@ -1256,8 +2418,21 @@ export function boundedEventWatchTimeoutMs(value: unknown): number {
1256
2418
  export function boundedPollIntervalMs(value: unknown): number {
1257
2419
  return Math.min(Math.max(parsePositiveIntegerMs(value, 100), 10), COORDINATOR_POLL_INTERVAL_MAX_MS);
1258
2420
  }
1259
- async function runCommand(command: string[]): Promise<{ exitCode: number; stdout: string; stderr: string }> {
1260
- const proc = Bun.spawn(command, { stdout: "pipe", stderr: "pipe" });
2421
+ async function runCommand(
2422
+ command: string[],
2423
+ stdinLine?: string,
2424
+ ): Promise<{ exitCode: number; stdout: string; stderr: string }> {
2425
+ const proc = Bun.spawn(command, {
2426
+ stdin: stdinLine === undefined ? "ignore" : "pipe",
2427
+ stdout: "pipe",
2428
+ stderr: "pipe",
2429
+ });
2430
+ if (stdinLine !== undefined) {
2431
+ const stdin = proc.stdin;
2432
+ if (!stdin) throw new Error("coordinator_command_stdin_unavailable");
2433
+ stdin.write(`${stdinLine}\n`);
2434
+ stdin.end();
2435
+ }
1261
2436
  const [stdout, stderr, exitCode] = await Promise.all([
1262
2437
  new Response(proc.stdout).text(),
1263
2438
  new Response(proc.stderr).text(),
@@ -1266,15 +2441,39 @@ async function runCommand(command: string[]): Promise<{ exitCode: number; stdout
1266
2441
  return { exitCode, stdout, stderr };
1267
2442
  }
1268
2443
 
1269
- type CommandRunner = (command: string[]) => Promise<{ exitCode: number; stdout: string; stderr: string }>;
1270
-
1271
2444
  async function sendTmuxPromptKeys(
1272
2445
  target: string,
1273
2446
  prompt: string,
1274
- runner: CommandRunner = runCommand,
2447
+ runner: CommandRunner,
2448
+ socketKey: string,
2449
+ proveCurrentServer: () => Promise<boolean>,
1275
2450
  ): Promise<boolean> {
1276
- const sent = await runner(["tmux", "send-keys", "-t", target, prompt, "C-m", "C-m"]);
1277
- return sent.exitCode === 0;
2451
+ const runMutation = async (argv: string[]): Promise<CommandResult | null> => {
2452
+ if (!(await proveCurrentServer())) return null;
2453
+ return await runner(argv);
2454
+ };
2455
+ const bufferName = `skc-coordinator-prompt-${randomUUID()}`;
2456
+ const tmux = ["tmux", "-L", socketKey];
2457
+ const zeroize = async (): Promise<boolean> => {
2458
+ for (let attempt = 0; attempt < 2; attempt++) {
2459
+ const cleared = await runMutation([...tmux, "set-buffer", "-b", bufferName, "--", ""]);
2460
+ const deleted =
2461
+ cleared?.exitCode === 0 ? await runMutation([...tmux, "delete-buffer", "-b", bufferName]) : null;
2462
+ if (cleared?.exitCode === 0 && deleted?.exitCode === 0) return true;
2463
+ }
2464
+ return false;
2465
+ };
2466
+ const buffered = await runMutation([...tmux, "set-buffer", "-b", bufferName, "--", prompt]);
2467
+ if (buffered?.exitCode !== 0) return false;
2468
+ const pasted = await runMutation([...tmux, "paste-buffer", "-d", "-b", bufferName, "-t", target]);
2469
+ if (pasted?.exitCode !== 0) {
2470
+ if (!(await zeroize())) throw new Error("coordinator_prompt_buffer_privacy_unverified");
2471
+ return false;
2472
+ }
2473
+ const dismissedAutocomplete = await runMutation([...tmux, "send-keys", "-t", target, "Escape"]);
2474
+ if (dismissedAutocomplete?.exitCode !== 0) return false;
2475
+ const submitted = await runMutation([...tmux, "send-keys", "-t", target, "Enter"]);
2476
+ return submitted?.exitCode === 0;
1278
2477
  }
1279
2478
 
1280
2479
  function boundedLineCount(value: unknown): number {
@@ -1299,7 +2498,10 @@ async function registerExistingTmuxSession(
1299
2498
  namespaceDir: string,
1300
2499
  sessionFilePath: string,
1301
2500
  runner: CommandRunner = runCommand,
1302
- ): Promise<{ session: Record<string, unknown>; sessionState: CoordinatorSessionState }> {
2501
+ ): Promise<{
2502
+ session: Record<string, unknown>;
2503
+ sessionState: CoordinatorSessionState;
2504
+ }> {
1303
2505
  await assertTmuxTargetAvailable(input.tmuxSession, input.tmuxTarget, runner);
1304
2506
  const existing = asRecord(await readJsonFile(sessionFilePath));
1305
2507
  if (existing) {
@@ -1325,6 +2527,7 @@ async function registerExistingTmuxSession(
1325
2527
  authoritative: true,
1326
2528
  warp_attached: input.warpAttached,
1327
2529
  source: input.source,
2530
+ origin: "registered",
1328
2531
  model: input.model,
1329
2532
  };
1330
2533
  await writeJsonFile(sessionFilePath, session);
@@ -1335,52 +2538,406 @@ async function registerExistingTmuxSession(
1335
2538
  return { session, sessionState: state };
1336
2539
  }
1337
2540
 
2541
+ async function portableProcessStartTime(pid: number, runner: CommandRunner): Promise<string | null> {
2542
+ const result = await runner(["ps", "-o", "lstart=", "-p", String(pid)]);
2543
+ const startTime = result.stdout.trim();
2544
+ return result.exitCode === 0 &&
2545
+ startTime.length > 0 &&
2546
+ startTime.length <= 160 &&
2547
+ !/[\r\n\u0000-\u001f\u007f]/.test(startTime)
2548
+ ? startTime
2549
+ : null;
2550
+ }
2551
+
2552
+ export async function coordinatorOwnerIsolationProbe(runner: CommandRunner): Promise<OwnerIsolationProbe> {
2553
+ return {
2554
+ readCallerCgroup: async () => await fs.readFile("/proc/self/cgroup", "utf8").catch(() => null),
2555
+ probeServer: async (socketKey): Promise<TmuxServerProof> => {
2556
+ try {
2557
+ const result = await runner(["tmux", "-L", socketKey, "list-sessions", "-F", "#{pid} #{session_name}"]);
2558
+ if (result.exitCode !== 0) {
2559
+ const diagnostic = `${result.stdout}\n${result.stderr}`.slice(0, 512);
2560
+ return /(?:no server running|failed to connect to server|no sessions|error connecting to)/i.test(
2561
+ diagnostic,
2562
+ )
2563
+ ? { state: "absent" }
2564
+ : { state: "unverifiable" };
2565
+ }
2566
+ const rows = result.stdout
2567
+ .split("\n")
2568
+ .map(line => line.trim().split(/\s+/, 2))
2569
+ .filter(([pid, name]) => Boolean(pid && name));
2570
+ const pid = Number(rows[0]?.[0]);
2571
+ const sessionNames = rows.map(([, name]) => name as string);
2572
+ if (!Number.isSafeInteger(pid) || pid <= 0 || rows.some(([rowPid]) => Number(rowPid) !== pid))
2573
+ return { state: "unverifiable" };
2574
+ const stat =
2575
+ process.platform === "linux" ? await fs.readFile(`/proc/${pid}/stat`, "utf8").catch(() => null) : null;
2576
+ const startTime =
2577
+ process.platform === "linux"
2578
+ ? ownerProcessStartTime(process.platform, stat)
2579
+ : await portableProcessStartTime(pid, runner);
2580
+ if (!startTime) return { state: "unverifiable", pid, sessionNames };
2581
+ if (process.platform !== "linux")
2582
+ return {
2583
+ state: "safe",
2584
+ pid,
2585
+ startTime,
2586
+ sessionNames,
2587
+ cgroup: { classification: "not_applicable" },
2588
+ };
2589
+ const cgroupText = await fs.readFile(`/proc/${pid}/cgroup`, "utf8").catch(() => null);
2590
+ const cgroup = classifyCgroup({ platform: process.platform, cgroupText });
2591
+ return {
2592
+ state:
2593
+ cgroup.classification === "safe"
2594
+ ? "safe"
2595
+ : cgroup.classification === "unsafe_service"
2596
+ ? "unsafe"
2597
+ : "unverifiable",
2598
+ pid,
2599
+ startTime,
2600
+ sessionNames,
2601
+ cgroup,
2602
+ };
2603
+ } catch {
2604
+ // A transient spawn failure (e.g. EAGAIN under load, or a momentary tmux/ps hiccup)
2605
+ // must not fatally abort the owner-server proof. Surfacing it as an unverifiable state
2606
+ // lets proveCoordinatorOwnerServer's bounded retry loop re-probe once the transient
2607
+ // clears, instead of propagating the throw and failing the entire delegate outright.
2608
+ return { state: "unverifiable" };
2609
+ }
2610
+ },
2611
+ };
2612
+ }
2613
+
2614
+ async function proveCoordinatorOwnerServer(probe: OwnerIsolationProbe, socketKey: string): Promise<TmuxServerProof> {
2615
+ for (let attempt = 0; attempt < 50; attempt++) {
2616
+ const proof = await probe.probeServer(socketKey);
2617
+ if (proof.state === "unsafe" || (proof.state === "safe" && proof.pid && proof.startTime)) return proof;
2618
+ if (attempt < 49) await Bun.sleep(10);
2619
+ }
2620
+ return { state: "unverifiable" };
2621
+ }
2622
+
1338
2623
  async function startTmuxSession(
1339
2624
  config: CoordinatorMcpConfig,
1340
2625
  input: SessionStartInput,
1341
2626
  namespaceDir: string,
1342
2627
  runner: CommandRunner = runCommand,
2628
+ ownerIsolationProbe?: OwnerIsolationProbe,
1343
2629
  ): Promise<Record<string, unknown>> {
1344
2630
  if (!config.sessionCommand) throw new Error("coordinator_session_command_required");
1345
- const sessionName = `skc-coordinator-${randomUUID().slice(0, 8)}`;
2631
+ const sessionName = input.sessionId;
2632
+ const socketKey = `skc-coordinator-${randomUUID().slice(0, 8)}`;
2633
+ const ownerStateDir = namespaceDir;
2634
+ const ownerGeneration = randomUUID();
2635
+ const generationBaseline = captureOwnerGenerationBaselineSync(ownerStateDir, sessionName);
1346
2636
  const runtimeStateFile = sessionStateFile(namespaceDir, sessionName);
1347
2637
  const sessionCommand = [
1348
2638
  "exec env",
1349
2639
  `${SKC_COORDINATOR_SESSION_STATE_FILE_ENV}=${shellQuote(runtimeStateFile)}`,
1350
2640
  `${SKC_COORDINATOR_SESSION_ID_ENV}=${shellQuote(sessionName)}`,
1351
- config.sessionCommand,
2641
+ `${SKC_TMUX_OWNER_GENERATION_ENV}=${shellQuote(ownerGeneration)}`,
2642
+ `${SKC_TMUX_OWNER_STATE_DIR_ENV}=${shellQuote(ownerStateDir)}`,
2643
+ `${SKC_TMUX_OWNER_SERVER_KEY_ENV}=${shellQuote(socketKey)}`,
2644
+ `${SKC_COORDINATOR_SESSION_LAUNCH_ID_ENV}=${shellQuote(input.launchId)}`,
2645
+ `${SKC_COORDINATOR_SESSION_READINESS_FILE_ENV}=${shellQuote(input.readinessMarkerFile)}`,
2646
+ buildCoordinatorSessionCommand(config.sessionCommand, input.mpreset),
1352
2647
  ].join(" ");
1353
- const started = await runner([
2648
+ const tmuxArgv = [
1354
2649
  "tmux",
2650
+ "-L",
2651
+ socketKey,
1355
2652
  "new-session",
1356
2653
  "-d",
1357
2654
  "-P",
1358
2655
  "-F",
1359
- "#{session_name}:#{window_index}.#{pane_index} #{pane_id}",
2656
+ "#{session_name}:#{window_index}.#{pane_index} #{pane_id} #{session_id}",
1360
2657
  "-s",
1361
2658
  sessionName,
1362
2659
  "-c",
1363
2660
  input.cwd,
1364
2661
  sessionCommand,
1365
- ]);
1366
- if (started.exitCode !== 0) throw new Error(`coordinator_tmux_start_failed:${started.stderr || started.stdout}`);
1367
- const [tmuxTarget, paneId] = started.stdout.trim().split(/\s+/, 2);
2662
+ ];
2663
+ const probe = ownerIsolationProbe ?? (await coordinatorOwnerIsolationProbe(runner));
2664
+ let plan = await planTmuxOwnerIsolation(
2665
+ {
2666
+ schema_version: 1,
2667
+ op: "plan",
2668
+ platform: process.platform,
2669
+ session_id: sessionName,
2670
+ owner_generation: ownerGeneration,
2671
+ baseline: generationBaseline,
2672
+ cwd: input.cwd,
2673
+ state_dir: ownerStateDir,
2674
+ socket_key: socketKey,
2675
+ tmux_argv: tmuxArgv,
2676
+ },
2677
+ probe,
2678
+ );
2679
+ if (!plan.ok) throw new Error(`coordinator_tmux_owner_${plan.code}`);
2680
+ if (plan.execution.mode === "direct" && plan.execution.server_absent_before) {
2681
+ plan = await planTmuxOwnerIsolation(
2682
+ {
2683
+ schema_version: 1,
2684
+ op: "plan",
2685
+ platform: process.platform,
2686
+ session_id: sessionName,
2687
+ owner_generation: ownerGeneration,
2688
+ baseline: generationBaseline,
2689
+ cwd: input.cwd,
2690
+ state_dir: ownerStateDir,
2691
+ socket_key: socketKey,
2692
+ tmux_argv: tmuxArgv,
2693
+ },
2694
+ {
2695
+ ...probe,
2696
+ readCallerCgroup: async () => "0::/skc-coordinator-bootstrap.service",
2697
+ },
2698
+ );
2699
+ if (!plan.ok) throw new Error(`coordinator_tmux_owner_${plan.code}`);
2700
+ }
2701
+ const preSpawnProof =
2702
+ plan.execution.mode === "direct" && plan.server_state === "safe"
2703
+ ? await proveCoordinatorOwnerServer(probe, socketKey)
2704
+ : null;
2705
+ if (
2706
+ preSpawnProof &&
2707
+ (preSpawnProof.state !== "safe" ||
2708
+ !preSpawnProof.pid ||
2709
+ !preSpawnProof.startTime ||
2710
+ (process.platform === "linux" && preSpawnProof.cgroup?.classification !== "safe"))
2711
+ )
2712
+ throw new Error("coordinator_tmux_owner_server_unverifiable");
2713
+ const started = await runner(
2714
+ plan.execution.argv,
2715
+ plan.execution.mode === "scoped" ? plan.execution.stdin_line : undefined,
2716
+ );
2717
+
2718
+ const rawScopedReceipt =
2719
+ plan.execution.mode === "scoped" && isExactScopedBootstrapSuccessReceipt(started.stdout)
2720
+ ? (JSON.parse(started.stdout.trim()) as {
2721
+ native_session_id: string;
2722
+ server_pid: number;
2723
+ server_start_time: string;
2724
+ session_name: string;
2725
+ })
2726
+ : undefined;
2727
+ const scopedReceipt =
2728
+ rawScopedReceipt?.session_name === plan.execution.attempt_session ? rawScopedReceipt : undefined;
2729
+ const directReceipt =
2730
+ plan.execution.mode === "direct"
2731
+ ? parseTmuxNewSessionRecord(started.stdout, plan.execution.attempt_session)
2732
+ : undefined;
2733
+ const directNativeSessionId = directReceipt?.nativeSessionId;
2734
+ const spawnedNativeSessionId = scopedReceipt?.native_session_id ?? directNativeSessionId;
2735
+ let expectedServer = scopedReceipt
2736
+ ? {
2737
+ pid: scopedReceipt.server_pid,
2738
+ startTime: scopedReceipt.server_start_time,
2739
+ }
2740
+ : preSpawnProof && preSpawnProof.state === "safe" && preSpawnProof.pid && preSpawnProof.startTime
2741
+ ? { pid: preSpawnProof.pid, startTime: preSpawnProof.startTime }
2742
+ : undefined;
2743
+ const cleanupFailedAttempt = async (): Promise<"cleaned" | "failed" | "unverifiable"> => {
2744
+ if (!spawnedNativeSessionId || !expectedServer) return "unverifiable";
2745
+ try {
2746
+ const result = await runner([
2747
+ "tmux",
2748
+ "-L",
2749
+ socketKey,
2750
+ "if-shell",
2751
+ "-t",
2752
+ spawnedNativeSessionId,
2753
+ "-F",
2754
+ coordinatorCleanupPredicate(expectedServer.pid, spawnedNativeSessionId, plan.execution.attempt_session),
2755
+ `kill-session -t '${spawnedNativeSessionId}' ; display-message -p __skc_coordinator_cleanup_ok__`,
2756
+ "display-message -p __skc_coordinator_cleanup_refused__",
2757
+ ]);
2758
+ if (result.exitCode !== 0) return "failed";
2759
+ return result.stdout.trim() === "__skc_coordinator_cleanup_ok__" ? "cleaned" : "unverifiable";
2760
+ } catch {
2761
+ return "unverifiable";
2762
+ }
2763
+ };
2764
+ const failAfterCleanup = async (reason: string): Promise<never> => {
2765
+ throw new CoordinatorStartError(reason, await cleanupFailedAttempt());
2766
+ };
2767
+ if (started.exitCode !== 0) return await failAfterCleanup("coordinator_tmux_start_failed");
2768
+ const bootstrapReceiptValid = plan.execution.mode !== "scoped" || scopedReceipt !== undefined;
2769
+ const initialProof = await proveCoordinatorOwnerServer(probe, socketKey).catch(error =>
2770
+ failAfterCleanup(error instanceof Error ? error.message : "coordinator_tmux_owner_server_unverifiable"),
2771
+ );
2772
+ if (
2773
+ initialProof.state !== "safe" ||
2774
+ !initialProof.pid ||
2775
+ !initialProof.startTime ||
2776
+ (process.platform === "linux" && initialProof.cgroup?.classification !== "safe")
2777
+ ) {
2778
+ return await failAfterCleanup("coordinator_tmux_owner_server_unverifiable");
2779
+ }
2780
+ if (
2781
+ scopedReceipt &&
2782
+ (initialProof.pid !== scopedReceipt.server_pid || initialProof.startTime !== scopedReceipt.server_start_time)
2783
+ ) {
2784
+ return await failAfterCleanup("coordinator_tmux_owner_server_race");
2785
+ }
2786
+ if (
2787
+ preSpawnProof &&
2788
+ (initialProof.pid !== preSpawnProof.pid || initialProof.startTime !== preSpawnProof.startTime)
2789
+ ) {
2790
+ return await failAfterCleanup("coordinator_tmux_owner_server_race");
2791
+ }
2792
+ if (!expectedServer)
2793
+ expectedServer = {
2794
+ pid: initialProof.pid,
2795
+ startTime: initialProof.startTime,
2796
+ };
2797
+ if (!bootstrapReceiptValid) {
2798
+ return await failAfterCleanup("coordinator_tmux_start_failed");
2799
+ }
2800
+
2801
+ const startRecord = await (async () => {
2802
+ const queried = await runner([
2803
+ "tmux",
2804
+ "-L",
2805
+ socketKey,
2806
+ "display-message",
2807
+ "-p",
2808
+ "-t",
2809
+ sessionName,
2810
+ "#{session_name}:#{window_index}.#{pane_index} #{pane_id} #{session_id}",
2811
+ ]);
2812
+ if (queried.exitCode !== 0) throw new Error("coordinator_tmux_start_failed");
2813
+ return queried.stdout.trim();
2814
+ })().catch(error => failAfterCleanup(error instanceof Error ? error.message : "coordinator_tmux_start_failed"));
2815
+ const postLaunchProof = await probe.probeServer(socketKey);
2816
+ if (
2817
+ postLaunchProof.state !== "safe" ||
2818
+ postLaunchProof.pid !== initialProof.pid ||
2819
+ postLaunchProof.startTime !== initialProof.startTime
2820
+ ) {
2821
+ return await failAfterCleanup("coordinator_tmux_owner_server_race");
2822
+ }
2823
+ const parsedStartRecord = parseTmuxNewSessionRecord(startRecord, plan.execution.attempt_session);
2824
+ if (!parsedStartRecord) {
2825
+ return await failAfterCleanup("coordinator_tmux_start_failed");
2826
+ }
2827
+ const { tmuxTarget, paneId, nativeSessionId } = parsedStartRecord;
2828
+ if (
2829
+ (scopedReceipt && nativeSessionId !== scopedReceipt.native_session_id) ||
2830
+ (plan.execution.mode === "direct" && nativeSessionId !== directNativeSessionId)
2831
+ ) {
2832
+ return await failAfterCleanup("coordinator_tmux_owner_server_race");
2833
+ }
2834
+ const ownerTransaction = {
2835
+ commit: () => replaceOwnerGenerationSync(ownerStateDir, sessionName, ownerGeneration, generationBaseline),
2836
+ rollback: cleanupFailedAttempt,
2837
+ };
1368
2838
  return {
1369
2839
  sessionId: sessionName,
1370
2840
  tmuxSession: sessionName,
1371
- tmuxTarget: tmuxTarget || sessionName,
2841
+ tmuxTarget,
1372
2842
  paneId,
2843
+ tmuxNativeSessionId: nativeSessionId,
2844
+ tmuxSocketKey: socketKey,
2845
+ tmuxOwnerGeneration: ownerGeneration,
2846
+ tmuxOwnerStateDir: ownerStateDir,
2847
+ tmuxOwnerServerKey: socketKey,
2848
+ tmuxOwnerServerPid: initialProof.pid,
2849
+ tmuxOwnerServerStartTime: initialProof.startTime,
2850
+
1373
2851
  cwd: input.cwd,
1374
2852
  createdAt: new Date().toISOString(),
1375
2853
  sessionCommand: config.sessionCommand,
2854
+ ...(input.mpreset ? { mpreset: input.mpreset } : {}),
1376
2855
  runtimeStateFile,
2856
+ __coordinatorOwnerTransaction: ownerTransaction,
2857
+ launchId: input.launchId,
2858
+ readinessMarkerFile: input.readinessMarkerFile,
2859
+ readinessFile: input.readinessMarkerFile,
2860
+ origin: "coordinator_created",
2861
+ runtime_readiness_latched: false,
1377
2862
  };
1378
2863
  }
1379
2864
 
1380
- async function captureTmuxTail(session: Record<string, unknown>, lines: number): Promise<string[]> {
2865
+ function hasPrivateTmuxOwnerIdentity(session: Record<string, unknown>): boolean {
2866
+ const socketKey = typeof session.tmux_socket_key === "string" ? session.tmux_socket_key : session.tmuxSocketKey;
2867
+ const serverKey =
2868
+ typeof session.tmux_owner_server_key === "string" ? session.tmux_owner_server_key : session.tmuxOwnerServerKey;
2869
+ const pid = session.tmux_owner_server_pid ?? session.tmuxOwnerServerPid;
2870
+ const startTime = session.tmux_owner_server_start_time ?? session.tmuxOwnerServerStartTime;
2871
+ const nativeSessionId = session.tmux_native_session_id ?? session.tmuxNativeSessionId;
2872
+ const paneId = session.pane_id ?? session.paneId;
2873
+ return (
2874
+ typeof socketKey === "string" &&
2875
+ socketKey.length > 0 &&
2876
+ socketKey === serverKey &&
2877
+ Number.isSafeInteger(pid) &&
2878
+ typeof startTime === "string" &&
2879
+ startTime.length > 0 &&
2880
+ typeof nativeSessionId === "string" &&
2881
+ nativeSessionId.length > 0 &&
2882
+ typeof paneId === "string" &&
2883
+ paneId.length > 0
2884
+ );
2885
+ }
2886
+
2887
+ async function proveImmutableTmuxTarget(session: Record<string, unknown>, runner: CommandRunner): Promise<boolean> {
2888
+ const target = typeof session.tmux_target === "string" ? session.tmux_target : session.tmuxTarget;
2889
+ const socketKey = typeof session.tmux_socket_key === "string" ? session.tmux_socket_key : session.tmuxSocketKey;
2890
+ const expectedSessionId = session.tmux_native_session_id ?? session.tmuxNativeSessionId;
2891
+ const expectedPaneId = session.pane_id ?? session.paneId;
2892
+ if (
2893
+ typeof target !== "string" ||
2894
+ typeof socketKey !== "string" ||
2895
+ typeof expectedSessionId !== "string" ||
2896
+ typeof expectedPaneId !== "string"
2897
+ )
2898
+ return false;
2899
+ const identity = await runner([
2900
+ "tmux",
2901
+ "-L",
2902
+ socketKey,
2903
+ "display-message",
2904
+ "-p",
2905
+ "-t",
2906
+ target,
2907
+ "#{session_id} #{pane_id}",
2908
+ ]);
2909
+ const parts = identity.stdout.trim().split(/\s+/);
2910
+ if (identity.exitCode !== 0) return false;
2911
+ return parts.length === 2 && parts[0] === expectedSessionId && parts[1] === expectedPaneId;
2912
+ }
2913
+ async function captureTmuxTail(
2914
+ session: Record<string, unknown>,
2915
+ lines: number,
2916
+ runner: CommandRunner = runCommand,
2917
+ ): Promise<string[]> {
1381
2918
  const target = typeof session.tmux_target === "string" ? session.tmux_target : session.tmuxTarget;
1382
- if (typeof target !== "string" || target.length === 0) return [];
1383
- const captured = await runCommand(["tmux", "capture-pane", "-t", target, "-p", "-S", `-${lines}`]);
2919
+ const socketKey = typeof session.tmux_socket_key === "string" ? session.tmux_socket_key : session.tmuxSocketKey;
2920
+ if (
2921
+ !hasPrivateTmuxOwnerIdentity(session) ||
2922
+ typeof socketKey !== "string" ||
2923
+ socketKey.length === 0 ||
2924
+ typeof target !== "string" ||
2925
+ target.length === 0
2926
+ )
2927
+ return [];
2928
+ const pid = session.tmux_owner_server_pid ?? session.tmuxOwnerServerPid;
2929
+ const startTime = session.tmux_owner_server_start_time ?? session.tmuxOwnerServerStartTime;
2930
+ const probe = await coordinatorOwnerIsolationProbe(runner);
2931
+ const proof = await probe.probeServer(socketKey);
2932
+ if (
2933
+ proof.state !== "safe" ||
2934
+ proof.pid !== pid ||
2935
+ proof.startTime !== startTime ||
2936
+ (process.platform === "linux" && proof.cgroup?.classification !== "safe") ||
2937
+ !(await proveImmutableTmuxTarget(session, runner))
2938
+ )
2939
+ return [];
2940
+ const captured = await runner(["tmux", "-L", socketKey, "capture-pane", "-t", target, "-p", "-S", `-${lines}`]);
1384
2941
  if (captured.exitCode !== 0) return [];
1385
2942
  return captured.stdout.split("\n").slice(-lines);
1386
2943
  }
@@ -1389,10 +2946,38 @@ async function sendTmuxPrompt(
1389
2946
  session: Record<string, unknown>,
1390
2947
  prompt: string,
1391
2948
  runner: CommandRunner = runCommand,
2949
+ ownerIsolationProbe?: OwnerIsolationProbe,
1392
2950
  ): Promise<boolean> {
1393
2951
  const target = typeof session.tmux_target === "string" ? session.tmux_target : session.tmuxTarget;
1394
- if (typeof target !== "string" || target.length === 0) return false;
1395
- return await sendTmuxPromptKeys(target, prompt, runner);
2952
+ const socketKey = typeof session.tmux_socket_key === "string" ? session.tmux_socket_key : session.tmuxSocketKey;
2953
+ const serverKey =
2954
+ typeof session.tmux_owner_server_key === "string" ? session.tmux_owner_server_key : session.tmuxOwnerServerKey;
2955
+ const pid = session.tmux_owner_server_pid ?? session.tmuxOwnerServerPid;
2956
+ const startTime = session.tmux_owner_server_start_time ?? session.tmuxOwnerServerStartTime;
2957
+ if (
2958
+ typeof target !== "string" ||
2959
+ target.length === 0 ||
2960
+ typeof socketKey !== "string" ||
2961
+ socketKey.length === 0 ||
2962
+ socketKey !== serverKey ||
2963
+ !Number.isSafeInteger(pid) ||
2964
+ typeof startTime !== "string" ||
2965
+ startTime.length === 0
2966
+ )
2967
+ return false;
2968
+ const paneId = session.pane_id ?? session.paneId;
2969
+ if (typeof paneId !== "string" || !(await proveImmutableTmuxTarget(session, runner))) return false;
2970
+ const probe = ownerIsolationProbe ?? (await coordinatorOwnerIsolationProbe(runner));
2971
+ const proveCurrentServer = async (): Promise<boolean> => {
2972
+ const proof = await probe.probeServer(socketKey);
2973
+ return (
2974
+ proof.state === "safe" &&
2975
+ proof.pid === pid &&
2976
+ proof.startTime === startTime &&
2977
+ (process.platform !== "linux" || proof.cgroup?.classification === "safe")
2978
+ );
2979
+ };
2980
+ return await sendTmuxPromptKeys(paneId, prompt, runner, socketKey, proveCurrentServer);
1396
2981
  }
1397
2982
 
1398
2983
  async function hasTmuxSession(
@@ -1400,8 +2985,16 @@ async function hasTmuxSession(
1400
2985
  runner: CommandRunner = runCommand,
1401
2986
  ): Promise<boolean | null> {
1402
2987
  const tmuxSession = typeof session.tmux_session === "string" ? session.tmux_session : session.tmuxSession;
1403
- if (typeof tmuxSession !== "string" || tmuxSession.length === 0) return null;
1404
- const checked = await runner(["tmux", "has-session", "-t", tmuxSession]);
2988
+ const socketKey = typeof session.tmux_socket_key === "string" ? session.tmux_socket_key : session.tmuxSocketKey;
2989
+ if (
2990
+ !hasPrivateTmuxOwnerIdentity(session) ||
2991
+ typeof socketKey !== "string" ||
2992
+ socketKey.length === 0 ||
2993
+ typeof tmuxSession !== "string" ||
2994
+ tmuxSession.length === 0
2995
+ )
2996
+ return null;
2997
+ const checked = await runner(["tmux", "-L", socketKey, "has-session", "-t", tmuxSession]);
1405
2998
  return checked.exitCode === 0;
1406
2999
  }
1407
3000
 
@@ -1436,7 +3029,8 @@ async function inspectTmuxSession(
1436
3029
  runner: CommandRunner = runCommand,
1437
3030
  ): Promise<Record<string, unknown>> {
1438
3031
  const live = await hasTmuxSession(session, runner);
1439
- const tail = live ? await captureTmuxTail(session, lines) : [];
3032
+ const tail = live ? await captureTmuxTail(session, lines, runner) : [];
3033
+
1440
3034
  return {
1441
3035
  live,
1442
3036
  ...summarizePaneTail(tail),
@@ -1490,14 +3084,27 @@ async function waitForCoordinatorEvents(namespaceDir: string, timeoutMs: number)
1490
3084
  };
1491
3085
  const timer = setTimeout(finish, Math.max(timeoutMs, 0));
1492
3086
  timer.unref?.();
1493
- await ensureDir(eventsDir(namespaceDir));
1494
- try {
1495
- const watcher = nodeFs.watch(eventsDir(namespaceDir), (_eventType, filename) => {
1496
- if (filename === "event-journal.jsonl" || filename === "latest-seq.json") finish();
1497
- });
1498
- watchers.push(watcher);
1499
- } catch {
1500
- // Directory may not exist yet; the timeout remains a bounded fallback.
3087
+ const eventDir = eventsDir(namespaceDir);
3088
+ const watchedDirs = [
3089
+ eventDir,
3090
+ turnsDir(namespaceDir),
3091
+ path.join(namespaceDir, "active-turns"),
3092
+ path.join(namespaceDir, "session-states"),
3093
+ ];
3094
+ for (const dir of watchedDirs) {
3095
+ await ensureDir(dir);
3096
+ try {
3097
+ const watcher = nodeFs.watch(dir, (_eventType, filename) => {
3098
+ if (dir === eventDir) {
3099
+ if (filename === "event-journal.jsonl" || filename === "latest-seq.json") finish();
3100
+ return;
3101
+ }
3102
+ if (typeof filename === "string" && filename.endsWith(".json")) finish();
3103
+ });
3104
+ watchers.push(watcher);
3105
+ } catch {
3106
+ // Directory may not be watchable on this platform; the timeout remains a bounded fallback.
3107
+ }
1501
3108
  }
1502
3109
  return deferred.promise;
1503
3110
  }
@@ -1538,7 +3145,7 @@ export async function readCoordinatorArtifact(
1538
3145
  } catch (error) {
1539
3146
  return {
1540
3147
  ok: false,
1541
- reason: (error instanceof Error ? error.message.split(":")[0] : String(error)).replace(/^coordinator_/, ""),
3148
+ reason: publicCoordinatorError(error),
1542
3149
  };
1543
3150
  } finally {
1544
3151
  await handle?.close();
@@ -1552,6 +3159,122 @@ export function createCoordinatorMcpServer(options: CoordinatorMcpServerOptions
1552
3159
  const services = options.services ?? {};
1553
3160
  const namespaceDir = coordinatorNamespacePath(config);
1554
3161
  const commandRunner = services.commandRunner ?? runCommand;
3162
+ const loadModelProfiles = services.resolveModelProfiles ?? loadCoordinatorModelProfiles;
3163
+ const readinessTimeoutMs = boundedRuntimeReadinessTimeoutMs(env.SKC_COORDINATOR_MCP_RUNTIME_READINESS_TIMEOUT_MS);
3164
+ async function withSessionTransition<T>(sessionId: string, operation: () => Promise<T>): Promise<T> {
3165
+ return await withSessionMutation(`${namespaceDir}::${sessionId}`, operation);
3166
+ }
3167
+
3168
+ async function startCoordinatorSession(
3169
+ cwd: string,
3170
+ prompt?: string,
3171
+ mpreset?: string | null,
3172
+ ): Promise<Record<string, unknown>> {
3173
+ const sessionId = `skc-coordinator-${randomUUID().slice(0, 8)}`;
3174
+ const launchId = randomUUID();
3175
+ const readinessRoot = path.join(namespaceDir, "launch-readiness");
3176
+ const readinessDir = path.join(readinessRoot, sessionId);
3177
+ await ensureDir(readinessDir);
3178
+ const canonicalRoot = await fs.realpath(readinessRoot);
3179
+ const canonicalDir = await fs.realpath(readinessDir);
3180
+ if (!canonicalDir.startsWith(`${canonicalRoot}${path.sep}`))
3181
+ throw new Error("coordinator_readiness_path_invalid");
3182
+ const readinessMarkerFile = path.join(canonicalDir, `${launchId}.json`);
3183
+ const input: SessionStartInput = {
3184
+ cwd,
3185
+ prompt,
3186
+ namespace: config.namespace,
3187
+ mpreset,
3188
+ worktree: true,
3189
+ sessionId,
3190
+ launchId,
3191
+ readinessMarkerFile,
3192
+ readinessFile: readinessMarkerFile,
3193
+ };
3194
+ const started = services.startSession
3195
+ ? await services.startSession(input)
3196
+ : await startTmuxSession(config, input, namespaceDir, commandRunner, services.ownerIsolationProbe);
3197
+ const record = asRecord(started);
3198
+ if (!record) throw new Error("coordinator_session_command_required");
3199
+ const attestedSessionId = optionalString(record.sessionId ?? record.session_id);
3200
+ const attestedLaunchId = optionalString(record.launchId ?? record.launch_id);
3201
+ const attestedMarkerFile = optionalString(
3202
+ record.readinessMarkerFile ?? record.readiness_marker_file ?? record.readinessFile ?? record.readiness_file,
3203
+ );
3204
+ if (!attestedSessionId || !attestedLaunchId || !attestedMarkerFile)
3205
+ throw new Error("coordinator_launch_attestation_missing");
3206
+ if (
3207
+ attestedSessionId !== sessionId ||
3208
+ attestedLaunchId !== launchId ||
3209
+ (await canonicalizePath(attestedMarkerFile)) !== readinessMarkerFile
3210
+ )
3211
+ throw new Error("coordinator_launch_attestation_mismatch");
3212
+ return normalizeSession({
3213
+ ...record,
3214
+ session_id: sessionId,
3215
+ launch_id: launchId,
3216
+ readiness_marker_file: readinessMarkerFile,
3217
+ readiness_marker_path: readinessMarkerFile,
3218
+ origin: "coordinator_created",
3219
+ runtime_ready_launch_id: null,
3220
+ runtime_ready_at: null,
3221
+ runtime_readiness_latched: false,
3222
+ });
3223
+ }
3224
+
3225
+ function sessionReadinessAuthorityError(
3226
+ reason: "coordinator_launch_metadata_missing" | "session_origin_unverified",
3227
+ session: Record<string, unknown>,
3228
+ ): Error {
3229
+ const error = new Error(reason);
3230
+ Object.assign(error, {
3231
+ sessionId: optionalString(session.session_id),
3232
+ action: "Start a new coordinator session or explicitly register the live external tmux session.",
3233
+ });
3234
+ return error;
3235
+ }
3236
+
3237
+ function assertSessionReadinessAuthority(session: Record<string, unknown>): void {
3238
+ if (session.origin === "registered") return;
3239
+ const tmuxTarget = session.tmux_target ?? session.tmuxTarget;
3240
+ if (
3241
+ session.origin == null &&
3242
+ typeof session.registered_at === "string" &&
3243
+ session.registered_at.length > 0 &&
3244
+ session.authoritative === true &&
3245
+ hasTmuxIdentity(session) &&
3246
+ typeof tmuxTarget === "string" &&
3247
+ tmuxTarget.length > 0
3248
+ )
3249
+ return;
3250
+ if (session.origin === "coordinator_created") {
3251
+ const sessionId = optionalString(session.session_id);
3252
+ const launchId = optionalString(session.launch_id);
3253
+ const markerFile = optionalString(session.readiness_marker_file ?? session.readiness_marker_path);
3254
+ if (!sessionId || !launchId || !markerFile) {
3255
+ throw sessionReadinessAuthorityError("coordinator_launch_metadata_missing", session);
3256
+ }
3257
+ return;
3258
+ }
3259
+ throw sessionReadinessAuthorityError("session_origin_unverified", session);
3260
+ }
3261
+
3262
+ async function awaitRuntimeReadiness(session: Record<string, unknown>): Promise<boolean> {
3263
+ assertSessionReadinessAuthority(session);
3264
+ if (session.origin === "registered" || session.origin == null) return true;
3265
+ const sessionId = optionalString(session.session_id);
3266
+ const launchId = optionalString(session.launch_id);
3267
+ const markerFile = optionalString(session.readiness_marker_file ?? session.readiness_marker_path);
3268
+ if (!sessionId || !launchId || !markerFile) throw new Error("coordinator_launch_metadata_missing");
3269
+ if (session.runtime_ready_launch_id === launchId) return true;
3270
+ return await waitForRuntimeReadinessMarker(
3271
+ markerFile,
3272
+ sessionId,
3273
+ launchId,
3274
+ path.join(namespaceDir, "launch-readiness"),
3275
+ readinessTimeoutMs,
3276
+ );
3277
+ }
1555
3278
 
1556
3279
  async function listSessions(): Promise<unknown[]> {
1557
3280
  if (!config.namespace.profile || !config.namespace.repo) return [];
@@ -1561,6 +3284,57 @@ export function createCoordinatorMcpServer(options: CoordinatorMcpServerOptions
1561
3284
  function sessionFile(sessionId: unknown): string {
1562
3285
  return path.join(namespaceDir, "sessions", `${safeExternalId("session", sessionId)}.json`);
1563
3286
  }
3287
+ async function compensateFailedOwnerStart(
3288
+ session: Record<string, unknown>,
3289
+ ownerTransaction: {
3290
+ rollback?: () => Promise<"cleaned" | "failed" | "unverifiable">;
3291
+ } | null,
3292
+ ): Promise<"cleaned" | "failed" | "unverifiable"> {
3293
+ let cleanupStatus: "cleaned" | "failed" | "unverifiable" = "unverifiable";
3294
+ try {
3295
+ cleanupStatus = (await ownerTransaction?.rollback?.()) ?? "unverifiable";
3296
+ } catch {
3297
+ cleanupStatus = "unverifiable";
3298
+ }
3299
+ try {
3300
+ const sessionId = String(session.session_id);
3301
+ const cwd = typeof session.cwd === "string" ? path.resolve(session.cwd) : null;
3302
+ if (!cwd) throw coordinatorStateError("invalid");
3303
+ const state: CoordinatorSessionState = {
3304
+ schema_version: 1,
3305
+ session_id: sessionId,
3306
+ state: "errored",
3307
+ ready_for_input: false,
3308
+ current_turn_id: null,
3309
+ last_turn_id: null,
3310
+ updated_at: new Date().toISOString(),
3311
+ source: "coordinator",
3312
+ live: false,
3313
+ reason: "coordinator_start_rolled_back",
3314
+ cwd,
3315
+ workdir: cwd,
3316
+ session_file: null,
3317
+ };
3318
+ await writeJsonFile(sessionStateFile(namespaceDir, sessionId), state);
3319
+ await fs.rm(sessionFile(sessionId), { force: true });
3320
+ await appendCoordinatorEvent(namespaceDir, {
3321
+ kind: "session.state_changed",
3322
+ sessionId,
3323
+ summary: `Session ${sessionId} start rolled back`,
3324
+ payloadRef: path.relative(namespaceDir, sessionStateFile(namespaceDir, sessionId)),
3325
+ metadata: {
3326
+ state: "errored",
3327
+ ready_for_input: false,
3328
+ live: false,
3329
+ reason: "coordinator_start_rolled_back",
3330
+ },
3331
+ });
3332
+ } catch {
3333
+ return "failed";
3334
+ }
3335
+ return cleanupStatus;
3336
+ }
3337
+
1564
3338
  async function listQuestions(args: Record<string, unknown>): Promise<unknown[]> {
1565
3339
  const sessionId = args.session_id == null ? null : safeExternalId("session", args.session_id);
1566
3340
  const status = typeof args.status === "string" && args.status.length > 0 ? args.status : null;
@@ -1584,11 +3358,12 @@ export function createCoordinatorMcpServer(options: CoordinatorMcpServerOptions
1584
3358
  return evidence;
1585
3359
  }
1586
3360
 
1587
- async function activateTurn(session: Record<string, unknown>, turn: TurnRecord): Promise<TurnRecord> {
3361
+ async function prepareActiveTurnUnlocked(session: Record<string, unknown>, turn: TurnRecord): Promise<TurnRecord> {
3362
+ const active = await readActiveTurn(namespaceDir, turn.session_id);
3363
+ if (active && active.turn_id !== turn.turn_id) return active;
1588
3364
  const timestamp = new Date().toISOString();
1589
3365
  const target = typeof session.tmux_target === "string" ? session.tmux_target : session.tmuxTarget;
1590
- const live = hasTmuxIdentity(session) ? await hasTmuxSession(session, commandRunner) : null;
1591
- const pendingTurn: TurnRecord = {
3366
+ const pending: TurnRecord = {
1592
3367
  ...turn,
1593
3368
  status: "active",
1594
3369
  delivery: {
@@ -1597,90 +3372,312 @@ export function createCoordinatorMcpServer(options: CoordinatorMcpServerOptions
1597
3372
  target: typeof target === "string" ? target : null,
1598
3373
  tmux_keys_sent: false,
1599
3374
  prompt_acknowledged: false,
1600
- state: "queued",
3375
+ state: "awaiting_runtime_readiness",
1601
3376
  attempts: [
1602
3377
  {
1603
3378
  delivered: false,
1604
3379
  tmux_keys_sent: false,
1605
3380
  channel: "tmux_keys",
1606
3381
  created_at: timestamp,
1607
- reason: "awaiting_tmux_delivery",
3382
+ reason: "awaiting_runtime_readiness",
1608
3383
  },
1609
3384
  ],
1610
3385
  },
1611
- liveness: { checked_at: timestamp, live, reason: live === false ? "tmux_session_missing" : null },
3386
+ liveness: { checked_at: null, live: null, reason: null },
1612
3387
  started_at: turn.started_at ?? timestamp,
1613
3388
  updated_at: timestamp,
1614
3389
  };
1615
- await writeTurnRecord(namespaceDir, pendingTurn);
1616
- await writeActiveTurn(namespaceDir, pendingTurn);
1617
- await writeSessionState(namespaceDir, pendingTurn.session_id, "running", {
1618
- currentTurnId: pendingTurn.turn_id,
1619
- live,
1620
- reason: null,
3390
+ await writeTurnRecord(namespaceDir, pending);
3391
+ await writeActiveTurn(namespaceDir, pending);
3392
+ await writeSessionState(namespaceDir, pending.session_id, "booting", {
3393
+ currentTurnId: pending.turn_id,
3394
+ live: null,
3395
+ reason: "awaiting_runtime_readiness",
1621
3396
  });
3397
+ return pending;
3398
+ }
1622
3399
 
1623
- const tmuxKeysSent = await sendTmuxPrompt(session, turn.prompt.text, commandRunner);
1624
- const deliveredAt = new Date().toISOString();
1625
- const activeTurn: TurnRecord = {
1626
- ...pendingTurn,
1627
- delivery: {
1628
- delivered: false,
1629
- queued: !tmuxKeysSent,
1630
- target: typeof target === "string" ? target : null,
1631
- tmux_keys_sent: tmuxKeysSent,
1632
- prompt_acknowledged: false,
1633
- state: tmuxKeysSent ? "tmux_keys_sent" : "unavailable",
1634
- attempts: [
1635
- {
3400
+ async function completePreparedTurn(session: Record<string, unknown>, pendingTurn: TurnRecord): Promise<TurnRecord> {
3401
+ const ready = await awaitRuntimeReadiness(session);
3402
+ const observedLive = hasTmuxIdentity(session) ? await hasTmuxSession(session, commandRunner) : null;
3403
+ return await withSessionTransition(pendingTurn.session_id, async () => {
3404
+ const current = await readTurnRecord(namespaceDir, pendingTurn.turn_id);
3405
+ const active = await readActiveTurn(namespaceDir, pendingTurn.session_id);
3406
+ if (
3407
+ !current ||
3408
+ !active ||
3409
+ active.turn_id !== pendingTurn.turn_id ||
3410
+ TERMINAL_TURN_STATUSES.has(current.status) ||
3411
+ current.delivery.state !== "awaiting_runtime_readiness" ||
3412
+ current.delivery.tmux_keys_sent === true
3413
+ )
3414
+ return current ?? pendingTurn;
3415
+ if (!ready) {
3416
+ const completedAt = new Date().toISOString();
3417
+ const message = `SKC runtime did not attest interactive input readiness within ${readinessTimeoutMs}ms.`;
3418
+ const failed: TurnRecord = {
3419
+ ...current,
3420
+ status: "failed",
3421
+ delivery: {
3422
+ ...current.delivery,
1636
3423
  delivered: false,
1637
- tmux_keys_sent: tmuxKeysSent,
1638
- channel: "tmux_keys",
1639
- created_at: deliveredAt,
1640
- reason: tmuxKeysSent ? "awaiting_runtime_ack" : "tmux_delivery_unavailable",
3424
+ queued: false,
3425
+ tmux_keys_sent: false,
3426
+ prompt_acknowledged: false,
3427
+ state: "awaiting_runtime_readiness",
1641
3428
  },
1642
- ],
1643
- },
1644
- updated_at: deliveredAt,
1645
- };
1646
- await writeTurnRecord(namespaceDir, activeTurn);
1647
- await writeActiveTurn(namespaceDir, activeTurn);
1648
- const sessionState = await readSessionState(namespaceDir, activeTurn.session_id);
1649
- const runtimeStateAlreadyAcknowledged =
1650
- sessionState !== null && runtimeStateAcknowledgesTurn(activeTurn, sessionState);
1651
- const resolvedTurn =
1652
- runtimeStateAlreadyAcknowledged && sessionState
1653
- ? await markTurnAcknowledgedFromRuntimeState(namespaceDir, activeTurn, sessionState)
1654
- : activeTurn;
1655
- if (!runtimeStateAlreadyAcknowledged && !tmuxKeysSent) {
1656
- await writeSessionState(namespaceDir, activeTurn.session_id, "stale", {
1657
- currentTurnId: activeTurn.turn_id,
1658
- live,
1659
- reason: "tmux_delivery_unavailable",
3429
+ final_response: {
3430
+ text: message,
3431
+ format: "markdown",
3432
+ source: "coordinator_runtime_readiness",
3433
+ artifact_path: null,
3434
+ truncated: false,
3435
+ },
3436
+ error: { code: RUNTIME_READINESS_TIMEOUT_REASON, message, recoverable: true },
3437
+ evidence: [
3438
+ ...current.evidence,
3439
+ {
3440
+ type: RUNTIME_READINESS_TIMEOUT_REASON,
3441
+ created_at: completedAt,
3442
+ tmux_keys_sent: false,
3443
+ prompt_acknowledged: false,
3444
+ },
3445
+ ],
3446
+ updated_at: completedAt,
3447
+ completed_at: completedAt,
3448
+ };
3449
+ await writeTurnRecord(namespaceDir, failed);
3450
+ await clearActiveTurn(namespaceDir, failed);
3451
+ await writeSessionState(namespaceDir, failed.session_id, "booting", {
3452
+ lastTurnId: failed.turn_id,
3453
+ live: null,
3454
+ reason: RUNTIME_READINESS_TIMEOUT_REASON,
3455
+ });
3456
+ return failed;
3457
+ }
3458
+ const currentSession = asRecord(await readJsonFile(sessionFile(pendingTurn.session_id))) ?? session;
3459
+ if (
3460
+ currentSession.origin === "coordinator_created" &&
3461
+ (currentSession.session_id !== session.session_id ||
3462
+ currentSession.launch_id !== session.launch_id ||
3463
+ currentSession.readiness_marker_file !== session.readiness_marker_file)
3464
+ )
3465
+ return current;
3466
+ const latched =
3467
+ currentSession.origin === "coordinator_created"
3468
+ ? {
3469
+ ...currentSession,
3470
+ runtime_ready_launch_id: currentSession.launch_id,
3471
+ runtime_ready_at: new Date().toISOString(),
3472
+ runtime_readiness_latched: true,
3473
+ }
3474
+ : currentSession;
3475
+ if (latched !== currentSession) await writeJsonFile(sessionFile(pendingTurn.session_id), latched);
3476
+ const target = typeof latched.tmux_target === "string" ? latched.tmux_target : latched.tmuxTarget;
3477
+ const attemptId = `delivery-${randomUUID()}`;
3478
+ const attemptedAt = new Date().toISOString();
3479
+ const delivering: TurnRecord = {
3480
+ ...current,
3481
+ status: "delivering",
3482
+ updated_at: attemptedAt,
3483
+ delivery: {
3484
+ ...current.delivery,
3485
+ queued: false,
3486
+ state: "delivering",
3487
+ attempts: [
3488
+ ...current.delivery.attempts,
3489
+ {
3490
+ attempt_id: attemptId,
3491
+ delivered: false,
3492
+ tmux_keys_sent: false,
3493
+ channel: "tmux_keys",
3494
+ created_at: attemptedAt,
3495
+ reason: "delivery_attempt",
3496
+ },
3497
+ ],
3498
+ },
3499
+ };
3500
+ await writeTurnRecord(namespaceDir, delivering);
3501
+ await writeActiveTurn(namespaceDir, delivering);
3502
+ await writeSessionState(namespaceDir, delivering.session_id, "running", {
3503
+ currentTurnId: delivering.turn_id,
3504
+ live: observedLive,
3505
+ reason: null,
1660
3506
  });
1661
- }
1662
- await appendCoordinatorEvent(namespaceDir, {
1663
- kind: tmuxKeysSent ? "tmux.delivery_succeeded" : "tmux.delivery_failed",
1664
- sessionId: activeTurn.session_id,
1665
- turnId: activeTurn.turn_id,
1666
- summary: tmuxKeysSent
1667
- ? `Tmux delivery succeeded for turn ${activeTurn.turn_id}`
1668
- : `Tmux delivery failed for turn ${activeTurn.turn_id}`,
1669
- payloadRef: path.relative(namespaceDir, turnFile(namespaceDir, activeTurn.turn_id)),
1670
- metadata: { target: typeof target === "string" ? target : null, live },
3507
+ const tmuxKeysSent = await sendTmuxPrompt(
3508
+ latched,
3509
+ delivering.prompt.text,
3510
+ commandRunner,
3511
+ services.ownerIsolationProbe,
3512
+ );
3513
+ const deliveredAt = new Date().toISOString();
3514
+ const delivered: TurnRecord = {
3515
+ ...delivering,
3516
+ status: "active",
3517
+ updated_at: deliveredAt,
3518
+ delivery: {
3519
+ ...delivering.delivery,
3520
+ delivered: false,
3521
+ queued: !tmuxKeysSent,
3522
+ tmux_keys_sent: tmuxKeysSent,
3523
+ prompt_acknowledged: false,
3524
+ state: tmuxKeysSent ? "tmux_keys_sent" : "unavailable",
3525
+ attempts: [
3526
+ ...delivering.delivery.attempts.slice(0, -1),
3527
+ {
3528
+ attempt_id: attemptId,
3529
+ delivered: false,
3530
+ tmux_keys_sent: tmuxKeysSent,
3531
+ channel: "tmux_keys",
3532
+ created_at: deliveredAt,
3533
+ reason: tmuxKeysSent ? "awaiting_runtime_ack" : "tmux_delivery_unavailable",
3534
+ },
3535
+ ],
3536
+ },
3537
+ };
3538
+ await writeTurnRecord(namespaceDir, delivered);
3539
+ await writeActiveTurn(namespaceDir, delivered);
3540
+ await appendCoordinatorEvent(namespaceDir, {
3541
+ kind: tmuxKeysSent ? "tmux.delivery_succeeded" : "tmux.delivery_failed",
3542
+ sessionId: delivered.session_id,
3543
+ turnId: delivered.turn_id,
3544
+ summary: tmuxKeysSent
3545
+ ? `Tmux delivery succeeded for turn ${delivered.turn_id}`
3546
+ : `Tmux delivery failed for turn ${delivered.turn_id}`,
3547
+ payloadRef: path.relative(namespaceDir, turnFile(namespaceDir, delivered.turn_id)),
3548
+ metadata: {
3549
+ target: typeof target === "string" ? target : null,
3550
+ live: observedLive,
3551
+ attempt_id: attemptId,
3552
+ },
3553
+ });
3554
+ return delivered;
1671
3555
  });
1672
- return resolvedTurn;
1673
3556
  }
1674
3557
 
1675
- async function promoteNextQueuedTurn(sessionId: string): Promise<TurnRecord | null> {
3558
+ async function activateTurn(session: Record<string, unknown>, turn: TurnRecord): Promise<TurnRecord> {
3559
+ const pendingTurn = await withSessionTransition(
3560
+ turn.session_id,
3561
+ async () => await prepareActiveTurnUnlocked(session, turn),
3562
+ );
3563
+ if (pendingTurn.turn_id !== turn.turn_id) return pendingTurn;
3564
+ return await completePreparedTurn(session, pendingTurn);
3565
+ }
3566
+
3567
+ async function prepareNextQueuedTurnUnlocked(sessionId: string): Promise<{
3568
+ session: Record<string, unknown>;
3569
+ turn: TurnRecord;
3570
+ } | null> {
1676
3571
  const session = asRecord(await readJsonFile(sessionFile(sessionId)));
1677
- if (!session) return null;
1678
- const queuedTurns = (await listJsonFiles(turnsDir(namespaceDir)))
1679
- .map(turn => asRecord(turn) as TurnRecord | null)
1680
- .filter((turn): turn is TurnRecord => turn?.session_id === sessionId && turn.status === "queued")
3572
+ if (!session || (await readActiveTurn(namespaceDir, sessionId))) return null;
3573
+ try {
3574
+ assertSessionReadinessAuthority(session);
3575
+ } catch {
3576
+ return null;
3577
+ }
3578
+ const queuedTurns = (await listTurnRecords(namespaceDir))
3579
+ .filter(turn => turn.session_id === sessionId && turn.status === "queued")
1681
3580
  .sort((left, right) => left.created_at.localeCompare(right.created_at));
1682
3581
  const nextTurn = queuedTurns[0];
1683
- return nextTurn ? await activateTurn(session, nextTurn) : null;
3582
+ if (!nextTurn) return null;
3583
+ return { session, turn: await prepareActiveTurnUnlocked(session, nextTurn) };
3584
+ }
3585
+
3586
+ async function reconcileRuntimeAcknowledgementFenced(
3587
+ turnId: string,
3588
+ sessionState: CoordinatorSessionState | null,
3589
+ failOnTimeout: boolean,
3590
+ ): Promise<TurnRecord | null> {
3591
+ const observed = await readTurnRecord(namespaceDir, turnId);
3592
+ if (!observed) return null;
3593
+ const observedTurnId = sessionState?.current_turn_id;
3594
+ return await withSessionTransition(observed.session_id, async () => {
3595
+ const current = await readTurnRecord(namespaceDir, turnId);
3596
+ if (!current || !ACTIVE_TURN_STATUSES.has(current.status)) return current;
3597
+ const currentState = await readSessionState(namespaceDir, current.session_id);
3598
+ const state =
3599
+ sessionState &&
3600
+ sessionState.updated_at === currentState?.updated_at &&
3601
+ observedTurnId === currentState.current_turn_id
3602
+ ? sessionState
3603
+ : currentState;
3604
+ return await reconcileRuntimeAcknowledgement(namespaceDir, current, state, promptAckTimeoutMs, {
3605
+ failOnTimeout,
3606
+ });
3607
+ });
3608
+ }
3609
+
3610
+ async function markTurnTerminalFromSessionStateFenced(
3611
+ turnId: string,
3612
+ observedState: CoordinatorSessionState,
3613
+ ): Promise<TurnRecord | null> {
3614
+ const observed = await readTurnRecord(namespaceDir, turnId);
3615
+ if (!observed) return null;
3616
+ return await withSessionTransition(observed.session_id, async () => {
3617
+ const current = await readTurnRecord(namespaceDir, turnId);
3618
+ if (!current || !ACTIVE_TURN_STATUSES.has(current.status)) return current;
3619
+ const currentState = await readSessionState(namespaceDir, current.session_id);
3620
+ if (!currentState || currentState.updated_at !== observedState.updated_at) return current;
3621
+ const matchesTurn =
3622
+ currentState.current_turn_id === current.turn_id ||
3623
+ (currentState.state === "errored" &&
3624
+ currentState.source === "agent_session_event" &&
3625
+ currentState.current_turn_id == null);
3626
+ if (!matchesTurn || (currentState.state !== "completed" && currentState.state !== "errored")) return current;
3627
+ return await markTurnTerminalFromSessionState(namespaceDir, current, currentState);
3628
+ });
3629
+ }
3630
+
3631
+ async function markTurnFailedForUnavailableSessionFenced(
3632
+ turnId: string,
3633
+ reason: string,
3634
+ ): Promise<TurnRecord | null> {
3635
+ const observed = await readTurnRecord(namespaceDir, turnId);
3636
+ if (!observed) return null;
3637
+ return await withSessionTransition(observed.session_id, async () => {
3638
+ const current = await readTurnRecord(namespaceDir, turnId);
3639
+ if (!current || !ACTIVE_TURN_STATUSES.has(current.status)) return current;
3640
+ return await markTurnFailedForUnavailableSession(namespaceDir, current, reason);
3641
+ });
3642
+ }
3643
+
3644
+ async function preparePromptTurn(
3645
+ session: Record<string, unknown>,
3646
+ prompt: string,
3647
+ options: { force: boolean; queue: boolean; queueWithoutActive?: boolean },
3648
+ ): Promise<
3649
+ | { ok: false; activeTurn: TurnRecord }
3650
+ | { ok: true; turn: TurnRecord; priorActiveTurn: TurnRecord | null; queued: boolean }
3651
+ > {
3652
+ const sessionId = safeExternalId("session", session.session_id);
3653
+ const prepared = await withSessionTransition(sessionId, async () => {
3654
+ const activeTurn = await readActiveTurn(namespaceDir, sessionId);
3655
+ if (activeTurn && !options.force && !options.queue) {
3656
+ return { ok: false as const, activeTurn };
3657
+ }
3658
+ if (activeTurn && options.force) {
3659
+ const timestamp = new Date().toISOString();
3660
+ const superseded: TurnRecord = {
3661
+ ...activeTurn,
3662
+ status: "superseded",
3663
+ updated_at: timestamp,
3664
+ completed_at: timestamp,
3665
+ };
3666
+ await writeTurnRecord(namespaceDir, superseded);
3667
+ await clearActiveTurn(namespaceDir, superseded);
3668
+ }
3669
+ const shouldQueue =
3670
+ options.queue && !options.force && (activeTurn !== null || options.queueWithoutActive === true);
3671
+ const turn = makeTurnRecord(config, sessionId, prompt, shouldQueue ? "queued" : "active");
3672
+ if (shouldQueue) {
3673
+ await writeTurnRecord(namespaceDir, turn);
3674
+ return { ok: true as const, turn, priorActiveTurn: activeTurn, queued: true };
3675
+ }
3676
+ const pending = await prepareActiveTurnUnlocked(session, turn);
3677
+ return { ok: true as const, turn: pending, priorActiveTurn: activeTurn, queued: false };
3678
+ });
3679
+ if (!prepared.ok || prepared.queued) return prepared;
3680
+ return { ...prepared, turn: await completePreparedTurn(session, prepared.turn) };
1684
3681
  }
1685
3682
 
1686
3683
  async function readTurnPayload(
@@ -1697,24 +3694,21 @@ export function createCoordinatorMcpServer(options: CoordinatorMcpServerOptions
1697
3694
  let resolvedTurn = turn;
1698
3695
  let advisoryStatus: Record<string, unknown> = { live: false };
1699
3696
  let sessionState = await readSessionState(namespaceDir, turn.session_id);
1700
- resolvedTurn = await reconcileRuntimeAcknowledgement(
1701
- namespaceDir,
1702
- resolvedTurn,
1703
- sessionState,
1704
- promptAckTimeoutMs,
1705
- { failOnTimeout: false },
1706
- );
3697
+ resolvedTurn =
3698
+ (await reconcileRuntimeAcknowledgementFenced(resolvedTurn.turn_id, sessionState, false)) ?? resolvedTurn;
1707
3699
  if (resolvedTurn !== turn) sessionState = await readSessionState(namespaceDir, resolvedTurn.session_id);
1708
3700
  if (
1709
3701
  sessionState &&
1710
3702
  ACTIVE_TURN_STATUSES.has(resolvedTurn.status) &&
3703
+ runtimeStateMatchesPrivateOwnerGeneration(sessionState, session) &&
1711
3704
  (sessionState.current_turn_id === resolvedTurn.turn_id ||
1712
3705
  (sessionState.state === "errored" &&
1713
3706
  sessionState.source === "agent_session_event" &&
1714
3707
  sessionState.current_turn_id == null)) &&
1715
3708
  (sessionState.state === "completed" || sessionState.state === "errored")
1716
3709
  ) {
1717
- resolvedTurn = await markTurnTerminalFromSessionState(namespaceDir, resolvedTurn, sessionState);
3710
+ resolvedTurn =
3711
+ (await markTurnTerminalFromSessionStateFenced(resolvedTurn.turn_id, sessionState)) ?? resolvedTurn;
1718
3712
  sessionState = await readSessionState(namespaceDir, resolvedTurn.session_id);
1719
3713
  } else if (
1720
3714
  sessionState &&
@@ -1726,14 +3720,14 @@ export function createCoordinatorMcpServer(options: CoordinatorMcpServerOptions
1726
3720
  session &&
1727
3721
  hasTmuxIdentity(session)
1728
3722
  ) {
1729
- resolvedTurn = await markTurnFailedForUnavailableSession(
1730
- namespaceDir,
1731
- resolvedTurn,
1732
- "tmux_delivery_unavailable",
1733
- );
3723
+ resolvedTurn =
3724
+ (await markTurnFailedForUnavailableSessionFenced(resolvedTurn.turn_id, "tmux_delivery_unavailable")) ??
3725
+ resolvedTurn;
1734
3726
  sessionState = await readSessionState(namespaceDir, resolvedTurn.session_id);
1735
3727
  } else if (!session && ACTIVE_TURN_STATUSES.has(resolvedTurn.status)) {
1736
- resolvedTurn = await markTurnFailedForUnavailableSession(namespaceDir, resolvedTurn, "session_record_missing");
3728
+ resolvedTurn =
3729
+ (await markTurnFailedForUnavailableSessionFenced(resolvedTurn.turn_id, "session_record_missing")) ??
3730
+ resolvedTurn;
1737
3731
  sessionState = await readSessionState(namespaceDir, resolvedTurn.session_id);
1738
3732
  } else if (session) {
1739
3733
  advisoryStatus = await inspectTmuxSession(session, boundedLineCount(lines), commandRunner);
@@ -1742,21 +3736,15 @@ export function createCoordinatorMcpServer(options: CoordinatorMcpServerOptions
1742
3736
  hasTmuxIdentity(session) &&
1743
3737
  advisoryStatus.live === false
1744
3738
  ) {
1745
- resolvedTurn = await markTurnFailedForUnavailableSession(
1746
- namespaceDir,
1747
- resolvedTurn,
1748
- "tmux_session_missing",
1749
- );
3739
+ resolvedTurn =
3740
+ (await markTurnFailedForUnavailableSessionFenced(resolvedTurn.turn_id, "tmux_session_missing")) ??
3741
+ resolvedTurn;
1750
3742
  sessionState = await readSessionState(namespaceDir, resolvedTurn.session_id);
1751
3743
  }
1752
3744
  }
1753
3745
  if (ACTIVE_TURN_STATUSES.has(resolvedTurn.status)) {
1754
- resolvedTurn = await reconcileRuntimeAcknowledgement(
1755
- namespaceDir,
1756
- resolvedTurn,
1757
- sessionState,
1758
- promptAckTimeoutMs,
1759
- );
3746
+ resolvedTurn =
3747
+ (await reconcileRuntimeAcknowledgementFenced(resolvedTurn.turn_id, sessionState, true)) ?? resolvedTurn;
1760
3748
  if (!ACTIVE_TURN_STATUSES.has(resolvedTurn.status)) {
1761
3749
  sessionState = await readSessionState(namespaceDir, resolvedTurn.session_id);
1762
3750
  }
@@ -1778,51 +3766,59 @@ export function createCoordinatorMcpServer(options: CoordinatorMcpServerOptions
1778
3766
  }
1779
3767
 
1780
3768
  async function reconcileActiveTurnAcknowledgements(): Promise<void> {
1781
- const turns = (await listJsonFiles(turnsDir(namespaceDir)))
1782
- .map(turn => asRecord(turn) as TurnRecord | null)
1783
- .filter((turn): turn is TurnRecord => turn !== null && ACTIVE_TURN_STATUSES.has(turn.status));
3769
+ await preflightCoordinatorMutation(namespaceDir, undefined, {
3770
+ rejectTerminalRuntimeEvidence: false,
3771
+ });
3772
+
3773
+ const turns = (await listTurnRecords(namespaceDir)).filter(turn => ACTIVE_TURN_STATUSES.has(turn.status));
1784
3774
  for (const turn of turns) {
1785
3775
  let sessionState = await readSessionState(namespaceDir, turn.session_id);
1786
- const resolvedTurn = await reconcileRuntimeAcknowledgement(
1787
- namespaceDir,
1788
- turn,
1789
- sessionState,
1790
- promptAckTimeoutMs,
1791
- { failOnTimeout: false },
1792
- );
1793
- if (!ACTIVE_TURN_STATUSES.has(resolvedTurn.status)) continue;
1794
- if (resolvedTurn !== turn) sessionState = await readSessionState(namespaceDir, resolvedTurn.session_id);
1795
- const session = asRecord(await readJsonFile(sessionFile(resolvedTurn.session_id)));
1796
3776
  if (
1797
3777
  sessionState &&
1798
- sessionState.current_turn_id === resolvedTurn.turn_id &&
3778
+ (sessionState.state === "completed" || sessionState.state === "errored") &&
3779
+ (sessionState.current_turn_id === turn.turn_id ||
3780
+ (sessionState.state === "errored" &&
3781
+ sessionState.source === "agent_session_event" &&
3782
+ sessionState.current_turn_id == null))
3783
+ ) {
3784
+ const terminal = await markTurnTerminalFromSessionStateFenced(turn.turn_id, sessionState);
3785
+ if (!terminal || !ACTIVE_TURN_STATUSES.has(terminal.status)) continue;
3786
+ }
3787
+ const acknowledged = (await reconcileRuntimeAcknowledgementFenced(turn.turn_id, sessionState, false)) ?? turn;
3788
+ if (!ACTIVE_TURN_STATUSES.has(acknowledged.status)) continue;
3789
+ if (acknowledged !== turn) sessionState = await readSessionState(namespaceDir, acknowledged.session_id);
3790
+ const session = asRecord(await readJsonFile(sessionFile(acknowledged.session_id)));
3791
+ if (
3792
+ sessionState &&
3793
+ sessionState.current_turn_id === acknowledged.turn_id &&
1799
3794
  sessionState.state === "stale" &&
1800
3795
  sessionState.reason === "tmux_delivery_unavailable" &&
1801
- resolvedTurn.delivery.state === "unavailable" &&
3796
+ acknowledged.delivery.state === "unavailable" &&
1802
3797
  session &&
1803
3798
  hasTmuxIdentity(session)
1804
3799
  ) {
1805
- await markTurnFailedForUnavailableSession(namespaceDir, resolvedTurn, "tmux_delivery_unavailable");
3800
+ await markTurnFailedForUnavailableSessionFenced(acknowledged.turn_id, "tmux_delivery_unavailable");
1806
3801
  continue;
1807
3802
  }
1808
3803
  if (!session) {
1809
- await markTurnFailedForUnavailableSession(namespaceDir, resolvedTurn, "session_record_missing");
3804
+ await markTurnFailedForUnavailableSessionFenced(acknowledged.turn_id, "session_record_missing");
1810
3805
  continue;
1811
3806
  }
1812
3807
  if (hasTmuxIdentity(session) && (await hasTmuxSession(session, commandRunner)) === false) {
1813
- await markTurnFailedForUnavailableSession(namespaceDir, resolvedTurn, "tmux_session_missing");
3808
+ await markTurnFailedForUnavailableSessionFenced(acknowledged.turn_id, "tmux_session_missing");
1814
3809
  continue;
1815
3810
  }
1816
- await reconcileRuntimeAcknowledgement(namespaceDir, resolvedTurn, sessionState, promptAckTimeoutMs);
3811
+ await reconcileRuntimeAcknowledgementFenced(acknowledged.turn_id, sessionState, true);
1817
3812
  }
1818
3813
  }
1819
3814
 
1820
- async function callTool(name: string, args: Record<string, unknown> = {}): Promise<Record<string, unknown>> {
3815
+ async function callToolUnlocked(name: string, args: Record<string, unknown> = {}): Promise<Record<string, unknown>> {
1821
3816
  try {
1822
3817
  if (name === "skc_coordinator_list_sessions") return { ok: true, sessions: await listSessions() };
1823
3818
  if (name === "skc_coordinator_register_session") {
1824
3819
  requireCoordinatorMutation(config, "sessions", args);
1825
3820
  const sessionId = safeExternalId("session", args.session_id);
3821
+ await preflightCoordinatorMutation(namespaceDir, sessionId);
1826
3822
  const cwd = await assertCoordinatorWorkdir(config, args.cwd);
1827
3823
  const tmuxSession = safeTmuxSessionName(args.tmux_session);
1828
3824
  const tmuxTarget = safeTmuxTarget(args.tmux_target);
@@ -1846,7 +3842,10 @@ export function createCoordinatorMcpServer(options: CoordinatorMcpServerOptions
1846
3842
  sessionId,
1847
3843
  summary: `Session ${sessionId} registered for coordinator control`,
1848
3844
  payloadRef: path.relative(namespaceDir, sessionFile(sessionId)),
1849
- metadata: { source: optionalString(args.source) ?? "register_session", visible: args.visible !== false },
3845
+ metadata: {
3846
+ source: optionalString(args.source) ?? "register_session",
3847
+ visible: args.visible !== false,
3848
+ },
1850
3849
  });
1851
3850
 
1852
3851
  return {
@@ -1883,7 +3882,10 @@ export function createCoordinatorMcpServer(options: CoordinatorMcpServerOptions
1883
3882
  }
1884
3883
  if (name === "skc_coordinator_read_tail") {
1885
3884
  const session = asRecord(await readJsonFile(sessionFile(args.session_id)));
1886
- return { ok: true, lines: session ? await captureTmuxTail(session, boundedLineCount(args.lines)) : [] };
3885
+ return {
3886
+ ok: true,
3887
+ lines: session ? await captureTmuxTail(session, boundedLineCount(args.lines), commandRunner) : [],
3888
+ };
1887
3889
  }
1888
3890
  if (name === "skc_coordinator_list_questions") return { ok: true, questions: await listQuestions(args) };
1889
3891
  if (name === "skc_coordinator_list_artifacts") return { ok: true, roots: config.allowedRoots };
@@ -1918,23 +3920,31 @@ export function createCoordinatorMcpServer(options: CoordinatorMcpServerOptions
1918
3920
  turns,
1919
3921
  questions,
1920
3922
  reports,
1921
- events: buildCanonicalCoordinatorEvents({ sessionStates, turns, questions, reports }),
3923
+ events: buildCanonicalCoordinatorEvents({
3924
+ sessionStates,
3925
+ turns,
3926
+ questions,
3927
+ reports,
3928
+ }),
1922
3929
  latest_event_seq: await readLatestEventSeq(namespaceDir),
1923
3930
  recent_events: eventSummaries(events.slice(-10)),
1924
3931
  };
1925
3932
  }
1926
3933
  if (name === "skc_coordinator_watch_events") {
1927
- await reconcileActiveTurnAcknowledgements();
3934
+ await withCoordinatorTransaction(namespaceDir, reconcileActiveTurnAcknowledgements);
1928
3935
  const limit = boundedEventLimit(args.limit);
1929
3936
  const timeoutMs = boundedEventWatchTimeoutMs(args.timeout_ms);
1930
3937
  let events = await readCoordinatorEvents(namespaceDir);
1931
3938
  let matched = filterCoordinatorEvents(events, args, limit);
1932
3939
  let timedOut = false;
1933
3940
  if (matched.length === 0 && timeoutMs > 0) {
1934
- await waitForCoordinatorEvents(namespaceDir, timeoutMs);
1935
- await reconcileActiveTurnAcknowledgements();
1936
- events = await readCoordinatorEvents(namespaceDir);
1937
- matched = filterCoordinatorEvents(events, args, limit);
3941
+ const deadline = Date.now() + timeoutMs;
3942
+ while (matched.length === 0 && Date.now() < deadline) {
3943
+ await waitForCoordinatorEvents(namespaceDir, Math.min(50, Math.max(1, deadline - Date.now())));
3944
+ await withCoordinatorTransaction(namespaceDir, reconcileActiveTurnAcknowledgements);
3945
+ events = await readCoordinatorEvents(namespaceDir);
3946
+ matched = filterCoordinatorEvents(events, args, limit);
3947
+ }
1938
3948
  timedOut = matched.length === 0;
1939
3949
  }
1940
3950
  return {
@@ -1953,6 +3963,17 @@ export function createCoordinatorMcpServer(options: CoordinatorMcpServerOptions
1953
3963
  const hasPrompt = typeof args.prompt === "string" && args.prompt.trim().length > 0;
1954
3964
  const task = hasTask ? String(args.task) : hasPrompt ? String(args.prompt) : null;
1955
3965
  if (!task) return { ok: false, reason: "task_required" };
3966
+ await preflightCoordinatorMutation(namespaceDir);
3967
+ const mpresetResolution = await resolveCoordinatorMpreset(args.mpreset, loadModelProfiles);
3968
+ if (!mpresetResolution.ok) {
3969
+ return {
3970
+ ok: false,
3971
+ reason: mpresetResolution.reason,
3972
+ mpreset: mpresetResolution.mpreset,
3973
+ available_profiles: mpresetResolution.available_profiles,
3974
+ };
3975
+ }
3976
+
1956
3977
  const promptAliasIgnored = hasTask && hasPrompt;
1957
3978
  const mutationRequested = args.allow_mutation === true;
1958
3979
  const taggedPrompt = workflowPrompt(delegateWorkflow, name, canonicalCwd, task, {
@@ -1964,70 +3985,85 @@ export function createCoordinatorMcpServer(options: CoordinatorMcpServerOptions
1964
3985
  let reusedSession = false;
1965
3986
  if (args.session_id != null) {
1966
3987
  const sessionId = safeExternalId("session", args.session_id);
3988
+ await preflightCoordinatorMutation(namespaceDir, sessionId);
1967
3989
  const existing = asRecord(await readJsonFile(sessionFile(sessionId)));
1968
- if (!existing) return { ok: false, reason: "unknown_session", session_id: sessionId };
3990
+ if (!existing)
3991
+ return {
3992
+ ok: false,
3993
+ reason: "unknown_session",
3994
+ session_id: sessionId,
3995
+ };
1969
3996
  const storedCwd = typeof existing.cwd === "string" ? existing.cwd : null;
1970
3997
  const canonicalStored = storedCwd ? await canonicalizePath(storedCwd) : null;
1971
3998
  const canonicalRequested = await canonicalizePath(canonicalCwd);
1972
3999
  if (!canonicalStored || canonicalStored !== canonicalRequested) {
1973
- return { ok: false, reason: "session_cwd_mismatch", session_id: sessionId };
4000
+ return {
4001
+ ok: false,
4002
+ reason: "session_cwd_mismatch",
4003
+ session_id: sessionId,
4004
+ };
4005
+ }
4006
+ if (mpresetResolution.mpreset !== null) {
4007
+ const sessionMpreset = typeof existing.mpreset === "string" ? existing.mpreset : null;
4008
+ if (sessionMpreset !== mpresetResolution.mpreset) {
4009
+ return {
4010
+ ok: false,
4011
+ reason: "mpreset_conflict",
4012
+ session_id: sessionId,
4013
+ session_mpreset: sessionMpreset,
4014
+ requested_mpreset: mpresetResolution.mpreset,
4015
+ };
4016
+ }
1974
4017
  }
4018
+
1975
4019
  session = existing;
1976
4020
  reusedSession = true;
1977
4021
  } else {
1978
- const input = {
1979
- cwd: canonicalCwd,
1980
- prompt: undefined,
1981
- namespace: config.namespace,
1982
- worktree: true as const,
1983
- };
1984
- const started = services.startSession
1985
- ? await services.startSession(input)
1986
- : await startTmuxSession(config, input, namespaceDir, commandRunner);
1987
- const startedRecord = asRecord(started);
1988
- if (!startedRecord) throw new Error("coordinator_session_command_required");
1989
- session = normalizeSession(startedRecord);
1990
- await writeJsonFile(sessionFile(session.session_id), session);
1991
- await appendCoordinatorEvent(namespaceDir, {
1992
- kind: "session.started",
1993
- sessionId: String(session.session_id),
1994
- summary: `Session ${String(session.session_id)} started by coordinator delegate`,
1995
- payloadRef: path.relative(namespaceDir, sessionFile(session.session_id)),
1996
- metadata: { delegate: true, workflow: delegateWorkflow },
1997
- });
1998
- const live = hasTmuxIdentity(session) ? await hasTmuxSession(session, commandRunner) : null;
1999
- await writeSessionState(namespaceDir, String(session.session_id), "ready_for_input", {
2000
- live,
2001
- reason: null,
2002
- });
4022
+ session = await startCoordinatorSession(canonicalCwd, undefined, mpresetResolution.mpreset);
4023
+ const ownerTransaction = asRecord(session.__coordinatorOwnerTransaction) as {
4024
+ commit?: () => void;
4025
+ rollback?: () => Promise<"cleaned" | "failed" | "unverifiable">;
4026
+ } | null;
4027
+ try {
4028
+ if (mpresetResolution.mpreset) session.mpreset = mpresetResolution.mpreset;
4029
+ await writeJsonFile(sessionFile(session.session_id), session);
4030
+ await appendCoordinatorEvent(namespaceDir, {
4031
+ kind: "session.started",
4032
+ sessionId: String(session.session_id),
4033
+ summary: `Session ${String(session.session_id)} started by coordinator delegate`,
4034
+ payloadRef: path.relative(namespaceDir, sessionFile(session.session_id)),
4035
+ metadata: { delegate: true, workflow: delegateWorkflow },
4036
+ });
4037
+ await writeSessionState(namespaceDir, String(session.session_id), "booting", {
4038
+ live: null,
4039
+ reason: "awaiting_runtime_readiness",
4040
+ });
4041
+ ownerTransaction?.commit?.();
4042
+ } catch (error) {
4043
+ if (ownerTransaction)
4044
+ throw new CoordinatorStartError(
4045
+ "coordinator_tmux_start_failed",
4046
+ await compensateFailedOwnerStart(session, ownerTransaction),
4047
+ );
4048
+ throw error;
4049
+ }
2003
4050
  }
2004
4051
 
2005
4052
  const sessionId = String(session.session_id);
2006
- const activeTurn = reusedSession ? await readActiveTurn(namespaceDir, sessionId) : null;
2007
- if (activeTurn && args.force !== true && args.queue !== true) {
4053
+ assertSessionReadinessAuthority(session);
4054
+ const prepared = await preparePromptTurn(session, taggedPrompt, {
4055
+ force: args.force === true,
4056
+ queue: args.queue === true,
4057
+ });
4058
+ if (!prepared.ok) {
2008
4059
  return {
2009
4060
  ok: false,
2010
4061
  reason: "active_turn_exists",
2011
4062
  session_id: sessionId,
2012
- active_turn_id: activeTurn.turn_id,
4063
+ active_turn_id: prepared.activeTurn.turn_id,
2013
4064
  };
2014
4065
  }
2015
- if (activeTurn && args.force === true) {
2016
- const timestamp = new Date().toISOString();
2017
- const superseded = {
2018
- ...activeTurn,
2019
- status: "superseded" as const,
2020
- updated_at: timestamp,
2021
- completed_at: timestamp,
2022
- };
2023
- await writeTurnRecord(namespaceDir, superseded);
2024
- await clearActiveTurn(namespaceDir, superseded);
2025
- }
2026
- const shouldQueue = args.queue === true && args.force !== true && !!activeTurn;
2027
- const turn = shouldQueue
2028
- ? makeTurnRecord(config, sessionId, taggedPrompt, "queued")
2029
- : await activateTurn(session, makeTurnRecord(config, sessionId, taggedPrompt, "active"));
2030
- if (shouldQueue) await writeTurnRecord(namespaceDir, turn);
4066
+ const { turn, priorActiveTurn: activeTurn, queued: shouldQueue } = prepared;
2031
4067
  await appendCoordinatorEvent(namespaceDir, {
2032
4068
  kind: "delegation.started",
2033
4069
  sessionId,
@@ -2101,31 +4137,50 @@ export function createCoordinatorMcpServer(options: CoordinatorMcpServerOptions
2101
4137
  if (name === "skc_coordinator_start_session") {
2102
4138
  requireCoordinatorMutation(config, "sessions", args);
2103
4139
  const cwd = await assertCoordinatorWorkdir(config, args.cwd);
2104
- const input = {
4140
+ await preflightCoordinatorMutation(namespaceDir);
4141
+ const mpresetResolution = await resolveCoordinatorMpreset(args.mpreset, loadModelProfiles);
4142
+ if (!mpresetResolution.ok) {
4143
+ return {
4144
+ ok: false,
4145
+ reason: mpresetResolution.reason,
4146
+ mpreset: mpresetResolution.mpreset,
4147
+ available_profiles: mpresetResolution.available_profiles,
4148
+ };
4149
+ }
4150
+
4151
+ const session = await startCoordinatorSession(
2105
4152
  cwd,
2106
- prompt: typeof args.prompt === "string" ? args.prompt : undefined,
2107
- namespace: config.namespace,
2108
- worktree: true as const,
2109
- };
2110
- const started = services.startSession
2111
- ? await services.startSession(input)
2112
- : await startTmuxSession(config, input, namespaceDir, commandRunner);
2113
- const startedRecord = asRecord(started);
2114
- if (!startedRecord) throw new Error("coordinator_session_command_required");
2115
- const session = normalizeSession(startedRecord);
2116
- await writeJsonFile(sessionFile(session.session_id), session);
2117
- await appendCoordinatorEvent(namespaceDir, {
2118
- kind: "session.started",
2119
- sessionId: String(session.session_id),
2120
- summary: `Session ${String(session.session_id)} started by coordinator`,
2121
- payloadRef: path.relative(namespaceDir, sessionFile(session.session_id)),
2122
- metadata: { prompted: typeof args.prompt === "string" && args.prompt.length > 0 },
2123
- });
2124
- const live = hasTmuxIdentity(session) ? await hasTmuxSession(session, commandRunner) : null;
2125
- let sessionState = await writeSessionState(namespaceDir, String(session.session_id), "ready_for_input", {
2126
- live,
2127
- reason: null,
2128
- });
4153
+ typeof args.prompt === "string" ? args.prompt : undefined,
4154
+ mpresetResolution.mpreset,
4155
+ );
4156
+ const ownerTransaction = asRecord(session.__coordinatorOwnerTransaction) as {
4157
+ commit?: () => void;
4158
+ rollback?: () => Promise<"cleaned" | "failed" | "unverifiable">;
4159
+ } | null;
4160
+ let sessionState: CoordinatorSessionState;
4161
+ try {
4162
+ if (mpresetResolution.mpreset) session.mpreset = mpresetResolution.mpreset;
4163
+ await writeJsonFile(sessionFile(session.session_id), session);
4164
+ await appendCoordinatorEvent(namespaceDir, {
4165
+ kind: "session.started",
4166
+ sessionId: String(session.session_id),
4167
+ summary: `Session ${String(session.session_id)} started by coordinator`,
4168
+ payloadRef: path.relative(namespaceDir, sessionFile(session.session_id)),
4169
+ metadata: { prompted: typeof args.prompt === "string" && args.prompt.length > 0 },
4170
+ });
4171
+ sessionState = await writeSessionState(namespaceDir, String(session.session_id), "booting", {
4172
+ live: null,
4173
+ reason: "awaiting_runtime_readiness",
4174
+ });
4175
+ ownerTransaction?.commit?.();
4176
+ } catch (error) {
4177
+ if (ownerTransaction)
4178
+ throw new CoordinatorStartError(
4179
+ "coordinator_tmux_start_failed",
4180
+ await compensateFailedOwnerStart(session, ownerTransaction),
4181
+ );
4182
+ throw error;
4183
+ }
2129
4184
  if (typeof args.prompt === "string" && args.prompt.length > 0) {
2130
4185
  const turn = await activateTurn(
2131
4186
  session,
@@ -2161,36 +4216,28 @@ export function createCoordinatorMcpServer(options: CoordinatorMcpServerOptions
2161
4216
  if (name === "skc_coordinator_send_prompt") {
2162
4217
  requireCoordinatorMutation(config, "sessions", args);
2163
4218
  const sessionId = safeExternalId("session", args.session_id);
4219
+ await preflightCoordinatorMutation(namespaceDir, sessionId);
2164
4220
  const session = asRecord(await readJsonFile(sessionFile(sessionId)));
2165
4221
  if (!session) return { ok: false, reason: "unknown_session", session_id: sessionId };
2166
4222
  if (typeof args.prompt !== "string" || args.prompt.length === 0)
2167
4223
  return { ok: false, reason: "prompt_required" };
2168
- const activeTurn = await readActiveTurn(namespaceDir, sessionId);
2169
- if (activeTurn && args.force !== true && args.queue !== true) {
4224
+ await readSessionState(namespaceDir, sessionId);
4225
+ await readLatestEventSeq(namespaceDir);
4226
+ assertSessionReadinessAuthority(session);
4227
+ const prepared = await preparePromptTurn(session, args.prompt, {
4228
+ force: args.force === true,
4229
+ queue: args.queue === true,
4230
+ queueWithoutActive: true,
4231
+ });
4232
+ if (!prepared.ok) {
2170
4233
  return {
2171
4234
  ok: false,
2172
4235
  reason: "active_turn_exists",
2173
4236
  session_id: sessionId,
2174
- active_turn_id: activeTurn.turn_id,
4237
+ active_turn_id: prepared.activeTurn.turn_id,
2175
4238
  };
2176
4239
  }
2177
- if (activeTurn && args.force === true) {
2178
- const timestamp = new Date().toISOString();
2179
- const superseded = {
2180
- ...activeTurn,
2181
- status: "superseded" as const,
2182
- updated_at: timestamp,
2183
- completed_at: timestamp,
2184
- };
2185
- await writeTurnRecord(namespaceDir, superseded);
2186
- await clearActiveTurn(namespaceDir, superseded);
2187
- }
2188
- const shouldQueue = args.queue === true && args.force !== true;
2189
- const turn = shouldQueue
2190
- ? makeTurnRecord(config, sessionId, args.prompt, "queued")
2191
- : await activateTurn(session, makeTurnRecord(config, sessionId, args.prompt, "active"));
2192
- if (shouldQueue) await writeTurnRecord(namespaceDir, turn);
2193
- const recordedTurn = turn;
4240
+ const { turn: recordedTurn, priorActiveTurn: activeTurn, queued: shouldQueue } = prepared;
2194
4241
  const prompt = {
2195
4242
  session_id: sessionId,
2196
4243
  turn_id: recordedTurn.turn_id,
@@ -2224,7 +4271,10 @@ export function createCoordinatorMcpServer(options: CoordinatorMcpServerOptions
2224
4271
  const timeoutMs = boundedAwaitTurnTimeoutMs(args.timeout_ms);
2225
4272
  const pollIntervalMs = boundedPollIntervalMs(args.poll_interval_ms);
2226
4273
  const deadline = Date.now() + timeoutMs;
2227
- let payload = await readTurnPayload(args.turn_id, args.session_id, args.lines);
4274
+ let payload = await withCoordinatorTransaction(
4275
+ namespaceDir,
4276
+ async () => await readTurnPayload(args.turn_id, args.session_id, args.lines),
4277
+ );
2228
4278
  while (
2229
4279
  payload.ok === true &&
2230
4280
  !TERMINAL_TURN_STATUSES.has((payload.turn as TurnRecord).status) &&
@@ -2236,7 +4286,10 @@ export function createCoordinatorMcpServer(options: CoordinatorMcpServerOptions
2236
4286
  payload.turn as TurnRecord,
2237
4287
  Math.min(pollIntervalMs, remainingMs),
2238
4288
  );
2239
- payload = await readTurnPayload(args.turn_id, args.session_id, args.lines);
4289
+ payload = await withCoordinatorTransaction(
4290
+ namespaceDir,
4291
+ async () => await readTurnPayload(args.turn_id, args.session_id, args.lines),
4292
+ );
2240
4293
  }
2241
4294
  if (payload.ok === true && !TERMINAL_TURN_STATUSES.has((payload.turn as TurnRecord).status)) {
2242
4295
  return {
@@ -2261,6 +4314,16 @@ export function createCoordinatorMcpServer(options: CoordinatorMcpServerOptions
2261
4314
  if (args.turn_id != null && question.turn_id !== safeTurnId(args.turn_id)) {
2262
4315
  return { ok: false, reason: "question_turn_mismatch" };
2263
4316
  }
4317
+ await preflightCoordinatorMutation(namespaceDir);
4318
+ const questionSessionId =
4319
+ typeof question.session_id === "string" ? safeExternalId("session", question.session_id) : null;
4320
+ if (!questionSessionId) throw coordinatorStateError("invalid");
4321
+ await preflightCoordinatorMutation(namespaceDir, questionSessionId);
4322
+ if (typeof question.turn_id === "string") {
4323
+ const referencedTurn = await readTurnRecord(namespaceDir, question.turn_id);
4324
+ if (!referencedTurn || referencedTurn.session_id !== questionSessionId)
4325
+ throw coordinatorStateError("invalid");
4326
+ }
2264
4327
  const answeredTurnId = typeof question.turn_id === "string" ? question.turn_id : null;
2265
4328
  const answered = {
2266
4329
  ...question,
@@ -2290,33 +4353,43 @@ export function createCoordinatorMcpServer(options: CoordinatorMcpServerOptions
2290
4353
 
2291
4354
  let turn: TurnRecord | null = null;
2292
4355
  if (answeredTurnId) {
2293
- turn = await readTurnRecord(namespaceDir, answeredTurnId);
2294
- if (turn) {
2295
- const timestamp = new Date().toISOString();
2296
- turn = {
2297
- ...turn,
2298
- status: "active",
2299
- question_ids: [...new Set([...turn.question_ids, questionId])],
2300
- updated_at: timestamp,
2301
- };
2302
- await writeTurnRecord(namespaceDir, turn);
2303
- await writeActiveTurn(namespaceDir, turn);
2304
- await writeSessionState(namespaceDir, turn.session_id, "running", {
2305
- currentTurnId: turn.turn_id,
2306
- live: null,
2307
- reason: null,
4356
+ const observedTurn = await readTurnRecord(namespaceDir, answeredTurnId);
4357
+ if (observedTurn) {
4358
+ turn = await withSessionTransition(observedTurn.session_id, async () => {
4359
+ const current = await readTurnRecord(namespaceDir, answeredTurnId);
4360
+ if (!current || !ACTIVE_TURN_STATUSES.has(current.status)) return current;
4361
+ const timestamp = new Date().toISOString();
4362
+ const answeredTurn: TurnRecord = {
4363
+ ...current,
4364
+ status: "active",
4365
+ question_ids: [...new Set([...current.question_ids, questionId])],
4366
+ updated_at: timestamp,
4367
+ };
4368
+ await writeTurnRecord(namespaceDir, answeredTurn);
4369
+ await writeActiveTurn(namespaceDir, answeredTurn);
4370
+ await writeSessionState(namespaceDir, answeredTurn.session_id, "running", {
4371
+ currentTurnId: answeredTurn.turn_id,
4372
+ live: null,
4373
+ reason: null,
4374
+ });
4375
+ const session = asRecord(await readJsonFile(sessionFile(answeredTurn.session_id)));
4376
+ if (session && typeof args.answer === "string") {
4377
+ await sendTmuxPrompt(session, args.answer, commandRunner, services.ownerIsolationProbe);
4378
+ }
4379
+ return answeredTurn;
2308
4380
  });
2309
- const session = asRecord(await readJsonFile(sessionFile(turn.session_id)));
2310
- if (session && typeof args.answer === "string")
2311
- await sendTmuxPrompt(session, args.answer, commandRunner);
2312
4381
  }
2313
4382
  }
2314
4383
  return { ok: true, question: answered, ...(turn ? { turn } : {}) };
2315
4384
  }
2316
4385
  if (name === "skc_coordinator_report_status") {
2317
4386
  requireCoordinatorMutation(config, "reports", args);
4387
+ await preflightCoordinatorMutation(namespaceDir);
4388
+
2318
4389
  const evidence = await validateEvidencePaths(args.evidence_paths);
2319
4390
  const sessionId = args.session_id == null ? null : safeExternalId("session", args.session_id);
4391
+ if (sessionId) await preflightCoordinatorMutation(namespaceDir, sessionId);
4392
+
2320
4393
  const report = {
2321
4394
  session_id: sessionId,
2322
4395
  turn_id: args.turn_id,
@@ -2329,61 +4402,79 @@ export function createCoordinatorMcpServer(options: CoordinatorMcpServerOptions
2329
4402
  };
2330
4403
  let turn: TurnRecord | null = null;
2331
4404
  let promotedTurn: TurnRecord | null = null;
4405
+ let promotedPreparation: { session: Record<string, unknown>; turn: TurnRecord } | null = null;
2332
4406
  if (args.turn_id != null) {
2333
4407
  turn = await readTurnRecord(namespaceDir, args.turn_id);
2334
4408
  if (!turn) return { ok: false, reason: "unknown_turn" };
2335
4409
  if (sessionId != null && turn.session_id !== sessionId) {
2336
4410
  return { ok: false, reason: "turn_session_mismatch" };
2337
4411
  }
4412
+ await preflightCoordinatorMutation(namespaceDir, turn.session_id);
2338
4413
  const terminalStatus = asTerminalTurnStatus(args.status);
2339
4414
  if (terminalStatus) {
2340
- const timestamp = new Date().toISOString();
2341
- turn = {
2342
- ...turn,
2343
- status: terminalStatus,
2344
- delivery: {
2345
- ...turn.delivery,
2346
- prompt_acknowledged: true,
2347
- state: "acknowledged",
2348
- },
2349
- final_response: {
2350
- text:
2351
- typeof args.summary === "string"
2352
- ? args.summary
2353
- : typeof args.blocker === "string"
2354
- ? args.blocker
2355
- : null,
2356
- format: "markdown",
2357
- source: "report_status",
2358
- artifact_path: null,
2359
- truncated: false,
2360
- },
2361
- evidence,
2362
- error:
2363
- terminalStatus === "failed"
2364
- ? {
2365
- code: "reported_failure",
2366
- message:
2367
- typeof args.blocker === "string" ? args.blocker : String(args.summary ?? "failed"),
2368
- recoverable: true,
2369
- }
2370
- : null,
2371
- updated_at: timestamp,
2372
- completed_at: timestamp,
2373
- };
2374
- await writeTurnRecord(namespaceDir, turn);
2375
- await clearActiveTurn(namespaceDir, turn);
2376
- await writeSessionState(
2377
- namespaceDir,
2378
- turn.session_id,
2379
- terminalStatus === "failed" ? "errored" : "completed",
2380
- {
2381
- lastTurnId: turn.turn_id,
2382
- live: null,
2383
- reason: terminalStatus === "failed" ? "reported_failure" : null,
2384
- },
2385
- );
2386
- promotedTurn = await promoteNextQueuedTurn(turn.session_id);
4415
+ const transition = await withSessionTransition(turn.session_id, async () => {
4416
+ const current = await readTurnRecord(namespaceDir, turn?.turn_id);
4417
+ if (!current || TERMINAL_TURN_STATUSES.has(current.status)) {
4418
+ return { turn: current, promotion: null };
4419
+ }
4420
+ const timestamp = new Date().toISOString();
4421
+ const terminalTurn: TurnRecord = {
4422
+ ...current,
4423
+ status: terminalStatus,
4424
+ delivery: {
4425
+ ...current.delivery,
4426
+ prompt_acknowledged: true,
4427
+ state: "acknowledged",
4428
+ },
4429
+ final_response: {
4430
+ text:
4431
+ typeof args.summary === "string"
4432
+ ? args.summary
4433
+ : typeof args.blocker === "string"
4434
+ ? args.blocker
4435
+ : null,
4436
+ format: "markdown",
4437
+ source: "report_status",
4438
+ artifact_path: null,
4439
+ truncated: false,
4440
+ },
4441
+ evidence,
4442
+ error:
4443
+ terminalStatus === "failed"
4444
+ ? {
4445
+ code: "reported_failure",
4446
+ message:
4447
+ typeof args.blocker === "string"
4448
+ ? args.blocker
4449
+ : String(args.summary ?? "failed"),
4450
+ recoverable: true,
4451
+ }
4452
+ : null,
4453
+ updated_at: timestamp,
4454
+ completed_at: timestamp,
4455
+ };
4456
+ await writeTurnRecord(namespaceDir, terminalTurn);
4457
+ await clearActiveTurn(namespaceDir, terminalTurn);
4458
+ await writeSessionState(
4459
+ namespaceDir,
4460
+ terminalTurn.session_id,
4461
+ terminalStatus === "failed" ? "errored" : "completed",
4462
+ {
4463
+ lastTurnId: terminalTurn.turn_id,
4464
+ live: null,
4465
+ reason: terminalStatus === "failed" ? "reported_failure" : null,
4466
+ },
4467
+ );
4468
+ return {
4469
+ turn: terminalTurn,
4470
+ promotion: await prepareNextQueuedTurnUnlocked(terminalTurn.session_id),
4471
+ };
4472
+ });
4473
+ turn = transition.turn;
4474
+ promotedPreparation = transition.promotion;
4475
+ if (promotedPreparation) {
4476
+ promotedTurn = await completePreparedTurn(promotedPreparation.session, promotedPreparation.turn);
4477
+ }
2387
4478
  }
2388
4479
  }
2389
4480
  const reportId = `report-${Date.now()}`;
@@ -2399,21 +4490,60 @@ export function createCoordinatorMcpServer(options: CoordinatorMcpServerOptions
2399
4490
  ? args.summary
2400
4491
  : `Report ${String(args.status ?? "unknown")} written`,
2401
4492
  payloadRef: path.relative(namespaceDir, reportPath),
2402
- metadata: { status: typeof args.status === "string" ? args.status : null },
4493
+ metadata: {
4494
+ status: typeof args.status === "string" ? args.status : null,
4495
+ },
2403
4496
  });
2404
4497
  return {
2405
4498
  ok: true,
2406
4499
  report,
2407
- ...(turn ? { turn, session_state: await readSessionState(namespaceDir, turn.session_id) } : {}),
4500
+ ...(turn
4501
+ ? {
4502
+ turn,
4503
+ session_state: await readSessionState(namespaceDir, turn.session_id),
4504
+ }
4505
+ : {}),
2408
4506
  ...(promotedTurn ? { promoted_turn: promotedTurn } : {}),
2409
4507
  };
2410
4508
  }
2411
4509
  return { ok: false, reason: "unknown_tool", tool: name };
2412
4510
  } catch (error) {
2413
- return { ok: false, reason: error instanceof Error ? error.message : String(error) };
4511
+ const failure = error as { sessionId?: unknown; action?: unknown };
4512
+ return {
4513
+ ok: false,
4514
+ reason: publicCoordinatorError(error),
4515
+ ...(publicCleanupStatus(error) ? { cleanup_status: publicCleanupStatus(error) } : {}),
4516
+ ...(typeof failure.sessionId === "string" ? { session_id: failure.sessionId } : {}),
4517
+ ...(typeof failure.action === "string" ? { action: failure.action } : {}),
4518
+ };
2414
4519
  }
2415
4520
  }
2416
4521
 
4522
+ async function callToolInternal(name: string, args: Record<string, unknown> = {}): Promise<Record<string, unknown>> {
4523
+ try {
4524
+ if (
4525
+ name === "skc_coordinator_await_turn" ||
4526
+ name === "skc_coordinator_watch_events" ||
4527
+ (name === "skc_coordinator_start_session" && typeof args.prompt === "string" && args.prompt.length > 0) ||
4528
+ name === "skc_coordinator_send_prompt" ||
4529
+ workflowForDelegateTool(name) !== null
4530
+ ) {
4531
+ return await callToolUnlocked(name, args);
4532
+ }
4533
+ return await withCoordinatorTransaction(namespaceDir, async () => await callToolUnlocked(name, args));
4534
+ } catch (error) {
4535
+ return {
4536
+ ok: false,
4537
+ reason: publicCoordinatorError(error),
4538
+ ...(publicCleanupStatus(error) ? { cleanup_status: publicCleanupStatus(error) } : {}),
4539
+ };
4540
+ }
4541
+ }
4542
+
4543
+ async function callTool(name: string, args: Record<string, unknown> = {}): Promise<Record<string, unknown>> {
4544
+ return publicCoordinatorResponse(await callToolInternal(name, args)) as Record<string, unknown>;
4545
+ }
4546
+
2417
4547
  async function handleJsonRpc(request: JsonRpcRequest): Promise<JsonRpcResponse> {
2418
4548
  const id = request.id ?? null;
2419
4549
  if (request.method === "initialize") {
@@ -2427,8 +4557,15 @@ export function createCoordinatorMcpServer(options: CoordinatorMcpServerOptions
2427
4557
  },
2428
4558
  };
2429
4559
  }
4560
+ if (request.method === "ping") {
4561
+ return { jsonrpc: "2.0", id, result: {} };
4562
+ }
2430
4563
  if (request.method === "tools/list") {
2431
- return { jsonrpc: "2.0", id, result: { tools: COORDINATOR_MCP_TOOL_NAMES.map(toolSchema) } };
4564
+ return {
4565
+ jsonrpc: "2.0",
4566
+ id,
4567
+ result: { tools: COORDINATOR_MCP_TOOL_NAMES.map(toolSchema) },
4568
+ };
2432
4569
  }
2433
4570
  if (request.method === "prompts/list") {
2434
4571
  return { jsonrpc: "2.0", id, result: { prompts: [] } };
@@ -2437,17 +4574,31 @@ export function createCoordinatorMcpServer(options: CoordinatorMcpServerOptions
2437
4574
  return { jsonrpc: "2.0", id, result: { resources: [] } };
2438
4575
  }
2439
4576
  if (request.method === "tools/call") {
2440
- const params = (request.params ?? {}) as { name?: string; arguments?: Record<string, unknown> };
4577
+ const params = (request.params ?? {}) as {
4578
+ name?: string;
4579
+ arguments?: Record<string, unknown>;
4580
+ };
2441
4581
  const payload = await callTool(params.name ?? "", params.arguments ?? {});
2442
- return { jsonrpc: "2.0", id, result: textResult(payload, payload.ok === false) };
4582
+ return {
4583
+ jsonrpc: "2.0",
4584
+ id,
4585
+ result: textResult(payload, payload.ok === false),
4586
+ };
2443
4587
  }
2444
- return { jsonrpc: "2.0", id, error: { code: -32601, message: `unknown_method:${request.method}` } };
4588
+ return {
4589
+ jsonrpc: "2.0",
4590
+ id,
4591
+ error: { code: -32601, message: `unknown_method:${request.method}` },
4592
+ };
2445
4593
  }
2446
4594
 
2447
4595
  return { config, callTool, handleJsonRpc, handle: handleJsonRpc };
2448
4596
  }
2449
4597
 
2450
- function legacyToolResult(payload: unknown): { content: Array<{ type: "text"; text: string }>; isError: boolean } {
4598
+ function legacyToolResult(payload: unknown): {
4599
+ content: Array<{ type: "text"; text: string }>;
4600
+ isError: boolean;
4601
+ } {
2451
4602
  const failed = typeof payload === "object" && payload !== null && (payload as { ok?: unknown }).ok === false;
2452
4603
  return textResult(payload, failed);
2453
4604
  }
@@ -2468,13 +4619,21 @@ export async function handleCoordinatorMcpRequest(
2468
4619
  };
2469
4620
  }
2470
4621
  if (request.method === "tools/list") {
2471
- return { jsonrpc: "2.0", id: request.id ?? null, result: { tools: COORDINATOR_MCP_TOOL_NAMES.map(toolSchema) } };
4622
+ return {
4623
+ jsonrpc: "2.0",
4624
+ id: request.id ?? null,
4625
+ result: { tools: COORDINATOR_MCP_TOOL_NAMES.map(toolSchema) },
4626
+ };
2472
4627
  }
2473
4628
  if (request.method === "prompts/list") {
2474
4629
  return { jsonrpc: "2.0", id: request.id ?? null, result: { prompts: [] } };
2475
4630
  }
2476
4631
  if (request.method === "resources/list") {
2477
- return { jsonrpc: "2.0", id: request.id ?? null, result: { resources: [] } };
4632
+ return {
4633
+ jsonrpc: "2.0",
4634
+ id: request.id ?? null,
4635
+ result: { resources: [] },
4636
+ };
2478
4637
  }
2479
4638
  if (request.method !== "tools/call")
2480
4639
  return {
@@ -2482,11 +4641,14 @@ export async function handleCoordinatorMcpRequest(
2482
4641
  id: request.id ?? null,
2483
4642
  error: { code: -32601, message: `unknown_method:${request.method}` },
2484
4643
  };
2485
- const params = (request.params ?? {}) as { name?: string; arguments?: Record<string, unknown> };
4644
+ const params = (request.params ?? {}) as {
4645
+ name?: string;
4646
+ arguments?: Record<string, unknown>;
4647
+ };
2486
4648
  const args = params.arguments ?? {};
2487
4649
  const server = createCoordinatorMcpServer({
2488
4650
  env: options.env ?? process.env,
2489
- services: options.createSession ? { startSession: () => options.createSession?.() } : undefined,
4651
+ services: options.createSession ? { startSession: input => options.createSession?.(input) } : undefined,
2490
4652
  });
2491
4653
  return {
2492
4654
  jsonrpc: "2.0",
@@ -2495,23 +4657,161 @@ export async function handleCoordinatorMcpRequest(
2495
4657
  };
2496
4658
  }
2497
4659
 
2498
- export async function runCoordinatorMcpStdio(options: CoordinatorMcpServerOptions = {}): Promise<void> {
2499
- const server = createCoordinatorMcpServer(options);
4660
+ export interface PumpCoordinatorOptions {
4661
+ /** Max concurrent in-flight *data* (non-control) handlers. Control frames (ping) bypass this. */
4662
+ maxDataConcurrency?: number;
4663
+ /** Max data requests queued waiting for a slot before overflow is rejected as server_busy. */
4664
+ maxQueueDepth?: number;
4665
+ /** Bounded wait for in-flight handlers/writes to settle after input ends. */
4666
+ drainTimeoutMs?: number;
4667
+ }
4668
+
4669
+ /**
4670
+ * Pump a newline-delimited JSON-RPC stream with BOUNDED concurrent dispatch.
4671
+ *
4672
+ * A long-running tool call (e.g. skc_coordinator_await_turn, which polls for
4673
+ * minutes) must not block the read loop from answering keepalive pings on the
4674
+ * same stdio channel. But naive unbounded concurrency reintroduces its own
4675
+ * hazards, so this pump enforces the safety envelope the coordinator needs:
4676
+ *
4677
+ * - Control frames (ping) bypass the data-concurrency cap → keepalive is always
4678
+ * answerable even while data handlers saturate.
4679
+ * - Data handlers are capped at `maxDataConcurrency`; excess is queued up to
4680
+ * `maxQueueDepth`, then rejected as `server_busy` (bounded memory / fanout).
4681
+ * - `writeLine` failures move the writer to a terminal closed state instead of
4682
+ * poisoning the serialized write chain or escaping as an unhandled rejection;
4683
+ * no writes happen after close.
4684
+ * - On EOF the pump drains in-flight handlers (bounded by `drainTimeoutMs`) and
4685
+ * flushes queued writes before returning, so shutdown never races live work.
4686
+ * - Byte chunks are decoded with a streaming decoder so multibyte characters
4687
+ * split across chunks are not corrupted.
4688
+ */
4689
+ export async function pumpCoordinatorMcpStream(
4690
+ handleJsonRpc: (request: JsonRpcRequest) => Promise<JsonRpcResponse>,
4691
+ input: AsyncIterable<string | Uint8Array>,
4692
+ writeLine: (line: string) => void | Promise<void>,
4693
+ options: PumpCoordinatorOptions = {},
4694
+ ): Promise<void> {
4695
+ const maxDataConcurrency = Math.max(1, options.maxDataConcurrency ?? 32);
4696
+ const maxQueueDepth = Math.max(0, options.maxQueueDepth ?? 256);
4697
+ const drainTimeoutMs = Math.max(0, options.drainTimeoutMs ?? 30_000);
4698
+
4699
+ let writeClosed = false;
4700
+ let draining = false;
4701
+ let writeChain: Promise<void> = Promise.resolve();
4702
+ const inFlight = new Set<Promise<void>>();
4703
+ let activeData = 0;
4704
+ const dataQueue: JsonRpcRequest[] = [];
4705
+
4706
+ const emit = (response: JsonRpcResponse): void => {
4707
+ writeChain = writeChain.then(async () => {
4708
+ if (writeClosed) return;
4709
+ try {
4710
+ await writeLine(`${JSON.stringify(response)}\n`);
4711
+ } catch {
4712
+ writeClosed = true; // terminal writer error: stop, but never poison the chain
4713
+ }
4714
+ });
4715
+ };
4716
+
4717
+ const launch = (request: JsonRpcRequest, control: boolean): void => {
4718
+ const task = (async () => {
4719
+ try {
4720
+ emit(await handleJsonRpc(request));
4721
+ } catch (err) {
4722
+ emit({
4723
+ jsonrpc: "2.0",
4724
+ id: request.id ?? null,
4725
+ error: { code: -32603, message: publicCoordinatorError(err) },
4726
+ });
4727
+ } finally {
4728
+ if (!control) {
4729
+ activeData -= 1;
4730
+ if (!draining) {
4731
+ const next = dataQueue.shift();
4732
+ if (next) {
4733
+ activeData += 1;
4734
+ launch(next, false);
4735
+ }
4736
+ }
4737
+ }
4738
+ }
4739
+ })();
4740
+ inFlight.add(task);
4741
+ void task.finally(() => inFlight.delete(task));
4742
+ };
4743
+
4744
+ const dispatch = (request: JsonRpcRequest): void => {
4745
+ // Notifications (no id) get no response; the coordinator has no side-effecting ones.
4746
+ if (request.id === undefined || request.id === null) return;
4747
+ if (request.method === "ping") {
4748
+ launch(request, true); // control frame: bypass the data cap
4749
+ return;
4750
+ }
4751
+ if (activeData < maxDataConcurrency) {
4752
+ activeData += 1;
4753
+ launch(request, false);
4754
+ return;
4755
+ }
4756
+ if (dataQueue.length < maxQueueDepth) {
4757
+ dataQueue.push(request);
4758
+ return;
4759
+ }
4760
+ emit({
4761
+ jsonrpc: "2.0",
4762
+ id: request.id,
4763
+ error: { code: -32000, message: "server_busy: coordinator request queue is full" },
4764
+ });
4765
+ };
4766
+
4767
+ const decoder = new TextDecoder();
2500
4768
  let buffer = "";
2501
- for await (const chunk of process.stdin) {
2502
- buffer += chunk.toString();
4769
+ for await (const chunk of input) {
4770
+ buffer += typeof chunk === "string" ? chunk : decoder.decode(chunk, { stream: true });
2503
4771
  let newline = buffer.indexOf("\n");
2504
4772
  while (newline >= 0) {
2505
4773
  const line = buffer.slice(0, newline).trim();
2506
4774
  buffer = buffer.slice(newline + 1);
2507
4775
  if (line.length > 0) {
2508
- const request = JSON.parse(line) as JsonRpcRequest;
2509
- if (request.id !== undefined && request.id !== null) {
2510
- const response = await server.handleJsonRpc(request);
2511
- process.stdout.write(`${JSON.stringify(response)}\n`);
4776
+ let request: JsonRpcRequest | null = null;
4777
+ try {
4778
+ request = JSON.parse(line) as JsonRpcRequest;
4779
+ } catch {
4780
+ request = null; // ignore malformed frames rather than crashing the loop
2512
4781
  }
4782
+ if (request) dispatch(request);
2513
4783
  }
2514
4784
  newline = buffer.indexOf("\n");
2515
4785
  }
2516
4786
  }
4787
+
4788
+ // EOF: stop promoting queued work, then drain in-flight handlers under a bound.
4789
+ draining = true;
4790
+ if (inFlight.size > 0) {
4791
+ const drain = Promise.allSettled(Array.from(inFlight)).then(() => undefined);
4792
+ if (drainTimeoutMs > 0) {
4793
+ let timer: ReturnType<typeof setTimeout> | undefined;
4794
+ const timeout = new Promise<void>(resolve => {
4795
+ timer = setTimeout(resolve, drainTimeoutMs);
4796
+ (timer as { unref?: () => void }).unref?.();
4797
+ });
4798
+ await Promise.race([drain, timeout]);
4799
+ if (timer) clearTimeout(timer);
4800
+ } else {
4801
+ await drain;
4802
+ }
4803
+ }
4804
+ await writeChain;
4805
+ }
4806
+
4807
+ export async function runCoordinatorMcpStdio(options: CoordinatorMcpServerOptions = {}): Promise<void> {
4808
+ const server = createCoordinatorMcpServer(options);
4809
+ await pumpCoordinatorMcpStream(
4810
+ request => server.handleJsonRpc(request),
4811
+ process.stdin,
4812
+ line =>
4813
+ new Promise<void>((resolve, reject) => {
4814
+ process.stdout.write(line, err => (err ? reject(err) : resolve()));
4815
+ }),
4816
+ );
2517
4817
  }