@cjhyy/code-shell-core 0.5.0-rc.2 → 0.6.0-rc.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 (387) hide show
  1. package/README.md +13 -10
  2. package/dist/agent/agent-definition-registry.d.ts +3 -0
  3. package/dist/agent/agent-definition-registry.js +3 -1
  4. package/dist/agent/agent-definition.d.ts +15 -0
  5. package/dist/agent/agent-definition.js +27 -3
  6. package/dist/arena/context/context-tools.js +47 -4
  7. package/dist/arena/ledger.js +9 -1
  8. package/dist/arena/strategies/utils.d.ts +1 -4
  9. package/dist/arena/strategies/utils.js +7 -84
  10. package/dist/automation/cron-expr.d.ts +10 -0
  11. package/dist/automation/cron-expr.js +23 -5
  12. package/dist/automation/runner.d.ts +21 -4
  13. package/dist/automation/runner.js +48 -12
  14. package/dist/automation/scheduler.d.ts +38 -0
  15. package/dist/automation/scheduler.js +89 -12
  16. package/dist/automation/store.js +5 -2
  17. package/dist/capability-control/disabled-lists.d.ts +25 -0
  18. package/dist/capability-control/disabled-lists.js +57 -0
  19. package/dist/capability-control/overlay.d.ts +15 -0
  20. package/dist/capability-control/overlay.js +27 -0
  21. package/dist/cc-orchestrator/agent-adapter.d.ts +50 -0
  22. package/dist/cc-orchestrator/agent-adapter.js +133 -0
  23. package/dist/cc-orchestrator/cc-capability.d.ts +19 -0
  24. package/dist/cc-orchestrator/cc-capability.js +53 -0
  25. package/dist/cc-orchestrator/codex-session-discovery.d.ts +24 -0
  26. package/dist/cc-orchestrator/codex-session-discovery.js +191 -0
  27. package/dist/cc-orchestrator/codex-session-history.d.ts +25 -0
  28. package/dist/cc-orchestrator/codex-session-history.js +187 -0
  29. package/dist/cc-orchestrator/external-agent-changes.d.ts +19 -0
  30. package/dist/cc-orchestrator/external-agent-changes.js +214 -0
  31. package/dist/cc-orchestrator/external-agent-driver.d.ts +18 -0
  32. package/dist/cc-orchestrator/external-agent-driver.js +69 -0
  33. package/dist/cc-orchestrator/index.d.ts +8 -0
  34. package/dist/cc-orchestrator/index.js +8 -0
  35. package/dist/cc-orchestrator/relevance-judge.d.ts +15 -0
  36. package/dist/cc-orchestrator/relevance-judge.js +29 -0
  37. package/dist/cc-orchestrator/session-discovery.d.ts +46 -0
  38. package/dist/cc-orchestrator/session-discovery.js +125 -0
  39. package/dist/cc-orchestrator/session-history.d.ts +19 -0
  40. package/dist/cc-orchestrator/session-history.js +67 -0
  41. package/dist/cli/agent-server-stdio.d.ts +15 -1
  42. package/dist/cli/agent-server-stdio.js +115 -17
  43. package/dist/cli/agent-server-tcp.js +40 -26
  44. package/dist/context/compaction.d.ts +56 -0
  45. package/dist/context/compaction.js +186 -0
  46. package/dist/context/manager.js +25 -4
  47. package/dist/cost-tracker.js +5 -61
  48. package/dist/credentials/cipher.d.ts +49 -0
  49. package/dist/credentials/cipher.js +45 -0
  50. package/dist/credentials/cookie-jar.d.ts +24 -0
  51. package/dist/credentials/cookie-jar.js +40 -0
  52. package/dist/credentials/index.d.ts +7 -0
  53. package/dist/credentials/index.js +5 -0
  54. package/dist/credentials/inject-credential-tool.d.ts +20 -0
  55. package/dist/credentials/inject-credential-tool.js +130 -0
  56. package/dist/credentials/store.d.ts +72 -0
  57. package/dist/credentials/store.js +184 -0
  58. package/dist/credentials/types.d.ts +56 -0
  59. package/dist/credentials/use-credential-tool.d.ts +29 -0
  60. package/dist/credentials/use-credential-tool.js +205 -0
  61. package/dist/credentials/use-gate.d.ts +56 -0
  62. package/dist/credentials/use-gate.js +52 -0
  63. package/dist/data/model-metadata.d.ts +77 -0
  64. package/dist/data/model-metadata.js +56 -0
  65. package/dist/data/model-metadata.json +216 -0
  66. package/dist/data/openrouter-models.d.ts +18 -7
  67. package/dist/data/openrouter-models.js +35 -8
  68. package/dist/engine/aux-key.d.ts +10 -0
  69. package/dist/engine/aux-key.js +11 -0
  70. package/dist/engine/dynamic-tool-defs.d.ts +19 -0
  71. package/dist/engine/dynamic-tool-defs.js +36 -0
  72. package/dist/engine/engine.d.ts +267 -138
  73. package/dist/engine/engine.js +957 -191
  74. package/dist/engine/friendly-error.d.ts +18 -0
  75. package/dist/engine/friendly-error.js +63 -0
  76. package/dist/engine/goal.d.ts +145 -0
  77. package/dist/engine/goal.js +144 -0
  78. package/dist/engine/image-policy.d.ts +13 -0
  79. package/dist/engine/image-policy.js +24 -0
  80. package/dist/engine/model-connections-pool.d.ts +17 -0
  81. package/dist/engine/model-connections-pool.js +66 -0
  82. package/dist/engine/model-facade.d.ts +10 -0
  83. package/dist/engine/model-facade.js +25 -0
  84. package/dist/engine/patch-orphaned-tools.js +3 -0
  85. package/dist/engine/resolve-llm-config.d.ts +16 -0
  86. package/dist/engine/resolve-llm-config.js +44 -0
  87. package/dist/engine/runtime.js +2 -1
  88. package/dist/engine/sandbox-cache-key.d.ts +10 -0
  89. package/dist/engine/sandbox-cache-key.js +9 -0
  90. package/dist/engine/sandbox-config.d.ts +31 -0
  91. package/dist/engine/sandbox-config.js +38 -0
  92. package/dist/engine/session-usage.d.ts +19 -0
  93. package/dist/engine/session-usage.js +25 -0
  94. package/dist/engine/steer-queue.d.ts +32 -0
  95. package/dist/engine/steer-queue.js +26 -0
  96. package/dist/engine/streaming-tool-queue.d.ts +12 -0
  97. package/dist/engine/streaming-tool-queue.js +50 -10
  98. package/dist/engine/turn-loop.d.ts +67 -3
  99. package/dist/engine/turn-loop.js +292 -41
  100. package/dist/engine/types.d.ts +175 -0
  101. package/dist/engine/types.js +13 -0
  102. package/dist/external-agents/config.d.ts +2 -0
  103. package/dist/external-agents/config.js +15 -0
  104. package/dist/external-agents/types.d.ts +31 -0
  105. package/dist/external-agents/types.js +1 -0
  106. package/dist/git/utils.js +11 -6
  107. package/dist/git/worktree.d.ts +48 -0
  108. package/dist/git/worktree.js +86 -10
  109. package/dist/hooks/goal-stop-hook.d.ts +28 -0
  110. package/dist/hooks/goal-stop-hook.js +186 -9
  111. package/dist/hooks/registry.d.ts +8 -0
  112. package/dist/hooks/registry.js +19 -0
  113. package/dist/hooks/shell-runner.js +10 -21
  114. package/dist/index.d.ts +49 -6
  115. package/dist/index.js +60 -4
  116. package/dist/llm/capabilities/param-specs.d.ts +14 -0
  117. package/dist/llm/capabilities/param-specs.js +62 -0
  118. package/dist/llm/capabilities/rules.js +4 -0
  119. package/dist/llm/capabilities/types.d.ts +10 -0
  120. package/dist/llm/client-base.d.ts +28 -1
  121. package/dist/llm/client-base.js +119 -13
  122. package/dist/llm/model-cache.js +4 -2
  123. package/dist/llm/model-pool.d.ts +14 -0
  124. package/dist/llm/model-pool.js +13 -0
  125. package/dist/llm/provider-auth.d.ts +41 -0
  126. package/dist/llm/provider-auth.js +76 -0
  127. package/dist/llm/provider-catalog.d.ts +4 -0
  128. package/dist/llm/providers/anthropic.js +64 -14
  129. package/dist/llm/providers/openai.d.ts +36 -0
  130. package/dist/llm/providers/openai.js +257 -51
  131. package/dist/llm/reasoning-setting.d.ts +3 -3
  132. package/dist/llm/reasoning-setting.js +9 -1
  133. package/dist/llm/stream-watchdog.js +5 -1
  134. package/dist/llm/token-counter.js +9 -2
  135. package/dist/llm/types.d.ts +4 -0
  136. package/dist/logging/sanitize-messages.js +22 -0
  137. package/dist/lsp/manager.d.ts +1 -1
  138. package/dist/lsp/manager.js +40 -10
  139. package/dist/model-catalog/builtin.d.ts +12 -0
  140. package/dist/model-catalog/builtin.js +407 -0
  141. package/dist/model-catalog/gen-connections.d.ts +20 -0
  142. package/dist/model-catalog/gen-connections.js +28 -0
  143. package/dist/model-catalog/index.d.ts +41 -0
  144. package/dist/model-catalog/index.js +90 -0
  145. package/dist/model-catalog/params.d.ts +20 -0
  146. package/dist/model-catalog/params.js +45 -0
  147. package/dist/model-catalog/resolve.d.ts +48 -0
  148. package/dist/model-catalog/resolve.js +33 -0
  149. package/dist/model-catalog/save-entry.d.ts +32 -0
  150. package/dist/model-catalog/save-entry.js +104 -0
  151. package/dist/model-catalog/types.d.ts +561 -0
  152. package/dist/model-catalog/types.js +93 -0
  153. package/dist/model-catalog/upsert.d.ts +9 -0
  154. package/dist/model-catalog/upsert.js +8 -0
  155. package/dist/onboarding.d.ts +12 -82
  156. package/dist/onboarding.js +61 -326
  157. package/dist/plugins/gitOps.d.ts +19 -0
  158. package/dist/plugins/gitOps.js +73 -4
  159. package/dist/plugins/installer/checkUpdate.d.ts +16 -0
  160. package/dist/plugins/installer/checkUpdate.js +32 -0
  161. package/dist/plugins/installer/codex/convertCommands.d.ts +19 -0
  162. package/dist/plugins/installer/codex/convertCommands.js +46 -0
  163. package/dist/plugins/installer/codex/convertMcp.d.ts +5 -2
  164. package/dist/plugins/installer/codex/convertMcp.js +45 -5
  165. package/dist/plugins/installer/install.js +25 -1
  166. package/dist/plugins/installer/installFromArchive.d.ts +43 -0
  167. package/dist/plugins/installer/installFromArchive.js +134 -0
  168. package/dist/plugins/installer/installFromSource.js +8 -2
  169. package/dist/plugins/installer/loadPluginAgents.js +6 -2
  170. package/dist/plugins/installer/loadPluginMcp.d.ts +9 -2
  171. package/dist/plugins/installer/loadPluginMcp.js +35 -2
  172. package/dist/plugins/installer/pruneDisabled.d.ts +24 -0
  173. package/dist/plugins/installer/pruneDisabled.js +73 -0
  174. package/dist/plugins/installer/types.d.ts +5 -2
  175. package/dist/plugins/installer/types.js +1 -0
  176. package/dist/plugins/installer/uninstall.js +4 -0
  177. package/dist/plugins/installer/unzip.d.ts +14 -0
  178. package/dist/plugins/installer/unzip.js +82 -0
  179. package/dist/plugins/installer/update.d.ts +14 -0
  180. package/dist/plugins/installer/update.js +53 -21
  181. package/dist/plugins/loadPluginHooks.d.ts +47 -1
  182. package/dist/plugins/loadPluginHooks.js +73 -1
  183. package/dist/plugins/marketplaceManager.d.ts +7 -0
  184. package/dist/plugins/marketplaceManager.js +20 -0
  185. package/dist/plugins/pluginCommandHook.js +4 -18
  186. package/dist/plugins/pluginContent.d.ts +30 -0
  187. package/dist/plugins/pluginContent.js +83 -0
  188. package/dist/plugins/pluginInstaller.d.ts +13 -0
  189. package/dist/plugins/pluginInstaller.js +42 -8
  190. package/dist/plugins/schemas.js +1 -0
  191. package/dist/plugins/types.d.ts +6 -0
  192. package/dist/preset/index.d.ts +7 -1
  193. package/dist/preset/index.js +96 -6
  194. package/dist/product/types.d.ts +1 -1
  195. package/dist/prompt/composer.d.ts +27 -0
  196. package/dist/prompt/composer.js +67 -21
  197. package/dist/prompt/instruction-scanner.js +5 -3
  198. package/dist/prompt/section-loader.js +1 -0
  199. package/dist/prompt/sections/base.md +1 -0
  200. package/dist/prompt/sections/browser.md +10 -0
  201. package/dist/prompt/sections/coding.md +4 -0
  202. package/dist/protocol/chat-session-manager.d.ts +8 -2
  203. package/dist/protocol/chat-session-manager.js +36 -0
  204. package/dist/protocol/chat-session.d.ts +65 -0
  205. package/dist/protocol/chat-session.js +94 -2
  206. package/dist/protocol/client.d.ts +27 -0
  207. package/dist/protocol/client.js +38 -0
  208. package/dist/protocol/server.d.ts +112 -0
  209. package/dist/protocol/server.js +586 -16
  210. package/dist/protocol/transport.js +3 -2
  211. package/dist/protocol/types.d.ts +46 -0
  212. package/dist/protocol/types.js +16 -0
  213. package/dist/quota/credentials.d.ts +3 -0
  214. package/dist/quota/credentials.js +80 -0
  215. package/dist/quota/index.d.ts +36 -0
  216. package/dist/quota/index.js +155 -0
  217. package/dist/quota/types.d.ts +48 -0
  218. package/dist/quota/types.js +13 -0
  219. package/dist/review/review-prompt.d.ts +28 -0
  220. package/dist/review/review-prompt.js +81 -0
  221. package/dist/run/FileRunStore.js +8 -3
  222. package/dist/run/RunApprovalBackend.js +25 -5
  223. package/dist/run/RunManager.d.ts +12 -0
  224. package/dist/run/RunManager.js +35 -0
  225. package/dist/run/factory.d.ts +1 -1
  226. package/dist/runtime/background-shell.d.ts +139 -0
  227. package/dist/runtime/background-shell.js +495 -0
  228. package/dist/runtime/output-clean.d.ts +24 -0
  229. package/dist/runtime/output-clean.js +41 -0
  230. package/dist/runtime/ring-file.d.ts +64 -0
  231. package/dist/runtime/ring-file.js +174 -0
  232. package/dist/runtime/safe-spawn.js +15 -34
  233. package/dist/runtime/spawn-common.d.ts +148 -0
  234. package/dist/runtime/spawn-common.js +303 -0
  235. package/dist/runtime/truncate-output.d.ts +22 -0
  236. package/dist/runtime/truncate-output.js +49 -0
  237. package/dist/runtime/utf8-cut.d.ts +11 -0
  238. package/dist/runtime/utf8-cut.js +23 -0
  239. package/dist/services/auto-dream.d.ts +4 -0
  240. package/dist/services/auto-dream.js +26 -26
  241. package/dist/services/diagnostics.d.ts +1 -2
  242. package/dist/services/diagnostics.js +12 -7
  243. package/dist/services/extract-memories.d.ts +14 -1
  244. package/dist/services/extract-memories.js +45 -6
  245. package/dist/services/memory-orchestrator.d.ts +21 -0
  246. package/dist/services/memory-orchestrator.js +125 -50
  247. package/dist/services/session-memory.js +24 -12
  248. package/dist/session/file-history.d.ts +124 -1
  249. package/dist/session/file-history.js +222 -6
  250. package/dist/session/memory.d.ts +116 -2
  251. package/dist/session/memory.js +250 -28
  252. package/dist/session/session-manager.d.ts +40 -0
  253. package/dist/session/session-manager.js +113 -3
  254. package/dist/session/simple-diff.d.ts +23 -0
  255. package/dist/session/simple-diff.js +84 -0
  256. package/dist/session/transcript.d.ts +25 -1
  257. package/dist/session/transcript.js +35 -2
  258. package/dist/session/undo-target.d.ts +67 -0
  259. package/dist/session/undo-target.js +144 -0
  260. package/dist/settings/disk-defaults.d.ts +9 -2
  261. package/dist/settings/disk-defaults.js +11 -2
  262. package/dist/settings/feature-flags.d.ts +64 -0
  263. package/dist/settings/feature-flags.js +61 -0
  264. package/dist/settings/manager.d.ts +68 -1
  265. package/dist/settings/manager.js +266 -29
  266. package/dist/settings/migrate-config.d.ts +45 -0
  267. package/dist/settings/migrate-config.js +125 -0
  268. package/dist/settings/schema-export.d.ts +25 -0
  269. package/dist/settings/schema-export.js +38 -0
  270. package/dist/settings/schema.d.ts +1186 -771
  271. package/dist/settings/schema.js +268 -97
  272. package/dist/skills/scanner.d.ts +9 -0
  273. package/dist/skills/scanner.js +30 -2
  274. package/dist/stt/resolve-transcribe.d.ts +31 -0
  275. package/dist/stt/resolve-transcribe.js +108 -0
  276. package/dist/stt/transcribe.d.ts +51 -0
  277. package/dist/stt/transcribe.js +65 -0
  278. package/dist/tool-system/browser-bridge.d.ts +226 -0
  279. package/dist/tool-system/browser-bridge.js +163 -0
  280. package/dist/tool-system/builtin/agent-heartbeat.d.ts +49 -0
  281. package/dist/tool-system/builtin/agent-heartbeat.js +89 -0
  282. package/dist/tool-system/builtin/agent-notifications.d.ts +12 -3
  283. package/dist/tool-system/builtin/agent-notifications.js +9 -3
  284. package/dist/tool-system/builtin/agent-output-file.d.ts +38 -0
  285. package/dist/tool-system/builtin/agent-output-file.js +72 -0
  286. package/dist/tool-system/builtin/agent-registry.d.ts +12 -0
  287. package/dist/tool-system/builtin/agent-registry.js +8 -0
  288. package/dist/tool-system/builtin/agent.d.ts +21 -1
  289. package/dist/tool-system/builtin/agent.js +489 -42
  290. package/dist/tool-system/builtin/apply-patch/applier.js +66 -8
  291. package/dist/tool-system/builtin/apply-patch/backup-targets.d.ts +10 -0
  292. package/dist/tool-system/builtin/apply-patch/backup-targets.js +30 -0
  293. package/dist/tool-system/builtin/apply-patch/index.js +0 -15
  294. package/dist/tool-system/builtin/background-jobs.d.ts +76 -0
  295. package/dist/tool-system/builtin/background-jobs.js +124 -0
  296. package/dist/tool-system/builtin/background-shell-tools.d.ts +20 -0
  297. package/dist/tool-system/builtin/background-shell-tools.js +108 -0
  298. package/dist/tool-system/builtin/background-work.d.ts +67 -0
  299. package/dist/tool-system/builtin/background-work.js +86 -0
  300. package/dist/tool-system/builtin/bash-output-style.d.ts +32 -0
  301. package/dist/tool-system/builtin/bash-output-style.js +40 -0
  302. package/dist/tool-system/builtin/bash.d.ts +5 -2
  303. package/dist/tool-system/builtin/bash.js +98 -62
  304. package/dist/tool-system/builtin/browser-tools.d.ts +33 -0
  305. package/dist/tool-system/builtin/browser-tools.js +312 -0
  306. package/dist/tool-system/builtin/cancel-goal.d.ts +31 -0
  307. package/dist/tool-system/builtin/cancel-goal.js +64 -0
  308. package/dist/tool-system/builtin/check-quota.d.ts +15 -0
  309. package/dist/tool-system/builtin/check-quota.js +34 -0
  310. package/dist/tool-system/builtin/config.js +7 -0
  311. package/dist/tool-system/builtin/cron.d.ts +7 -0
  312. package/dist/tool-system/builtin/cron.js +55 -3
  313. package/dist/tool-system/builtin/drive-claude-code.d.ts +30 -0
  314. package/dist/tool-system/builtin/drive-claude-code.js +157 -0
  315. package/dist/tool-system/builtin/edit-model-catalog.d.ts +3 -0
  316. package/dist/tool-system/builtin/edit-model-catalog.js +94 -0
  317. package/dist/tool-system/builtin/edit.js +20 -16
  318. package/dist/tool-system/builtin/eol.d.ts +29 -0
  319. package/dist/tool-system/builtin/eol.js +37 -0
  320. package/dist/tool-system/builtin/file-cache.d.ts +6 -0
  321. package/dist/tool-system/builtin/file-cache.js +8 -0
  322. package/dist/tool-system/builtin/generate-image.d.ts +35 -0
  323. package/dist/tool-system/builtin/generate-image.js +278 -50
  324. package/dist/tool-system/builtin/generate-video.d.ts +55 -0
  325. package/dist/tool-system/builtin/generate-video.js +361 -0
  326. package/dist/tool-system/builtin/glob.js +0 -7
  327. package/dist/tool-system/builtin/grep.js +6 -8
  328. package/dist/tool-system/builtin/image-providers.d.ts +86 -0
  329. package/dist/tool-system/builtin/image-providers.js +190 -0
  330. package/dist/tool-system/builtin/image-uploader.d.ts +33 -0
  331. package/dist/tool-system/builtin/image-uploader.js +74 -0
  332. package/dist/tool-system/builtin/index.d.ts +7 -1
  333. package/dist/tool-system/builtin/index.js +249 -25
  334. package/dist/tool-system/builtin/mcp-tools.js +23 -3
  335. package/dist/tool-system/builtin/memory.js +45 -7
  336. package/dist/tool-system/builtin/notebook-edit.js +0 -7
  337. package/dist/tool-system/builtin/powershell.js +4 -1
  338. package/dist/tool-system/builtin/read.js +10 -10
  339. package/dist/tool-system/builtin/repl.js +4 -1
  340. package/dist/tool-system/builtin/skill.js +9 -0
  341. package/dist/tool-system/builtin/sleep.js +3 -2
  342. package/dist/tool-system/builtin/tool-search.js +25 -7
  343. package/dist/tool-system/builtin/video-providers.d.ts +154 -0
  344. package/dist/tool-system/builtin/video-providers.js +235 -0
  345. package/dist/tool-system/builtin/web-fetch.js +12 -2
  346. package/dist/tool-system/builtin/web-search.js +21 -7
  347. package/dist/tool-system/builtin/worktree.d.ts +2 -1
  348. package/dist/tool-system/builtin/worktree.js +31 -4
  349. package/dist/tool-system/builtin/write.js +0 -6
  350. package/dist/tool-system/context.d.ts +154 -6
  351. package/dist/tool-system/executor.d.ts +3 -1
  352. package/dist/tool-system/executor.js +172 -88
  353. package/dist/tool-system/investigation-guard.js +1 -1
  354. package/dist/tool-system/mcp-manager.d.ts +42 -4
  355. package/dist/tool-system/mcp-manager.js +186 -20
  356. package/dist/tool-system/path-policy.d.ts +5 -0
  357. package/dist/tool-system/path-policy.js +307 -8
  358. package/dist/tool-system/permission.d.ts +36 -1
  359. package/dist/tool-system/permission.js +281 -69
  360. package/dist/tool-system/plan-mode-allowlist.d.ts +13 -2
  361. package/dist/tool-system/plan-mode-allowlist.js +24 -2
  362. package/dist/tool-system/registry.d.ts +1 -0
  363. package/dist/tool-system/registry.js +16 -2
  364. package/dist/tool-system/sandbox/index.d.ts +8 -0
  365. package/dist/tool-system/sandbox/index.js +7 -2
  366. package/dist/tool-system/validate-tool-metadata.d.ts +36 -0
  367. package/dist/tool-system/validate-tool-metadata.js +63 -0
  368. package/dist/types.d.ts +217 -6
  369. package/dist/updater.js +20 -9
  370. package/dist/utils/envUtils.d.ts +0 -9
  371. package/dist/utils/envUtils.js +3 -28
  372. package/dist/utils/exec.d.ts +40 -0
  373. package/dist/utils/exec.js +144 -0
  374. package/dist/utils/json.d.ts +12 -0
  375. package/dist/utils/json.js +92 -0
  376. package/dist/utils/theme.d.ts +1 -1
  377. package/dist/utils/theme.js +1 -1
  378. package/dist/utils/toolDisplay.js +0 -1
  379. package/package.json +13 -7
  380. package/dist/agent/coordinator.d.ts +0 -49
  381. package/dist/agent/coordinator.js +0 -77
  382. package/dist/settings/manager.test.js +0 -73
  383. package/dist/tool-system/builtin/remote-trigger.d.ts +0 -6
  384. package/dist/tool-system/builtin/remote-trigger.js +0 -54
  385. package/dist/tool-system/builtin/send-message.d.ts +0 -6
  386. package/dist/tool-system/builtin/send-message.js +0 -47
  387. /package/dist/{settings/manager.test.d.ts → credentials/types.js} +0 -0
@@ -4,10 +4,12 @@
4
4
  * Priority: CLI flags > local > project > user > managed
5
5
  */
6
6
  import { readFileSync, existsSync, mkdirSync, writeFileSync, renameSync, copyFileSync, } from "node:fs";
7
- import { join, dirname } from "node:path";
7
+ import { join, dirname, extname } from "node:path";
8
8
  import { homedir } from "node:os";
9
+ import { parse as parseYaml } from "yaml";
9
10
  import { validateSettings } from "./schema.js";
10
11
  import { migrateModels } from "../migrate-models.js";
12
+ import { migrateConfig, CONFIG_VERSION_KEY } from "./migrate-config.js";
11
13
  /**
12
14
  * Resolve the user's home directory. Prefers `process.env.HOME` so that
13
15
  * runtime env overrides (set after process start, e.g. in tests) actually
@@ -18,14 +20,92 @@ import { migrateModels } from "../migrate-models.js";
18
20
  export function userHome() {
19
21
  return process.env.HOME ?? homedir();
20
22
  }
23
+ /**
24
+ * Settings keys that form the trust/permission root and therefore must NOT be
25
+ * writable through the generic `config_set` / provider-agnostic write path.
26
+ * A protocol peer (external driver, paired phone, or a compromised renderer)
27
+ * can send arbitrary config writes; letting it set these would be equivalent to
28
+ * remotely disabling the workspace-trust / permission model:
29
+ * - permissions → self-authorize any tool (permissions.rules / defaultMode)
30
+ * - env / localEnvironment → inject BASH_ENV / NODE_OPTIONS / LD_PRELOAD / PATH
31
+ * - hooks → run arbitrary commands on tool events
32
+ * - mcpServers / mcpServerOverrides → point tools at attacker-controlled servers
33
+ * These are only meant to be changed by the local settings UI (a trusted write
34
+ * path) or by hand-editing the file, never by a generic remote config write.
35
+ */
36
+ const PROTECTED_SETTING_ROOTS = new Set([
37
+ "permissions",
38
+ "env",
39
+ "localEnvironment",
40
+ "hooks",
41
+ "mcpServers",
42
+ "mcpServerOverrides",
43
+ ]);
44
+ /**
45
+ * True if `key` (a dotted path like "permissions.rules" or just "env") targets a
46
+ * protected trust-root field. Matches on the FIRST segment so nested writes
47
+ * ("permissions.defaultMode", "env.FOO") are caught too.
48
+ */
49
+ export function isProtectedSettingKey(key) {
50
+ const root = key.split(".")[0] ?? "";
51
+ return PROTECTED_SETTING_ROOTS.has(root);
52
+ }
53
+ /**
54
+ * The fixed cwd used for "no-repo" pure-chat conversations (a chat not bound to
55
+ * any code project). Same location as desktop's `resolveNoRepoCwd`
56
+ * (`join(homedir(), ".code-shell", "no-repo")`), but built from {@link userHome}
57
+ * so tests can isolate it by overriding `$HOME`. The engine compares
58
+ * `config.cwd === noRepoDir()` to flip skill/plugin filtering to whitelist mode.
59
+ */
60
+ export function noRepoDir() {
61
+ return join(userHome(), ".code-shell", "no-repo");
62
+ }
63
+ /**
64
+ * Top-level settings fields that can influence code execution and are therefore
65
+ * stripped from an UNTRUSTED project's disk layers (project + local) before
66
+ * merge. A repo commits these into its `.code-shell/settings.json`, so an
67
+ * untrusted clone must not have them take effect:
68
+ * - permissions — `rules` can self-authorize (allow Bash …); `defaultMode`
69
+ * can set bypassPermissions.
70
+ * - env — injected into Bash child env unfiltered (BASH_ENV,
71
+ * LD_PRELOAD, PATH → arbitrary code on next command).
72
+ * - localEnvironment — same, as the env floor.
73
+ * - hooks — arbitrary commands on lifecycle events.
74
+ * - mcpServers — auto-connect to attacker-controlled MCP servers.
75
+ * Only project-scoped layers are filtered; user/managed/flag are trusted.
76
+ * Mirrors Claude Code's TRUSTED_SETTING_SOURCES model (project sources excluded
77
+ * from dangerous env application until trust is granted).
78
+ */
79
+ export const DANGEROUS_PROJECT_FIELDS = [
80
+ "permissions",
81
+ "env",
82
+ "localEnvironment",
83
+ "hooks",
84
+ "mcpServers",
85
+ ];
21
86
  export class SettingsManager {
22
87
  cwd;
23
88
  scope;
89
+ projectTrusted;
24
90
  sources = [];
25
91
  merged = null;
26
- constructor(cwd = process.cwd(), scope = "project") {
92
+ constructor(cwd = process.cwd(), scope = "project",
93
+ /**
94
+ * Workspace trust for the project directory. When false, dangerous fields
95
+ * committed into the project's own `.code-shell/settings.{json,local.json}`
96
+ * ({@link DANGEROUS_PROJECT_FIELDS}) are stripped before merge, so a cloned
97
+ * malicious repo can't self-authorize permission rules, inject `env`
98
+ * (BASH_ENV/LD_PRELOAD/…), register hooks, or connect MCP servers. Safe
99
+ * fields (model choice, UI prefs, …) still merge. The user/managed/flag
100
+ * layers are never gated — the user put those there deliberately.
101
+ *
102
+ * Defaults to `true` so existing embedders/tests keep their behavior; the
103
+ * host (desktop) passes the real trust decision from its trust-store.
104
+ */
105
+ projectTrusted = true) {
27
106
  this.cwd = cwd;
28
107
  this.scope = scope;
108
+ this.projectTrusted = projectTrusted;
29
109
  }
30
110
  /**
31
111
  * Load settings from all sources.
@@ -59,6 +139,33 @@ export class SettingsManager {
59
139
  }
60
140
  // Sort by priority ascending (merge in order, later wins)
61
141
  this.sources.sort((a, b) => a.priority - b.priority);
142
+ // Version-based config migration (migrate-config.ts), applied per physical
143
+ // file (user + project settings.json) so the write-back lands in the file
144
+ // the data came from. A file is only rewritten (with a .bak) when a step
145
+ // actually changed its content — a version-stamp-only diff isn't worth
146
+ // dirtying the user's (or a repo-tracked project) file for; steps are
147
+ // idempotent, so re-running on unstamped files each load is fine.
148
+ if (readUser) {
149
+ this.applyConfigMigration(join(userHome(), ".code-shell", "settings.json"), "user");
150
+ }
151
+ if (readProject) {
152
+ this.applyConfigMigration(join(this.cwd, ".code-shell", "settings.json"), "project");
153
+ }
154
+ // Workspace-trust gate: an untrusted project must not influence execution
155
+ // through dangerous fields committed into its own .code-shell/settings.*.
156
+ // Strip them from the project/local layers (after migration re-read the
157
+ // files, so we filter the final data) before merge. See
158
+ // DANGEROUS_PROJECT_FIELDS and the `projectTrusted` ctor arg.
159
+ if (!this.projectTrusted) {
160
+ for (const source of this.sources) {
161
+ if (source.name !== "project" && source.name !== "local")
162
+ continue;
163
+ for (const field of DANGEROUS_PROJECT_FIELDS) {
164
+ if (field in source.data)
165
+ delete source.data[field];
166
+ }
167
+ }
168
+ }
62
169
  // Deep merge
63
170
  const raw = this.deepMerge();
64
171
  // Auto-migrate legacy models[] in the user settings file. Runs directly
@@ -81,7 +188,9 @@ export class SettingsManager {
81
188
  providers: result.providers,
82
189
  models: result.models,
83
190
  };
84
- writeFileSync(userPath, JSON.stringify(migrated, null, 2), "utf-8");
191
+ // Atomic write (tmp+rename) — a concurrent load must not see a
192
+ // half-written file. File exists here (existsSync guard above).
193
+ this.atomicWriteJson(userPath, migrated);
85
194
  // Re-deep-merge with the migrated user data so the validate
86
195
  // call sees the new shape rather than the legacy one.
87
196
  const userSource = this.sources.find((s) => s.name === "user");
@@ -99,6 +208,47 @@ export class SettingsManager {
99
208
  this.merged = validateSettings(raw);
100
209
  return this.merged;
101
210
  }
211
+ /**
212
+ * Run the version-based migrations (migrate-config.ts MIGRATIONS) against a
213
+ * single physical settings file. Best-effort: any read/parse/write error
214
+ * falls through silently and the original source data is used as-is.
215
+ * Writes back (with a .bak, like the models[] migration above) ONLY when a
216
+ * step changed actual content — the configVersion stamp alone doesn't
217
+ * justify touching the file. On write-back the in-memory source is updated
218
+ * so this load() already sees the migrated shape.
219
+ */
220
+ applyConfigMigration(path, sourceName) {
221
+ if (!existsSync(path))
222
+ return;
223
+ try {
224
+ const parsed = JSON.parse(readFileSync(path, "utf-8"));
225
+ if (!parsed || typeof parsed !== "object" || Array.isArray(parsed))
226
+ return;
227
+ const raw = parsed;
228
+ const result = migrateConfig(raw);
229
+ if (!result.changed)
230
+ return;
231
+ // Compare content with the version stamp normalized away — stamp-only
232
+ // changes are not persisted.
233
+ const stripStamp = (c) => {
234
+ const { [CONFIG_VERSION_KEY]: _v, ...rest } = c;
235
+ return rest;
236
+ };
237
+ if (JSON.stringify(stripStamp(raw)) === JSON.stringify(stripStamp(result.config)))
238
+ return;
239
+ copyFileSync(path, `${path}.bak`);
240
+ // Atomic write (tmp+rename) so a concurrent load can't read a half-written
241
+ // migrated file — matches the normal save path (atomicWriteJson). The file
242
+ // exists here (existsSync guard above), so the recursive mkdir is a no-op.
243
+ this.atomicWriteJson(path, result.config);
244
+ const source = this.sources.find((s) => s.name === sourceName);
245
+ if (source)
246
+ source.data = result.config;
247
+ }
248
+ catch {
249
+ // Best-effort — fall through to normal merge/validate.
250
+ }
251
+ }
102
252
  /**
103
253
  * Get current effective settings.
104
254
  */
@@ -149,9 +299,11 @@ export class SettingsManager {
149
299
  target[parts[parts.length - 1]] = value;
150
300
  mkdirSync(dirname(path), { recursive: true });
151
301
  // Atomic write: stage to .tmp, then rename, so a concurrent read can't
152
- // catch a half-written file.
302
+ // catch a half-written file. mode 0o600 — settings.json can hold plaintext
303
+ // API keys, so it must be owner-only like credentials.json (store.ts:56),
304
+ // not world-readable (default umask leaves 0o644 otherwise).
153
305
  const tmp = `${path}.${process.pid}.${Date.now()}.tmp`;
154
- writeFileSync(tmp, JSON.stringify(current, null, 2), "utf-8");
306
+ writeFileSync(tmp, JSON.stringify(current, null, 2), { encoding: "utf-8", mode: 0o600 });
155
307
  renameSync(tmp, path);
156
308
  this.invalidate();
157
309
  }
@@ -162,7 +314,14 @@ export class SettingsManager {
162
314
  * cache invalidation mirror saveUserSetting.
163
315
  */
164
316
  saveProjectSetting(key, value, cwd) {
317
+ // projectSettingsPath throws on an empty cwd (boundary guard) — keep that.
165
318
  const path = this.projectSettingsPath(cwd);
319
+ // Don't resurrect a deleted project root: atomicWriteJson's recursive mkdir
320
+ // of <cwd>/.code-shell recreates `cwd` itself as an empty shell when cwd is
321
+ // gone. A non-empty cwd that no longer exists means the project was deleted
322
+ // — skip the write rather than recreate it.
323
+ if (!existsSync(cwd))
324
+ return;
166
325
  const current = this.readJsonObject(path);
167
326
  const parts = key.split(".");
168
327
  let target = current;
@@ -185,7 +344,13 @@ export class SettingsManager {
185
344
  */
186
345
  deleteProjectSetting(key, cwd) {
187
346
  const path = this.projectSettingsPath(cwd);
188
- if (!existsSync(path))
347
+ // Must be YAML-aware, symmetric with saveProjectSetting: a project with only
348
+ // settings.yaml has no .json, so the old `existsSync(path)` guard returned
349
+ // here and the override survived (read/merge ARE yaml-aware → UI shows
350
+ // "inherited" but the key still applies). readJsonObject resolves the sibling
351
+ // YAML; the cleaned object is written back as JSON (JSON is the write-back
352
+ // format and wins over YAML, exactly as save does).
353
+ if (!resolveConfigPath(path))
189
354
  return;
190
355
  const current = this.readJsonObject(path);
191
356
  const parts = key.split(".");
@@ -220,6 +385,14 @@ export class SettingsManager {
220
385
  const out = {};
221
386
  for (const k of Object.keys(raw))
222
387
  out[k] = validated[k];
388
+ // Same workspace-trust gate as load(): getForScope("project") is a direct
389
+ // file read that bypasses the merge, so an untrusted project's dangerous
390
+ // fields (e.g. localEnvironment.setupScripts — shell run at worktree setup)
391
+ // must be stripped here too. See DANGEROUS_PROJECT_FIELDS.
392
+ if (scope === "project" && !this.projectTrusted) {
393
+ for (const field of DANGEROUS_PROJECT_FIELDS)
394
+ delete out[field];
395
+ }
223
396
  return out;
224
397
  }
225
398
  projectSettingsPath(cwd) {
@@ -229,47 +402,111 @@ export class SettingsManager {
229
402
  return join(cwd, ".code-shell", "settings.json");
230
403
  }
231
404
  readJsonObject(path) {
232
- if (!existsSync(path))
405
+ // Resolve to a sibling .yaml/.yml when the .json layer is absent so
406
+ // scope views (getForScope) see hand-written YAML too. JSON still wins.
407
+ const resolved = resolveConfigPath(path);
408
+ if (!resolved)
233
409
  return {};
234
- try {
235
- const parsed = JSON.parse(readFileSync(path, "utf-8"));
236
- if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
237
- return parsed;
238
- }
239
- }
240
- catch {
241
- // Corrupt file — overwrite rather than crash.
242
- }
243
- return {};
410
+ return parseConfigFile(resolved) ?? {};
244
411
  }
245
412
  atomicWriteJson(path, data) {
246
413
  mkdirSync(dirname(path), { recursive: true });
414
+ // mode 0o600: settings.json may hold plaintext API keys — owner-only, see
415
+ // saveUserSetting above and credentials/store.ts.
247
416
  const tmp = `${path}.${process.pid}.${Date.now()}.tmp`;
248
- writeFileSync(tmp, JSON.stringify(data, null, 2), "utf-8");
417
+ writeFileSync(tmp, JSON.stringify(data, null, 2), { encoding: "utf-8", mode: 0o600 });
249
418
  renameSync(tmp, path);
250
419
  }
251
420
  loadJsonFile(path, name, priority) {
252
- if (!existsSync(path))
421
+ // `path` is the canonical .json path for this layer. When it's absent but
422
+ // a sibling settings.yaml/.yml exists, read the YAML instead (JSON wins
423
+ // when both exist — JSON is the write-back format, YAML is hand-written).
424
+ const resolved = resolveConfigPath(path);
425
+ if (!resolved)
253
426
  return;
254
- try {
255
- const content = readFileSync(path, "utf-8");
256
- const data = JSON.parse(content);
257
- if (typeof data === "object" && data !== null) {
258
- this.sources.push({ name, priority, data });
259
- }
260
- }
261
- catch {
262
- // Skip invalid files
263
- }
427
+ const data = parseConfigFile(resolved);
428
+ if (data)
429
+ this.sources.push({ name, priority, data });
264
430
  }
265
431
  deepMerge() {
266
432
  let result = {};
267
433
  for (const source of this.sources) {
268
434
  result = merge(result, source.data);
269
435
  }
436
+ // Top-level `hooks` is the one array that CONCATENATES across layers
437
+ // instead of being replaced wholesale: a user-level (global) hook and a
438
+ // project-level hook should BOTH run, mirroring how Claude Code merges
439
+ // hooks from all settings files. Order follows layer priority (user
440
+ // first, project after). An explicit `"hooks": null` in a layer still
441
+ // resets everything below it (the escape hatch merge() already gives
442
+ // every other key); per-entry opt-out is the `disabled` field.
443
+ let hooks;
444
+ let sawHooks = false;
445
+ for (const source of this.sources) {
446
+ if (!("hooks" in source.data))
447
+ continue;
448
+ const v = source.data.hooks;
449
+ if (v === null) {
450
+ hooks = undefined;
451
+ sawHooks = true;
452
+ }
453
+ else if (Array.isArray(v)) {
454
+ hooks = [...(hooks ?? []), ...v];
455
+ sawHooks = true;
456
+ }
457
+ // Non-array garbage is left to merge()'s wholesale result so
458
+ // validateSettings still sees (and rejects) it unchanged.
459
+ }
460
+ if (sawHooks) {
461
+ if (hooks !== undefined)
462
+ result.hooks = hooks;
463
+ else
464
+ delete result.hooks;
465
+ }
270
466
  return result;
271
467
  }
272
468
  }
469
+ /**
470
+ * Parse a config file by extension: .yaml/.yml go through the YAML parser,
471
+ * everything else through JSON.parse. Mirrors the loader's existing
472
+ * "corrupt file never crashes — silently skip" contract: on any read/parse
473
+ * error, or a non-object top-level value, returns null. The caller decides
474
+ * what an absent/empty layer means.
475
+ */
476
+ function parseConfigFile(path) {
477
+ if (!existsSync(path))
478
+ return null;
479
+ try {
480
+ const content = readFileSync(path, "utf-8");
481
+ const ext = extname(path).toLowerCase();
482
+ const parsed = ext === ".yaml" || ext === ".yml" ? parseYaml(content) : JSON.parse(content);
483
+ if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
484
+ return parsed;
485
+ }
486
+ }
487
+ catch {
488
+ // Corrupt file — skip rather than crash.
489
+ }
490
+ return null;
491
+ }
492
+ /**
493
+ * Given the JSON path for a settings layer (e.g. .../settings.json or
494
+ * .../settings.local.json), return the path that should actually be read:
495
+ * the .json file if it exists, otherwise a sibling .yaml/.yml if present.
496
+ * JSON is the write-back format and wins when both exist; YAML is a
497
+ * hand-written read-only alternative. Returns null when no layer file exists.
498
+ */
499
+ function resolveConfigPath(jsonPath) {
500
+ if (existsSync(jsonPath))
501
+ return jsonPath;
502
+ const base = jsonPath.replace(/\.json$/, "");
503
+ for (const ext of [".yaml", ".yml"]) {
504
+ const candidate = `${base}${ext}`;
505
+ if (existsSync(candidate))
506
+ return candidate;
507
+ }
508
+ return null;
509
+ }
273
510
  function merge(base, override) {
274
511
  const result = { ...base };
275
512
  for (const [key, value] of Object.entries(override)) {
@@ -0,0 +1,45 @@
1
+ /**
2
+ * Version-based settings migration (TODO 8.5).
3
+ *
4
+ * A settings file carries an optional `configVersion` number. As the schema
5
+ * evolves, breaking shape changes register a migration step here; on load we
6
+ * apply every step whose `from` is >= the file's current version, in order,
7
+ * bringing the object up to CURRENT_CONFIG_VERSION. Pure + ordered so it's
8
+ * deterministic and unit-testable; the SettingsManager wires the result to a
9
+ * write-back (with a .bak) the same way the existing models[] migration does.
10
+ *
11
+ * Files with no `configVersion` are treated as version 0 (pre-versioning).
12
+ * A file already at CURRENT is a no-op (changed:false).
13
+ */
14
+ export declare const CONFIG_VERSION_KEY = "configVersion";
15
+ export interface MigrationStep {
16
+ /** Apply this step to a config at exactly this version. */
17
+ from: number;
18
+ /** Version the config is at after this step runs. */
19
+ to: number;
20
+ /** Transform the raw config object (must not mutate the input). */
21
+ migrate: (config: Record<string, unknown>) => Record<string, unknown>;
22
+ }
23
+ /**
24
+ * Registered migrations, in ascending `from` order. New breaking changes
25
+ * append a step here; CURRENT_CONFIG_VERSION follows automatically.
26
+ */
27
+ export declare const MIGRATIONS: readonly MigrationStep[];
28
+ /** The version a freshly-written config is stamped with. */
29
+ export declare const CURRENT_CONFIG_VERSION: number;
30
+ /** Read the config's version, defaulting to 0 for pre-versioning files. */
31
+ export declare function configVersionOf(config: Record<string, unknown>): number;
32
+ export interface MigrateResult {
33
+ config: Record<string, unknown>;
34
+ changed: boolean;
35
+ fromVersion: number;
36
+ toVersion: number;
37
+ }
38
+ /**
39
+ * Bring a raw config up to CURRENT_CONFIG_VERSION by applying each registered
40
+ * step whose `from` matches the running version, in order. Always stamps the
41
+ * resulting (and an already-current) config with the current version key.
42
+ * `changed` is true when any step ran OR the version stamp was added/updated.
43
+ * Never mutates the input.
44
+ */
45
+ export declare function migrateConfig(input: Record<string, unknown>, migrations?: readonly MigrationStep[]): MigrateResult;
@@ -0,0 +1,125 @@
1
+ /**
2
+ * Version-based settings migration (TODO 8.5).
3
+ *
4
+ * A settings file carries an optional `configVersion` number. As the schema
5
+ * evolves, breaking shape changes register a migration step here; on load we
6
+ * apply every step whose `from` is >= the file's current version, in order,
7
+ * bringing the object up to CURRENT_CONFIG_VERSION. Pure + ordered so it's
8
+ * deterministic and unit-testable; the SettingsManager wires the result to a
9
+ * write-back (with a .bak) the same way the existing models[] migration does.
10
+ *
11
+ * Files with no `configVersion` are treated as version 0 (pre-versioning).
12
+ * A file already at CURRENT is a no-op (changed:false).
13
+ */
14
+ import { BUILTIN_CATALOG } from "../model-catalog/builtin.js";
15
+ export const CONFIG_VERSION_KEY = "configVersion";
16
+ /**
17
+ * v0 → v1: backfill `catalogId` on imageGen/videoGen `providers[]` entries
18
+ * written before the model catalog existed (Catalog v1). Legacy entries only
19
+ * carry `kind`; the connections UI resolves its template by `catalogId`, so
20
+ * without it the model-preset dropdown degrades to an empty text box. The
21
+ * match mirrors the renderer's legacy fallback: builtin entry whose
22
+ * `adapterKind === kind` within the section's tag. Unmatched entries are left
23
+ * untouched (the UI shows a manual-input hint for those).
24
+ */
25
+ function backfillGenCatalogIds(config) {
26
+ const next = { ...config };
27
+ for (const [key, tag] of [
28
+ ["imageGen", "image"],
29
+ ["videoGen", "video"],
30
+ ]) {
31
+ const gen = next[key];
32
+ if (!gen || typeof gen !== "object" || Array.isArray(gen))
33
+ continue;
34
+ const providers = gen.providers;
35
+ if (!Array.isArray(providers))
36
+ continue;
37
+ let touched = false;
38
+ const out = providers.map((p) => {
39
+ if (!p || typeof p !== "object" || Array.isArray(p))
40
+ return p;
41
+ const rec = p;
42
+ if (typeof rec.catalogId === "string")
43
+ return p;
44
+ const entry = BUILTIN_CATALOG.find((e) => e.adapterKind === rec.kind && e.tag === tag);
45
+ if (!entry)
46
+ return p;
47
+ touched = true;
48
+ return { ...rec, catalogId: entry.id };
49
+ });
50
+ if (touched)
51
+ next[key] = { ...gen, providers: out };
52
+ }
53
+ return next;
54
+ }
55
+ /**
56
+ * v1 → v2: drop the sandbox config the 设置页 mis-wrote. The local-env page
57
+ * used to write `sandbox:{mode:"auto", network:"allow", writableRoots:[],
58
+ * deniedReads:[]}` (its display default) on every save — opting users into a
59
+ * sandbox they never chose. That exact fingerprint = not a real choice, so we
60
+ * remove the whole sandbox field (→ follow/off). A user who actually configured
61
+ * it (changed mode/network or set roots/reads) is left untouched.
62
+ */
63
+ function clearMisWrittenSandboxAuto(config) {
64
+ const sb = config.sandbox;
65
+ if (!sb || typeof sb !== "object" || Array.isArray(sb))
66
+ return config;
67
+ const s = sb;
68
+ const isMisWritten = s.mode === "auto" &&
69
+ s.network === "allow" &&
70
+ Array.isArray(s.writableRoots) && s.writableRoots.length === 0 &&
71
+ Array.isArray(s.deniedReads) && s.deniedReads.length === 0;
72
+ if (!isMisWritten)
73
+ return config;
74
+ const next = { ...config };
75
+ delete next.sandbox;
76
+ return next;
77
+ }
78
+ /**
79
+ * Registered migrations, in ascending `from` order. New breaking changes
80
+ * append a step here; CURRENT_CONFIG_VERSION follows automatically.
81
+ */
82
+ export const MIGRATIONS = [
83
+ { from: 0, to: 1, migrate: backfillGenCatalogIds },
84
+ { from: 1, to: 2, migrate: clearMisWrittenSandboxAuto },
85
+ ];
86
+ /** The version a freshly-written config is stamped with. */
87
+ export const CURRENT_CONFIG_VERSION = MIGRATIONS.reduce((max, m) => Math.max(max, m.to), 0);
88
+ /** Read the config's version, defaulting to 0 for pre-versioning files. */
89
+ export function configVersionOf(config) {
90
+ const v = config[CONFIG_VERSION_KEY];
91
+ return typeof v === "number" && Number.isFinite(v) && v >= 0 ? Math.floor(v) : 0;
92
+ }
93
+ /**
94
+ * Bring a raw config up to CURRENT_CONFIG_VERSION by applying each registered
95
+ * step whose `from` matches the running version, in order. Always stamps the
96
+ * resulting (and an already-current) config with the current version key.
97
+ * `changed` is true when any step ran OR the version stamp was added/updated.
98
+ * Never mutates the input.
99
+ */
100
+ export function migrateConfig(input, migrations = MIGRATIONS) {
101
+ const target = migrations.reduce((max, m) => Math.max(max, m.to), 0);
102
+ const fromVersion = configVersionOf(input);
103
+ let config = { ...input };
104
+ let v = fromVersion;
105
+ let stepsRan = false;
106
+ // Apply steps deterministically: for the current version, find the matching
107
+ // step, run it, advance. A migration list with gaps simply stops when no
108
+ // step matches the running version.
109
+ for (;;) {
110
+ const step = migrations.find((m) => m.from === v);
111
+ if (!step)
112
+ break;
113
+ config = { ...step.migrate(config) };
114
+ v = step.to;
115
+ stepsRan = true;
116
+ }
117
+ const alreadyStamped = input[CONFIG_VERSION_KEY] === target;
118
+ config[CONFIG_VERSION_KEY] = target;
119
+ return {
120
+ config,
121
+ changed: stepsRan || !alreadyStamped,
122
+ fromVersion,
123
+ toVersion: target,
124
+ };
125
+ }
@@ -0,0 +1,25 @@
1
+ /**
2
+ * JSON Schema export for the settings file.
3
+ *
4
+ * Editors (VS Code et al.) can point a `$schema` at the emitted
5
+ * `settings.schema.json` to get autocomplete + validation when hand-editing
6
+ * `~/.code-shell/settings.json` or `<project>/.code-shell/settings.json`.
7
+ *
8
+ * This module is side-effect free: it only EXPOSES the generator and a writer.
9
+ * `manager.load()` deliberately does NOT call these — emitting a file during
10
+ * load would pollute test HOMEs and add disk I/O to every boot. A host that
11
+ * wants the file on disk should call `writeSettingsSchemaFile()` explicitly
12
+ * (wiring TBD — see the task return note).
13
+ */
14
+ /**
15
+ * Generate the JSON Schema for the settings object. The `name` option makes
16
+ * the root a `$ref` into `definitions.CodeShellSettings`, which is the shape
17
+ * editors expect when wiring up `$schema`.
18
+ */
19
+ export declare function settingsJsonSchema(): Record<string, unknown>;
20
+ /**
21
+ * Write the schema to `<dir>/settings.schema.json` using the same atomic
22
+ * tmp-file + rename dance as SettingsManager.atomicWriteJson, so a concurrent
23
+ * reader never sees a half-written file. Returns the absolute output path.
24
+ */
25
+ export declare function writeSettingsSchemaFile(dir: string): string;
@@ -0,0 +1,38 @@
1
+ /**
2
+ * JSON Schema export for the settings file.
3
+ *
4
+ * Editors (VS Code et al.) can point a `$schema` at the emitted
5
+ * `settings.schema.json` to get autocomplete + validation when hand-editing
6
+ * `~/.code-shell/settings.json` or `<project>/.code-shell/settings.json`.
7
+ *
8
+ * This module is side-effect free: it only EXPOSES the generator and a writer.
9
+ * `manager.load()` deliberately does NOT call these — emitting a file during
10
+ * load would pollute test HOMEs and add disk I/O to every boot. A host that
11
+ * wants the file on disk should call `writeSettingsSchemaFile()` explicitly
12
+ * (wiring TBD — see the task return note).
13
+ */
14
+ import { mkdirSync, renameSync, writeFileSync } from "node:fs";
15
+ import { dirname, join } from "node:path";
16
+ import { zodToJsonSchema } from "zod-to-json-schema";
17
+ import { SettingsSchema } from "./schema.js";
18
+ /**
19
+ * Generate the JSON Schema for the settings object. The `name` option makes
20
+ * the root a `$ref` into `definitions.CodeShellSettings`, which is the shape
21
+ * editors expect when wiring up `$schema`.
22
+ */
23
+ export function settingsJsonSchema() {
24
+ return zodToJsonSchema(SettingsSchema, { name: "CodeShellSettings" });
25
+ }
26
+ /**
27
+ * Write the schema to `<dir>/settings.schema.json` using the same atomic
28
+ * tmp-file + rename dance as SettingsManager.atomicWriteJson, so a concurrent
29
+ * reader never sees a half-written file. Returns the absolute output path.
30
+ */
31
+ export function writeSettingsSchemaFile(dir) {
32
+ const out = join(dir, "settings.schema.json");
33
+ mkdirSync(dirname(out), { recursive: true });
34
+ const tmp = `${out}.${process.pid}.${Date.now()}.tmp`;
35
+ writeFileSync(tmp, JSON.stringify(settingsJsonSchema(), null, 2), "utf-8");
36
+ renameSync(tmp, out);
37
+ return out;
38
+ }