@sayknow-cli/coding-agent 0.4.7 → 0.5.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (428) hide show
  1. package/CHANGELOG.md +122 -0
  2. package/dist/types/capability/index.d.ts +10 -20
  3. package/dist/types/capability/types.d.ts +3 -0
  4. package/dist/types/cli/args.d.ts +0 -2
  5. package/dist/types/cli/read-cli.d.ts +1 -0
  6. package/dist/types/cli.d.ts +9 -1
  7. package/dist/types/commands/daemon.d.ts +2 -2
  8. package/dist/types/commands/deep-interview.d.ts +9 -0
  9. package/dist/types/commands/harness.d.ts +22 -0
  10. package/dist/types/commands/read.d.ts +6 -0
  11. package/dist/types/commands/sdk.d.ts +20 -2
  12. package/dist/types/config/keybindings.d.ts +34 -4
  13. package/dist/types/config/model-profile-contract.d.ts +40 -0
  14. package/dist/types/config/model-profiles.d.ts +2 -3
  15. package/dist/types/config/model-registry.d.ts +5 -0
  16. package/dist/types/config/provider-auth-health.d.ts +14 -0
  17. package/dist/types/config/provider-ranking.d.ts +59 -0
  18. package/dist/types/config/settings-schema.d.ts +156 -10
  19. package/dist/types/config/settings.d.ts +3 -0
  20. package/dist/types/edit/modes/patch.d.ts +13 -0
  21. package/dist/types/edit/path-mutation-lock.d.ts +15 -0
  22. package/dist/types/eval/py/executor.d.ts +1 -1
  23. package/dist/types/eval/py/runner-artifact.d.ts +8 -0
  24. package/dist/types/eval/py/tool-bridge.d.ts +1 -2
  25. package/dist/types/extensibility/extensions/types.d.ts +43 -2
  26. package/dist/types/extensibility/shared-events.d.ts +6 -0
  27. package/dist/types/internal-urls/local-root-gc.d.ts +2 -0
  28. package/dist/types/main.d.ts +33 -2
  29. package/dist/types/memories/index.d.ts +2 -0
  30. package/dist/types/modes/acp/acp-agent.d.ts +22 -9
  31. package/dist/types/modes/components/assistant-message.d.ts +3 -1
  32. package/dist/types/modes/components/btw-panel.d.ts +1 -0
  33. package/dist/types/modes/components/irc-sidebar.d.ts +41 -0
  34. package/dist/types/modes/components/queue-pane.d.ts +8 -0
  35. package/dist/types/modes/components/queued-message-selector.d.ts +6 -0
  36. package/dist/types/modes/components/read-tool-group.d.ts +4 -4
  37. package/dist/types/modes/components/runtime-mcp-add-wizard.d.ts +2 -1
  38. package/dist/types/modes/components/session-observer-overlay.d.ts +4 -0
  39. package/dist/types/modes/components/settings-selector.d.ts +2 -0
  40. package/dist/types/modes/components/tool-execution.d.ts +8 -0
  41. package/dist/types/modes/components/tool-status-header.d.ts +4 -2
  42. package/dist/types/modes/components/welcome.d.ts +5 -0
  43. package/dist/types/modes/controllers/btw-controller.d.ts +5 -0
  44. package/dist/types/modes/controllers/command-controller.d.ts +2 -0
  45. package/dist/types/modes/controllers/event-controller.d.ts +3 -0
  46. package/dist/types/modes/interactive-mode.d.ts +13 -2
  47. package/dist/types/modes/irc-observation-ledger.d.ts +17 -1
  48. package/dist/types/modes/prompt-action-autocomplete.d.ts +6 -1
  49. package/dist/types/modes/prompt-suggestion-controller.d.ts +35 -0
  50. package/dist/types/modes/shared/agent-wire/workflow-gate-broker.d.ts +2 -2
  51. package/dist/types/modes/types.d.ts +6 -0
  52. package/dist/types/modes/utils/hotkeys-markdown.d.ts +2 -1
  53. package/dist/types/modes/utils/ui-helpers.d.ts +16 -0
  54. package/dist/types/runtime/memory-domain.d.ts +9 -0
  55. package/dist/types/runtime/memory-guard-contract.d.ts +71 -0
  56. package/dist/types/runtime/memory-guard.d.ts +39 -0
  57. package/dist/types/runtime/memory-limit.d.ts +11 -0
  58. package/dist/types/runtime-mcp/content-limits.d.ts +7 -0
  59. package/dist/types/runtime-mcp/discoverable-tool-metadata.d.ts +1 -1
  60. package/dist/types/runtime-mcp/manager.d.ts +9 -1
  61. package/dist/types/runtime-mcp/oauth-flow.d.ts +0 -5
  62. package/dist/types/runtime-mcp/plugin-network-boundary.d.ts +14 -0
  63. package/dist/types/runtime-mcp/redaction.d.ts +2 -0
  64. package/dist/types/runtime-mcp/smithery-auth.d.ts +5 -0
  65. package/dist/types/sdk/acp/final-text.d.ts +19 -0
  66. package/dist/types/sdk/acp/mcp.d.ts +23 -0
  67. package/dist/types/sdk/broker/broker.d.ts +2 -0
  68. package/dist/types/sdk/broker/lifecycle.d.ts +13 -0
  69. package/dist/types/sdk/bus/chat-daemon-control.d.ts +9 -1
  70. package/dist/types/sdk/bus/config-commands.d.ts +2 -2
  71. package/dist/types/sdk/bus/config.d.ts +2 -0
  72. package/dist/types/sdk/bus/control-drain-lease.d.ts +52 -0
  73. package/dist/types/sdk/bus/conversation-store.d.ts +1 -0
  74. package/dist/types/sdk/bus/daemon-paths.d.ts +1 -0
  75. package/dist/types/sdk/bus/index.d.ts +52 -4
  76. package/dist/types/sdk/bus/kind-aware-reconciliation.d.ts +37 -0
  77. package/dist/types/sdk/bus/lifecycle-commands.d.ts +5 -4
  78. package/dist/types/sdk/bus/notification-orchestration.d.ts +1 -0
  79. package/dist/types/sdk/bus/notification-service.d.ts +34 -0
  80. package/dist/types/sdk/bus/operator-runtime.d.ts +1 -0
  81. package/dist/types/sdk/bus/prompt-reconciliation.d.ts +106 -0
  82. package/dist/types/sdk/bus/rate-limit-pool.d.ts +2 -0
  83. package/dist/types/sdk/bus/reconciliation-store.d.ts +65 -0
  84. package/dist/types/sdk/bus/telegram-daemon-contract.d.ts +33 -9
  85. package/dist/types/sdk/bus/telegram-daemon-control.d.ts +5 -1
  86. package/dist/types/sdk/bus/telegram-daemon.d.ts +286 -12
  87. package/dist/types/sdk/bus/telegram-reference.d.ts +2 -0
  88. package/dist/types/sdk/bus/topic-registry.d.ts +88 -2
  89. package/dist/types/sdk/client/discovery.d.ts +2 -0
  90. package/dist/types/sdk/client/liveness.d.ts +7 -0
  91. package/dist/types/sdk/host/control/operations.d.ts +3 -2
  92. package/dist/types/sdk/host/host.d.ts +3 -1
  93. package/dist/types/sdk/host/query/handlers.d.ts +14 -0
  94. package/dist/types/sdk/index.d.ts +2 -0
  95. package/dist/types/sdk/lifecycle-session.d.ts +10 -1
  96. package/dist/types/sdk/prompt-status.d.ts +88 -0
  97. package/dist/types/sdk/session-directory.d.ts +1 -1
  98. package/dist/types/sdk/session.d.ts +7 -0
  99. package/dist/types/sdk/startup-capability.d.ts +9 -0
  100. package/dist/types/sdk/transport/auth-preface.d.ts +9 -0
  101. package/dist/types/sdk/transport/index.d.ts +12 -0
  102. package/dist/types/sdk/transport/relay.d.ts +43 -0
  103. package/dist/types/sdk/transport/serve-cli.d.ts +4 -0
  104. package/dist/types/sdk/transport/socket.d.ts +7 -0
  105. package/dist/types/sdk/transport/stdio.d.ts +3 -0
  106. package/dist/types/session/agent-session.d.ts +83 -24
  107. package/dist/types/session/btw-contract.d.ts +16 -0
  108. package/dist/types/session/fallback-chain-controller.d.ts +18 -2
  109. package/dist/types/session/internal/managed-session-scope.d.ts +70 -10
  110. package/dist/types/session/internal/managed-session-storage.d.ts +34 -12
  111. package/dist/types/session/internal/native-publish-outcome.d.ts +48 -0
  112. package/dist/types/session/memory-guard-checkpoint-participant.d.ts +69 -0
  113. package/dist/types/session/session-manager.d.ts +89 -18
  114. package/dist/types/session/session-storage.d.ts +31 -0
  115. package/dist/types/session/streaming-output.d.ts +60 -5
  116. package/dist/types/setup/credential-import.d.ts +5 -1
  117. package/dist/types/setup/provider-onboarding.d.ts +2 -0
  118. package/dist/types/skc-runtime/deep-interview-runtime.d.ts +1 -1
  119. package/dist/types/skc-runtime/deep-interview-stage.d.ts +37 -0
  120. package/dist/types/skc-runtime/gc-runtime.d.ts +1 -1
  121. package/dist/types/skc-runtime/linux-proc.d.ts +22 -14
  122. package/dist/types/skc-runtime/memory-guard-owner-claims.d.ts +47 -0
  123. package/dist/types/skc-runtime/ralplan-runtime.d.ts +112 -1
  124. package/dist/types/skc-runtime/repository-binding.d.ts +63 -0
  125. package/dist/types/skc-runtime/state-runtime.d.ts +3 -1
  126. package/dist/types/skc-runtime/state-writer.d.ts +5 -1
  127. package/dist/types/skc-runtime/team-launch.d.ts +1 -1
  128. package/dist/types/skc-runtime/team-runtime.d.ts +9 -2
  129. package/dist/types/skc-runtime/team-store.d.ts +12 -0
  130. package/dist/types/skc-runtime/team-worker-memory-guard.d.ts +92 -0
  131. package/dist/types/skc-runtime/tmux-owner-isolation.d.ts +7 -0
  132. package/dist/types/skc-runtime/ultragoal-guard.d.ts +6 -5
  133. package/dist/types/skc-runtime/ultragoal-receipt-freshness.d.ts +28 -0
  134. package/dist/types/skc-runtime/ultragoal-runtime.d.ts +43 -5
  135. package/dist/types/skill-state/workflow-hud.d.ts +3 -0
  136. package/dist/types/ssh/utils.d.ts +1 -0
  137. package/dist/types/task/discovery.d.ts +3 -1
  138. package/dist/types/task/executor.d.ts +6 -0
  139. package/dist/types/task/index.d.ts +3 -1
  140. package/dist/types/task/provider-retry-status.d.ts +13 -0
  141. package/dist/types/task/receipt.d.ts +2 -0
  142. package/dist/types/task/types.d.ts +133 -0
  143. package/dist/types/task/ultragoal-redteam-activation.d.ts +33 -0
  144. package/dist/types/tools/browser/launch.d.ts +7 -0
  145. package/dist/types/tools/fetch.d.ts +22 -9
  146. package/dist/types/tools/image-gen.d.ts +63 -1
  147. package/dist/types/tools/index.d.ts +7 -1
  148. package/dist/types/tools/output-meta.d.ts +21 -1
  149. package/dist/types/tools/read-internals.d.ts +12 -0
  150. package/dist/types/tools/read.d.ts +45 -1
  151. package/dist/types/tools/resource-gc.d.ts +23 -0
  152. package/dist/types/tools/skill-discovery.d.ts +7 -0
  153. package/dist/types/tools/sqlite-reader.d.ts +7 -0
  154. package/dist/types/tools/tool-result.d.ts +7 -1
  155. package/dist/types/utils/prompt-suggestion.d.ts +33 -0
  156. package/dist/types/utils/shell-snapshot.d.ts +12 -0
  157. package/dist/types/web/insane/bridge.d.ts +1 -0
  158. package/dist/types/web/insane/url-guard.d.ts +17 -0
  159. package/package.json +8 -12
  160. package/scripts/benchmark-sticky-viewport-pr1.ts +239 -0
  161. package/scripts/capture-platform-shortcut-labels-showcase.ts +342 -0
  162. package/scripts/capture-sticky-viewport-showcase.ts +292 -0
  163. package/scripts/compile-args.ts +0 -1
  164. package/scripts/dogfood-repository-binding.ts +209 -0
  165. package/scripts/generate-docs-index.ts +4 -1
  166. package/scripts/generate-sdk-operation-inventory.ts +9 -0
  167. package/scripts/run-test-manifest.ts +6 -4
  168. package/scripts/verify-skc-sdk-canonicalization.ts +122 -18
  169. package/scripts/verify-sticky-viewport-showcase.ts +611 -0
  170. package/src/capability/index.ts +58 -42
  171. package/src/capability/types.ts +3 -0
  172. package/src/cli/args.ts +16 -100
  173. package/src/cli/fast-help.ts +1 -1
  174. package/src/cli/mcp-cli.ts +4 -21
  175. package/src/cli/notify-cli.ts +6 -38
  176. package/src/cli/read-cli.ts +2 -1
  177. package/src/cli/setup-cli.ts +10 -4
  178. package/src/cli.ts +133 -11
  179. package/src/commands/daemon.ts +22 -5
  180. package/src/commands/deep-interview.ts +26 -1
  181. package/src/commands/harness.ts +35 -12
  182. package/src/commands/read.ts +11 -2
  183. package/src/commands/sdk.ts +134 -17
  184. package/src/commands/team.ts +1 -1
  185. package/src/commit/agentic/index.ts +3 -3
  186. package/src/commit/map-reduce/index.ts +2 -2
  187. package/src/config/keybindings.ts +158 -25
  188. package/src/config/model-profile-contract.ts +174 -0
  189. package/src/config/model-profiles.ts +51 -10
  190. package/src/config/model-registry.ts +24 -4
  191. package/src/config/model-resolver.ts +2 -2
  192. package/src/config/provider-auth-health.ts +42 -0
  193. package/src/config/provider-ranking.ts +119 -0
  194. package/src/config/settings-schema.ts +157 -10
  195. package/src/config/settings.ts +254 -108
  196. package/src/coordinator-mcp/model-preset.ts +21 -74
  197. package/src/defaults/skc/skills/deep-interview/SKILL.md +147 -24
  198. package/src/defaults/skc/skills/ralplan/SKILL.md +91 -34
  199. package/src/defaults/skc/skills/team/SKILL.md +1 -1
  200. package/src/defaults/skc/skills/ultragoal/SKILL.md +68 -17
  201. package/src/discovery/ssh.ts +12 -1
  202. package/src/edit/index.ts +3 -6
  203. package/src/edit/modes/patch.ts +38 -1
  204. package/src/edit/modes/replace.ts +42 -0
  205. package/src/edit/path-mutation-lock.ts +112 -0
  206. package/src/eval/js/tool-bridge.ts +1 -1
  207. package/src/eval/py/executor.ts +37 -13
  208. package/src/eval/py/kernel.ts +1 -21
  209. package/src/eval/py/prelude.py +2 -2
  210. package/src/eval/py/runner-artifact.ts +84 -0
  211. package/src/eval/py/tool-bridge.ts +34 -18
  212. package/src/extensibility/extensions/runner.ts +4 -0
  213. package/src/extensibility/extensions/types.ts +42 -4
  214. package/src/extensibility/plugins/marketplace/fetcher.ts +191 -7
  215. package/src/extensibility/shared-events.ts +5 -0
  216. package/src/extensibility/skc-plugins/runtime-adapters.ts +2 -1
  217. package/src/hooks/native-skill-hook.ts +17 -4
  218. package/src/internal-urls/docs-index.generated.ts +17 -16
  219. package/src/internal-urls/local-protocol.ts +87 -25
  220. package/src/internal-urls/local-root-gc.ts +127 -0
  221. package/src/internal-urls/mcp-protocol.ts +73 -11
  222. package/src/main.ts +120 -42
  223. package/src/memories/index.ts +10 -0
  224. package/src/modes/DESIGN.md +52 -0
  225. package/src/modes/acp/acp-agent.ts +647 -83
  226. package/src/modes/acp/acp-event-mapper.ts +106 -12
  227. package/src/modes/acp/acp-mode.ts +39 -3
  228. package/src/modes/action-registry.ts +1 -1
  229. package/src/modes/components/assistant-message.ts +123 -22
  230. package/src/modes/components/btw-panel.ts +82 -16
  231. package/src/modes/components/irc-sidebar.ts +176 -62
  232. package/src/modes/components/model-selector.ts +61 -4
  233. package/src/modes/components/notifications-settings-editor.ts +1 -1
  234. package/src/modes/components/oauth-selector.ts +42 -12
  235. package/src/modes/components/plan-preview-overlay.ts +1 -1
  236. package/src/modes/components/queue-pane.ts +55 -9
  237. package/src/modes/components/queued-message-selector.ts +53 -10
  238. package/src/modes/components/read-tool-group.ts +82 -14
  239. package/src/modes/components/runtime-mcp-add-wizard.ts +5 -0
  240. package/src/modes/components/sayknow-pet-widget.ts +20 -8
  241. package/src/modes/components/session-observer-overlay.ts +187 -32
  242. package/src/modes/components/settings-selector.ts +60 -18
  243. package/src/modes/components/tool-execution.ts +138 -27
  244. package/src/modes/components/tool-status-header.ts +26 -8
  245. package/src/modes/components/welcome.ts +50 -23
  246. package/src/modes/controllers/btw-controller.ts +119 -36
  247. package/src/modes/controllers/command-controller.ts +101 -54
  248. package/src/modes/controllers/event-controller.ts +119 -27
  249. package/src/modes/controllers/extension-ui-controller.ts +23 -12
  250. package/src/modes/controllers/input-controller.ts +100 -8
  251. package/src/modes/controllers/runtime-mcp-command-controller.ts +29 -6
  252. package/src/modes/controllers/selector-controller.ts +4 -1
  253. package/src/modes/interactive-mode.ts +201 -60
  254. package/src/modes/irc-observation-ledger.ts +71 -15
  255. package/src/modes/prompt-action-autocomplete.ts +32 -13
  256. package/src/modes/prompt-suggestion-controller.ts +96 -0
  257. package/src/modes/runtime-init.ts +20 -2
  258. package/src/modes/shared/agent-wire/command-dispatch.ts +3 -3
  259. package/src/modes/shared/agent-wire/workflow-gate-broker.ts +2 -2
  260. package/src/modes/types.ts +6 -0
  261. package/src/modes/utils/hotkeys-markdown.ts +64 -36
  262. package/src/modes/utils/ui-helpers.ts +99 -4
  263. package/src/prompts/agent-fragments/restricted-bash.md +1 -1
  264. package/src/prompts/agents/architect.md +17 -3
  265. package/src/prompts/agents/critic.md +17 -2
  266. package/src/prompts/agents/executor.md +1 -1
  267. package/src/prompts/agents/planner.md +8 -1
  268. package/src/prompts/system/btw-user.md +3 -8
  269. package/src/prompts/system/prompt-suggestion-system.md +32 -0
  270. package/src/prompts/system/system-prompt.md +6 -3
  271. package/src/prompts/tools/cron.md +3 -1
  272. package/src/prompts/tools/read.md +4 -2
  273. package/src/prompts/tools/skill-discovery.md +1 -0
  274. package/src/runtime/memory-domain.ts +66 -0
  275. package/src/runtime/memory-guard-contract.ts +73 -0
  276. package/src/runtime/memory-guard.ts +292 -0
  277. package/src/runtime/memory-limit.ts +50 -0
  278. package/src/runtime-mcp/client.ts +41 -55
  279. package/src/runtime-mcp/content-limits.ts +71 -0
  280. package/src/runtime-mcp/discoverable-tool-metadata.ts +1 -1
  281. package/src/runtime-mcp/json-rpc.ts +28 -11
  282. package/src/runtime-mcp/manager.ts +77 -59
  283. package/src/runtime-mcp/oauth-flow.ts +0 -72
  284. package/src/runtime-mcp/plugin-network-boundary.ts +101 -0
  285. package/src/runtime-mcp/redaction.ts +30 -0
  286. package/src/runtime-mcp/smithery-auth.ts +17 -2
  287. package/src/runtime-mcp/smithery-connect.ts +11 -1
  288. package/src/runtime-mcp/transports/http.ts +63 -22
  289. package/src/sdk/acp/adapter.ts +8 -1
  290. package/src/sdk/acp/final-text.ts +40 -0
  291. package/src/sdk/acp/mcp.ts +26 -0
  292. package/src/sdk/broker/broker.ts +34 -20
  293. package/src/sdk/broker/identity.ts +22 -12
  294. package/src/sdk/broker/lifecycle.ts +293 -43
  295. package/src/sdk/broker/session-index.ts +11 -1
  296. package/src/sdk/bus/chat-daemon-cli.ts +2 -2
  297. package/src/sdk/bus/chat-daemon-control.ts +22 -5
  298. package/src/sdk/bus/config-commands.ts +2 -2
  299. package/src/sdk/bus/config.ts +10 -2
  300. package/src/sdk/bus/control-drain-lease.ts +104 -0
  301. package/src/sdk/bus/conversation-store.ts +40 -6
  302. package/src/sdk/bus/daemon-paths.ts +2 -0
  303. package/src/sdk/bus/index.ts +793 -121
  304. package/src/sdk/bus/kind-aware-reconciliation.ts +242 -0
  305. package/src/sdk/bus/lifecycle-commands.ts +63 -10
  306. package/src/sdk/bus/lifecycle-control-runtime.ts +11 -2
  307. package/src/sdk/bus/lifecycle-orchestrator.ts +32 -1
  308. package/src/sdk/bus/notification-orchestration.ts +12 -0
  309. package/src/sdk/bus/notification-service.ts +87 -25
  310. package/src/sdk/bus/operator-runtime.ts +31 -5
  311. package/src/sdk/bus/prompt-reconciliation.ts +240 -0
  312. package/src/sdk/bus/rate-limit-pool.ts +8 -0
  313. package/src/sdk/bus/recent-activity.ts +90 -26
  314. package/src/sdk/bus/reconciliation-store.ts +224 -0
  315. package/src/sdk/bus/telegram-cli.ts +6 -13
  316. package/src/sdk/bus/telegram-daemon-cli.ts +8 -3
  317. package/src/sdk/bus/telegram-daemon-contract.ts +34 -9
  318. package/src/sdk/bus/telegram-daemon-control.ts +161 -40
  319. package/src/sdk/bus/telegram-daemon.ts +3649 -593
  320. package/src/sdk/bus/telegram-reference.ts +20 -2
  321. package/src/sdk/bus/topic-registry.ts +451 -12
  322. package/src/sdk/cli/session-cli.ts +15 -2
  323. package/src/sdk/client/discovery.ts +19 -3
  324. package/src/sdk/client/liveness.ts +49 -0
  325. package/src/sdk/host/control/dispatch.ts +8 -2
  326. package/src/sdk/host/control/operations.ts +3 -2
  327. package/src/sdk/host/host.ts +27 -9
  328. package/src/sdk/host/query/handlers.ts +125 -5
  329. package/src/sdk/index.ts +12 -0
  330. package/src/sdk/lifecycle-session.ts +19 -3
  331. package/src/sdk/mcp/server.ts +46 -18
  332. package/src/sdk/prompt-status.ts +97 -0
  333. package/src/sdk/protocol/operation-inventory.generated.json +145 -0
  334. package/src/sdk/protocol/operation-registry.ts +24 -4
  335. package/src/sdk/session-directory.ts +5 -1
  336. package/src/sdk/session.ts +69 -8
  337. package/src/sdk/startup-capability.ts +15 -1
  338. package/src/sdk/transport/auth-preface.ts +82 -0
  339. package/src/sdk/transport/index.ts +18 -0
  340. package/src/sdk/transport/relay.ts +252 -0
  341. package/src/sdk/transport/serve-cli.ts +105 -0
  342. package/src/sdk/transport/socket.ts +197 -0
  343. package/src/sdk/transport/stdio.ts +17 -0
  344. package/src/session/agent-session.ts +1112 -314
  345. package/src/session/blob-store.ts +25 -7
  346. package/src/session/btw-contract.ts +59 -0
  347. package/src/session/fallback-chain-controller.ts +47 -8
  348. package/src/session/internal/managed-session-scope.ts +1426 -259
  349. package/src/session/internal/managed-session-storage.ts +414 -238
  350. package/src/session/internal/native-publish-outcome.ts +271 -0
  351. package/src/session/memory-guard-checkpoint-participant.ts +131 -0
  352. package/src/session/session-manager.ts +2079 -469
  353. package/src/session/session-storage.ts +151 -11
  354. package/src/session/streaming-output.ts +262 -43
  355. package/src/setup/credential-import.ts +28 -3
  356. package/src/setup/provider-onboarding.ts +49 -5
  357. package/src/setup/provider-presets.json +2 -1
  358. package/src/skc-runtime/deep-interview-runtime.ts +303 -36
  359. package/src/skc-runtime/deep-interview-stage.ts +1016 -0
  360. package/src/skc-runtime/deep-interview-state.ts +8 -5
  361. package/src/skc-runtime/gc-render.ts +1 -0
  362. package/src/skc-runtime/gc-runtime.ts +11 -1
  363. package/src/skc-runtime/linux-proc.ts +70 -36
  364. package/src/skc-runtime/managed-owner-supervisor.ts +83 -4
  365. package/src/skc-runtime/memory-guard-owner-claims.ts +389 -0
  366. package/src/skc-runtime/ralplan-runtime.ts +1057 -75
  367. package/src/skc-runtime/repository-binding.ts +267 -0
  368. package/src/skc-runtime/session-state-sidecar.ts +25 -9
  369. package/src/skc-runtime/state-runtime.ts +50 -12
  370. package/src/skc-runtime/state-writer.ts +68 -56
  371. package/src/skc-runtime/team-launch.ts +34 -5
  372. package/src/skc-runtime/team-runtime.ts +41 -1
  373. package/src/skc-runtime/team-store.ts +32 -0
  374. package/src/skc-runtime/team-worker-memory-guard.ts +363 -0
  375. package/src/skc-runtime/team-workers.ts +1 -0
  376. package/src/skc-runtime/tmux-common.ts +13 -2
  377. package/src/skc-runtime/tmux-owner-isolation.ts +20 -1
  378. package/src/skc-runtime/tmux-sessions.ts +45 -6
  379. package/src/skc-runtime/ultragoal-guard.ts +93 -15
  380. package/src/skc-runtime/ultragoal-receipt-freshness.ts +123 -0
  381. package/src/skc-runtime/ultragoal-runtime.ts +469 -38
  382. package/src/skc-runtime/workflow-manifest.generated.json +195 -5
  383. package/src/skc-runtime/workflow-manifest.ts +67 -3
  384. package/src/skill-state/workflow-hud.ts +25 -2
  385. package/src/skill-state/workflow-mutation-guard.ts +104 -12
  386. package/src/slash-commands/builtin-registry.ts +1 -1
  387. package/src/ssh/utils.ts +14 -0
  388. package/src/task/discovery.ts +8 -2
  389. package/src/task/executor.ts +165 -47
  390. package/src/task/index.ts +123 -15
  391. package/src/task/provider-retry-status.ts +88 -0
  392. package/src/task/receipt.ts +3 -0
  393. package/src/task/render.ts +45 -26
  394. package/src/task/skc-command.ts +2 -2
  395. package/src/task/types.ts +55 -10
  396. package/src/task/ultragoal-redteam-activation.ts +75 -0
  397. package/src/tools/ask.ts +1 -1
  398. package/src/tools/ast-edit.ts +2 -2
  399. package/src/tools/bash-allowed-prefixes.ts +45 -1
  400. package/src/tools/bash-pty-selection.ts +2 -2
  401. package/src/tools/browser/launch.ts +41 -6
  402. package/src/tools/browser/screenshot-format.ts +6 -1
  403. package/src/tools/cron.ts +1 -1
  404. package/src/tools/fetch.ts +127 -154
  405. package/src/tools/image-gen.ts +476 -23
  406. package/src/tools/index.ts +7 -1
  407. package/src/tools/output-meta.ts +176 -9
  408. package/src/tools/path-utils.ts +5 -1
  409. package/src/tools/read-internals.ts +44 -0
  410. package/src/tools/read.ts +1544 -310
  411. package/src/tools/resource-gc.ts +665 -125
  412. package/src/tools/skill-discovery.ts +39 -3
  413. package/src/tools/sqlite-reader.ts +63 -5
  414. package/src/tools/subagent-render.ts +74 -16
  415. package/src/tools/subagent.ts +2 -0
  416. package/src/tools/tool-result.ts +14 -1
  417. package/src/utils/edit-mode.ts +2 -2
  418. package/src/utils/pasted-image-path.ts +15 -6
  419. package/src/utils/prompt-suggestion.ts +265 -0
  420. package/src/utils/shell-snapshot.ts +173 -63
  421. package/src/utils/sixel.ts +3 -3
  422. package/src/web/insane/bridge.ts +5 -1
  423. package/src/web/insane/url-guard.ts +140 -27
  424. package/src/web/scrapers/docs-rs.ts +30 -5
  425. package/src/web/scrapers/types.ts +19 -49
  426. package/src/web/scrapers/utils.ts +12 -26
  427. package/src/web/search/providers/codex.ts +1 -1
  428. package/src/workflow/workflow-intent-diff.ts +4 -1
@@ -0,0 +1,267 @@
1
+ /**
2
+ * Authoritative repository/worktree binding for plans and delegated tasks (#2901).
3
+ *
4
+ * Multi-repo parent directories must not let QA/review lanes infer a sibling repo
5
+ * from prose. Bindings carry a validated worktree root + git common-dir identity
6
+ * so spawn/handoff can fail closed on mismatch.
7
+ */
8
+ import * as fssync from "node:fs";
9
+ import * as fs from "node:fs/promises";
10
+ import * as path from "node:path";
11
+ import { type GitRepository, head, repo } from "../utils/git";
12
+
13
+ export const REPOSITORY_BINDING_SCHEMA = "skc.repository_binding.v1" as const;
14
+
15
+ export interface RepositoryBinding {
16
+ schema: typeof REPOSITORY_BINDING_SCHEMA;
17
+ /** Canonical realpath of the git worktree root (or resolved cwd root). */
18
+ worktreeRoot: string;
19
+ /** Git common dir realpath when available; null outside a git checkout. */
20
+ commonDir: string | null;
21
+ /** Optional repo-relative subdirectory the lane should operate under. */
22
+ relativeSubdir?: string;
23
+ /** Optional display path (may be non-canonical); never used for authority. */
24
+ displayPath?: string;
25
+ /** Optional baseline HEAD at capture time. */
26
+ head?: string;
27
+ /** Optional branch name at capture time (when not detached). */
28
+ branch?: string;
29
+ }
30
+
31
+ export type RepositoryBindingErrorCode =
32
+ | "not_a_repository"
33
+ | "identity_mismatch"
34
+ | "path_outside_root"
35
+ | "invalid_binding";
36
+
37
+ export class RepositoryBindingError extends Error {
38
+ readonly code: RepositoryBindingErrorCode;
39
+
40
+ constructor(code: RepositoryBindingErrorCode, message: string) {
41
+ super(message);
42
+ this.name = "RepositoryBindingError";
43
+ this.code = code;
44
+ }
45
+ }
46
+
47
+ async function realpathOrResolve(target: string): Promise<string> {
48
+ try {
49
+ return await fs.realpath(target);
50
+ } catch {
51
+ return path.resolve(target);
52
+ }
53
+ }
54
+
55
+ /** Sync realpath for path-under-root checks (handles macOS /var → /private/var). */
56
+ function realpathSyncOrResolve(target: string): string {
57
+ try {
58
+ return fssync.realpathSync(target);
59
+ } catch {
60
+ return path.resolve(target);
61
+ }
62
+ }
63
+
64
+ function isObject(value: unknown): value is Record<string, unknown> {
65
+ return typeof value === "object" && value !== null && !Array.isArray(value);
66
+ }
67
+
68
+ function nonEmptyString(value: unknown): string | undefined {
69
+ return typeof value === "string" && value.trim() !== "" ? value.trim() : undefined;
70
+ }
71
+
72
+ /** Capture a durable binding from the active cwd/worktree. */
73
+ export async function captureRepositoryBinding(
74
+ cwd: string,
75
+ options: { relativeSubdir?: string; displayPath?: string } = {},
76
+ ): Promise<RepositoryBinding> {
77
+ const resolvedCwd = await realpathOrResolve(cwd);
78
+ const repository = await repo.resolve(resolvedCwd);
79
+ const worktreeRoot = repository ? await realpathOrResolve(repository.repoRoot) : resolvedCwd;
80
+ const commonDir = repository ? await realpathOrResolve(repository.commonDir) : null;
81
+
82
+ let headSha: string | undefined;
83
+ let branch: string | undefined;
84
+ if (repository) {
85
+ const headState = await head.resolve(resolvedCwd);
86
+ if (headState) {
87
+ headSha = headState.commit || undefined;
88
+ branch = headState.kind === "ref" ? headState.branchName || undefined : undefined;
89
+ }
90
+ }
91
+
92
+ const binding: RepositoryBinding = {
93
+ schema: REPOSITORY_BINDING_SCHEMA,
94
+ worktreeRoot,
95
+ commonDir,
96
+ ...(options.relativeSubdir ? { relativeSubdir: normalizeRelativeSubdir(options.relativeSubdir) } : {}),
97
+ ...(options.displayPath ? { displayPath: options.displayPath } : {}),
98
+ ...(headSha ? { head: headSha } : {}),
99
+ ...(branch ? { branch } : {}),
100
+ };
101
+ return binding;
102
+ }
103
+
104
+ function normalizeRelativeSubdir(relative: string): string {
105
+ const normalized = relative
106
+ .replaceAll("\\", "/")
107
+ .replace(/^\.\/+/u, "")
108
+ .replace(/\/+$/u, "");
109
+ if (normalized === "" || normalized === ".") {
110
+ throw new RepositoryBindingError("invalid_binding", "relativeSubdir must be a non-empty relative path");
111
+ }
112
+ if (path.isAbsolute(normalized) || normalized.split("/").includes("..")) {
113
+ throw new RepositoryBindingError("invalid_binding", "relativeSubdir must be repo-relative without '..' segments");
114
+ }
115
+ return normalized;
116
+ }
117
+
118
+ /** Parse a binding from JSON/plan payload; fail closed on malformed shapes. */
119
+ export function parseRepositoryBinding(value: unknown): RepositoryBinding {
120
+ if (!isObject(value)) {
121
+ throw new RepositoryBindingError("invalid_binding", "repository binding must be an object");
122
+ }
123
+ const schema = nonEmptyString(value.schema);
124
+ if (schema !== REPOSITORY_BINDING_SCHEMA) {
125
+ throw new RepositoryBindingError(
126
+ "invalid_binding",
127
+ `repository binding schema must be ${REPOSITORY_BINDING_SCHEMA}`,
128
+ );
129
+ }
130
+ const worktreeRoot = nonEmptyString(value.worktreeRoot ?? value.worktree_root);
131
+ if (!worktreeRoot) {
132
+ throw new RepositoryBindingError("invalid_binding", "repository binding requires worktreeRoot");
133
+ }
134
+ const commonDirRaw = value.commonDir ?? value.common_dir;
135
+ const commonDir =
136
+ commonDirRaw === null || commonDirRaw === undefined
137
+ ? null
138
+ : (nonEmptyString(commonDirRaw) ??
139
+ (() => {
140
+ throw new RepositoryBindingError("invalid_binding", "commonDir must be a string or null");
141
+ })());
142
+ const relativeSubdirRaw = value.relativeSubdir ?? value.relative_subdir;
143
+ const relativeSubdir =
144
+ relativeSubdirRaw === undefined ? undefined : normalizeRelativeSubdir(String(relativeSubdirRaw));
145
+ const displayPath = nonEmptyString(value.displayPath ?? value.display_path);
146
+ const head = nonEmptyString(value.head);
147
+ const branch = nonEmptyString(value.branch);
148
+ return {
149
+ schema: REPOSITORY_BINDING_SCHEMA,
150
+ worktreeRoot: path.resolve(worktreeRoot),
151
+ commonDir: commonDir === null ? null : path.resolve(commonDir),
152
+ ...(relativeSubdir ? { relativeSubdir } : {}),
153
+ ...(displayPath ? { displayPath } : {}),
154
+ ...(head ? { head } : {}),
155
+ ...(branch ? { branch } : {}),
156
+ };
157
+ }
158
+
159
+ /** True when two bindings refer to the same repository identity (linked worktrees ok). */
160
+ export function repositoryBindingsMatch(left: RepositoryBinding, right: RepositoryBinding): boolean {
161
+ if (left.commonDir && right.commonDir) {
162
+ return path.resolve(left.commonDir) === path.resolve(right.commonDir);
163
+ }
164
+ // Non-git workspaces: require exact worktree root match.
165
+ return path.resolve(left.worktreeRoot) === path.resolve(right.worktreeRoot);
166
+ }
167
+
168
+ /**
169
+ * Ensure `cwd` is inside the bound repository (or the same linked worktree family).
170
+ * Fails closed on sibling-repo drift.
171
+ */
172
+ export async function assertCwdMatchesRepositoryBinding(
173
+ cwd: string,
174
+ binding: RepositoryBinding,
175
+ ): Promise<RepositoryBinding> {
176
+ const active = await captureRepositoryBinding(cwd);
177
+ if (!repositoryBindingsMatch(active, binding)) {
178
+ throw new RepositoryBindingError(
179
+ "identity_mismatch",
180
+ `Active worktree does not match plan/task repository binding. active=${active.worktreeRoot} (commonDir=${active.commonDir ?? "none"}) bound=${binding.worktreeRoot} (commonDir=${binding.commonDir ?? "none"}).`,
181
+ );
182
+ }
183
+ return active;
184
+ }
185
+
186
+ /** Ensure a declared target path resolves under the bound worktree root. */
187
+ export function assertPathUnderRepositoryBinding(binding: RepositoryBinding, targetPath: string): string {
188
+ const root = realpathSyncOrResolve(binding.worktreeRoot);
189
+ const base = binding.relativeSubdir ? realpathSyncOrResolve(path.resolve(root, binding.relativeSubdir)) : root;
190
+ const candidate = path.isAbsolute(targetPath) ? path.resolve(targetPath) : path.resolve(base, targetPath);
191
+ // Prefer realpath when the path exists so macOS /var ↔ /private/var aliases match.
192
+ const resolved = realpathSyncOrResolve(candidate);
193
+ const relative = path.relative(root, resolved);
194
+ if (relative.startsWith("..") || path.isAbsolute(relative)) {
195
+ throw new RepositoryBindingError(
196
+ "path_outside_root",
197
+ `Path escapes bound repository root: ${targetPath} (root=${root})`,
198
+ );
199
+ }
200
+ return resolved;
201
+ }
202
+
203
+ /**
204
+ * Public identity snapshot for receipts/handoffs (no display-only fields required).
205
+ * Always includes the schema + durable roots so downstream lanes can re-verify.
206
+ */
207
+ export function publicRepositoryBinding(binding: RepositoryBinding): RepositoryBinding {
208
+ return {
209
+ schema: REPOSITORY_BINDING_SCHEMA,
210
+ worktreeRoot: path.resolve(binding.worktreeRoot),
211
+ commonDir: binding.commonDir === null ? null : path.resolve(binding.commonDir),
212
+ ...(binding.relativeSubdir ? { relativeSubdir: binding.relativeSubdir } : {}),
213
+ ...(binding.displayPath ? { displayPath: binding.displayPath } : {}),
214
+ ...(binding.head ? { head: binding.head } : {}),
215
+ ...(binding.branch ? { branch: binding.branch } : {}),
216
+ };
217
+ }
218
+
219
+ /**
220
+ * Resolve the authoritative binding for a delegated task before discovery/spawn.
221
+ *
222
+ * - Missing declaration → stamp from session cwd (never leave authority implicit).
223
+ * - Declared binding → parse + fail closed unless it matches the active session worktree.
224
+ * - relativeSubdir (when present) must resolve under the bound root.
225
+ */
226
+ export async function resolveTaskRepositoryBinding(
227
+ sessionCwd: string,
228
+ declared: unknown | undefined,
229
+ ): Promise<RepositoryBinding> {
230
+ const sessionBinding = await captureRepositoryBinding(sessionCwd, { displayPath: sessionCwd });
231
+ if (declared === undefined || declared === null) {
232
+ return publicRepositoryBinding(sessionBinding);
233
+ }
234
+ const taskBinding = parseRepositoryBinding(declared);
235
+ await assertCwdMatchesRepositoryBinding(sessionCwd, taskBinding);
236
+ if (taskBinding.relativeSubdir) {
237
+ assertPathUnderRepositoryBinding(taskBinding, ".");
238
+ }
239
+ return publicRepositoryBinding(taskBinding);
240
+ }
241
+
242
+ /**
243
+ * Ensure an execution/isolation root (cwd or worktree) still matches the bound identity.
244
+ * Used after isolation workspace creation so linked worktrees keep the source repository.
245
+ */
246
+ export async function assertExecutionRootMatchesRepositoryBinding(
247
+ executionRoot: string,
248
+ binding: RepositoryBinding,
249
+ ): Promise<RepositoryBinding> {
250
+ return await assertCwdMatchesRepositoryBinding(executionRoot, binding);
251
+ }
252
+
253
+ /** Optional helper for tests and diagnostics. */
254
+ export function bindingFromGitRepository(
255
+ repository: GitRepository,
256
+ options: { relativeSubdir?: string; displayPath?: string; head?: string; branch?: string } = {},
257
+ ): RepositoryBinding {
258
+ return {
259
+ schema: REPOSITORY_BINDING_SCHEMA,
260
+ worktreeRoot: path.resolve(repository.repoRoot),
261
+ commonDir: path.resolve(repository.commonDir),
262
+ ...(options.relativeSubdir ? { relativeSubdir: normalizeRelativeSubdir(options.relativeSubdir) } : {}),
263
+ ...(options.displayPath ? { displayPath: options.displayPath } : {}),
264
+ ...(options.head ? { head: options.head } : {}),
265
+ ...(options.branch ? { branch: options.branch } : {}),
266
+ };
267
+ }
@@ -911,15 +911,11 @@ async function operatorDispatchIdForOwner(
911
911
  }
912
912
  }
913
913
 
914
- async function persistCoordinatorRuntimeStateFromOwnerTerminalPostmortem(
914
+ async function observeOwnerTerminalPostmortem(
915
915
  reason: postmortem.Reason,
916
- context: RuntimeStateContext,
917
- stateFile: string,
916
+ owner: OwnerTerminalContext,
918
917
  sessionId: string,
919
- previous: Record<string, unknown>,
920
- ): Promise<void> {
921
- const owner = context.ownerTerminal;
922
- if (!owner) return;
918
+ ): Promise<OwnerVerdict | null> {
923
919
  try {
924
920
  const now = new Date().toISOString();
925
921
  const observation: Omit<ObserveTerminalRequest, "operator_dispatch_id"> = {
@@ -937,10 +933,27 @@ async function persistCoordinatorRuntimeStateFromOwnerTerminalPostmortem(
937
933
  reason: "process_postmortem",
938
934
  };
939
935
  const operatorDispatchId = await operatorDispatchIdForOwner(owner, observation);
940
- const verdict = await observeOwnerTerminal({
936
+ return await observeOwnerTerminal({
941
937
  ...observation,
942
938
  ...(operatorDispatchId ? { operator_dispatch_id: operatorDispatchId } : {}),
943
939
  });
940
+ } catch {
941
+ return null;
942
+ }
943
+ }
944
+
945
+ async function persistCoordinatorRuntimeStateFromOwnerTerminalPostmortem(
946
+ context: RuntimeStateContext,
947
+ stateFile: string,
948
+ sessionId: string,
949
+ previous: Record<string, unknown>,
950
+ verdict: OwnerVerdict | null,
951
+ ): Promise<void> {
952
+ const owner = context.ownerTerminal;
953
+ if (!owner) return;
954
+ try {
955
+ if (!verdict) throw new Error("owner terminal verdict unavailable");
956
+ const now = new Date().toISOString();
944
957
  const expected = verdict.classification === "expected_operator_shutdown";
945
958
  const state: RuntimeState = expected ? "completed" : "errored";
946
959
  const payload = {
@@ -1009,6 +1022,9 @@ export async function persistCoordinatorRuntimeStateFromPostmortem(
1009
1022
  const stateFile = runtimeStateFileForContext(context);
1010
1023
  if (!stateFile) return;
1011
1024
  const identity = normalizedIdentity(context);
1025
+ const ownerTerminalVerdict = context.ownerTerminal
1026
+ ? await observeOwnerTerminalPostmortem(reason, context.ownerTerminal, identity.sessionId)
1027
+ : null;
1012
1028
  await serializeStateFileWrite(
1013
1029
  stateFile,
1014
1030
  async () =>
@@ -1033,11 +1049,11 @@ export async function persistCoordinatorRuntimeStateFromPostmortem(
1033
1049
  }
1034
1050
  if (context.ownerTerminal) {
1035
1051
  await persistCoordinatorRuntimeStateFromOwnerTerminalPostmortem(
1036
- reason,
1037
1052
  context,
1038
1053
  stateFile,
1039
1054
  identity.sessionId,
1040
1055
  previous,
1056
+ ownerTerminalVerdict,
1041
1057
  );
1042
1058
  return;
1043
1059
  }
@@ -1,6 +1,9 @@
1
1
  import { createHash } from "node:crypto";
2
2
  import * as fs from "node:fs/promises";
3
3
  import * as path from "node:path";
4
+ // Subpath import keeps this module native-free for the skc-state-gates shards:
5
+ // the package barrel pulls procmgr/ptree → @sayknow-cli/natives.
6
+ import * as logger from "@sayknow-cli/utils/logger";
4
7
  import type { WorkflowHudSummary } from "../skill-state/active-state";
5
8
  import {
6
9
  applyHandoffToActiveState,
@@ -265,7 +268,21 @@ async function describeStaleClearState(
265
268
  return undefined;
266
269
  }
267
270
 
268
- async function readJsonFile(filePath: string): Promise<Record<string, unknown> | null> {
271
+ /**
272
+ * Route a workflow-state warning through the TUI-safe centralized file logger
273
+ * (console transport off by default) so interactive sessions never paint raw
274
+ * bytes into the alternate-screen stream (#3002). CLI command handlers may also
275
+ * collect the warning via an `onWarning` sink to surface it on the structured
276
+ * {@link StateCommandResult.stderr} channel, so `skc state` automation still
277
+ * distinguishes corrupt state from absent state.
278
+ */
279
+ function emitStateWarning(warning: string, context?: Record<string, unknown>): void {
280
+ logger.warn(warning, context);
281
+ }
282
+
283
+ type StateWarningSink = (warning: string) => void;
284
+
285
+ async function readJsonFile(filePath: string, onWarning?: StateWarningSink): Promise<Record<string, unknown> | null> {
269
286
  try {
270
287
  const raw = await fs.readFile(filePath, "utf-8");
271
288
  const parsed = JSON.parse(raw);
@@ -276,18 +293,22 @@ async function readJsonFile(filePath: string): Promise<Record<string, unknown> |
276
293
  } catch (error) {
277
294
  const err = error as NodeJS.ErrnoException;
278
295
  if (err.code === "ENOENT") return null;
279
- process.stderr.write(`WARNING: failed to read ${filePath}; ignoring corrupt state: ${err.message}\n`);
296
+ const warning = `WARNING: failed to read ${filePath}; ignoring corrupt state: ${err.message}`;
297
+ emitStateWarning(warning, { filePath, error: err.message });
298
+ onWarning?.(warning);
280
299
  return null;
281
300
  }
282
301
  }
283
302
 
284
- async function readJsonValue(filePath: string): Promise<unknown | null> {
303
+ async function readJsonValue(filePath: string, onWarning?: StateWarningSink): Promise<unknown | null> {
285
304
  try {
286
305
  return JSON.parse(await fs.readFile(filePath, "utf-8"));
287
306
  } catch (error) {
288
307
  const err = error as NodeJS.ErrnoException;
289
308
  if (err.code === "ENOENT") return null;
290
- process.stderr.write(`WARNING: failed to read ${filePath}; ignoring corrupt state: ${err.message}\n`);
309
+ const warning = `WARNING: failed to read ${filePath}; ignoring corrupt state: ${err.message}`;
310
+ emitStateWarning(warning, { filePath, error: err.message });
311
+ onWarning?.(warning);
291
312
  return null;
292
313
  }
293
314
  }
@@ -836,7 +857,8 @@ function buildHudForMode(
836
857
  : typeof payload.mode === "string"
837
858
  ? (payload.mode as string)
838
859
  : undefined;
839
- const verdict = typeof payload.verdict === "string" ? (payload.verdict as string) : undefined;
860
+ const rawVerdict = payload.last_review_verdict ?? payload.verdict;
861
+ const verdict = typeof rawVerdict === "string" ? rawVerdict : undefined;
840
862
  const iteration = typeof payload.iteration === "number" ? (payload.iteration as number) : undefined;
841
863
  const pendingApproval = payload.pending_approval === true || stage === "final";
842
864
  return buildRalplanHudSummary({
@@ -1101,21 +1123,30 @@ export async function readWorkflowStateJson(
1101
1123
  cwd: string,
1102
1124
  skill: CanonicalSkcWorkflowSkill,
1103
1125
  sessionId?: string,
1126
+ onWarning?: StateWarningSink,
1104
1127
  ): Promise<Record<string, unknown>> {
1105
1128
  const session = await resolveSkcSessionForRead(cwd, {
1106
1129
  payloadSessionId: sessionId,
1107
1130
  envSessionId: process.env.SKC_SESSION_ID,
1108
1131
  });
1109
- return (await readJsonFile(modeStateFile(cwd, skill, session.skcSessionId))) ?? {};
1132
+ return (await readJsonFile(modeStateFile(cwd, skill, session.skcSessionId), onWarning)) ?? {};
1110
1133
  }
1111
1134
 
1112
1135
  async function handleRead(args: readonly string[], cwd: string): Promise<StateCommandResult> {
1113
1136
  const selectors = await resolveSelectors(args, cwd, "read");
1114
1137
  const mode = selectors.mode ?? (await inferModeFromActiveState(cwd, selectors.skcSessionId));
1115
1138
  const fields = parseFieldsFlag(args);
1139
+ // Corrupt-state warnings are TUI-safe file-logged inside the readers; the CLI
1140
+ // path also surfaces them on the command result so `skc state read`
1141
+ // automation can tell corrupt state from absent state (#3002).
1142
+ const warnings: string[] = [];
1143
+ const warningStderr = (): Pick<StateCommandResult, "stderr"> =>
1144
+ warnings.length ? { stderr: warnings.map(warning => `${warning}\n`).join("") } : {};
1116
1145
  if (mode) {
1117
1146
  const filePath = modeStateFile(cwd, mode, selectors.skcSessionId);
1118
- const existing = await readWorkflowStateJson(cwd, mode, selectors.skcSessionId);
1147
+ const existing = await readWorkflowStateJson(cwd, mode, selectors.skcSessionId, warning =>
1148
+ warnings.push(warning),
1149
+ );
1119
1150
  const envelope = { skill: mode, state: existing, storage_path: filePath };
1120
1151
  const manifest = getSkillManifest(mode);
1121
1152
  if (fields) {
@@ -1125,6 +1156,7 @@ async function handleRead(args: readonly string[], cwd: string): Promise<StateCo
1125
1156
  stdout: hasFlag(args, "--json")
1126
1157
  ? `${JSON.stringify(projected, null, 2)}\n`
1127
1158
  : renderStateMarkdown(mode, projected, manifest),
1159
+ ...warningStderr(),
1128
1160
  };
1129
1161
  }
1130
1162
  if (hasFlag(args, "--compact")) {
@@ -1134,6 +1166,7 @@ async function handleRead(args: readonly string[], cwd: string): Promise<StateCo
1134
1166
  stdout: hasFlag(args, "--json")
1135
1167
  ? `${JSON.stringify(compact, null, 2)}\n`
1136
1168
  : renderStateMarkdown(mode, envelope, manifest),
1169
+ ...warningStderr(),
1137
1170
  };
1138
1171
  }
1139
1172
  return {
@@ -1141,12 +1174,13 @@ async function handleRead(args: readonly string[], cwd: string): Promise<StateCo
1141
1174
  stdout: hasFlag(args, "--json")
1142
1175
  ? `${JSON.stringify(envelope, null, 2)}\n`
1143
1176
  : renderStateMarkdown(mode, envelope, manifest),
1177
+ ...warningStderr(),
1144
1178
  };
1145
1179
  }
1146
1180
  const filePath = activeStateFile(cwd, selectors.skcSessionId);
1147
- const existingRaw = await readJsonValue(filePath);
1181
+ const existingRaw = await readJsonValue(filePath, warning => warnings.push(warning));
1148
1182
  const existing = isPlainObject(existingRaw) ? existingRaw : null;
1149
- return { status: 0, stdout: `${JSON.stringify(existing ?? {}, null, 2)}\n` };
1183
+ return { status: 0, stdout: `${JSON.stringify(existing ?? {}, null, 2)}\n`, ...warningStderr() };
1150
1184
  }
1151
1185
 
1152
1186
  async function handleStatus(args: readonly string[], cwd: string): Promise<StateCommandResult> {
@@ -1159,7 +1193,8 @@ async function handleStatus(args: readonly string[], cwd: string): Promise<State
1159
1193
  );
1160
1194
  }
1161
1195
  const filePath = modeStateFile(cwd, mode, selectors.skcSessionId);
1162
- const existing = await readWorkflowStateJson(cwd, mode, selectors.skcSessionId);
1196
+ const warnings: string[] = [];
1197
+ const existing = await readWorkflowStateJson(cwd, mode, selectors.skcSessionId, warning => warnings.push(warning));
1163
1198
  const summary = buildStateStatusSummary(
1164
1199
  mode,
1165
1200
  { skill: mode, state: existing, storage_path: filePath },
@@ -1169,6 +1204,7 @@ async function handleStatus(args: readonly string[], cwd: string): Promise<State
1169
1204
  return {
1170
1205
  status: 0,
1171
1206
  stdout: hasFlag(args, "--json") ? `${JSON.stringify(summary, null, 2)}\n` : renderStateStatusLine(summary),
1207
+ ...(warnings.length ? { stderr: warnings.map(warning => `${warning}\n`).join("") } : {}),
1172
1208
  };
1173
1209
  }
1174
1210
 
@@ -1603,7 +1639,7 @@ async function handleHandoffUnlocked(args: readonly string[], cwd: string): Prom
1603
1639
  toPhase: "handoff",
1604
1640
  });
1605
1641
  await updateWorkflowTransactionJournal(cwd, sessionId, mutationId, { steps: ["caller-mode-state"] });
1606
- if (callerWrite.warning) process.stderr.write(`${callerWrite.warning}\n`);
1642
+ if (callerWrite.warning) emitStateWarning(callerWrite.warning);
1607
1643
  const stampedCallerReceipt = isPlainObject(callerWrite.stamped.receipt) ? callerWrite.stamped.receipt : {};
1608
1644
  await syncSkillActiveState({
1609
1645
  cwd,
@@ -1646,6 +1682,7 @@ async function handleHandoffUnlocked(args: readonly string[], cwd: string): Prom
1646
1682
  active_state: activeStateFile(cwd, sessionId),
1647
1683
  },
1648
1684
  }),
1685
+ ...(callerWrite.warning ? { stderr: `${callerWrite.warning}\n` } : {}),
1649
1686
  };
1650
1687
  }
1651
1688
 
@@ -1746,7 +1783,7 @@ async function handleHandoffUnlocked(args: readonly string[], cwd: string): Prom
1746
1783
  );
1747
1784
  const stampedCallerReceipt = isPlainObject(callerWrite.stamped.receipt) ? callerWrite.stamped.receipt : {};
1748
1785
  const stampedCalleeReceipt = isPlainObject(calleeWrite.stamped.receipt) ? calleeWrite.stamped.receipt : {};
1749
- for (const warning of warnings) process.stderr.write(`${warning}\n`);
1786
+ for (const warning of warnings) emitStateWarning(warning);
1750
1787
  if (process.env.SKC_STATE_HANDOFF_FAIL_AFTER_CALLER === mutationId) {
1751
1788
  throw new StateCommandError(1, `injected handoff failure after caller write for ${mutationId}`);
1752
1789
  }
@@ -1818,6 +1855,7 @@ async function handleHandoffUnlocked(args: readonly string[], cwd: string): Prom
1818
1855
  active_state: activeStateFile(cwd, sessionId),
1819
1856
  },
1820
1857
  }),
1858
+ ...(warnings.length ? { stderr: warnings.map(warning => `${warning}\n`).join("") } : {}),
1821
1859
  };
1822
1860
  }
1823
1861