@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
@@ -13,6 +13,7 @@
13
13
  import * as fs from "node:fs";
14
14
  import * as os from "node:os";
15
15
  import * as path from "node:path";
16
+ import * as util from "node:util";
16
17
  import {
17
18
  getAgentDbPath,
18
19
  getAgentDir,
@@ -43,6 +44,7 @@ import {
43
44
  atomicYamlPathHash,
44
45
  type CasReceipt,
45
46
  deleteByPath,
47
+ enqueueAtomicYamlOperation,
46
48
  reserveAtomicYamlUpdateSlot,
47
49
  setByPath,
48
50
  } from "./atomic-yaml-patch";
@@ -364,6 +366,7 @@ export class Settings implements NotificationSettingsReader {
364
366
  /** Pending debounced ordinary save; its queue slot is reserved immediately. */
365
367
  #saveTimer?: NodeJS.Timeout;
366
368
  #savePromise?: Promise<void>;
369
+ #changeListeners = new Set<(path: SettingPath) => void>();
367
370
  #pendingSaveSlot?: PendingSaveSlot;
368
371
 
369
372
  /** Legacy fallback migration warnings emitted once per settings instance. */
@@ -374,6 +377,8 @@ export class Settings implements NotificationSettingsReader {
374
377
  /** A newer config schema must never be rewritten by legacy migrations. */
375
378
  #futureSchemaVersion = false;
376
379
  #hasMalformedConfigRoot = false;
380
+ /** YAML syntax was unrecoverable, so the loaded defaults are read-only until config.yml is repaired. */
381
+ #hasRecoveredConfigSyntax = false;
377
382
  #hasInvalidNotificationConfiguration = false;
378
383
  #notificationValidationGeneration = 0;
379
384
  /** Notification subtree fingerprint from the last raw durable config read. */
@@ -385,7 +390,7 @@ export class Settings implements NotificationSettingsReader {
385
390
  private constructor(options: SettingsOptions = {}) {
386
391
  this.#cwd = path.normalize(options.cwd ?? getProjectDir());
387
392
  this.#agentDir = path.normalize(options.agentDir ?? getAgentDir());
388
- this.#configPath = options.inMemory ? null : path.join(this.#agentDir, "config.yml");
393
+ this.#configPath = options.inMemory ? null : path.resolve(this.#agentDir, "config.yml");
389
394
  this.#persist = !options.inMemory;
390
395
 
391
396
  if (options.overrides) {
@@ -523,6 +528,16 @@ export class Settings implements NotificationSettingsReader {
523
528
  return structuredClone(this.#schemaReport);
524
529
  }
525
530
 
531
+ onChanged(listener: (path: SettingPath) => void): () => void {
532
+ this.#changeListeners.add(listener);
533
+ return () => this.#changeListeners.delete(listener);
534
+ }
535
+
536
+ /** Whether durable settings mutations are permitted for the loaded configuration. */
537
+ canWriteDurableConfig(): boolean {
538
+ return !this.#persist || !this.#hasRecoveredConfigSyntax;
539
+ }
540
+
526
541
  /**
527
542
  * Set a setting value (sync).
528
543
  * Updates global settings and reserves its background persistence slot before
@@ -533,6 +548,7 @@ export class Settings implements NotificationSettingsReader {
533
548
  this.unset(path);
534
549
  return;
535
550
  }
551
+ this.#assertDurableConfigWritable();
536
552
  this.#set(path, value, true);
537
553
  }
538
554
 
@@ -556,6 +572,7 @@ export class Settings implements NotificationSettingsReader {
556
572
 
557
573
  const hook = SETTING_HOOKS[path];
558
574
  if (hook) hook(value, prev);
575
+ for (const listener of this.#changeListeners) listener(path);
559
576
  }
560
577
 
561
578
  /**
@@ -563,6 +580,7 @@ export class Settings implements NotificationSettingsReader {
563
580
  * `undefined` value. Defaults/project settings become visible immediately.
564
581
  */
565
582
  unset<P extends SettingPath>(path: P): void {
583
+ this.#assertDurableConfigWritable();
566
584
  const prev = this.get(path);
567
585
  const patch: SettingsPatch = {
568
586
  path,
@@ -580,6 +598,7 @@ export class Settings implements NotificationSettingsReader {
580
598
 
581
599
  const hook = SETTING_HOOKS[path];
582
600
  if (hook) hook(this.get(path), prev);
601
+ for (const listener of this.#changeListeners) listener(path);
583
602
  }
584
603
 
585
604
  /**
@@ -587,6 +606,7 @@ export class Settings implements NotificationSettingsReader {
587
606
  * {@link set}, canonical state and hooks change only after the rename succeeds.
588
607
  */
589
608
  async commitAtomicBatch(patches: readonly SettingsAtomicPatch[]): Promise<CasReceipt> {
609
+ this.#assertDurableConfigWritable();
590
610
  if (!this.#persist || !this.#configPath) {
591
611
  const notificationValidationGuard = this.#notificationValidationRestoreGuard();
592
612
  const changes = new Map<string, { before: unknown; beforeHash: string; afterHash: string }>();
@@ -687,13 +707,16 @@ export class Settings implements NotificationSettingsReader {
687
707
  }));
688
708
  for (const entry of revisions) this.#pathRevisions.set(entry.patch.path, entry.revision);
689
709
 
710
+ const commit = applyAtomicYamlPatches(this.#configPath, durablePatches, {
711
+ validateRoot: (root, currentPatches) =>
712
+ this.#rejectAtomicNotificationRepairForMalformedRoot(currentPatches, root),
713
+ onRestored: restoredPatches =>
714
+ this.#applyRestoredDurableBatch(revisions, restoredPatches, notificationValidationGuard),
715
+ });
716
+ const failureRefresh = this.#reserveAtomicFailureRefresh(commit);
690
717
  try {
691
- const receipt = await applyAtomicYamlPatches(this.#configPath, durablePatches, {
692
- validateRoot: (root, currentPatches) =>
693
- this.#rejectAtomicNotificationRepairForMalformedRoot(currentPatches, root),
694
- onRestored: restoredPatches =>
695
- this.#applyRestoredDurableBatch(revisions, restoredPatches, notificationValidationGuard),
696
- });
718
+ const receipt = await commit;
719
+ await failureRefresh;
697
720
  const appliedNotificationMutation = this.#applyDurableBatch(revisions);
698
721
  this.#recordNotificationValidationBatchApply(notificationValidationGuard, appliedNotificationMutation);
699
722
  return receipt;
@@ -704,6 +727,7 @@ export class Settings implements NotificationSettingsReader {
704
727
  else this.#pathRevisions.set(entry.patch.path, entry.previousRevision);
705
728
  }
706
729
  }
730
+ await failureRefresh;
707
731
  if (this.#modified.size > 0 && !this.#pendingSaveSlot) this.#queueSave();
708
732
  throw error;
709
733
  }
@@ -715,6 +739,7 @@ export class Settings implements NotificationSettingsReader {
715
739
  current: Readonly<RawSettings>,
716
740
  ) => Promise<readonly SettingsAtomicPatch[]> | readonly SettingsAtomicPatch[],
717
741
  ): Promise<CasReceipt> {
742
+ this.#assertDurableConfigWritable();
718
743
  if (!this.#persist || !this.#configPath) {
719
744
  const patches = await buildPatches(structuredClone(this.#global));
720
745
  return this.commitAtomicBatch(patches);
@@ -723,38 +748,41 @@ export class Settings implements NotificationSettingsReader {
723
748
  this.#releasePendingSaveSlot();
724
749
  let revisions: DurableBatchRevision[] = [];
725
750
  const notificationValidationGuard = this.#notificationValidationRestoreGuard();
751
+ const commit = applyAtomicYamlPatchesWithCurrent(
752
+ this.#configPath,
753
+ async current => {
754
+ const patches = await buildPatches(structuredClone(current));
755
+ const durablePatches: AtomicYamlPatch[] = patches.map(patch => {
756
+ if (!isAtomicSettingsPath(patch.path)) {
757
+ throw new Error(`Unknown setting path for atomic batch: ${patch.path}`);
758
+ }
759
+ if (patch.op === "unset") return { path: patch.path, op: "unset" };
760
+ if (patch.value === undefined) {
761
+ throw new TypeError(
762
+ `Settings set patch for ${patch.path} cannot carry undefined; use unset instead.`,
763
+ );
764
+ }
765
+ return { path: patch.path, op: "set", value: structuredClone(patch.value) };
766
+ });
767
+ revisions = durablePatches.map(patch => ({
768
+ patch,
769
+ revision: ++this.#nextRevision,
770
+ previousRevision: this.#pathRevisions.get(patch.path),
771
+ }));
772
+ for (const entry of revisions) this.#pathRevisions.set(entry.patch.path, entry.revision);
773
+ return durablePatches;
774
+ },
775
+ {
776
+ validateRoot: (root, currentPatches) =>
777
+ this.#rejectAtomicNotificationRepairForMalformedRoot(currentPatches, root),
778
+ onRestored: restoredPatches =>
779
+ this.#applyRestoredDurableBatch(revisions, restoredPatches, notificationValidationGuard),
780
+ },
781
+ );
782
+ const failureRefresh = this.#reserveAtomicFailureRefresh(commit);
726
783
  try {
727
- const receipt = await applyAtomicYamlPatchesWithCurrent(
728
- this.#configPath,
729
- async current => {
730
- const patches = await buildPatches(structuredClone(current));
731
- const durablePatches: AtomicYamlPatch[] = patches.map(patch => {
732
- if (!isAtomicSettingsPath(patch.path)) {
733
- throw new Error(`Unknown setting path for atomic batch: ${patch.path}`);
734
- }
735
- if (patch.op === "unset") return { path: patch.path, op: "unset" };
736
- if (patch.value === undefined) {
737
- throw new TypeError(
738
- `Settings set patch for ${patch.path} cannot carry undefined; use unset instead.`,
739
- );
740
- }
741
- return { path: patch.path, op: "set", value: structuredClone(patch.value) };
742
- });
743
- revisions = durablePatches.map(patch => ({
744
- patch,
745
- revision: ++this.#nextRevision,
746
- previousRevision: this.#pathRevisions.get(patch.path),
747
- }));
748
- for (const entry of revisions) this.#pathRevisions.set(entry.patch.path, entry.revision);
749
- return durablePatches;
750
- },
751
- {
752
- validateRoot: (root, currentPatches) =>
753
- this.#rejectAtomicNotificationRepairForMalformedRoot(currentPatches, root),
754
- onRestored: restoredPatches =>
755
- this.#applyRestoredDurableBatch(revisions, restoredPatches, notificationValidationGuard),
756
- },
757
- );
784
+ const receipt = await commit;
785
+ await failureRefresh;
758
786
  const appliedNotificationMutation = this.#applyDurableBatch(revisions);
759
787
  this.#recordNotificationValidationBatchApply(notificationValidationGuard, appliedNotificationMutation);
760
788
  return receipt;
@@ -765,6 +793,7 @@ export class Settings implements NotificationSettingsReader {
765
793
  else this.#pathRevisions.set(entry.patch.path, entry.previousRevision);
766
794
  }
767
795
  }
796
+ await failureRefresh;
768
797
  if (this.#modified.size > 0 && !this.#pendingSaveSlot) this.#queueSave();
769
798
  throw error;
770
799
  }
@@ -819,6 +848,15 @@ export class Settings implements NotificationSettingsReader {
819
848
  }
820
849
  }
821
850
  await this.#refreshDurableSettings();
851
+ if (this.#modified.size > 0 && !this.#pendingSaveSlot) {
852
+ this.#queueSave();
853
+ this.#releasePendingSaveSlot();
854
+ try {
855
+ await this.#savePromise;
856
+ } catch {
857
+ // Keep dirty state for a later explicit flush or mutation.
858
+ }
859
+ }
822
860
  }
823
861
 
824
862
  /** Like {@link flush}, but reports a durable save failure to the caller. */
@@ -826,8 +864,20 @@ export class Settings implements NotificationSettingsReader {
826
864
  this.#releasePendingSaveSlot();
827
865
  if (this.#modified.size > 0 && !this.#pendingSaveSlot) this.#queueSave();
828
866
  this.#releasePendingSaveSlot();
829
- await this.#savePromise;
867
+ let saveError: unknown;
868
+ try {
869
+ await this.#savePromise;
870
+ } catch (error) {
871
+ saveError = error;
872
+ }
830
873
  await this.#refreshDurableSettings();
874
+ if (this.#modified.size > 0 && !this.#pendingSaveSlot) {
875
+ this.#queueSave();
876
+ this.#releasePendingSaveSlot();
877
+ await this.#savePromise;
878
+ return;
879
+ }
880
+ if (saveError !== undefined) throw saveError;
831
881
  }
832
882
 
833
883
  async cloneForCwd(cwd: string): Promise<Settings> {
@@ -841,15 +891,23 @@ export class Settings implements NotificationSettingsReader {
841
891
  inMemory: !this.#persist,
842
892
  });
843
893
  cloned.#storage = this.#storage;
894
+ cloned.#schemaReport = structuredClone(this.#schemaReport);
895
+ cloned.#schemaMigrationPending = this.#schemaMigrationPending;
844
896
  cloned.#futureSchemaVersion = this.#futureSchemaVersion;
845
-
897
+ cloned.#hasMalformedConfigRoot = this.#hasMalformedConfigRoot;
898
+ cloned.#hasRecoveredConfigSyntax = this.#hasRecoveredConfigSyntax;
899
+ cloned.#hasInvalidNotificationConfiguration = this.#hasInvalidNotificationConfiguration;
900
+ cloned.#notificationValidationGeneration = this.#notificationValidationGeneration;
846
901
  cloned.#global = structuredClone(this.#global);
847
902
  cloned.#rawNotificationConfig = structuredClone(this.#rawNotificationConfig);
848
903
  cloned.#durableRawNotificationConfig = structuredClone(this.#durableRawNotificationConfig);
849
904
  cloned.#durableNotificationFingerprint = this.#durableNotificationFingerprint;
850
905
  cloned.#project = this.#persist ? await cloned.#loadProjectSettings() : structuredClone(this.#project);
851
906
  cloned.#overrides = structuredClone(this.#overrides);
852
- await cloned.#normalizeAfterLoad();
907
+ if (cloned.#hasRecoveredConfigSyntax) {
908
+ cloned.#sanitizeModelSelectorRecords();
909
+ cloned.#rebuildMerged();
910
+ } else await cloned.#normalizeAfterLoad();
853
911
  cloned.#fireAllHooks();
854
912
  return cloned;
855
913
  }
@@ -941,6 +999,7 @@ export class Settings implements NotificationSettingsReader {
941
999
  }
942
1000
 
943
1001
  setGlobalModelRole(role: ModelRole | string, modelId: ModelSelectorValue | undefined): void {
1002
+ this.#assertDurableConfigWritable();
944
1003
  const revision = ++this.#nextRevision;
945
1004
  const patch: SettingsPatch = {
946
1005
  path: "modelRoles",
@@ -974,13 +1033,27 @@ export class Settings implements NotificationSettingsReader {
974
1033
  }
975
1034
 
976
1035
  #replaceGlobalWithDurable(current: RawSettings): void {
1036
+ const previous = new Map<SettingPath, unknown>();
1037
+ for (const settingPath of Object.keys(SETTINGS_SCHEMA) as SettingPath[]) {
1038
+ previous.set(settingPath, structuredClone(this.get(settingPath)));
1039
+ }
977
1040
  this.#global = current;
978
1041
  for (const patch of this.#pendingPatchesInGenerationOrder()) {
979
1042
  applySettingsPatch(this.#global, { ...patch, value: structuredClone(patch.value) });
980
- this.#applyNotificationMutationToRaw(patch.path, patch.value);
1043
+ if (this.#rawNotificationConfig !== undefined) {
1044
+ this.#applyNotificationMutationToRaw(patch.path, patch.value);
1045
+ }
981
1046
  }
982
1047
  this.#rebuildMerged();
983
1048
  this.#recomputeNotificationValidationFromRaw();
1049
+ for (const settingPath of Object.keys(SETTINGS_SCHEMA) as SettingPath[]) {
1050
+ const previousValue = previous.get(settingPath);
1051
+ const nextValue = this.get(settingPath);
1052
+ if (util.isDeepStrictEqual(previousValue, nextValue)) continue;
1053
+ const hook = SETTING_HOOKS[settingPath];
1054
+ if (hook) hook(nextValue, previousValue);
1055
+ for (const listener of this.#changeListeners) listener(settingPath);
1056
+ }
984
1057
  }
985
1058
  /**
986
1059
  * Set an agent model override while keeping any live runtime override aligned.
@@ -1074,60 +1147,97 @@ export class Settings implements NotificationSettingsReader {
1074
1147
  }
1075
1148
  }
1076
1149
 
1077
- async #loadYaml(filePath: string): Promise<RawSettings> {
1150
+ #resetYamlLoadState(): void {
1078
1151
  this.#hasMalformedConfigRoot = false;
1152
+ this.#hasRecoveredConfigSyntax = false;
1079
1153
  this.#hasInvalidNotificationConfiguration = false;
1154
+ this.#schemaReport = { issues: [], valid: true };
1155
+ this.#schemaMigrationPending = false;
1156
+ this.#futureSchemaVersion = false;
1080
1157
  this.#captureRawNotificationConfig({});
1081
- try {
1082
- const content = await Bun.file(filePath).text();
1083
- const parsed = YAML.parse(content);
1084
- if (parsed === undefined) return {};
1085
- if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
1086
- this.#hasMalformedConfigRoot = true;
1087
- this.#captureRawNotificationConfig(undefined);
1088
- return {};
1089
- }
1090
- const parsedRaw = parsed as RawSettings;
1091
- if (filePath === this.#configPath) this.#captureRawNotificationConfig(parsedRaw);
1092
- if (filePath === this.#configPath) {
1093
- try {
1094
- parseNotificationSettingsSnapshot(parsedRaw);
1095
- } catch (error) {
1096
- if (!(error instanceof Error) || error.message !== "skc_notify_daemon_invalid_configuration")
1097
- throw error;
1098
- this.#hasInvalidNotificationConfiguration = true;
1099
- }
1100
- }
1101
- this.#futureSchemaVersion =
1102
- filePath === this.#configPath &&
1103
- typeof parsedRaw.configSchemaVersion === "number" &&
1104
- parsedRaw.configSchemaVersion > CONFIG_SCHEMA_VERSION;
1158
+ }
1105
1159
 
1106
- const configSchemaVersion = parsedRaw.configSchemaVersion;
1107
- if (
1108
- filePath === this.#configPath &&
1109
- (typeof configSchemaVersion !== "number" || configSchemaVersion < CONFIG_SCHEMA_VERSION)
1110
- ) {
1111
- this.#schemaMigrationPending = true;
1112
- }
1113
- const migrated = this.#migrateRawSettings(parsedRaw);
1114
- const reconciled = reconcileSettingsSchema(migrated);
1115
- if (typeof configSchemaVersion === "number" && configSchemaVersion > CONFIG_SCHEMA_VERSION) {
1116
- reconciled.report.issues.push({
1117
- path: "configSchemaVersion",
1118
- kind: "pending-migration",
1119
- detail: `Configuration requires schema version ${configSchemaVersion}.`,
1120
- });
1121
- }
1122
- this.#schemaReport = reconciled.report;
1123
- return reconciled.settings;
1160
+ async #loadYaml(filePath: string): Promise<RawSettings> {
1161
+ let content: string;
1162
+ try {
1163
+ content = await Bun.file(filePath).text();
1124
1164
  } catch (error) {
1125
1165
  if (isEnoent(error)) {
1126
- this.#captureRawNotificationConfig({});
1166
+ this.#resetYamlLoadState();
1127
1167
  return {};
1128
1168
  }
1129
1169
  throw error;
1130
1170
  }
1171
+ this.#resetYamlLoadState();
1172
+ if (content.trim() === "") return {};
1173
+ let parsed: unknown;
1174
+ try {
1175
+ parsed = YAML.parse(content);
1176
+ } catch {
1177
+ this.#hasRecoveredConfigSyntax = true;
1178
+ this.#hasMalformedConfigRoot = true;
1179
+ this.#schemaReport = {
1180
+ valid: false,
1181
+ issues: [
1182
+ {
1183
+ path: "config.yml",
1184
+ kind: "invalid",
1185
+ detail: "Configuration YAML syntax is invalid; repair config.yml before changing settings.",
1186
+ },
1187
+ ],
1188
+ };
1189
+ this.#captureRawNotificationConfig(undefined);
1190
+ return {};
1191
+ }
1192
+ if (parsed === undefined) return {};
1193
+ if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
1194
+ this.#hasMalformedConfigRoot = true;
1195
+ this.#schemaReport = {
1196
+ valid: false,
1197
+ issues: [
1198
+ {
1199
+ path: "config.yml",
1200
+ kind: "invalid",
1201
+ detail: "Configuration root must be a YAML mapping.",
1202
+ },
1203
+ ],
1204
+ };
1205
+ this.#captureRawNotificationConfig(undefined);
1206
+ return {};
1207
+ }
1208
+ const parsedRaw = parsed as RawSettings;
1209
+ if (filePath === this.#configPath) this.#captureRawNotificationConfig(parsedRaw);
1210
+ if (filePath === this.#configPath) {
1211
+ try {
1212
+ parseNotificationSettingsSnapshot(parsedRaw);
1213
+ } catch (error) {
1214
+ if (!(error instanceof Error) || error.message !== "skc_notify_daemon_invalid_configuration") throw error;
1215
+ this.#hasInvalidNotificationConfiguration = true;
1216
+ }
1217
+ }
1218
+ this.#futureSchemaVersion =
1219
+ filePath === this.#configPath &&
1220
+ typeof parsedRaw.configSchemaVersion === "number" &&
1221
+ parsedRaw.configSchemaVersion > CONFIG_SCHEMA_VERSION;
1222
+
1223
+ const configSchemaVersion = parsedRaw.configSchemaVersion;
1224
+ if (
1225
+ filePath === this.#configPath &&
1226
+ (typeof configSchemaVersion !== "number" || configSchemaVersion < CONFIG_SCHEMA_VERSION)
1227
+ ) {
1228
+ this.#schemaMigrationPending = true;
1229
+ }
1230
+ const migrated = this.#migrateRawSettings(parsedRaw);
1231
+ const reconciled = reconcileSettingsSchema(migrated);
1232
+ if (typeof configSchemaVersion === "number" && configSchemaVersion > CONFIG_SCHEMA_VERSION) {
1233
+ reconciled.report.issues.push({
1234
+ path: "configSchemaVersion",
1235
+ kind: "pending-migration",
1236
+ detail: `Configuration requires schema version ${configSchemaVersion}.`,
1237
+ });
1238
+ }
1239
+ this.#schemaReport = reconciled.report;
1240
+ return reconciled.settings;
1131
1241
  }
1132
1242
 
1133
1243
  async #loadProjectSettings(): Promise<RawSettings> {
@@ -1520,7 +1630,7 @@ export class Settings implements NotificationSettingsReader {
1520
1630
  // ─────────────────────────────────────────────────────────────────────────
1521
1631
 
1522
1632
  #queueSave(): void {
1523
- if (!this.#persist || !this.#configPath) return;
1633
+ if (!this.#persist || !this.#configPath || this.#hasRecoveredConfigSyntax) return;
1524
1634
 
1525
1635
  const currentSlot = this.#pendingSaveSlot;
1526
1636
  if (currentSlot && !currentSlot.captured && !currentSlot.released) {
@@ -1587,25 +1697,33 @@ export class Settings implements NotificationSettingsReader {
1587
1697
  this.#recomputeNotificationValidationFromRaw();
1588
1698
  },
1589
1699
  };
1590
- }).then(() => undefined);
1591
- this.#savePromise = save;
1592
- void save.catch(error => {
1593
- logger.warn("Settings: background save failed", { error: String(error) });
1594
- for (const patch of captured) {
1595
- const key = settingsPatchKey(patch);
1596
- if (this.#modified.get(key)?.generation === patch.generation) this.#modified.set(key, patch);
1597
- }
1598
- if (durableBeforeWrite) {
1599
- this.#global = durableBeforeWrite;
1600
- this.#captureRawNotificationConfig(durableBeforeWrite);
1601
- for (const patch of this.#pendingPatchesInGenerationOrder()) {
1602
- applySettingsPatch(this.#global, { ...patch, value: structuredClone(patch.value) });
1603
- this.#applyNotificationMutationToRaw(patch.path, patch.value);
1700
+ })
1701
+ .then(() => undefined)
1702
+ .catch(async error => {
1703
+ logger.warn("Settings: background save failed", { error: String(error) });
1704
+ for (const patch of captured) {
1705
+ const key = settingsPatchKey(patch);
1706
+ if (this.#modified.get(key)?.generation === patch.generation) this.#modified.set(key, patch);
1604
1707
  }
1605
- this.#rebuildMerged();
1606
- this.#recomputeNotificationValidationFromRaw();
1607
- }
1608
- });
1708
+ if (durableBeforeWrite) {
1709
+ this.#global = durableBeforeWrite;
1710
+ this.#captureRawNotificationConfig(durableBeforeWrite);
1711
+ for (const patch of this.#pendingPatchesInGenerationOrder()) {
1712
+ applySettingsPatch(this.#global, { ...patch, value: structuredClone(patch.value) });
1713
+ this.#applyNotificationMutationToRaw(patch.path, patch.value);
1714
+ }
1715
+ this.#rebuildMerged();
1716
+ this.#recomputeNotificationValidationFromRaw();
1717
+ }
1718
+ try {
1719
+ await this.#refreshDurableSettings();
1720
+ } catch (refreshError) {
1721
+ logger.warn("Settings: refresh after background save failure failed", { error: String(refreshError) });
1722
+ }
1723
+ throw error;
1724
+ });
1725
+ this.#savePromise = save;
1726
+ void save.catch(() => {});
1609
1727
  this.#armSaveTimer(slot);
1610
1728
  }
1611
1729
 
@@ -1709,13 +1827,41 @@ export class Settings implements NotificationSettingsReader {
1709
1827
  return applicable.some(patch => isNotificationSettingsPath(patch.path));
1710
1828
  }
1711
1829
 
1712
- async #refreshDurableSettings(): Promise<void> {
1713
- if (!this.#persist || !this.#configPath) return;
1830
+ #reserveAtomicFailureRefresh(commit: Promise<unknown>): Promise<void> {
1831
+ if (!this.#persist || !this.#configPath) return Promise.resolve();
1832
+ return enqueueAtomicYamlOperation(this.#configPath, async canonicalPath => {
1833
+ try {
1834
+ await commit;
1835
+ return;
1836
+ } catch {
1837
+ // The original commit error remains authoritative. Recovery failures
1838
+ // are diagnostic only and must not replace it.
1839
+ }
1840
+ try {
1841
+ await this.#refreshDurableSettingsUnderQueue(canonicalPath);
1842
+ } catch (refreshError) {
1843
+ logger.warn("Settings: refresh after atomic batch failure failed", { error: String(refreshError) });
1844
+ }
1845
+ });
1846
+ }
1847
+ async #refreshDurableSettingsUnderQueue(canonicalPath: string): Promise<void> {
1714
1848
  const previousFingerprint = this.#durableNotificationFingerprint;
1715
- const current = await this.#loadYaml(this.#configPath);
1849
+ const current = await this.#loadYaml(canonicalPath);
1716
1850
  if (previousFingerprint !== this.#durableNotificationFingerprint) this.#notificationValidationGeneration++;
1717
1851
  this.#replaceGlobalWithDurable(current);
1718
1852
  }
1853
+ async #refreshDurableSettings(): Promise<void> {
1854
+ if (!this.#persist || !this.#configPath) return;
1855
+ await enqueueAtomicYamlOperation(this.#configPath, canonicalPath =>
1856
+ this.#refreshDurableSettingsUnderQueue(canonicalPath),
1857
+ );
1858
+ }
1859
+ #assertDurableConfigWritable(): void {
1860
+ if (this.canWriteDurableConfig()) return;
1861
+ throw new Error(
1862
+ "Cannot change settings while config.yml has invalid YAML syntax. Repair config.yml and reload settings.",
1863
+ );
1864
+ }
1719
1865
 
1720
1866
  // ─────────────────────────────────────────────────────────────────────────
1721
1867
  // Utilities