@kr78/pi-coding-agent 17.2.10

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 (1593) hide show
  1. package/CHANGELOG.md +14861 -0
  2. package/README.md +35 -0
  3. package/dist/CHANGELOG-chtdxh1z.md +14861 -0
  4. package/dist/cli.js +20483 -0
  5. package/dist/template-c2hyaytt.js +1653 -0
  6. package/dist/template-f8wx9vfn.css +1355 -0
  7. package/dist/template-qat058wr.html +55 -0
  8. package/dist/tool-views.generated-jdfmzwmn.js +35 -0
  9. package/examples/README.md +21 -0
  10. package/examples/custom-tools/README.md +104 -0
  11. package/examples/custom-tools/hello/index.ts +20 -0
  12. package/examples/extensions/README.md +142 -0
  13. package/examples/extensions/api-demo.ts +78 -0
  14. package/examples/extensions/chalk-logger.ts +25 -0
  15. package/examples/extensions/hello.ts +31 -0
  16. package/examples/extensions/pirate.ts +43 -0
  17. package/examples/extensions/plan-mode.ts +549 -0
  18. package/examples/extensions/reload-runtime.ts +38 -0
  19. package/examples/extensions/thinking-note.ts +13 -0
  20. package/examples/extensions/tools.ts +145 -0
  21. package/examples/extensions/with-deps/index.ts +36 -0
  22. package/examples/extensions/with-deps/package-lock.json +31 -0
  23. package/examples/extensions/with-deps/package.json +17 -0
  24. package/examples/hooks/README.md +56 -0
  25. package/examples/hooks/auto-commit-on-exit.ts +48 -0
  26. package/examples/hooks/confirm-destructive.ts +58 -0
  27. package/examples/hooks/custom-compaction.ts +115 -0
  28. package/examples/hooks/dirty-repo-guard.ts +51 -0
  29. package/examples/hooks/file-trigger.ts +40 -0
  30. package/examples/hooks/git-checkpoint.ts +52 -0
  31. package/examples/hooks/handoff.ts +149 -0
  32. package/examples/hooks/permission-gate.ts +33 -0
  33. package/examples/hooks/protected-paths.ts +29 -0
  34. package/examples/hooks/qna.ts +118 -0
  35. package/examples/hooks/status-line.ts +39 -0
  36. package/examples/sdk/01-minimal.ts +21 -0
  37. package/examples/sdk/02-custom-model.ts +49 -0
  38. package/examples/sdk/03-custom-prompt.ts +46 -0
  39. package/examples/sdk/04-skills.ts +43 -0
  40. package/examples/sdk/06-extensions.ts +82 -0
  41. package/examples/sdk/06-hooks.ts +61 -0
  42. package/examples/sdk/07-context-files.ts +35 -0
  43. package/examples/sdk/08-prompt-templates.ts +41 -0
  44. package/examples/sdk/08-slash-commands.ts +46 -0
  45. package/examples/sdk/09-api-keys-and-oauth.ts +54 -0
  46. package/examples/sdk/11-sessions.ts +47 -0
  47. package/examples/sdk/12-redis-sessions.ts +54 -0
  48. package/examples/sdk/13-sql-sessions.ts +61 -0
  49. package/examples/sdk/README.md +169 -0
  50. package/package.json +587 -0
  51. package/scripts/bench-guard.ts +71 -0
  52. package/scripts/bench-title-models.ts +332 -0
  53. package/scripts/build-binary.ts +117 -0
  54. package/scripts/bundle-dist.ts +123 -0
  55. package/scripts/compile-binary.ts +69 -0
  56. package/scripts/embed-mupdf-wasm.ts +67 -0
  57. package/scripts/format-prompts.ts +68 -0
  58. package/scripts/generate-aria-snapshot.ts +134 -0
  59. package/scripts/generate-docs-index.ts +58 -0
  60. package/scripts/generate-share-viewer.ts +34 -0
  61. package/scripts/legacy-pi-virtual-module.ts +205 -0
  62. package/scripts/measure-prompt-tokens.ts +63 -0
  63. package/scripts/omp +51 -0
  64. package/scripts/omp.ts +19 -0
  65. package/scripts/security-compare.ts +40 -0
  66. package/src/advisor/advise-tool.ts +234 -0
  67. package/src/advisor/config.ts +341 -0
  68. package/src/advisor/emission-guard.ts +172 -0
  69. package/src/advisor/index.ts +6 -0
  70. package/src/advisor/runtime.ts +1412 -0
  71. package/src/advisor/transcript-recorder.ts +215 -0
  72. package/src/advisor/watchdog.ts +135 -0
  73. package/src/async/index.ts +1 -0
  74. package/src/async/job-manager.ts +847 -0
  75. package/src/auto-thinking/classifier.ts +220 -0
  76. package/src/autolearn/controller.ts +152 -0
  77. package/src/autolearn/managed-skills.ts +255 -0
  78. package/src/autoresearch/command-resume.md +14 -0
  79. package/src/autoresearch/dashboard.ts +436 -0
  80. package/src/autoresearch/git.ts +331 -0
  81. package/src/autoresearch/helpers.ts +218 -0
  82. package/src/autoresearch/index.ts +541 -0
  83. package/src/autoresearch/prompt-setup.md +43 -0
  84. package/src/autoresearch/prompt.md +103 -0
  85. package/src/autoresearch/resume-message.md +10 -0
  86. package/src/autoresearch/state.ts +273 -0
  87. package/src/autoresearch/storage.ts +700 -0
  88. package/src/autoresearch/tools/init-experiment.ts +268 -0
  89. package/src/autoresearch/tools/log-experiment.ts +520 -0
  90. package/src/autoresearch/tools/run-experiment.ts +407 -0
  91. package/src/autoresearch/tools/update-notes.ts +109 -0
  92. package/src/autoresearch/types.ts +168 -0
  93. package/src/capability/context-file.ts +44 -0
  94. package/src/capability/extension-module.ts +34 -0
  95. package/src/capability/extension.ts +47 -0
  96. package/src/capability/fs.ts +117 -0
  97. package/src/capability/hook.ts +40 -0
  98. package/src/capability/index.ts +467 -0
  99. package/src/capability/instruction.ts +37 -0
  100. package/src/capability/mcp.ts +115 -0
  101. package/src/capability/prompt.ts +35 -0
  102. package/src/capability/rule-buckets.ts +66 -0
  103. package/src/capability/rule.ts +298 -0
  104. package/src/capability/settings.ts +34 -0
  105. package/src/capability/skill.ts +69 -0
  106. package/src/capability/slash-command.ts +40 -0
  107. package/src/capability/ssh.ts +41 -0
  108. package/src/capability/system-prompt.ts +34 -0
  109. package/src/capability/tool.ts +38 -0
  110. package/src/capability/types.ts +187 -0
  111. package/src/cleanse/agent.ts +226 -0
  112. package/src/cleanse/balance.ts +79 -0
  113. package/src/cleanse/checkers.ts +996 -0
  114. package/src/cleanse/index.ts +190 -0
  115. package/src/cleanse/loop.ts +51 -0
  116. package/src/cleanse/parsers.ts +726 -0
  117. package/src/cleanse/progress.ts +50 -0
  118. package/src/cleanse/prompts/assignment.md +47 -0
  119. package/src/cleanse/types.ts +72 -0
  120. package/src/cli/agents-cli.ts +138 -0
  121. package/src/cli/args.ts +368 -0
  122. package/src/cli/auth-broker-cli.ts +940 -0
  123. package/src/cli/auth-gateway-cli.ts +674 -0
  124. package/src/cli/bench-cli.ts +990 -0
  125. package/src/cli/browser-relay-cli.ts +119 -0
  126. package/src/cli/classify-install-target.ts +76 -0
  127. package/src/cli/claude-trace-cli.ts +795 -0
  128. package/src/cli/command-help.ts +107 -0
  129. package/src/cli/commands/init-xdg.ts +27 -0
  130. package/src/cli/completion-gen.ts +550 -0
  131. package/src/cli/config-cli.ts +459 -0
  132. package/src/cli/dry-balance-cli.ts +864 -0
  133. package/src/cli/extension-flags.ts +48 -0
  134. package/src/cli/file-processor.ts +132 -0
  135. package/src/cli/flag-tables.ts +367 -0
  136. package/src/cli/gallery-cli.ts +272 -0
  137. package/src/cli/gallery-fixtures/agentic.ts +420 -0
  138. package/src/cli/gallery-fixtures/codeintel.ts +187 -0
  139. package/src/cli/gallery-fixtures/edit.ts +254 -0
  140. package/src/cli/gallery-fixtures/fs.ts +245 -0
  141. package/src/cli/gallery-fixtures/index.ts +40 -0
  142. package/src/cli/gallery-fixtures/interaction.ts +46 -0
  143. package/src/cli/gallery-fixtures/memory.ts +81 -0
  144. package/src/cli/gallery-fixtures/misc.ts +177 -0
  145. package/src/cli/gallery-fixtures/search.ts +135 -0
  146. package/src/cli/gallery-fixtures/shell.ts +241 -0
  147. package/src/cli/gallery-fixtures/types.ts +57 -0
  148. package/src/cli/gallery-fixtures/web.ts +158 -0
  149. package/src/cli/gallery-screenshot.ts +279 -0
  150. package/src/cli/gc-cli.ts +1566 -0
  151. package/src/cli/grep-cli.ts +161 -0
  152. package/src/cli/grievances-cli.ts +256 -0
  153. package/src/cli/help-extra.ts +89 -0
  154. package/src/cli/initial-message.ts +58 -0
  155. package/src/cli/models-cli.ts +385 -0
  156. package/src/cli/plugin-cli.ts +996 -0
  157. package/src/cli/profile-alias.ts +369 -0
  158. package/src/cli/profile-bootstrap.ts +233 -0
  159. package/src/cli/read-cli.ts +99 -0
  160. package/src/cli/session-picker.ts +110 -0
  161. package/src/cli/setup-cli.ts +312 -0
  162. package/src/cli/setup-model-picker.ts +43 -0
  163. package/src/cli/shell-cli.ts +176 -0
  164. package/src/cli/ssh-cli.ts +179 -0
  165. package/src/cli/startup-cwd.ts +58 -0
  166. package/src/cli/stats-cli.ts +229 -0
  167. package/src/cli/thinking-levels.ts +7 -0
  168. package/src/cli/tiny-models-cli.ts +153 -0
  169. package/src/cli/ttsr-cli.ts +1013 -0
  170. package/src/cli/update-cli.ts +1184 -0
  171. package/src/cli/usage-cli.ts +1084 -0
  172. package/src/cli/usage-error.ts +7 -0
  173. package/src/cli/web-search-cli.ts +144 -0
  174. package/src/cli/worktree-cli.ts +311 -0
  175. package/src/cli-commands.ts +308 -0
  176. package/src/cli.ts +428 -0
  177. package/src/collab/crypto.ts +63 -0
  178. package/src/collab/display-name.ts +13 -0
  179. package/src/collab/guest.ts +764 -0
  180. package/src/collab/host.ts +692 -0
  181. package/src/collab/protocol.ts +296 -0
  182. package/src/collab/relay-client.ts +282 -0
  183. package/src/collab/replication-shrink.ts +111 -0
  184. package/src/commands/acp.ts +35 -0
  185. package/src/commands/agents.ts +58 -0
  186. package/src/commands/auth-broker.ts +100 -0
  187. package/src/commands/auth-gateway.ts +70 -0
  188. package/src/commands/bench.ts +64 -0
  189. package/src/commands/browser-relay.ts +53 -0
  190. package/src/commands/cleanse.ts +45 -0
  191. package/src/commands/commit.ts +47 -0
  192. package/src/commands/complete.ts +67 -0
  193. package/src/commands/completions.ts +61 -0
  194. package/src/commands/config.ts +52 -0
  195. package/src/commands/dry-balance.ts +43 -0
  196. package/src/commands/gallery.ts +61 -0
  197. package/src/commands/gc.ts +47 -0
  198. package/src/commands/grep.ts +49 -0
  199. package/src/commands/grievances.ts +52 -0
  200. package/src/commands/install.ts +107 -0
  201. package/src/commands/join.ts +40 -0
  202. package/src/commands/launch-help.ts +116 -0
  203. package/src/commands/launch.ts +34 -0
  204. package/src/commands/models.ts +61 -0
  205. package/src/commands/plugin.ts +79 -0
  206. package/src/commands/read.ts +39 -0
  207. package/src/commands/say.ts +146 -0
  208. package/src/commands/setup.ts +68 -0
  209. package/src/commands/share.ts +71 -0
  210. package/src/commands/shell.ts +30 -0
  211. package/src/commands/ssh.ts +61 -0
  212. package/src/commands/stats.ts +30 -0
  213. package/src/commands/tiny-models.ts +36 -0
  214. package/src/commands/token.ts +164 -0
  215. package/src/commands/ttsr.ts +125 -0
  216. package/src/commands/update.ts +34 -0
  217. package/src/commands/usage.ts +55 -0
  218. package/src/commands/web-search.ts +43 -0
  219. package/src/commands/worktree.ts +63 -0
  220. package/src/commit/agentic/agent.ts +322 -0
  221. package/src/commit/agentic/fallback.ts +96 -0
  222. package/src/commit/agentic/index.ts +383 -0
  223. package/src/commit/agentic/lock-files.ts +107 -0
  224. package/src/commit/agentic/prompts/analyze-file.md +22 -0
  225. package/src/commit/agentic/prompts/session-user.md +25 -0
  226. package/src/commit/agentic/prompts/split-confirm.md +1 -0
  227. package/src/commit/agentic/prompts/system.md +38 -0
  228. package/src/commit/agentic/state.ts +60 -0
  229. package/src/commit/agentic/tools/analyze-file.ts +148 -0
  230. package/src/commit/agentic/tools/git-file-diff.ts +191 -0
  231. package/src/commit/agentic/tools/git-hunk.ts +52 -0
  232. package/src/commit/agentic/tools/git-overview.ts +62 -0
  233. package/src/commit/agentic/tools/index.ts +54 -0
  234. package/src/commit/agentic/tools/propose-changelog.ts +147 -0
  235. package/src/commit/agentic/tools/propose-commit.ts +109 -0
  236. package/src/commit/agentic/tools/recent-commits.ts +81 -0
  237. package/src/commit/agentic/tools/schemas.ts +11 -0
  238. package/src/commit/agentic/tools/split-commit.ts +241 -0
  239. package/src/commit/agentic/topo-sort.ts +44 -0
  240. package/src/commit/agentic/trivial.ts +51 -0
  241. package/src/commit/agentic/validation.ts +183 -0
  242. package/src/commit/analysis/conventional.ts +64 -0
  243. package/src/commit/analysis/index.ts +4 -0
  244. package/src/commit/analysis/scope.ts +242 -0
  245. package/src/commit/analysis/summary.ts +107 -0
  246. package/src/commit/analysis/validation.ts +66 -0
  247. package/src/commit/changelog/detect.ts +40 -0
  248. package/src/commit/changelog/generate.ts +101 -0
  249. package/src/commit/changelog/index.ts +234 -0
  250. package/src/commit/changelog/parse.ts +44 -0
  251. package/src/commit/cli.ts +85 -0
  252. package/src/commit/git/diff.ts +148 -0
  253. package/src/commit/index.ts +5 -0
  254. package/src/commit/map-reduce/index.ts +69 -0
  255. package/src/commit/map-reduce/map-phase.ts +193 -0
  256. package/src/commit/map-reduce/reduce-phase.ts +49 -0
  257. package/src/commit/map-reduce/utils.ts +9 -0
  258. package/src/commit/message.ts +11 -0
  259. package/src/commit/model-selection.ts +95 -0
  260. package/src/commit/pipeline.ts +244 -0
  261. package/src/commit/prompts/analysis-system.md +148 -0
  262. package/src/commit/prompts/analysis-user.md +38 -0
  263. package/src/commit/prompts/changelog-system.md +50 -0
  264. package/src/commit/prompts/changelog-user.md +18 -0
  265. package/src/commit/prompts/file-observer-system.md +24 -0
  266. package/src/commit/prompts/file-observer-user.md +8 -0
  267. package/src/commit/prompts/reduce-system.md +50 -0
  268. package/src/commit/prompts/reduce-user.md +17 -0
  269. package/src/commit/prompts/summary-retry.md +3 -0
  270. package/src/commit/prompts/summary-system.md +38 -0
  271. package/src/commit/prompts/summary-user.md +13 -0
  272. package/src/commit/prompts/types-description.md +2 -0
  273. package/src/commit/shared-llm.ts +70 -0
  274. package/src/commit/types.ts +118 -0
  275. package/src/commit/utils/exclusions.ts +42 -0
  276. package/src/commit/utils.ts +58 -0
  277. package/src/config/api-key-resolver.ts +81 -0
  278. package/src/config/append-only-context-mode.ts +76 -0
  279. package/src/config/config-file.ts +347 -0
  280. package/src/config/inline-tool-descriptors-mode.ts +26 -0
  281. package/src/config/keybindings.ts +703 -0
  282. package/src/config/mcp-schema.json +252 -0
  283. package/src/config/model-discovery.ts +1077 -0
  284. package/src/config/model-registry.ts +2822 -0
  285. package/src/config/model-resolver.ts +2115 -0
  286. package/src/config/model-roles.ts +113 -0
  287. package/src/config/models-config-schema-bundle.ts +315 -0
  288. package/src/config/models-config-schema.ts +14 -0
  289. package/src/config/models-config.ts +130 -0
  290. package/src/config/prompt-templates.ts +205 -0
  291. package/src/config/provider-globals.ts +25 -0
  292. package/src/config/resolve-config-value.ts +95 -0
  293. package/src/config/service-tier.ts +146 -0
  294. package/src/config/settings-schema.ts +5887 -0
  295. package/src/config/settings.ts +2394 -0
  296. package/src/config.ts +242 -0
  297. package/src/cursor-bridge-tools.ts +81 -0
  298. package/src/cursor.ts +960 -0
  299. package/src/dap/client.ts +1043 -0
  300. package/src/dap/config.ts +480 -0
  301. package/src/dap/defaults.json +212 -0
  302. package/src/dap/index.ts +4 -0
  303. package/src/dap/session.ts +1841 -0
  304. package/src/dap/types.ts +611 -0
  305. package/src/debug/index.ts +584 -0
  306. package/src/debug/log-formatting.ts +58 -0
  307. package/src/debug/log-viewer.ts +966 -0
  308. package/src/debug/profiler.ts +168 -0
  309. package/src/debug/protocol-probe.ts +267 -0
  310. package/src/debug/raw-sse-buffer.ts +421 -0
  311. package/src/debug/raw-sse.ts +312 -0
  312. package/src/debug/remote-debugger.ts +151 -0
  313. package/src/debug/report-bundle.ts +411 -0
  314. package/src/debug/system-info.ts +111 -0
  315. package/src/debug/terminal-info.ts +124 -0
  316. package/src/discovery/agent-plugin-format.ts +551 -0
  317. package/src/discovery/agent-plugins.ts +341 -0
  318. package/src/discovery/agents-md.ts +67 -0
  319. package/src/discovery/agents.ts +300 -0
  320. package/src/discovery/at-imports.ts +273 -0
  321. package/src/discovery/builtin-defaults.ts +39 -0
  322. package/src/discovery/builtin-rules/go-add-cleanup.md +33 -0
  323. package/src/discovery/builtin-rules/go-bench-loop.md +36 -0
  324. package/src/discovery/builtin-rules/go-exp-promoted.md +40 -0
  325. package/src/discovery/builtin-rules/go-ioutil.md +37 -0
  326. package/src/discovery/builtin-rules/go-join-hostport.md +30 -0
  327. package/src/discovery/builtin-rules/go-new-expr.md +44 -0
  328. package/src/discovery/builtin-rules/go-rand-v2.md +41 -0
  329. package/src/discovery/builtin-rules/go-range-int.md +45 -0
  330. package/src/discovery/builtin-rules/index.ts +74 -0
  331. package/src/discovery/builtin-rules/rs-box-leak.md +49 -0
  332. package/src/discovery/builtin-rules/rs-future-prelude.md +24 -0
  333. package/src/discovery/builtin-rules/rs-lazylock.md +52 -0
  334. package/src/discovery/builtin-rules/rs-match-ergonomics.md +68 -0
  335. package/src/discovery/builtin-rules/rs-parking-lot.md +45 -0
  336. package/src/discovery/builtin-rules/rs-result-type.md +20 -0
  337. package/src/discovery/builtin-rules/ts-bare-catch.md +39 -0
  338. package/src/discovery/builtin-rules/ts-import-type.md +43 -0
  339. package/src/discovery/builtin-rules/ts-no-any.md +66 -0
  340. package/src/discovery/builtin-rules/ts-no-deprecated-leftovers.md +45 -0
  341. package/src/discovery/builtin-rules/ts-no-dynamic-import.md +40 -0
  342. package/src/discovery/builtin-rules/ts-no-inline-cast-access.md +56 -0
  343. package/src/discovery/builtin-rules/ts-no-local-is-record.md +48 -0
  344. package/src/discovery/builtin-rules/ts-no-return-type.md +45 -0
  345. package/src/discovery/builtin-rules/ts-no-test-timers.md +55 -0
  346. package/src/discovery/builtin-rules/ts-no-tiny-functions.md +51 -0
  347. package/src/discovery/builtin-rules/ts-promise-with-resolvers.md +66 -0
  348. package/src/discovery/builtin-rules/ts-redundant-clear-guard.md +75 -0
  349. package/src/discovery/builtin-rules/ts-set-map.md +28 -0
  350. package/src/discovery/builtin.ts +945 -0
  351. package/src/discovery/claude-plugins.ts +640 -0
  352. package/src/discovery/claude.ts +591 -0
  353. package/src/discovery/cline.ts +83 -0
  354. package/src/discovery/codex.ts +553 -0
  355. package/src/discovery/contained-path.ts +78 -0
  356. package/src/discovery/cursor.ts +223 -0
  357. package/src/discovery/gemini.ts +386 -0
  358. package/src/discovery/github.ts +337 -0
  359. package/src/discovery/helpers.ts +1183 -0
  360. package/src/discovery/index.ts +82 -0
  361. package/src/discovery/mcp-json.ts +182 -0
  362. package/src/discovery/omp-extension-roots.ts +272 -0
  363. package/src/discovery/omp-plugins.ts +409 -0
  364. package/src/discovery/opencode.ts +441 -0
  365. package/src/discovery/plugin-dir-roots.ts +28 -0
  366. package/src/discovery/ssh.ts +153 -0
  367. package/src/discovery/substitute-plugin-root.ts +77 -0
  368. package/src/discovery/vscode.ts +106 -0
  369. package/src/discovery/windsurf.ts +149 -0
  370. package/src/edit/apply-patch/index.ts +87 -0
  371. package/src/edit/apply-patch/parser.ts +174 -0
  372. package/src/edit/diff.ts +1008 -0
  373. package/src/edit/edit-clipboard.ts +23 -0
  374. package/src/edit/file-snapshot-store.ts +148 -0
  375. package/src/edit/hashline/block-resolver.ts +33 -0
  376. package/src/edit/hashline/diff.ts +402 -0
  377. package/src/edit/hashline/execute.ts +313 -0
  378. package/src/edit/hashline/filesystem.ts +247 -0
  379. package/src/edit/hashline/index.ts +5 -0
  380. package/src/edit/hashline/noop-loop-guard.ts +99 -0
  381. package/src/edit/hashline/params.ts +12 -0
  382. package/src/edit/index.ts +699 -0
  383. package/src/edit/modes/apply-patch.lark +19 -0
  384. package/src/edit/modes/apply-patch.ts +53 -0
  385. package/src/edit/modes/patch.ts +1958 -0
  386. package/src/edit/modes/replace.ts +1191 -0
  387. package/src/edit/normalize.ts +345 -0
  388. package/src/edit/notebook.ts +242 -0
  389. package/src/edit/read-file.ts +25 -0
  390. package/src/edit/renderer.ts +998 -0
  391. package/src/edit/snapshot-details.ts +77 -0
  392. package/src/edit/streaming.ts +710 -0
  393. package/src/eval/agent-bridge.ts +224 -0
  394. package/src/eval/backend-helpers.ts +48 -0
  395. package/src/eval/backend.ts +71 -0
  396. package/src/eval/bridge-timeout.ts +65 -0
  397. package/src/eval/budget-bridge.ts +48 -0
  398. package/src/eval/completion-bridge.ts +212 -0
  399. package/src/eval/concurrency-bridge.ts +34 -0
  400. package/src/eval/executor-base.ts +562 -0
  401. package/src/eval/idle-timeout.ts +91 -0
  402. package/src/eval/index.ts +6 -0
  403. package/src/eval/jl/executor.ts +551 -0
  404. package/src/eval/jl/index.ts +54 -0
  405. package/src/eval/jl/kernel.ts +236 -0
  406. package/src/eval/jl/prelude.jl +736 -0
  407. package/src/eval/jl/prelude.ts +3 -0
  408. package/src/eval/jl/runner.jl +666 -0
  409. package/src/eval/jl/runtime.ts +118 -0
  410. package/src/eval/js/context-manager.ts +872 -0
  411. package/src/eval/js/executor.ts +178 -0
  412. package/src/eval/js/index.ts +41 -0
  413. package/src/eval/js/process-entry.ts +31 -0
  414. package/src/eval/js/shared/helpers.ts +170 -0
  415. package/src/eval/js/shared/indirect-eval.ts +30 -0
  416. package/src/eval/js/shared/local-module-loader.ts +364 -0
  417. package/src/eval/js/shared/prelude.ts +2 -0
  418. package/src/eval/js/shared/prelude.txt +294 -0
  419. package/src/eval/js/shared/rewrite-imports.ts +550 -0
  420. package/src/eval/js/shared/runtime.ts +590 -0
  421. package/src/eval/js/shared/types.ts +18 -0
  422. package/src/eval/js/tool-bridge.ts +163 -0
  423. package/src/eval/js/worker-core.ts +380 -0
  424. package/src/eval/js/worker-entry.ts +37 -0
  425. package/src/eval/js/worker-protocol.ts +47 -0
  426. package/src/eval/kernel-base.ts +569 -0
  427. package/src/eval/py/display.ts +71 -0
  428. package/src/eval/py/executor.ts +643 -0
  429. package/src/eval/py/index.ts +57 -0
  430. package/src/eval/py/kernel.ts +235 -0
  431. package/src/eval/py/prelude.py +673 -0
  432. package/src/eval/py/prelude.ts +3 -0
  433. package/src/eval/py/runner.py +1425 -0
  434. package/src/eval/py/runtime.ts +276 -0
  435. package/src/eval/py/spawn-options.ts +134 -0
  436. package/src/eval/py/tool-bridge.ts +201 -0
  437. package/src/eval/rb/executor.ts +511 -0
  438. package/src/eval/rb/index.ts +54 -0
  439. package/src/eval/rb/kernel.ts +231 -0
  440. package/src/eval/rb/prelude.rb +551 -0
  441. package/src/eval/rb/prelude.ts +3 -0
  442. package/src/eval/rb/runner.rb +581 -0
  443. package/src/eval/rb/runtime.ts +132 -0
  444. package/src/eval/runtime-env.ts +104 -0
  445. package/src/eval/session-id.ts +8 -0
  446. package/src/eval/types.ts +48 -0
  447. package/src/exa/index.ts +2 -0
  448. package/src/exa/mcp-client.ts +370 -0
  449. package/src/exa/types.ts +69 -0
  450. package/src/exec/bash-executor.ts +627 -0
  451. package/src/exec/direnv.ts +145 -0
  452. package/src/exec/exec.ts +53 -0
  453. package/src/exec/non-interactive-env.ts +118 -0
  454. package/src/export/custom-share.ts +65 -0
  455. package/src/export/html/args.ts +20 -0
  456. package/src/export/html/index.ts +320 -0
  457. package/src/export/html/share-loader.js +102 -0
  458. package/src/export/html/template.css +1355 -0
  459. package/src/export/html/template.html +55 -0
  460. package/src/export/html/template.js +1653 -0
  461. package/src/export/html/tool-views.generated.js +35 -0
  462. package/src/export/html/vendor/highlight.min.js +1213 -0
  463. package/src/export/html/vendor/marked.min.js +6 -0
  464. package/src/export/html/web-palette.ts +126 -0
  465. package/src/export/share.ts +686 -0
  466. package/src/export/ttsr.ts +590 -0
  467. package/src/extensibility/custom-commands/bundled/ci-green/index.ts +54 -0
  468. package/src/extensibility/custom-commands/bundled/review/index.ts +698 -0
  469. package/src/extensibility/custom-commands/index.ts +2 -0
  470. package/src/extensibility/custom-commands/loader.ts +242 -0
  471. package/src/extensibility/custom-commands/types.ts +119 -0
  472. package/src/extensibility/custom-tools/index.ts +7 -0
  473. package/src/extensibility/custom-tools/loader.ts +301 -0
  474. package/src/extensibility/custom-tools/types.ts +287 -0
  475. package/src/extensibility/custom-tools/wrapper.ts +50 -0
  476. package/src/extensibility/extensions/compact-handler.ts +40 -0
  477. package/src/extensibility/extensions/get-commands-handler.ts +78 -0
  478. package/src/extensibility/extensions/index.ts +16 -0
  479. package/src/extensibility/extensions/load-errors.ts +13 -0
  480. package/src/extensibility/extensions/loader.ts +663 -0
  481. package/src/extensibility/extensions/managed-timers.ts +83 -0
  482. package/src/extensibility/extensions/model-api.ts +39 -0
  483. package/src/extensibility/extensions/runner.ts +1396 -0
  484. package/src/extensibility/extensions/types.ts +1603 -0
  485. package/src/extensibility/extensions/wrapper.ts +395 -0
  486. package/src/extensibility/hooks/index.ts +6 -0
  487. package/src/extensibility/hooks/loader.ts +243 -0
  488. package/src/extensibility/hooks/runner.ts +425 -0
  489. package/src/extensibility/hooks/tool-wrapper.ts +124 -0
  490. package/src/extensibility/hooks/types.ts +612 -0
  491. package/src/extensibility/legacy-pi-ai-shim.ts +161 -0
  492. package/src/extensibility/legacy-pi-coding-agent-shim.ts +1460 -0
  493. package/src/extensibility/legacy-pi-tui-shim.ts +43 -0
  494. package/src/extensibility/legacy-typebox.ts +101 -0
  495. package/src/extensibility/plugins/bun-git-cache.ts +91 -0
  496. package/src/extensibility/plugins/doctor.ts +65 -0
  497. package/src/extensibility/plugins/git-url.ts +367 -0
  498. package/src/extensibility/plugins/index.ts +9 -0
  499. package/src/extensibility/plugins/installer.ts +201 -0
  500. package/src/extensibility/plugins/legacy-pi-compat.ts +2643 -0
  501. package/src/extensibility/plugins/legacy-pi-virtual-modules.d.ts +4 -0
  502. package/src/extensibility/plugins/loader.ts +535 -0
  503. package/src/extensibility/plugins/manager.ts +1142 -0
  504. package/src/extensibility/plugins/marketplace/cache.ts +136 -0
  505. package/src/extensibility/plugins/marketplace/fetcher.ts +316 -0
  506. package/src/extensibility/plugins/marketplace/index.ts +6 -0
  507. package/src/extensibility/plugins/marketplace/manager.ts +927 -0
  508. package/src/extensibility/plugins/marketplace/registry.ts +192 -0
  509. package/src/extensibility/plugins/marketplace/source-resolver.ts +147 -0
  510. package/src/extensibility/plugins/marketplace/types.ts +192 -0
  511. package/src/extensibility/plugins/marketplace-auto-update.ts +49 -0
  512. package/src/extensibility/plugins/parser.ts +107 -0
  513. package/src/extensibility/plugins/runtime-config.ts +9 -0
  514. package/src/extensibility/plugins/types.ts +194 -0
  515. package/src/extensibility/session-handler-types.ts +21 -0
  516. package/src/extensibility/shared-events.ts +401 -0
  517. package/src/extensibility/skills.ts +531 -0
  518. package/src/extensibility/slash-commands.ts +131 -0
  519. package/src/extensibility/tool-event-input.ts +80 -0
  520. package/src/extensibility/tool-proxy.ts +28 -0
  521. package/src/extensibility/utils.ts +192 -0
  522. package/src/goals/index.ts +3 -0
  523. package/src/goals/runtime.ts +521 -0
  524. package/src/goals/state.ts +37 -0
  525. package/src/goals/tools/goal-tool.ts +251 -0
  526. package/src/hindsight/backend.ts +354 -0
  527. package/src/hindsight/bank.ts +156 -0
  528. package/src/hindsight/client.ts +680 -0
  529. package/src/hindsight/config.ts +193 -0
  530. package/src/hindsight/content.ts +266 -0
  531. package/src/hindsight/index.ts +8 -0
  532. package/src/hindsight/mental-models.ts +429 -0
  533. package/src/hindsight/seeds.json +32 -0
  534. package/src/hindsight/state.ts +530 -0
  535. package/src/hindsight/transcript.ts +71 -0
  536. package/src/index.ts +66 -0
  537. package/src/internal-urls/agent-protocol.ts +180 -0
  538. package/src/internal-urls/artifact-protocol.ts +151 -0
  539. package/src/internal-urls/docs-index.ts +102 -0
  540. package/src/internal-urls/filesystem-resource.ts +34 -0
  541. package/src/internal-urls/history-protocol.ts +198 -0
  542. package/src/internal-urls/index.ts +28 -0
  543. package/src/internal-urls/issue-pr-protocol.ts +594 -0
  544. package/src/internal-urls/json-query.ts +126 -0
  545. package/src/internal-urls/local-protocol.ts +471 -0
  546. package/src/internal-urls/mcp-protocol.ts +168 -0
  547. package/src/internal-urls/memory-protocol.ts +380 -0
  548. package/src/internal-urls/omp-protocol.ts +94 -0
  549. package/src/internal-urls/parse.ts +103 -0
  550. package/src/internal-urls/registry-helpers.ts +132 -0
  551. package/src/internal-urls/router.ts +153 -0
  552. package/src/internal-urls/rule-protocol.ts +45 -0
  553. package/src/internal-urls/security-protocol.ts +261 -0
  554. package/src/internal-urls/skill-protocol.ts +131 -0
  555. package/src/internal-urls/ssh-protocol.ts +368 -0
  556. package/src/internal-urls/types.ts +196 -0
  557. package/src/internal-urls/vault-protocol.ts +940 -0
  558. package/src/internal-urls/xd-protocol.ts +46 -0
  559. package/src/irc/bus.ts +380 -0
  560. package/src/jsonrpc/message-framing.ts +142 -0
  561. package/src/launch/broker.ts +1368 -0
  562. package/src/launch/client.ts +521 -0
  563. package/src/launch/ensure.ts +73 -0
  564. package/src/launch/paths.ts +14 -0
  565. package/src/launch/presence.ts +82 -0
  566. package/src/launch/protocol.ts +448 -0
  567. package/src/launch/spawn-options.ts +17 -0
  568. package/src/launch/terminal-output-worker-client.ts +53 -0
  569. package/src/launch/terminal-output-worker-protocol.ts +11 -0
  570. package/src/launch/terminal-output-worker.ts +23 -0
  571. package/src/launch/terminal-output.ts +46 -0
  572. package/src/lib/xai-http.ts +150 -0
  573. package/src/live/attestation.ts +91 -0
  574. package/src/live/controller.ts +517 -0
  575. package/src/live/prompts/agent-final-message.md +3 -0
  576. package/src/live/prompts/live-instructions.md +23 -0
  577. package/src/live/protocol.ts +233 -0
  578. package/src/live/transport.ts +422 -0
  579. package/src/live/visualizer.ts +221 -0
  580. package/src/live/voices.ts +18 -0
  581. package/src/lsp/client.ts +1465 -0
  582. package/src/lsp/clients/biome-client.ts +263 -0
  583. package/src/lsp/clients/index.ts +50 -0
  584. package/src/lsp/clients/lsp-linter-client.ts +85 -0
  585. package/src/lsp/clients/swiftlint-client.ts +120 -0
  586. package/src/lsp/config.ts +549 -0
  587. package/src/lsp/defaults.json +499 -0
  588. package/src/lsp/deferred-diagnostics.ts +66 -0
  589. package/src/lsp/diagnostics-ledger.ts +51 -0
  590. package/src/lsp/edits.ts +288 -0
  591. package/src/lsp/format-options.ts +119 -0
  592. package/src/lsp/index.ts +2821 -0
  593. package/src/lsp/lspmux.ts +233 -0
  594. package/src/lsp/mux/daemon.ts +348 -0
  595. package/src/lsp/mux/protocol.ts +96 -0
  596. package/src/lsp/mux/server.ts +797 -0
  597. package/src/lsp/render.ts +668 -0
  598. package/src/lsp/startup-events.ts +13 -0
  599. package/src/lsp/types.ts +479 -0
  600. package/src/lsp/utils.ts +747 -0
  601. package/src/main.ts +1799 -0
  602. package/src/markit/NOTICE +32 -0
  603. package/src/markit/converters/docx.ts +56 -0
  604. package/src/markit/converters/epub.ts +136 -0
  605. package/src/markit/converters/pdf/columns.ts +103 -0
  606. package/src/markit/converters/pdf/extract.ts +598 -0
  607. package/src/markit/converters/pdf/grid.ts +780 -0
  608. package/src/markit/converters/pdf/headers.ts +106 -0
  609. package/src/markit/converters/pdf/index.ts +146 -0
  610. package/src/markit/converters/pdf/render.ts +501 -0
  611. package/src/markit/converters/pdf/types.ts +84 -0
  612. package/src/markit/converters/pptx.ts +325 -0
  613. package/src/markit/converters/xlsx.ts +173 -0
  614. package/src/markit/index.ts +2 -0
  615. package/src/markit/registry.ts +59 -0
  616. package/src/markit/types.ts +35 -0
  617. package/src/mcp/client.ts +511 -0
  618. package/src/mcp/config-writer.ts +377 -0
  619. package/src/mcp/config.ts +385 -0
  620. package/src/mcp/index.ts +29 -0
  621. package/src/mcp/json-rpc.ts +122 -0
  622. package/src/mcp/loader.ts +125 -0
  623. package/src/mcp/manager.ts +1524 -0
  624. package/src/mcp/oauth-credentials.ts +104 -0
  625. package/src/mcp/oauth-discovery.ts +587 -0
  626. package/src/mcp/oauth-flow.ts +830 -0
  627. package/src/mcp/render.ts +214 -0
  628. package/src/mcp/request-id.ts +24 -0
  629. package/src/mcp/smithery-auth.ts +108 -0
  630. package/src/mcp/smithery-connect.ts +154 -0
  631. package/src/mcp/smithery-registry.ts +500 -0
  632. package/src/mcp/startup-events.ts +116 -0
  633. package/src/mcp/timeout.ts +59 -0
  634. package/src/mcp/tool-bridge.ts +691 -0
  635. package/src/mcp/tool-cache.ts +117 -0
  636. package/src/mcp/transports/header-policy.ts +95 -0
  637. package/src/mcp/transports/http.ts +508 -0
  638. package/src/mcp/transports/index.ts +7 -0
  639. package/src/mcp/transports/sse.ts +372 -0
  640. package/src/mcp/transports/stdio.ts +905 -0
  641. package/src/mcp/types.ts +467 -0
  642. package/src/memories/index.ts +1434 -0
  643. package/src/memories/storage.ts +578 -0
  644. package/src/memory-backend/index.ts +19 -0
  645. package/src/memory-backend/local-backend.ts +47 -0
  646. package/src/memory-backend/messages.ts +19 -0
  647. package/src/memory-backend/off-backend.ts +25 -0
  648. package/src/memory-backend/resolve.ts +25 -0
  649. package/src/memory-backend/runtime.ts +66 -0
  650. package/src/memory-backend/tool-names.ts +2 -0
  651. package/src/memory-backend/types.ts +166 -0
  652. package/src/mnemopi/backend.ts +629 -0
  653. package/src/mnemopi/config.ts +267 -0
  654. package/src/mnemopi/embed-client.ts +293 -0
  655. package/src/mnemopi/embed-protocol.ts +35 -0
  656. package/src/mnemopi/embed-worker.ts +114 -0
  657. package/src/mnemopi/index.ts +3 -0
  658. package/src/mnemopi/state.ts +940 -0
  659. package/src/modes/acp/acp-agent.ts +2595 -0
  660. package/src/modes/acp/acp-client-bridge.ts +154 -0
  661. package/src/modes/acp/acp-event-mapper.ts +1084 -0
  662. package/src/modes/acp/acp-mode.ts +48 -0
  663. package/src/modes/acp/index.ts +2 -0
  664. package/src/modes/acp/terminal-auth.ts +37 -0
  665. package/src/modes/components/advisor-config.ts +635 -0
  666. package/src/modes/components/advisor-message.ts +109 -0
  667. package/src/modes/components/agent-dashboard.ts +1252 -0
  668. package/src/modes/components/agent-hub-projection.ts +248 -0
  669. package/src/modes/components/agent-hub-renderer.ts +194 -0
  670. package/src/modes/components/agent-hub.ts +1126 -0
  671. package/src/modes/components/agent-transcript-viewer.ts +647 -0
  672. package/src/modes/components/ask-dialog.ts +1017 -0
  673. package/src/modes/components/assistant-message.ts +978 -0
  674. package/src/modes/components/background-tan-message.ts +36 -0
  675. package/src/modes/components/bash-execution.ts +233 -0
  676. package/src/modes/components/bordered-loader.ts +41 -0
  677. package/src/modes/components/btw-panel.ts +161 -0
  678. package/src/modes/components/cache-invalidation-marker.ts +110 -0
  679. package/src/modes/components/chat-block.ts +111 -0
  680. package/src/modes/components/chat-transcript-builder.ts +518 -0
  681. package/src/modes/components/codex-reset-fireworks.ts +369 -0
  682. package/src/modes/components/collab-prompt-message.ts +32 -0
  683. package/src/modes/components/compaction-summary-message.ts +221 -0
  684. package/src/modes/components/copy-selector.ts +218 -0
  685. package/src/modes/components/countdown-timer.ts +75 -0
  686. package/src/modes/components/custom-editor.ts +1036 -0
  687. package/src/modes/components/custom-message.ts +70 -0
  688. package/src/modes/components/diff.ts +254 -0
  689. package/src/modes/components/dynamic-border.ts +37 -0
  690. package/src/modes/components/error-banner.ts +33 -0
  691. package/src/modes/components/eval-execution.ts +169 -0
  692. package/src/modes/components/execution-shared.ts +101 -0
  693. package/src/modes/components/extensions/extension-dashboard.ts +492 -0
  694. package/src/modes/components/extensions/extension-list.ts +507 -0
  695. package/src/modes/components/extensions/index.ts +9 -0
  696. package/src/modes/components/extensions/inspector-panel.ts +326 -0
  697. package/src/modes/components/extensions/state-manager.ts +648 -0
  698. package/src/modes/components/extensions/types.ts +186 -0
  699. package/src/modes/components/footer.ts +274 -0
  700. package/src/modes/components/history-search.ts +268 -0
  701. package/src/modes/components/hook-editor.ts +213 -0
  702. package/src/modes/components/hook-input.ts +87 -0
  703. package/src/modes/components/hook-message.ts +67 -0
  704. package/src/modes/components/hook-selector.ts +691 -0
  705. package/src/modes/components/index.ts +42 -0
  706. package/src/modes/components/keybinding-hints.ts +56 -0
  707. package/src/modes/components/late-diagnostics-message.ts +60 -0
  708. package/src/modes/components/login-dialog.ts +197 -0
  709. package/src/modes/components/logout-account-selector.ts +130 -0
  710. package/src/modes/components/mcp-add-wizard.ts +1413 -0
  711. package/src/modes/components/message-frame.ts +98 -0
  712. package/src/modes/components/model-browser.ts +888 -0
  713. package/src/modes/components/model-hub.ts +2014 -0
  714. package/src/modes/components/model-picker.ts +237 -0
  715. package/src/modes/components/move-overlay.ts +293 -0
  716. package/src/modes/components/oauth-selector.ts +474 -0
  717. package/src/modes/components/omfg-panel.ts +141 -0
  718. package/src/modes/components/overlay-box.ts +109 -0
  719. package/src/modes/components/pause-screen.ts +208 -0
  720. package/src/modes/components/plan-review-overlay.ts +1226 -0
  721. package/src/modes/components/plan-toc.ts +138 -0
  722. package/src/modes/components/plugin-selector.ts +100 -0
  723. package/src/modes/components/plugin-settings.ts +745 -0
  724. package/src/modes/components/queue-mode-selector.ts +61 -0
  725. package/src/modes/components/read-tool-group.ts +856 -0
  726. package/src/modes/components/reset-usage-selector.ts +161 -0
  727. package/src/modes/components/segment-track.ts +89 -0
  728. package/src/modes/components/select-list-mouse-routing.ts +35 -0
  729. package/src/modes/components/selector-helpers.ts +129 -0
  730. package/src/modes/components/session-account-selector.ts +62 -0
  731. package/src/modes/components/session-selector.ts +1035 -0
  732. package/src/modes/components/settings-defs.ts +267 -0
  733. package/src/modes/components/settings-selector.ts +1445 -0
  734. package/src/modes/components/show-images-selector.ts +50 -0
  735. package/src/modes/components/skill-message.ts +110 -0
  736. package/src/modes/components/snapcompact-shape-preview-doc.md +14 -0
  737. package/src/modes/components/snapcompact-shape-preview.ts +192 -0
  738. package/src/modes/components/status-line/component.ts +1843 -0
  739. package/src/modes/components/status-line/context-thresholds.ts +86 -0
  740. package/src/modes/components/status-line/git-utils.ts +42 -0
  741. package/src/modes/components/status-line/index.ts +5 -0
  742. package/src/modes/components/status-line/presets.ts +106 -0
  743. package/src/modes/components/status-line/segments.ts +710 -0
  744. package/src/modes/components/status-line/separators.ts +55 -0
  745. package/src/modes/components/status-line/types.ts +159 -0
  746. package/src/modes/components/stripped-tool-calls-placeholder.ts +35 -0
  747. package/src/modes/components/theme-selector.ts +68 -0
  748. package/src/modes/components/thinking-selector.ts +57 -0
  749. package/src/modes/components/tiny-title-download-progress.ts +90 -0
  750. package/src/modes/components/tips.txt +26 -0
  751. package/src/modes/components/todo-reminder.ts +43 -0
  752. package/src/modes/components/tool-execution.ts +1467 -0
  753. package/src/modes/components/transcript-container.ts +527 -0
  754. package/src/modes/components/tree-selector.ts +1006 -0
  755. package/src/modes/components/ttsr-notification.ts +123 -0
  756. package/src/modes/components/usage-row.ts +52 -0
  757. package/src/modes/components/user-message-selector.ts +227 -0
  758. package/src/modes/components/user-message.ts +167 -0
  759. package/src/modes/components/visual-truncate.ts +63 -0
  760. package/src/modes/components/welcome.ts +578 -0
  761. package/src/modes/controllers/btw-controller.ts +246 -0
  762. package/src/modes/controllers/command-controller-shared.ts +109 -0
  763. package/src/modes/controllers/command-controller.ts +2024 -0
  764. package/src/modes/controllers/event-controller.ts +2173 -0
  765. package/src/modes/controllers/extension-ui-controller.ts +1243 -0
  766. package/src/modes/controllers/input-controller.ts +2037 -0
  767. package/src/modes/controllers/live-command-controller.ts +261 -0
  768. package/src/modes/controllers/mcp-command-controller.ts +2552 -0
  769. package/src/modes/controllers/omfg-controller.ts +287 -0
  770. package/src/modes/controllers/omfg-rule.ts +647 -0
  771. package/src/modes/controllers/selector-controller.ts +2058 -0
  772. package/src/modes/controllers/session-focus-controller.ts +117 -0
  773. package/src/modes/controllers/ssh-command-controller.ts +385 -0
  774. package/src/modes/controllers/streaming-reveal.ts +399 -0
  775. package/src/modes/controllers/tan-command-controller.ts +243 -0
  776. package/src/modes/controllers/todo-command-controller.ts +487 -0
  777. package/src/modes/controllers/tool-args-reveal.ts +591 -0
  778. package/src/modes/data/emojis.json +1 -0
  779. package/src/modes/emoji-autocomplete.ts +285 -0
  780. package/src/modes/github-ref-autocomplete.ts +75 -0
  781. package/src/modes/gradient-highlight.ts +99 -0
  782. package/src/modes/image-references.ts +137 -0
  783. package/src/modes/index.ts +10 -0
  784. package/src/modes/interactive-mode.ts +5055 -0
  785. package/src/modes/internal-url-autocomplete.ts +158 -0
  786. package/src/modes/loop-limit.ts +192 -0
  787. package/src/modes/magic-keyword-boundary.ts +23 -0
  788. package/src/modes/magic-keywords.ts +42 -0
  789. package/src/modes/markdown-prose.ts +247 -0
  790. package/src/modes/oauth-manual-input.ts +69 -0
  791. package/src/modes/orchestrate.ts +43 -0
  792. package/src/modes/print-mode.ts +282 -0
  793. package/src/modes/prompt-action-autocomplete.ts +322 -0
  794. package/src/modes/queue-input.ts +132 -0
  795. package/src/modes/rpc/host-tools.ts +204 -0
  796. package/src/modes/rpc/host-uris.ts +241 -0
  797. package/src/modes/rpc/rpc-client.ts +1213 -0
  798. package/src/modes/rpc/rpc-frame.ts +316 -0
  799. package/src/modes/rpc/rpc-input.ts +38 -0
  800. package/src/modes/rpc/rpc-messages.ts +127 -0
  801. package/src/modes/rpc/rpc-mode.ts +1519 -0
  802. package/src/modes/rpc/rpc-subagents.ts +265 -0
  803. package/src/modes/rpc/rpc-types.ts +544 -0
  804. package/src/modes/running-subagent-badge.ts +13 -0
  805. package/src/modes/runtime-init.ts +144 -0
  806. package/src/modes/session-observer-registry.ts +223 -0
  807. package/src/modes/session-teardown.ts +82 -0
  808. package/src/modes/setup-version.ts +11 -0
  809. package/src/modes/setup-wizard/index.ts +103 -0
  810. package/src/modes/setup-wizard/lazy.ts +16 -0
  811. package/src/modes/setup-wizard/scenes/glyph.ts +103 -0
  812. package/src/modes/setup-wizard/scenes/model.ts +132 -0
  813. package/src/modes/setup-wizard/scenes/outro.ts +35 -0
  814. package/src/modes/setup-wizard/scenes/providers.ts +105 -0
  815. package/src/modes/setup-wizard/scenes/sign-in.ts +312 -0
  816. package/src/modes/setup-wizard/scenes/splash.ts +201 -0
  817. package/src/modes/setup-wizard/scenes/theme.ts +330 -0
  818. package/src/modes/setup-wizard/scenes/types.ts +65 -0
  819. package/src/modes/setup-wizard/scenes/web-search.ts +153 -0
  820. package/src/modes/setup-wizard/startup-splash.ts +107 -0
  821. package/src/modes/setup-wizard/wizard-overlay.ts +335 -0
  822. package/src/modes/shared.ts +49 -0
  823. package/src/modes/skill-command.ts +91 -0
  824. package/src/modes/theme/dark.json +95 -0
  825. package/src/modes/theme/defaults/alabaster.json +93 -0
  826. package/src/modes/theme/defaults/amethyst.json +96 -0
  827. package/src/modes/theme/defaults/anthracite.json +93 -0
  828. package/src/modes/theme/defaults/basalt.json +91 -0
  829. package/src/modes/theme/defaults/birch.json +95 -0
  830. package/src/modes/theme/defaults/dark-abyss.json +91 -0
  831. package/src/modes/theme/defaults/dark-arctic.json +104 -0
  832. package/src/modes/theme/defaults/dark-aurora.json +95 -0
  833. package/src/modes/theme/defaults/dark-catppuccin.json +107 -0
  834. package/src/modes/theme/defaults/dark-cavern.json +91 -0
  835. package/src/modes/theme/defaults/dark-copper.json +95 -0
  836. package/src/modes/theme/defaults/dark-cosmos.json +90 -0
  837. package/src/modes/theme/defaults/dark-cyberpunk.json +102 -0
  838. package/src/modes/theme/defaults/dark-dracula.json +98 -0
  839. package/src/modes/theme/defaults/dark-eclipse.json +91 -0
  840. package/src/modes/theme/defaults/dark-ember.json +95 -0
  841. package/src/modes/theme/defaults/dark-equinox.json +90 -0
  842. package/src/modes/theme/defaults/dark-forest.json +96 -0
  843. package/src/modes/theme/defaults/dark-github.json +105 -0
  844. package/src/modes/theme/defaults/dark-gruvbox.json +112 -0
  845. package/src/modes/theme/defaults/dark-lavender.json +95 -0
  846. package/src/modes/theme/defaults/dark-lunar.json +89 -0
  847. package/src/modes/theme/defaults/dark-midnight.json +95 -0
  848. package/src/modes/theme/defaults/dark-monochrome.json +94 -0
  849. package/src/modes/theme/defaults/dark-monokai.json +98 -0
  850. package/src/modes/theme/defaults/dark-nebula.json +90 -0
  851. package/src/modes/theme/defaults/dark-nord.json +97 -0
  852. package/src/modes/theme/defaults/dark-ocean.json +101 -0
  853. package/src/modes/theme/defaults/dark-one.json +100 -0
  854. package/src/modes/theme/defaults/dark-poimandres.json +143 -0
  855. package/src/modes/theme/defaults/dark-rainforest.json +91 -0
  856. package/src/modes/theme/defaults/dark-reef.json +91 -0
  857. package/src/modes/theme/defaults/dark-retro.json +92 -0
  858. package/src/modes/theme/defaults/dark-rose-pine.json +96 -0
  859. package/src/modes/theme/defaults/dark-sakura.json +95 -0
  860. package/src/modes/theme/defaults/dark-slate.json +95 -0
  861. package/src/modes/theme/defaults/dark-solarized.json +97 -0
  862. package/src/modes/theme/defaults/dark-solstice.json +90 -0
  863. package/src/modes/theme/defaults/dark-starfall.json +91 -0
  864. package/src/modes/theme/defaults/dark-sunset.json +99 -0
  865. package/src/modes/theme/defaults/dark-swamp.json +90 -0
  866. package/src/modes/theme/defaults/dark-synthwave.json +103 -0
  867. package/src/modes/theme/defaults/dark-taiga.json +91 -0
  868. package/src/modes/theme/defaults/dark-terminal.json +95 -0
  869. package/src/modes/theme/defaults/dark-tokyo-night.json +101 -0
  870. package/src/modes/theme/defaults/dark-tundra.json +91 -0
  871. package/src/modes/theme/defaults/dark-twilight.json +91 -0
  872. package/src/modes/theme/defaults/dark-volcanic.json +91 -0
  873. package/src/modes/theme/defaults/graphite.json +92 -0
  874. package/src/modes/theme/defaults/index.ts +199 -0
  875. package/src/modes/theme/defaults/light-arctic.json +107 -0
  876. package/src/modes/theme/defaults/light-aurora-day.json +91 -0
  877. package/src/modes/theme/defaults/light-canyon.json +91 -0
  878. package/src/modes/theme/defaults/light-catppuccin.json +106 -0
  879. package/src/modes/theme/defaults/light-cirrus.json +90 -0
  880. package/src/modes/theme/defaults/light-coral.json +95 -0
  881. package/src/modes/theme/defaults/light-cyberpunk.json +96 -0
  882. package/src/modes/theme/defaults/light-dawn.json +90 -0
  883. package/src/modes/theme/defaults/light-dunes.json +91 -0
  884. package/src/modes/theme/defaults/light-eucalyptus.json +95 -0
  885. package/src/modes/theme/defaults/light-forest.json +100 -0
  886. package/src/modes/theme/defaults/light-frost.json +95 -0
  887. package/src/modes/theme/defaults/light-github.json +115 -0
  888. package/src/modes/theme/defaults/light-glacier.json +91 -0
  889. package/src/modes/theme/defaults/light-gruvbox.json +108 -0
  890. package/src/modes/theme/defaults/light-haze.json +90 -0
  891. package/src/modes/theme/defaults/light-honeycomb.json +95 -0
  892. package/src/modes/theme/defaults/light-lagoon.json +91 -0
  893. package/src/modes/theme/defaults/light-lavender.json +95 -0
  894. package/src/modes/theme/defaults/light-meadow.json +91 -0
  895. package/src/modes/theme/defaults/light-mint.json +95 -0
  896. package/src/modes/theme/defaults/light-monochrome.json +101 -0
  897. package/src/modes/theme/defaults/light-ocean.json +99 -0
  898. package/src/modes/theme/defaults/light-one.json +99 -0
  899. package/src/modes/theme/defaults/light-opal.json +91 -0
  900. package/src/modes/theme/defaults/light-orchard.json +91 -0
  901. package/src/modes/theme/defaults/light-paper.json +95 -0
  902. package/src/modes/theme/defaults/light-poimandres.json +143 -0
  903. package/src/modes/theme/defaults/light-prism.json +90 -0
  904. package/src/modes/theme/defaults/light-retro.json +98 -0
  905. package/src/modes/theme/defaults/light-sand.json +95 -0
  906. package/src/modes/theme/defaults/light-savanna.json +91 -0
  907. package/src/modes/theme/defaults/light-solarized.json +102 -0
  908. package/src/modes/theme/defaults/light-soleil.json +90 -0
  909. package/src/modes/theme/defaults/light-sunset.json +99 -0
  910. package/src/modes/theme/defaults/light-synthwave.json +98 -0
  911. package/src/modes/theme/defaults/light-tokyo-night.json +111 -0
  912. package/src/modes/theme/defaults/light-wetland.json +91 -0
  913. package/src/modes/theme/defaults/light-zenith.json +89 -0
  914. package/src/modes/theme/defaults/limestone.json +94 -0
  915. package/src/modes/theme/defaults/mahogany.json +97 -0
  916. package/src/modes/theme/defaults/marble.json +93 -0
  917. package/src/modes/theme/defaults/obsidian.json +91 -0
  918. package/src/modes/theme/defaults/onyx.json +91 -0
  919. package/src/modes/theme/defaults/pearl.json +93 -0
  920. package/src/modes/theme/defaults/porcelain.json +91 -0
  921. package/src/modes/theme/defaults/quartz.json +96 -0
  922. package/src/modes/theme/defaults/sandstone.json +95 -0
  923. package/src/modes/theme/defaults/titanium.json +90 -0
  924. package/src/modes/theme/light.json +93 -0
  925. package/src/modes/theme/mermaid-cache.ts +92 -0
  926. package/src/modes/theme/shimmer.ts +305 -0
  927. package/src/modes/theme/theme-schema.json +463 -0
  928. package/src/modes/theme/theme.ts +3171 -0
  929. package/src/modes/turn-budget.ts +31 -0
  930. package/src/modes/types.ts +494 -0
  931. package/src/modes/ultrathink.ts +42 -0
  932. package/src/modes/utils/context-usage.ts +518 -0
  933. package/src/modes/utils/copy-targets.ts +378 -0
  934. package/src/modes/utils/hotkeys-markdown.ts +65 -0
  935. package/src/modes/utils/interactive-context-helpers.ts +31 -0
  936. package/src/modes/utils/keybinding-matchers.ts +86 -0
  937. package/src/modes/utils/tools-markdown.ts +31 -0
  938. package/src/modes/utils/transcript-render-helpers.ts +253 -0
  939. package/src/modes/utils/ui-helpers.ts +976 -0
  940. package/src/modes/warp-events.ts +232 -0
  941. package/src/modes/workflow.ts +55 -0
  942. package/src/plan-mode/approved-plan.ts +194 -0
  943. package/src/plan-mode/model-transition.ts +51 -0
  944. package/src/plan-mode/plan-files.ts +40 -0
  945. package/src/plan-mode/plan-handoff.ts +37 -0
  946. package/src/plan-mode/plan-protection.ts +31 -0
  947. package/src/plan-mode/state.ts +6 -0
  948. package/src/priority.json +60 -0
  949. package/src/prompts/advisor/active-repo-watchdog.md +6 -0
  950. package/src/prompts/advisor/advise-tool.md +3 -0
  951. package/src/prompts/advisor/context-files.md +8 -0
  952. package/src/prompts/advisor/system.md +98 -0
  953. package/src/prompts/agents/designer.md +74 -0
  954. package/src/prompts/agents/frontmatter.md +12 -0
  955. package/src/prompts/agents/init.md +33 -0
  956. package/src/prompts/agents/librarian.md +119 -0
  957. package/src/prompts/agents/reviewer.md +139 -0
  958. package/src/prompts/agents/scout.md +58 -0
  959. package/src/prompts/agents/security-reviewer.md +75 -0
  960. package/src/prompts/agents/task.md +17 -0
  961. package/src/prompts/bench/cache-prefix-chunk.md +1 -0
  962. package/src/prompts/bench/cache-prefix.md +3 -0
  963. package/src/prompts/bench/cache-suffix.md +1 -0
  964. package/src/prompts/bench.md +6 -0
  965. package/src/prompts/ci-green-request.md +36 -0
  966. package/src/prompts/dry-balance-bench.md +8 -0
  967. package/src/prompts/goals/goal-budget-limit.md +16 -0
  968. package/src/prompts/goals/goal-continuation.md +28 -0
  969. package/src/prompts/goals/goal-mode-active.md +23 -0
  970. package/src/prompts/goals/goal-mode-context.md +4 -0
  971. package/src/prompts/goals/goal-todo-context.md +12 -0
  972. package/src/prompts/goals/guided-goal-interview.md +43 -0
  973. package/src/prompts/memories/consolidation.md +30 -0
  974. package/src/prompts/memories/consolidation_system.md +4 -0
  975. package/src/prompts/memories/read-path.md +17 -0
  976. package/src/prompts/memories/stage_one_input.md +6 -0
  977. package/src/prompts/memories/stage_one_system.md +21 -0
  978. package/src/prompts/review-custom-request.md +21 -0
  979. package/src/prompts/review-headless-request.md +16 -0
  980. package/src/prompts/review-request.md +68 -0
  981. package/src/prompts/security/scan-coordinator.md +7 -0
  982. package/src/prompts/security/scan-request.md +21 -0
  983. package/src/prompts/security/validate-request.md +8 -0
  984. package/src/prompts/session/launch-completion.md +1 -0
  985. package/src/prompts/skills/autoload.md +8 -0
  986. package/src/prompts/skills/user-invocation.md +11 -0
  987. package/src/prompts/steering/parent-irc.md +5 -0
  988. package/src/prompts/steering/user-interjection.md +6 -0
  989. package/src/prompts/system/active-repo-context.md +4 -0
  990. package/src/prompts/system/agent-creation-architect.md +50 -0
  991. package/src/prompts/system/agent-creation-user.md +6 -0
  992. package/src/prompts/system/auto-continue.md +1 -0
  993. package/src/prompts/system/auto-thinking-difficulty-local.md +14 -0
  994. package/src/prompts/system/auto-thinking-difficulty.md +14 -0
  995. package/src/prompts/system/autolearn-guidance-learn.md +1 -0
  996. package/src/prompts/system/autolearn-guidance.md +7 -0
  997. package/src/prompts/system/autolearn-nudge-autocontinue.md +5 -0
  998. package/src/prompts/system/background-tan-dispatch.md +8 -0
  999. package/src/prompts/system/btw-user.md +8 -0
  1000. package/src/prompts/system/commit-message-system.md +14 -0
  1001. package/src/prompts/system/computer-safety.md +14 -0
  1002. package/src/prompts/system/custom-system-prompt.md +64 -0
  1003. package/src/prompts/system/eager-task.md +7 -0
  1004. package/src/prompts/system/eager-todo.md +18 -0
  1005. package/src/prompts/system/empty-stop-retry.md +4 -0
  1006. package/src/prompts/system/gemini-tool-call-reminder.md +9 -0
  1007. package/src/prompts/system/interrupted-thinking.md +7 -0
  1008. package/src/prompts/system/irc-autoreply.md +6 -0
  1009. package/src/prompts/system/irc-incoming.md +9 -0
  1010. package/src/prompts/system/manual-continue.md +7 -0
  1011. package/src/prompts/system/mcp-xdev-guidance.md +11 -0
  1012. package/src/prompts/system/memory-consolidation-system.md +8 -0
  1013. package/src/prompts/system/memory-extraction-system.md +26 -0
  1014. package/src/prompts/system/mid-run-todo-nudge.md +3 -0
  1015. package/src/prompts/system/omfg-user.md +50 -0
  1016. package/src/prompts/system/orchestrate-notice.md +40 -0
  1017. package/src/prompts/system/personalities/default.md +18 -0
  1018. package/src/prompts/system/personalities/friendly.md +17 -0
  1019. package/src/prompts/system/personalities/pragmatic.md +15 -0
  1020. package/src/prompts/system/plan-mode-active.md +125 -0
  1021. package/src/prompts/system/plan-mode-approved.md +22 -0
  1022. package/src/prompts/system/plan-mode-compact-instructions.md +17 -0
  1023. package/src/prompts/system/plan-mode-reference.md +10 -0
  1024. package/src/prompts/system/plan-mode-subagent.md +33 -0
  1025. package/src/prompts/system/plan-mode-tool-decision-reminder.md +9 -0
  1026. package/src/prompts/system/plan-yolo-handoff.md +5 -0
  1027. package/src/prompts/system/prewalk-checklist.md +7 -0
  1028. package/src/prompts/system/prewalk-continue.md +1 -0
  1029. package/src/prompts/system/prewalk-plan.md +13 -0
  1030. package/src/prompts/system/project-prompt.md +61 -0
  1031. package/src/prompts/system/recap-user.md +9 -0
  1032. package/src/prompts/system/resolve-device-reminder.md +3 -0
  1033. package/src/prompts/system/rewind-report.md +6 -0
  1034. package/src/prompts/system/side-channel-no-tools.md +3 -0
  1035. package/src/prompts/system/snapcompact-context-frames-note.md +1 -0
  1036. package/src/prompts/system/snapcompact-context-stub.md +1 -0
  1037. package/src/prompts/system/snapcompact-system-frames-note.md +1 -0
  1038. package/src/prompts/system/snapcompact-system-stub.md +1 -0
  1039. package/src/prompts/system/snapcompact-toolresult-note.md +1 -0
  1040. package/src/prompts/system/speech-rewrite.md +15 -0
  1041. package/src/prompts/system/subagent-async-pending.md +6 -0
  1042. package/src/prompts/system/subagent-system-prompt.md +73 -0
  1043. package/src/prompts/system/subagent-user-prompt.md +3 -0
  1044. package/src/prompts/system/subagent-yield-reminder.md +23 -0
  1045. package/src/prompts/system/system-prompt.md +263 -0
  1046. package/src/prompts/system/tan-context-switch.md +17 -0
  1047. package/src/prompts/system/task-label.md +23 -0
  1048. package/src/prompts/system/thinking-loop-redirect.md +10 -0
  1049. package/src/prompts/system/title-marker-instruction.md +1 -0
  1050. package/src/prompts/system/title-system.md +16 -0
  1051. package/src/prompts/system/tool-call-loop-redirect.md +8 -0
  1052. package/src/prompts/system/ttsr-interrupt.md +7 -0
  1053. package/src/prompts/system/ttsr-tool-reminder.md +5 -0
  1054. package/src/prompts/system/ultrathink-notice.md +3 -0
  1055. package/src/prompts/system/unexpected-stop-classifier.md +17 -0
  1056. package/src/prompts/system/unexpected-stop-retry.md +4 -0
  1057. package/src/prompts/system/vibe-mode-active.md +26 -0
  1058. package/src/prompts/system/web-search.md +25 -0
  1059. package/src/prompts/system/workflow-notice.md +112 -0
  1060. package/src/prompts/system/xdev-mount-notice.md +20 -0
  1061. package/src/prompts/tools/apply-patch.md +65 -0
  1062. package/src/prompts/tools/ask.md +22 -0
  1063. package/src/prompts/tools/ast-edit.md +11 -0
  1064. package/src/prompts/tools/ast-grep.md +19 -0
  1065. package/src/prompts/tools/async-result.md +8 -0
  1066. package/src/prompts/tools/bash.md +23 -0
  1067. package/src/prompts/tools/browser.md +29 -0
  1068. package/src/prompts/tools/checkpoint.md +15 -0
  1069. package/src/prompts/tools/computer.md +26 -0
  1070. package/src/prompts/tools/debug.md +3 -0
  1071. package/src/prompts/tools/eval.md +45 -0
  1072. package/src/prompts/tools/github.md +22 -0
  1073. package/src/prompts/tools/glob.md +16 -0
  1074. package/src/prompts/tools/goal.md +11 -0
  1075. package/src/prompts/tools/grep.md +13 -0
  1076. package/src/prompts/tools/hub.md +34 -0
  1077. package/src/prompts/tools/image-attachment-describe-system.md +8 -0
  1078. package/src/prompts/tools/image-attachment-describe.md +10 -0
  1079. package/src/prompts/tools/image-gen.md +7 -0
  1080. package/src/prompts/tools/inspect-image-system.md +20 -0
  1081. package/src/prompts/tools/inspect-image.md +22 -0
  1082. package/src/prompts/tools/learn.md +7 -0
  1083. package/src/prompts/tools/lsp-late-diagnostic.md +8 -0
  1084. package/src/prompts/tools/lsp.md +19 -0
  1085. package/src/prompts/tools/manage-skill.md +9 -0
  1086. package/src/prompts/tools/memory-edit.md +12 -0
  1087. package/src/prompts/tools/patch.md +57 -0
  1088. package/src/prompts/tools/read.md +27 -0
  1089. package/src/prompts/tools/recall.md +7 -0
  1090. package/src/prompts/tools/reflect.md +5 -0
  1091. package/src/prompts/tools/replace.md +30 -0
  1092. package/src/prompts/tools/retain.md +6 -0
  1093. package/src/prompts/tools/rewind.md +14 -0
  1094. package/src/prompts/tools/security-publish.md +1 -0
  1095. package/src/prompts/tools/security-scan.md +1 -0
  1096. package/src/prompts/tools/task-async-contract.md +1 -0
  1097. package/src/prompts/tools/task-summary.md +20 -0
  1098. package/src/prompts/tools/task.md +83 -0
  1099. package/src/prompts/tools/todo.md +42 -0
  1100. package/src/prompts/tools/vibe-kill.md +3 -0
  1101. package/src/prompts/tools/vibe-list.md +3 -0
  1102. package/src/prompts/tools/vibe-send.md +9 -0
  1103. package/src/prompts/tools/vibe-spawn.md +10 -0
  1104. package/src/prompts/tools/vibe-turn-result.md +19 -0
  1105. package/src/prompts/tools/vibe-wait.md +8 -0
  1106. package/src/prompts/tools/web-search.md +8 -0
  1107. package/src/prompts/tools/write.md +14 -0
  1108. package/src/registry/agent-lifecycle.ts +505 -0
  1109. package/src/registry/agent-registry.ts +287 -0
  1110. package/src/registry/persisted-agents.ts +423 -0
  1111. package/src/sdk.ts +3820 -0
  1112. package/src/secrets/index.ts +375 -0
  1113. package/src/secrets/obfuscator.ts +2629 -0
  1114. package/src/secrets/regex.ts +21 -0
  1115. package/src/security/auth.ts +98 -0
  1116. package/src/security/cloud.ts +686 -0
  1117. package/src/security/comparison.ts +255 -0
  1118. package/src/security/contracts/ids.ts +111 -0
  1119. package/src/security/contracts/index.ts +4 -0
  1120. package/src/security/contracts/schemas.ts +219 -0
  1121. package/src/security/contracts/types.ts +254 -0
  1122. package/src/security/contracts/validation.ts +69 -0
  1123. package/src/security/coordinator.ts +708 -0
  1124. package/src/security/importers/codex-security.ts +387 -0
  1125. package/src/security/importers/index.ts +2 -0
  1126. package/src/security/importers/sarif.ts +357 -0
  1127. package/src/security/index.ts +13 -0
  1128. package/src/security/preflight.ts +405 -0
  1129. package/src/security/provenance.ts +106 -0
  1130. package/src/security/publication.ts +326 -0
  1131. package/src/security/remediation.ts +93 -0
  1132. package/src/security/resource-output.ts +50 -0
  1133. package/src/security/sarif.ts +78 -0
  1134. package/src/security/store.ts +430 -0
  1135. package/src/session/acp-permission-gate.ts +165 -0
  1136. package/src/session/agent-session-events.ts +67 -0
  1137. package/src/session/agent-session-types.ts +407 -0
  1138. package/src/session/agent-session.ts +9000 -0
  1139. package/src/session/agent-storage.ts +807 -0
  1140. package/src/session/artifacts.ts +154 -0
  1141. package/src/session/async-job-delivery.ts +82 -0
  1142. package/src/session/auth-broker-config.ts +92 -0
  1143. package/src/session/auth-storage.ts +25 -0
  1144. package/src/session/bash-runner.ts +326 -0
  1145. package/src/session/blob-store.ts +295 -0
  1146. package/src/session/checkpoint-entries.ts +81 -0
  1147. package/src/session/claude-session-store.ts +426 -0
  1148. package/src/session/client-bridge.ts +85 -0
  1149. package/src/session/codex-auto-reset.ts +673 -0
  1150. package/src/session/codex-session-store.ts +673 -0
  1151. package/src/session/compact-modes.ts +105 -0
  1152. package/src/session/credential-pin.ts +93 -0
  1153. package/src/session/eval-runner.ts +219 -0
  1154. package/src/session/exit-diagnostics.ts +310 -0
  1155. package/src/session/foreign-session-import.ts +52 -0
  1156. package/src/session/foreign-session-jsonl.ts +29 -0
  1157. package/src/session/foreign-session-store.ts +26 -0
  1158. package/src/session/history-storage.ts +329 -0
  1159. package/src/session/indexed-session-storage.ts +553 -0
  1160. package/src/session/irc-bridge.ts +203 -0
  1161. package/src/session/launch-completion.ts +37 -0
  1162. package/src/session/messages.ts +1304 -0
  1163. package/src/session/model-controls.ts +757 -0
  1164. package/src/session/prewalk.ts +279 -0
  1165. package/src/session/provider-image-budget.ts +86 -0
  1166. package/src/session/queued-messages.ts +99 -0
  1167. package/src/session/redis-session-storage.ts +257 -0
  1168. package/src/session/retry-fallback-chains.ts +455 -0
  1169. package/src/session/role-models.ts +85 -0
  1170. package/src/session/session-advisors.ts +1896 -0
  1171. package/src/session/session-context.ts +584 -0
  1172. package/src/session/session-dump-format.ts +216 -0
  1173. package/src/session/session-entries.ts +306 -0
  1174. package/src/session/session-handoff.ts +325 -0
  1175. package/src/session/session-history-format.ts +459 -0
  1176. package/src/session/session-listing.ts +715 -0
  1177. package/src/session/session-loader.ts +362 -0
  1178. package/src/session/session-maintenance.ts +3115 -0
  1179. package/src/session/session-manager.ts +2747 -0
  1180. package/src/session/session-memory.ts +222 -0
  1181. package/src/session/session-metadata.ts +53 -0
  1182. package/src/session/session-migrations.ts +78 -0
  1183. package/src/session/session-paths.ts +280 -0
  1184. package/src/session/session-persistence.ts +293 -0
  1185. package/src/session/session-provider-boundary.ts +306 -0
  1186. package/src/session/session-stats.ts +349 -0
  1187. package/src/session/session-storage.ts +774 -0
  1188. package/src/session/session-title-slot.ts +141 -0
  1189. package/src/session/session-tools.ts +1385 -0
  1190. package/src/session/session-workspace.ts +53 -0
  1191. package/src/session/settings-stream-fn.ts +79 -0
  1192. package/src/session/shake-types.ts +43 -0
  1193. package/src/session/snapcompact-inline.ts +545 -0
  1194. package/src/session/snapcompact-savings-journal.ts +113 -0
  1195. package/src/session/sql-session-storage.ts +374 -0
  1196. package/src/session/stream-guards.ts +417 -0
  1197. package/src/session/streaming-output.ts +1459 -0
  1198. package/src/session/todo-tracker.ts +380 -0
  1199. package/src/session/tool-choice-queue.ts +305 -0
  1200. package/src/session/ttsr-coordinator.ts +496 -0
  1201. package/src/session/turn-persistence.ts +142 -0
  1202. package/src/session/turn-recovery.ts +2005 -0
  1203. package/src/session/unexpected-stop-classifier.ts +141 -0
  1204. package/src/session/yield-queue.ts +288 -0
  1205. package/src/slash-commands/acp-builtins.ts +70 -0
  1206. package/src/slash-commands/available-commands.ts +105 -0
  1207. package/src/slash-commands/builtin-registry.ts +3149 -0
  1208. package/src/slash-commands/helpers/active-oauth-account.ts +80 -0
  1209. package/src/slash-commands/helpers/collab-qrcode.ts +28 -0
  1210. package/src/slash-commands/helpers/context-report.ts +66 -0
  1211. package/src/slash-commands/helpers/format.ts +46 -0
  1212. package/src/slash-commands/helpers/logout.ts +108 -0
  1213. package/src/slash-commands/helpers/marketplace-manager.ts +25 -0
  1214. package/src/slash-commands/helpers/mcp.ts +533 -0
  1215. package/src/slash-commands/helpers/parse.ts +85 -0
  1216. package/src/slash-commands/helpers/reset-usage.ts +68 -0
  1217. package/src/slash-commands/helpers/security.ts +451 -0
  1218. package/src/slash-commands/helpers/session-pin.ts +44 -0
  1219. package/src/slash-commands/helpers/ssh.ts +196 -0
  1220. package/src/slash-commands/helpers/stats-dashboard.ts +86 -0
  1221. package/src/slash-commands/helpers/todo.ts +285 -0
  1222. package/src/slash-commands/helpers/usage-report.ts +198 -0
  1223. package/src/slash-commands/marketplace-install-parser.ts +99 -0
  1224. package/src/slash-commands/types.ts +139 -0
  1225. package/src/ssh/config-writer.ts +183 -0
  1226. package/src/ssh/connection-manager.ts +667 -0
  1227. package/src/ssh/file-transfer.ts +214 -0
  1228. package/src/ssh/sshfs-mount.ts +163 -0
  1229. package/src/ssh/utils.ts +51 -0
  1230. package/src/startup-splash.ts +19 -0
  1231. package/src/stt/asr-client.ts +401 -0
  1232. package/src/stt/asr-protocol.ts +65 -0
  1233. package/src/stt/asr-worker.ts +603 -0
  1234. package/src/stt/downloader.ts +142 -0
  1235. package/src/stt/endpointer.ts +259 -0
  1236. package/src/stt/index.ts +6 -0
  1237. package/src/stt/models.ts +150 -0
  1238. package/src/stt/sherpa-runtime.ts +71 -0
  1239. package/src/stt/stt-controller.ts +320 -0
  1240. package/src/stt/submit-trigger.ts +74 -0
  1241. package/src/subprocess/worker-client.ts +463 -0
  1242. package/src/subprocess/worker-runtime.ts +494 -0
  1243. package/src/system-prompt.ts +921 -0
  1244. package/src/task/agents.ts +170 -0
  1245. package/src/task/commands.ts +132 -0
  1246. package/src/task/discovery.ts +145 -0
  1247. package/src/task/executor.ts +3431 -0
  1248. package/src/task/index.ts +1515 -0
  1249. package/src/task/isolation-ownership.ts +106 -0
  1250. package/src/task/isolation-runner.ts +444 -0
  1251. package/src/task/label.ts +40 -0
  1252. package/src/task/name-generator.ts +1577 -0
  1253. package/src/task/omp-command.ts +26 -0
  1254. package/src/task/output-manager.ts +115 -0
  1255. package/src/task/parallel.ts +221 -0
  1256. package/src/task/persisted-revive.ts +173 -0
  1257. package/src/task/prewalk.ts +6 -0
  1258. package/src/task/prompt-policy.ts +8 -0
  1259. package/src/task/provider-concurrency.ts +100 -0
  1260. package/src/task/read-only-policy.ts +27 -0
  1261. package/src/task/render.ts +1830 -0
  1262. package/src/task/renderer.ts +14 -0
  1263. package/src/task/repair-args.ts +118 -0
  1264. package/src/task/spawn-policy.ts +72 -0
  1265. package/src/task/structured-subagent.ts +676 -0
  1266. package/src/task/subprocess-tool-registry.ts +88 -0
  1267. package/src/task/types.ts +555 -0
  1268. package/src/task/worktree.ts +967 -0
  1269. package/src/task/yield-assembly.ts +198 -0
  1270. package/src/telemetry-export.ts +504 -0
  1271. package/src/thinking.ts +377 -0
  1272. package/src/tiny/device.ts +111 -0
  1273. package/src/tiny/dtype.ts +101 -0
  1274. package/src/tiny/message-preproc.ts +155 -0
  1275. package/src/tiny/models.ts +268 -0
  1276. package/src/tiny/text.ts +290 -0
  1277. package/src/tiny/title-client.ts +460 -0
  1278. package/src/tiny/title-protocol.ts +56 -0
  1279. package/src/tiny/worker.ts +354 -0
  1280. package/src/tools/acp-bridge.ts +125 -0
  1281. package/src/tools/approval.ts +245 -0
  1282. package/src/tools/ask.ts +1459 -0
  1283. package/src/tools/ast-edit.ts +718 -0
  1284. package/src/tools/ast-grep.ts +526 -0
  1285. package/src/tools/auto-generated-guard.ts +335 -0
  1286. package/src/tools/bash-interactive.ts +435 -0
  1287. package/src/tools/bash-interceptor.ts +148 -0
  1288. package/src/tools/bash-pty-selection.ts +14 -0
  1289. package/src/tools/bash-skill-urls.ts +350 -0
  1290. package/src/tools/bash.ts +1826 -0
  1291. package/src/tools/browser/aria/aria-snapshot.bundle.txt +7 -0
  1292. package/src/tools/browser/aria/aria-snapshot.ts +131 -0
  1293. package/src/tools/browser/attach.ts +219 -0
  1294. package/src/tools/browser/cmux/cmux-tab.ts +1531 -0
  1295. package/src/tools/browser/cmux/rpc.ts +200 -0
  1296. package/src/tools/browser/cmux/socket-client.ts +445 -0
  1297. package/src/tools/browser/launch.ts +939 -0
  1298. package/src/tools/browser/readable.ts +112 -0
  1299. package/src/tools/browser/registry.ts +447 -0
  1300. package/src/tools/browser/relay/bridge.ts +945 -0
  1301. package/src/tools/browser/relay/daemon.ts +117 -0
  1302. package/src/tools/browser/relay/extension-assets/background.js.txt +242 -0
  1303. package/src/tools/browser/relay/extension-assets/manifest.json.txt +14 -0
  1304. package/src/tools/browser/relay/extension-assets/options.html.txt +53 -0
  1305. package/src/tools/browser/relay/extension-assets/options.js.txt +23 -0
  1306. package/src/tools/browser/relay/kind.ts +41 -0
  1307. package/src/tools/browser/relay/protocol.ts +54 -0
  1308. package/src/tools/browser/relay/server.ts +141 -0
  1309. package/src/tools/browser/render.ts +229 -0
  1310. package/src/tools/browser/run-output.ts +76 -0
  1311. package/src/tools/browser/shared-daemon.ts +139 -0
  1312. package/src/tools/browser/tab-protocol.ts +123 -0
  1313. package/src/tools/browser/tab-supervisor.ts +1077 -0
  1314. package/src/tools/browser/tab-worker-entry.ts +29 -0
  1315. package/src/tools/browser/tab-worker.ts +1982 -0
  1316. package/src/tools/browser.ts +487 -0
  1317. package/src/tools/builtin-names.ts +67 -0
  1318. package/src/tools/checkpoint.ts +137 -0
  1319. package/src/tools/computer/exposure.ts +14 -0
  1320. package/src/tools/computer/protocol.ts +69 -0
  1321. package/src/tools/computer/supervisor.ts +409 -0
  1322. package/src/tools/computer/worker-entry.ts +39 -0
  1323. package/src/tools/computer/worker.ts +745 -0
  1324. package/src/tools/computer-renderer.ts +147 -0
  1325. package/src/tools/computer.ts +222 -0
  1326. package/src/tools/conflict-detect.ts +815 -0
  1327. package/src/tools/context.ts +49 -0
  1328. package/src/tools/debug.ts +1121 -0
  1329. package/src/tools/default-renderer.ts +154 -0
  1330. package/src/tools/essential-tools.ts +46 -0
  1331. package/src/tools/eval-backends.ts +34 -0
  1332. package/src/tools/eval-format/index.ts +24 -0
  1333. package/src/tools/eval-format/javascript.ts +952 -0
  1334. package/src/tools/eval-format/julia.ts +446 -0
  1335. package/src/tools/eval-format/python.ts +544 -0
  1336. package/src/tools/eval-format/ruby.ts +380 -0
  1337. package/src/tools/eval-render.ts +784 -0
  1338. package/src/tools/eval.ts +774 -0
  1339. package/src/tools/fetch.ts +1889 -0
  1340. package/src/tools/file-recorder.ts +35 -0
  1341. package/src/tools/fs-cache-invalidation.ts +28 -0
  1342. package/src/tools/gh-cache-invalidation.ts +175 -0
  1343. package/src/tools/gh-format.ts +12 -0
  1344. package/src/tools/gh-renderer.ts +484 -0
  1345. package/src/tools/gh.ts +3958 -0
  1346. package/src/tools/github-cache.ts +663 -0
  1347. package/src/tools/glob.ts +691 -0
  1348. package/src/tools/grep.ts +1920 -0
  1349. package/src/tools/grouped-file-output.ts +210 -0
  1350. package/src/tools/hub/index.ts +579 -0
  1351. package/src/tools/hub/jobs.ts +714 -0
  1352. package/src/tools/hub/launch.ts +690 -0
  1353. package/src/tools/hub/messaging.ts +735 -0
  1354. package/src/tools/hub/types.ts +117 -0
  1355. package/src/tools/image-gen.ts +1689 -0
  1356. package/src/tools/image-providers.ts +50 -0
  1357. package/src/tools/index.ts +736 -0
  1358. package/src/tools/inspect-image-renderer.ts +133 -0
  1359. package/src/tools/inspect-image.ts +316 -0
  1360. package/src/tools/json-tree.ts +260 -0
  1361. package/src/tools/jtd-to-json-schema.ts +219 -0
  1362. package/src/tools/jtd-to-typescript.ts +136 -0
  1363. package/src/tools/jtd-utils.ts +102 -0
  1364. package/src/tools/learn.ts +141 -0
  1365. package/src/tools/list-limit.ts +40 -0
  1366. package/src/tools/manage-skill.ts +102 -0
  1367. package/src/tools/match-line-format.ts +20 -0
  1368. package/src/tools/memory-edit.ts +61 -0
  1369. package/src/tools/memory-recall.ts +102 -0
  1370. package/src/tools/memory-reflect.ts +88 -0
  1371. package/src/tools/memory-render.ts +211 -0
  1372. package/src/tools/memory-retain.ts +89 -0
  1373. package/src/tools/output-meta.ts +860 -0
  1374. package/src/tools/output-schema-validator.ts +307 -0
  1375. package/src/tools/path-utils.ts +1489 -0
  1376. package/src/tools/plan-mode-guard.ts +155 -0
  1377. package/src/tools/puppeteer/00_stealth_tampering.txt +44 -0
  1378. package/src/tools/puppeteer/01_stealth_activity.txt +80 -0
  1379. package/src/tools/puppeteer/02_stealth_hairline.txt +57 -0
  1380. package/src/tools/puppeteer/03_stealth_botd.txt +380 -0
  1381. package/src/tools/puppeteer/04_stealth_iframe.txt +174 -0
  1382. package/src/tools/puppeteer/05_stealth_webgl.txt +233 -0
  1383. package/src/tools/puppeteer/06_stealth_screen.txt +260 -0
  1384. package/src/tools/puppeteer/07_stealth_fonts.txt +99 -0
  1385. package/src/tools/puppeteer/08_stealth_audio.txt +63 -0
  1386. package/src/tools/puppeteer/09_stealth_locale.txt +51 -0
  1387. package/src/tools/puppeteer/10_stealth_plugins.txt +212 -0
  1388. package/src/tools/puppeteer/11_stealth_hardware.txt +59 -0
  1389. package/src/tools/puppeteer/12_stealth_codecs.txt +42 -0
  1390. package/src/tools/puppeteer/13_stealth_worker.txt +235 -0
  1391. package/src/tools/read.ts +3762 -0
  1392. package/src/tools/render-utils.ts +926 -0
  1393. package/src/tools/renderers.ts +133 -0
  1394. package/src/tools/report-tool-issue.ts +568 -0
  1395. package/src/tools/resolve.ts +423 -0
  1396. package/src/tools/review.ts +103 -0
  1397. package/src/tools/run-scope.ts +417 -0
  1398. package/src/tools/security-scan.ts +287 -0
  1399. package/src/tools/shell-tokenize.ts +213 -0
  1400. package/src/tools/sqlite-reader.ts +884 -0
  1401. package/src/tools/terminal-output.ts +141 -0
  1402. package/src/tools/todo.ts +1226 -0
  1403. package/src/tools/tool-errors.ts +62 -0
  1404. package/src/tools/tool-result.ts +102 -0
  1405. package/src/tools/tool-timeouts.ts +38 -0
  1406. package/src/tools/tts.ts +266 -0
  1407. package/src/tools/vibe.ts +608 -0
  1408. package/src/tools/write.ts +1646 -0
  1409. package/src/tools/xdev.ts +560 -0
  1410. package/src/tools/yield.ts +486 -0
  1411. package/src/tts/downloader.ts +64 -0
  1412. package/src/tts/index.ts +10 -0
  1413. package/src/tts/models.ts +137 -0
  1414. package/src/tts/runtime.ts +21 -0
  1415. package/src/tts/speakable.ts +392 -0
  1416. package/src/tts/speech-enhancer.ts +206 -0
  1417. package/src/tts/streaming-player.ts +120 -0
  1418. package/src/tts/tts-client.ts +475 -0
  1419. package/src/tts/tts-protocol.ts +69 -0
  1420. package/src/tts/tts-worker.ts +434 -0
  1421. package/src/tts/vocalizer.ts +419 -0
  1422. package/src/tts/wav.ts +58 -0
  1423. package/src/tui/code-cell.ts +268 -0
  1424. package/src/tui/file-list.ts +55 -0
  1425. package/src/tui/hyperlink.ts +178 -0
  1426. package/src/tui/index.ts +13 -0
  1427. package/src/tui/output-block.ts +268 -0
  1428. package/src/tui/status-line.ts +54 -0
  1429. package/src/tui/tree-list.ts +172 -0
  1430. package/src/tui/types.ts +15 -0
  1431. package/src/tui/utils.ts +103 -0
  1432. package/src/tui/width-aware-text.ts +58 -0
  1433. package/src/utils/active-repo-context.ts +143 -0
  1434. package/src/utils/block-context.ts +312 -0
  1435. package/src/utils/changelog.ts +373 -0
  1436. package/src/utils/clipboard.ts +361 -0
  1437. package/src/utils/command-args.ts +74 -0
  1438. package/src/utils/commit-message-generator.ts +148 -0
  1439. package/src/utils/cpuprofile.ts +235 -0
  1440. package/src/utils/edit-mode.ts +61 -0
  1441. package/src/utils/enhanced-paste.ts +230 -0
  1442. package/src/utils/event-bus.ts +33 -0
  1443. package/src/utils/external-editor.ts +80 -0
  1444. package/src/utils/fetch-timeout.ts +10 -0
  1445. package/src/utils/file-display-mode.ts +44 -0
  1446. package/src/utils/file-mentions.ts +293 -0
  1447. package/src/utils/git.ts +2472 -0
  1448. package/src/utils/image-loading.ts +236 -0
  1449. package/src/utils/image-resize.ts +420 -0
  1450. package/src/utils/image-vision-fallback.ts +196 -0
  1451. package/src/utils/inspect-image-mode.ts +39 -0
  1452. package/src/utils/ipc.ts +38 -0
  1453. package/src/utils/jj.ts +422 -0
  1454. package/src/utils/lang-from-path.ts +251 -0
  1455. package/src/utils/late-cleanup.ts +17 -0
  1456. package/src/utils/local-date.ts +7 -0
  1457. package/src/utils/mac-file-urls.applescript +37 -0
  1458. package/src/utils/markit-cache.ts +166 -0
  1459. package/src/utils/markit.ts +223 -0
  1460. package/src/utils/mupdf-wasm-embed.ts +12 -0
  1461. package/src/utils/open.ts +126 -0
  1462. package/src/utils/profile-tree.ts +111 -0
  1463. package/src/utils/prompt-path.ts +3 -0
  1464. package/src/utils/qrcode.ts +535 -0
  1465. package/src/utils/sample-profile.ts +437 -0
  1466. package/src/utils/session-color.ts +142 -0
  1467. package/src/utils/shell-snapshot-fn-env.sh +63 -0
  1468. package/src/utils/shell-snapshot.ts +326 -0
  1469. package/src/utils/sixel.ts +69 -0
  1470. package/src/utils/thinking-display.ts +163 -0
  1471. package/src/utils/title-generator.ts +597 -0
  1472. package/src/utils/token-rate.ts +72 -0
  1473. package/src/utils/tool-choice.ts +50 -0
  1474. package/src/utils/tools-manager.ts +411 -0
  1475. package/src/utils/turndown.ts +82 -0
  1476. package/src/utils/zip.ts +1106 -0
  1477. package/src/vibe/runtime.ts +1684 -0
  1478. package/src/vibe/state.ts +4 -0
  1479. package/src/web/kagi.ts +305 -0
  1480. package/src/web/parallel.ts +354 -0
  1481. package/src/web/scrapers/artifacthub.ts +207 -0
  1482. package/src/web/scrapers/arxiv.ts +83 -0
  1483. package/src/web/scrapers/aur.ts +162 -0
  1484. package/src/web/scrapers/biorxiv.ts +133 -0
  1485. package/src/web/scrapers/bluesky.ts +262 -0
  1486. package/src/web/scrapers/brew.ts +172 -0
  1487. package/src/web/scrapers/cheatsh.ts +68 -0
  1488. package/src/web/scrapers/chocolatey.ts +196 -0
  1489. package/src/web/scrapers/choosealicense.ts +95 -0
  1490. package/src/web/scrapers/cisa-kev.ts +87 -0
  1491. package/src/web/scrapers/clojars.ts +154 -0
  1492. package/src/web/scrapers/coingecko.ts +177 -0
  1493. package/src/web/scrapers/crates-io.ts +97 -0
  1494. package/src/web/scrapers/crossref.ts +136 -0
  1495. package/src/web/scrapers/devto.ts +147 -0
  1496. package/src/web/scrapers/discogs.ts +306 -0
  1497. package/src/web/scrapers/discourse.ts +197 -0
  1498. package/src/web/scrapers/dockerhub.ts +138 -0
  1499. package/src/web/scrapers/docs-rs.ts +663 -0
  1500. package/src/web/scrapers/fdroid.ts +134 -0
  1501. package/src/web/scrapers/firefox-addons.ts +191 -0
  1502. package/src/web/scrapers/flathub.ts +223 -0
  1503. package/src/web/scrapers/github-gist.ts +58 -0
  1504. package/src/web/scrapers/github.ts +800 -0
  1505. package/src/web/scrapers/gitlab.ts +401 -0
  1506. package/src/web/scrapers/go-pkg.ts +266 -0
  1507. package/src/web/scrapers/hackage.ts +129 -0
  1508. package/src/web/scrapers/hackernews.ts +189 -0
  1509. package/src/web/scrapers/hex.ts +105 -0
  1510. package/src/web/scrapers/huggingface.ts +321 -0
  1511. package/src/web/scrapers/iacr.ts +89 -0
  1512. package/src/web/scrapers/index.ts +252 -0
  1513. package/src/web/scrapers/jetbrains-marketplace.ts +159 -0
  1514. package/src/web/scrapers/lemmy.ts +203 -0
  1515. package/src/web/scrapers/lobsters.ts +175 -0
  1516. package/src/web/scrapers/mastodon.ts +292 -0
  1517. package/src/web/scrapers/maven.ts +138 -0
  1518. package/src/web/scrapers/mdn.ts +173 -0
  1519. package/src/web/scrapers/metacpan.ts +222 -0
  1520. package/src/web/scrapers/musicbrainz.ts +250 -0
  1521. package/src/web/scrapers/npm.ts +98 -0
  1522. package/src/web/scrapers/nuget.ts +183 -0
  1523. package/src/web/scrapers/nvd.ts +222 -0
  1524. package/src/web/scrapers/ollama.ts +239 -0
  1525. package/src/web/scrapers/open-vsx.ts +106 -0
  1526. package/src/web/scrapers/opencorporates.ts +292 -0
  1527. package/src/web/scrapers/openlibrary.ts +336 -0
  1528. package/src/web/scrapers/orcid.ts +286 -0
  1529. package/src/web/scrapers/osv.ts +176 -0
  1530. package/src/web/scrapers/packagist.ts +160 -0
  1531. package/src/web/scrapers/pub-dev.ts +143 -0
  1532. package/src/web/scrapers/pubmed.ts +211 -0
  1533. package/src/web/scrapers/pypi.ts +112 -0
  1534. package/src/web/scrapers/rawg.ts +110 -0
  1535. package/src/web/scrapers/readthedocs.ts +121 -0
  1536. package/src/web/scrapers/reddit.ts +95 -0
  1537. package/src/web/scrapers/repology.ts +251 -0
  1538. package/src/web/scrapers/rfc.ts +201 -0
  1539. package/src/web/scrapers/rubygems.ts +103 -0
  1540. package/src/web/scrapers/searchcode.ts +189 -0
  1541. package/src/web/scrapers/sec-edgar.ts +261 -0
  1542. package/src/web/scrapers/semantic-scholar.ts +171 -0
  1543. package/src/web/scrapers/snapcraft.ts +187 -0
  1544. package/src/web/scrapers/sourcegraph.ts +336 -0
  1545. package/src/web/scrapers/spdx.ts +108 -0
  1546. package/src/web/scrapers/spotify.ts +198 -0
  1547. package/src/web/scrapers/stackoverflow.ts +120 -0
  1548. package/src/web/scrapers/terraform.ts +277 -0
  1549. package/src/web/scrapers/tldr.ts +47 -0
  1550. package/src/web/scrapers/twitter.ts +94 -0
  1551. package/src/web/scrapers/types.ts +354 -0
  1552. package/src/web/scrapers/utils.ts +109 -0
  1553. package/src/web/scrapers/vimeo.ts +133 -0
  1554. package/src/web/scrapers/vscode-marketplace.ts +187 -0
  1555. package/src/web/scrapers/w3c.ts +156 -0
  1556. package/src/web/scrapers/wikidata.ts +344 -0
  1557. package/src/web/scrapers/wikipedia.ts +84 -0
  1558. package/src/web/scrapers/youtube.ts +325 -0
  1559. package/src/web/search/index.ts +393 -0
  1560. package/src/web/search/provider.ts +272 -0
  1561. package/src/web/search/providers/anthropic.ts +406 -0
  1562. package/src/web/search/providers/base.ts +112 -0
  1563. package/src/web/search/providers/brave.ts +181 -0
  1564. package/src/web/search/providers/browser-headers.ts +82 -0
  1565. package/src/web/search/providers/browser-page.ts +125 -0
  1566. package/src/web/search/providers/codex.ts +788 -0
  1567. package/src/web/search/providers/duckduckgo.ts +382 -0
  1568. package/src/web/search/providers/ecosia.ts +183 -0
  1569. package/src/web/search/providers/exa.ts +513 -0
  1570. package/src/web/search/providers/firecrawl.ts +213 -0
  1571. package/src/web/search/providers/gemini.ts +634 -0
  1572. package/src/web/search/providers/google.ts +195 -0
  1573. package/src/web/search/providers/jina.ts +144 -0
  1574. package/src/web/search/providers/kagi.ts +98 -0
  1575. package/src/web/search/providers/kimi.ts +221 -0
  1576. package/src/web/search/providers/mojeek.ts +220 -0
  1577. package/src/web/search/providers/parallel.ts +186 -0
  1578. package/src/web/search/providers/perplexity-auth.ts +142 -0
  1579. package/src/web/search/providers/perplexity.ts +997 -0
  1580. package/src/web/search/providers/public.ts +199 -0
  1581. package/src/web/search/providers/searxng.ts +467 -0
  1582. package/src/web/search/providers/startpage.ts +225 -0
  1583. package/src/web/search/providers/synthetic.ts +126 -0
  1584. package/src/web/search/providers/tavily.ts +244 -0
  1585. package/src/web/search/providers/tinyfish.ts +166 -0
  1586. package/src/web/search/providers/utils.ts +130 -0
  1587. package/src/web/search/providers/xai.ts +372 -0
  1588. package/src/web/search/providers/zai.ts +452 -0
  1589. package/src/web/search/query.ts +850 -0
  1590. package/src/web/search/render.ts +262 -0
  1591. package/src/web/search/types.ts +518 -0
  1592. package/src/web/search/utils.ts +17 -0
  1593. package/src/workspace-tree.ts +326 -0
@@ -0,0 +1,2394 @@
1
+ /**
2
+ * Settings singleton with sync get/set and background persistence.
3
+ *
4
+ * Usage:
5
+ * import { settings } from "./settings";
6
+ *
7
+ * const enabled = settings.get("compaction.enabled"); // sync read
8
+ * settings.set("theme.dark", "titanium"); // sync write, saves in background
9
+ *
10
+ * For tests:
11
+ * const isolated = Settings.isolated({ "compaction.enabled": false });
12
+ */
13
+
14
+ import { randomUUID } from "node:crypto";
15
+ import * as fs from "node:fs";
16
+ import * as os from "node:os";
17
+ import * as path from "node:path";
18
+ import { configureCredentialRedaction } from "@oh-my-pi/pi-ai/providers/transform-messages";
19
+ import { configureProviderMaxInFlightRequests } from "@oh-my-pi/pi-ai/stream";
20
+ import {
21
+ getAgentDbPath,
22
+ getAgentDir,
23
+ getLastChangelogVersionPath,
24
+ getProjectDir,
25
+ hasFsCode,
26
+ isEnoent,
27
+ logger,
28
+ MAIN_CONFIG_FILENAMES,
29
+ procmgr,
30
+ setWorktreesDir,
31
+ toError,
32
+ } from "@oh-my-pi/pi-utils";
33
+ import { withFileLock } from "@oh-my-pi/pi-utils/file-lock";
34
+ import { JSONC, YAML } from "bun";
35
+ import { invalidate as invalidateCapabilityFsCache } from "../capability/fs";
36
+ import { type Settings as SettingsCapabilityItem, settingsCapability } from "../capability/settings";
37
+ import type { ModelRole } from "../config/model-roles";
38
+ import { loadCapability } from "../discovery";
39
+ import { isLightTheme, setAutoThemeMapping, setColorBlindMode, setSymbolPreset } from "../modes/theme/theme";
40
+ import { AgentStorage } from "../session/agent-storage";
41
+ import { AUTO_IMAGE_PROVIDER_ORDER, isImageProviderId } from "../tools/image-providers";
42
+ import { type EditMode, normalizeEditMode } from "../utils/edit-mode";
43
+ import { INSPECT_IMAGE_MODES } from "../utils/inspect-image-mode";
44
+ import { isSearchProviderId, SEARCH_PROVIDER_ORDER } from "../web/search/types";
45
+ import {
46
+ type BashInterceptorRule,
47
+ type GroupPrefix,
48
+ type GroupTypeMap,
49
+ getDefault,
50
+ SETTINGS_SCHEMA,
51
+ type SettingPath,
52
+ type SettingValue,
53
+ } from "./settings-schema";
54
+
55
+ // Re-export types that callers need
56
+ export type * from "./settings-schema";
57
+ export * from "./settings-schema";
58
+
59
+ // ═══════════════════════════════════════════════════════════════════════════
60
+ // Types
61
+ // ═══════════════════════════════════════════════════════════════════════════
62
+
63
+ /** Raw settings object as stored in YAML */
64
+ export interface RawSettings {
65
+ [key: string]: unknown;
66
+ }
67
+
68
+ type YamlLoadResult =
69
+ | { kind: "missing" }
70
+ | { kind: "loaded"; settings: RawSettings }
71
+ | { kind: "invalid"; error: unknown; backupPath?: string }
72
+ | { kind: "unreadable"; error: unknown };
73
+
74
+ export interface SettingsOptions {
75
+ /** Current working directory for project settings discovery */
76
+ cwd?: string;
77
+ /** Agent directory for config.yml/config.yaml storage */
78
+ agentDir?: string;
79
+ /** Don't persist to disk (for tests) */
80
+ inMemory?: boolean;
81
+ /** Read config sources without opening storage or writing migrations */
82
+ readOnly?: boolean;
83
+ /** Initial overrides */
84
+ overrides?: Partial<Record<SettingPath, unknown>>;
85
+ /** Extra config.yml-style overlays loaded after global/project settings */
86
+ configFiles?: string[];
87
+ }
88
+
89
+ // ═══════════════════════════════════════════════════════════════════════════
90
+ // Path Utilities
91
+ // ═══════════════════════════════════════════════════════════════════════════
92
+
93
+ /**
94
+ * Get a nested value from an object by path segments.
95
+ */
96
+ function getByPath(obj: RawSettings, segments: readonly string[]): unknown {
97
+ let current: unknown = obj;
98
+ for (const segment of segments) {
99
+ if (current === null || current === undefined || typeof current !== "object") {
100
+ return undefined;
101
+ }
102
+ current = (current as Record<string, unknown>)[segment];
103
+ }
104
+ return current;
105
+ }
106
+
107
+ const SETTING_PATH_SEGMENTS: Record<SettingPath, readonly string[]> = Object.fromEntries(
108
+ (Object.keys(SETTINGS_SCHEMA) as SettingPath[]).map(settingPath => [settingPath, settingPath.split(".")]),
109
+ ) as unknown as Record<SettingPath, readonly string[]>;
110
+
111
+ /**
112
+ * Set a nested value in an object by path segments.
113
+ * Creates intermediate objects as needed.
114
+ */
115
+ function setByPath(obj: RawSettings, segments: string[], value: unknown): void {
116
+ let current = obj;
117
+ for (let i = 0; i < segments.length - 1; i++) {
118
+ const segment = segments[i];
119
+ if (!(segment in current) || typeof current[segment] !== "object" || current[segment] === null) {
120
+ current[segment] = {};
121
+ }
122
+ current = current[segment] as RawSettings;
123
+ }
124
+ current[segments[segments.length - 1]] = value;
125
+ }
126
+
127
+ export function normalizeProviderMaxInFlightRequests(value: unknown): Record<string, number> {
128
+ if (!value || typeof value !== "object" || Array.isArray(value)) return {};
129
+ const normalized: Record<string, number> = {};
130
+ for (const [provider, rawLimit] of Object.entries(value)) {
131
+ if (typeof rawLimit !== "number" || !Number.isFinite(rawLimit) || rawLimit <= 0) continue;
132
+ normalized[provider] = Math.max(1, Math.floor(rawLimit));
133
+ }
134
+ return normalized;
135
+ }
136
+
137
+ export function validateProviderMaxInFlightRequests(value: unknown): Record<string, number> {
138
+ if (!value || typeof value !== "object" || Array.isArray(value)) return {};
139
+ const invalidProviders: string[] = [];
140
+ const normalized: Record<string, number> = {};
141
+ for (const [provider, rawLimit] of Object.entries(value)) {
142
+ if (typeof rawLimit !== "number" || !Number.isFinite(rawLimit) || rawLimit <= 0) {
143
+ invalidProviders.push(provider);
144
+ continue;
145
+ }
146
+ normalized[provider] = Math.max(1, Math.floor(rawLimit));
147
+ }
148
+ if (invalidProviders.length > 0) {
149
+ throw new Error(`Provider request limits must be positive numbers: ${invalidProviders.join(", ")}`);
150
+ }
151
+ return normalized;
152
+ }
153
+
154
+ const PATH_SCOPED_ARRAY_SETTINGS = new Set<SettingPath>(["enabledModels", "disabledProviders"]);
155
+ type PathScopedStringArrayEntry = {
156
+ path?: unknown;
157
+ paths?: unknown;
158
+ pathPrefix?: unknown;
159
+ pathPrefixes?: unknown;
160
+ values?: unknown;
161
+ items?: unknown;
162
+ models?: unknown;
163
+ providers?: unknown;
164
+ };
165
+
166
+ function expandTilde(p: string): string {
167
+ return p === "~" ? os.homedir() : p.startsWith("~/") ? path.join(os.homedir(), p.slice(2)) : p;
168
+ }
169
+
170
+ function normalizePathPrefix(prefix: string): string {
171
+ return path.resolve(expandTilde(prefix));
172
+ }
173
+
174
+ function pathMatchesPrefix(cwd: string, prefix: string): boolean {
175
+ const relative = path.relative(normalizePathPrefix(prefix), path.resolve(cwd));
176
+ return relative === "" || (!!relative && !relative.startsWith("..") && !path.isAbsolute(relative));
177
+ }
178
+
179
+ function stringArrayFromUnknown(value: unknown): string[] {
180
+ if (typeof value === "string") return [value];
181
+ if (Array.isArray(value)) return value.filter((item): item is string => typeof item === "string");
182
+ return [];
183
+ }
184
+
185
+ function isRecord(value: unknown): value is Record<string, unknown> {
186
+ return !!value && typeof value === "object" && !Array.isArray(value);
187
+ }
188
+
189
+ /**
190
+ * Migrate a v17 leaf rename that used to nest under a boolean parent path
191
+ * (`dev.autoqa.consent` → `dev.autoqaConsent`, `todo.reminders.max` →
192
+ * `todo.remindersMax`). Pre-rename configs left the leaf beneath the parent,
193
+ * so the parent path resolved to an object and truthy checks like
194
+ * `isAutoQaEnabled` treated a consent-only container as "enabled".
195
+ *
196
+ * Handles nested (`{ parent: { leaf } }`) and quoted-dotted (`"parent.leaf"`)
197
+ * legacy sources. An explicit new key always wins; a separately configured
198
+ * boolean parent is preserved; an irrecoverable object-valued parent (only ever
199
+ * a container for the old leaf) is dropped so the schema default applies.
200
+ */
201
+ function migrateNestedLeafRename(
202
+ raw: RawSettings,
203
+ root: string,
204
+ parent: string,
205
+ oldLeaf: string,
206
+ newLeaf: string,
207
+ isLeafValue: (value: unknown) => boolean,
208
+ ): void {
209
+ const rootObj = isRecord(raw[root]) ? (raw[root] as Record<string, unknown>) : undefined;
210
+ const nestedParent = rootObj?.[parent];
211
+ const flatParent = raw[`${root}.${parent}`];
212
+ const oldParentPath = `${root}.${parent}`;
213
+
214
+ const candidates = [
215
+ rootObj?.[newLeaf],
216
+ raw[`${root}.${newLeaf}`],
217
+ isRecord(nestedParent) ? nestedParent[oldLeaf] : undefined,
218
+ raw[`${oldParentPath}.${oldLeaf}`],
219
+ ];
220
+ const resolvedLeaf = candidates.find(isLeafValue);
221
+
222
+ const recoveredParent =
223
+ typeof nestedParent === "boolean" ? nestedParent : typeof flatParent === "boolean" ? flatParent : undefined;
224
+
225
+ const ensureRoot = (): Record<string, unknown> => {
226
+ const current = raw[root];
227
+ if (isRecord(current)) return current;
228
+ const created: Record<string, unknown> = {};
229
+ raw[root] = created;
230
+ return created;
231
+ };
232
+
233
+ if (resolvedLeaf !== undefined) {
234
+ const target = ensureRoot();
235
+ if (!isLeafValue(target[newLeaf])) {
236
+ target[newLeaf] = resolvedLeaf;
237
+ }
238
+ }
239
+
240
+ // Strip legacy leaf sources (nested + flat dotted).
241
+ delete raw[`${oldParentPath}.${oldLeaf}`];
242
+ delete raw[`${root}.${newLeaf}`];
243
+ if (isRecord(raw[root]) && isRecord((raw[root] as Record<string, unknown>)[parent])) {
244
+ const parentObj = (raw[root] as Record<string, unknown>)[parent] as Record<string, unknown>;
245
+ delete parentObj[oldLeaf];
246
+ if (Object.keys(parentObj).length === 0) {
247
+ delete (raw[root] as Record<string, unknown>)[parent];
248
+ }
249
+ }
250
+
251
+ // The parent path must be a boolean or absent — never a leftover object.
252
+ if (recoveredParent !== undefined) {
253
+ const target = ensureRoot();
254
+ if (typeof target[parent] !== "boolean") {
255
+ target[parent] = recoveredParent;
256
+ }
257
+ } else if (isRecord(raw[root]) && isRecord((raw[root] as Record<string, unknown>)[parent])) {
258
+ delete (raw[root] as Record<string, unknown>)[parent];
259
+ }
260
+ delete raw[oldParentPath];
261
+ if (isRecord(raw[root]) && Object.keys(raw[root] as Record<string, unknown>).length === 0) {
262
+ delete raw[root];
263
+ }
264
+ }
265
+
266
+ function modelRoleValueFromUnknown(value: unknown): string | undefined {
267
+ if (typeof value === "string") return value;
268
+ if (!Array.isArray(value)) return undefined;
269
+
270
+ const entries = stringArrayFromUnknown(value);
271
+ return entries.length === value.length ? entries.join(",") : undefined;
272
+ }
273
+
274
+ type EditVariantEntry = {
275
+ patternLower: string;
276
+ mode: EditMode;
277
+ };
278
+
279
+ function resolvePathScopedStringArray(settingPath: SettingPath, value: unknown, cwd: string): string[] | undefined {
280
+ if (!PATH_SCOPED_ARRAY_SETTINGS.has(settingPath) || !Array.isArray(value)) return undefined;
281
+
282
+ const resolved: string[] = [];
283
+ for (const entry of value) {
284
+ if (typeof entry === "string") {
285
+ resolved.push(entry);
286
+ continue;
287
+ }
288
+ if (!entry || typeof entry !== "object" || Array.isArray(entry)) continue;
289
+
290
+ const scoped = entry as PathScopedStringArrayEntry;
291
+ const prefixes = [
292
+ ...stringArrayFromUnknown(scoped.path),
293
+ ...stringArrayFromUnknown(scoped.paths),
294
+ ...stringArrayFromUnknown(scoped.pathPrefix),
295
+ ...stringArrayFromUnknown(scoped.pathPrefixes),
296
+ ];
297
+ if (prefixes.length === 0 || !prefixes.some(prefix => pathMatchesPrefix(cwd, prefix))) continue;
298
+
299
+ const values =
300
+ settingPath === "enabledModels"
301
+ ? [
302
+ ...stringArrayFromUnknown(scoped.values),
303
+ ...stringArrayFromUnknown(scoped.items),
304
+ ...stringArrayFromUnknown(scoped.models),
305
+ ]
306
+ : [
307
+ ...stringArrayFromUnknown(scoped.values),
308
+ ...stringArrayFromUnknown(scoped.items),
309
+ ...stringArrayFromUnknown(scoped.providers),
310
+ ];
311
+ resolved.push(...values);
312
+ }
313
+
314
+ return resolved;
315
+ }
316
+
317
+ // ═══════════════════════════════════════════════════════════════════════════
318
+ // Settings Class
319
+ // ═══════════════════════════════════════════════════════════════════════════
320
+
321
+ export class Settings {
322
+ #configPath: string | null;
323
+ #cwd: string;
324
+ #agentDir: string;
325
+ #storage: AgentStorage | null = null;
326
+
327
+ #configFiles: string[] = [];
328
+ /** Global settings from config.yml/config.yaml */
329
+ #global: RawSettings = {};
330
+ /** Project settings from .claude/settings.yml etc */
331
+ #project: RawSettings = {};
332
+ /** Last successfully loaded native .omp/config.yml contents. */
333
+ #projectFileSettings: RawSettings = {};
334
+ /** Logical config paths whose malformed targets were moved aside. */
335
+ #quarantinedYamlTargets = new Map<string, string>();
336
+ /** Extra config.yml-style overlays passed by CLI */
337
+ #configOverlay: RawSettings = {};
338
+ /** Project settings file that most recently supplied shellPath. */
339
+ #projectShellPathSource: string | undefined;
340
+ /** Explicit config overlay that most recently supplied shellPath. */
341
+ #overlayShellPathSource: string | undefined;
342
+ /** Runtime overrides (not persisted) */
343
+ #overrides: RawSettings = {};
344
+ /** Merged view (global + project + overrides) */
345
+ #merged: RawSettings = {};
346
+ /** Cached resolved values from the merged view, including defaults/path scoping */
347
+ #resolvedCache = new Map<SettingPath, unknown>();
348
+ #editVariantCache: readonly EditVariantEntry[] | undefined;
349
+
350
+ /** Paths modified during this session (for partial save) */
351
+ #modified = new Set<string>();
352
+ /** Individual project model roles modified during this session */
353
+ #modifiedProjectModelRoles = new Set<string>();
354
+ /** Individual global model roles modified during this session (for partial save) */
355
+ #modifiedGlobalModelRoles = new Set<string>();
356
+ /**
357
+ * Original process-wide model-role overrides captured before a project edit
358
+ * temporarily replaced them via `#updateRuntimeModelRoleOverride`. Restored
359
+ * on `reloadForCwd` / `cloneForCwd` so destination projects never inherit the
360
+ * source-project value. Maps role → original override value (`undefined`
361
+ * when the role had no runtime override).
362
+ */
363
+ #savedRuntimeModelRoleOverrides = new Map<string, string | undefined>();
364
+
365
+ /** Legacy `lastChangelogVersion` captured from config.yml during migration (now a marker file). */
366
+ #legacyLastChangelogVersion?: string;
367
+
368
+ /** Pending save (debounced) */
369
+ #saveTimer?: NodeJS.Timeout;
370
+ #savePromise?: Promise<void>;
371
+ #projectSaveTimer?: NodeJS.Timeout;
372
+ #projectSavePromise?: Promise<void>;
373
+
374
+ /** Whether to persist changes */
375
+ #persist: boolean;
376
+
377
+ private constructor(options: SettingsOptions = {}) {
378
+ this.#cwd = path.normalize(options.cwd ?? getProjectDir());
379
+ this.#agentDir = path.normalize(options.agentDir ?? getAgentDir());
380
+ this.#configPath = options.inMemory ? null : path.join(this.#agentDir, MAIN_CONFIG_FILENAMES[0]);
381
+ const configFiles = process.env.PI_CONFIG_FILES?.split(path.delimiter).filter(Boolean) ?? [];
382
+ if (options.configFiles) configFiles.push(...options.configFiles);
383
+ this.#configFiles = configFiles.map(file => path.resolve(this.#cwd, expandTilde(file)));
384
+ this.#persist = !options.inMemory && options.readOnly !== true;
385
+ liveSettingsInstances.add(new WeakRef(this));
386
+
387
+ if (options.overrides) {
388
+ for (const [key, value] of Object.entries(options.overrides)) {
389
+ setByPath(this.#overrides, key.split("."), value);
390
+ }
391
+
392
+ this.#overrides = this.#migrateRawSettings(this.#overrides);
393
+ }
394
+ }
395
+
396
+ // ─────────────────────────────────────────────────────────────────────────
397
+ // Factory Methods
398
+ // ─────────────────────────────────────────────────────────────────────────
399
+
400
+ /**
401
+ * Initialize the global singleton.
402
+ * Call once at startup before accessing `settings`.
403
+ */
404
+ static init(options: SettingsOptions = {}): Promise<Settings> {
405
+ if (globalInstancePromise) return globalInstancePromise;
406
+
407
+ const instance = new Settings(options);
408
+ const promise = instance.#load();
409
+ globalInstancePromise = promise;
410
+
411
+ return promise.then(
412
+ instance => {
413
+ globalInstance = instance;
414
+ clearBoundSettingsMethods();
415
+ globalInstancePromise = Promise.resolve(instance);
416
+ return instance;
417
+ },
418
+ error => {
419
+ globalInstance = null;
420
+ globalInstancePromise = null;
421
+ clearBoundSettingsMethods();
422
+ throw error;
423
+ },
424
+ );
425
+ }
426
+
427
+ /**
428
+ * Load effective settings from config.yml and project providers without
429
+ * opening agent.db, migrating legacy settings, or writing marker files.
430
+ */
431
+ static loadReadOnly(options: SettingsOptions = {}): Promise<Settings> {
432
+ const instance = new Settings({ ...options, readOnly: true });
433
+ return instance.#loadReadOnly();
434
+ }
435
+
436
+ /**
437
+ * Load a persisted settings instance without touching the global singleton.
438
+ */
439
+ static loadIsolated(options: SettingsOptions = {}): Promise<Settings> {
440
+ const instance = new Settings(options);
441
+ return instance.#load();
442
+ }
443
+
444
+ /**
445
+ * Create an isolated instance for testing.
446
+ * Does not affect the global singleton.
447
+ */
448
+ static isolated(overrides: Partial<Record<SettingPath, unknown>> = {}): Settings {
449
+ const instance = new Settings({ inMemory: true, overrides });
450
+ instance.#rebuildMerged();
451
+ return instance;
452
+ }
453
+
454
+ /**
455
+ * Get the global singleton.
456
+ * Throws if not initialized.
457
+ */
458
+ static get instance(): Settings {
459
+ if (!globalInstance) {
460
+ throw new Error("Settings not initialized. Call Settings.init() first.");
461
+ }
462
+ return globalInstance;
463
+ }
464
+
465
+ // ─────────────────────────────────────────────────────────────────────────
466
+ // Core API
467
+ // ─────────────────────────────────────────────────────────────────────────
468
+
469
+ /**
470
+ * Get a setting value (sync).
471
+ * Returns the merged value from global + project + overrides, or the default.
472
+ */
473
+ get<P extends SettingPath>(path: P): SettingValue<P> {
474
+ if (this.#resolvedCache.has(path)) {
475
+ return this.#resolvedCache.get(path) as SettingValue<P>;
476
+ }
477
+
478
+ const value = getByPath(this.#merged, SETTING_PATH_SEGMENTS[path]);
479
+ const resolved =
480
+ value !== undefined ? (resolvePathScopedStringArray(path, value, this.#cwd) ?? value) : getDefault(path);
481
+ this.#resolvedCache.set(path, resolved);
482
+ return resolved as SettingValue<P>;
483
+ }
484
+
485
+ /**
486
+ * Whether `path` has an explicitly configured value (global config, project
487
+ * config, or runtime override) rather than falling back to the schema default.
488
+ */
489
+ isConfigured(path: SettingPath): boolean {
490
+ return getByPath(this.#merged, SETTING_PATH_SEGMENTS[path]) !== undefined;
491
+ }
492
+
493
+ /**
494
+ * Set a setting value (sync).
495
+ * Updates global settings and queues a background save.
496
+ * Triggers hooks for settings that have side effects.
497
+ */
498
+ set<P extends SettingPath>(path: P, value: SettingValue<P>): void {
499
+ const prev = this.get(path);
500
+ const segments = path.split(".");
501
+ setByPath(this.#global, segments, value);
502
+ this.#modified.add(path);
503
+ this.#rebuildMerged();
504
+ const next = this.get(path);
505
+ this.#queueSave();
506
+
507
+ // Trigger hook if exists
508
+ const hook = SETTING_HOOKS[path];
509
+ if (hook) {
510
+ hook(next, prev);
511
+ }
512
+ this.#fireEffectiveSettingChanged(path, next, prev);
513
+ }
514
+
515
+ /**
516
+ * Apply runtime overrides (not persisted).
517
+ */
518
+ override<P extends SettingPath>(path: P, value: SettingValue<P>): void {
519
+ if (path === "modelRoles") {
520
+ this.#savedRuntimeModelRoleOverrides.clear();
521
+ }
522
+ const prev = this.get(path);
523
+ const segments = path.split(".");
524
+ setByPath(this.#overrides, segments, value);
525
+ this.#rebuildMerged();
526
+ this.#fireEffectiveSettingChanged(path, this.get(path), prev);
527
+ }
528
+
529
+ /**
530
+ * Clear a runtime override.
531
+ */
532
+ clearOverride(path: SettingPath): void {
533
+ if (path === "modelRoles") {
534
+ this.#savedRuntimeModelRoleOverrides.clear();
535
+ }
536
+ const prev = this.get(path);
537
+ const segments = path.split(".");
538
+ let current = this.#overrides;
539
+ for (let i = 0; i < segments.length - 1; i++) {
540
+ const segment = segments[i];
541
+ if (!(segment in current)) return;
542
+ current = current[segment] as RawSettings;
543
+ }
544
+ delete current[segments[segments.length - 1]];
545
+ this.#rebuildMerged();
546
+ this.#fireEffectiveSettingChanged(path, this.get(path), prev);
547
+ }
548
+
549
+ #fireEffectiveSettingChanged(path: SettingPath, value: unknown, prev: unknown): void {
550
+ if (Object.is(value, prev)) return;
551
+ if (path === "statusLine.sessionAccent") {
552
+ statusLineSessionAccentSignal.fire();
553
+ }
554
+ if (path === "modelRoles") {
555
+ modelRolesSignal.fire();
556
+ }
557
+ }
558
+
559
+ /** Set once this instance is discarded; background saves become no-ops. */
560
+ #savesCancelled = false;
561
+
562
+ /**
563
+ * Drop pending debounced saves and refuse any further background writes.
564
+ * Used when an instance is being discarded (test teardown): an armed timer
565
+ * or a chained in-flight save on a dropped instance would otherwise fire
566
+ * later and race the successor's file locks.
567
+ */
568
+ cancelPendingSaves(): void {
569
+ this.#savesCancelled = true;
570
+ clearTimeout(this.#saveTimer);
571
+ this.#saveTimer = undefined;
572
+ clearTimeout(this.#projectSaveTimer);
573
+ this.#projectSaveTimer = undefined;
574
+ }
575
+
576
+ /**
577
+ * Flush any pending saves to disk.
578
+ * Call before exit to ensure all changes are persisted.
579
+ */
580
+ async flush(): Promise<void> {
581
+ if (this.#saveTimer) {
582
+ clearTimeout(this.#saveTimer);
583
+ this.#saveTimer = undefined;
584
+ }
585
+ if (this.#projectSaveTimer) {
586
+ clearTimeout(this.#projectSaveTimer);
587
+ this.#projectSaveTimer = undefined;
588
+ }
589
+ if (this.#savePromise) {
590
+ await this.#savePromise;
591
+ }
592
+ if (this.#projectSavePromise) {
593
+ await this.#projectSavePromise;
594
+ }
595
+ if (this.#modified.size > 0 || this.#modifiedGlobalModelRoles.size > 0) {
596
+ await this.#saveNow();
597
+ }
598
+ if (this.#modifiedProjectModelRoles.size > 0) {
599
+ await this.#saveProjectNow();
600
+ }
601
+ }
602
+
603
+ async cloneForCwd(cwd: string): Promise<Settings> {
604
+ const cloned = new Settings({
605
+ cwd,
606
+ agentDir: this.#agentDir,
607
+ inMemory: !this.#persist,
608
+ });
609
+ cloned.#storage = this.#storage;
610
+ cloned.#configPath = this.#configPath;
611
+ cloned.#global = structuredClone(this.#global);
612
+ cloned.#project = this.#persist ? await cloned.#loadProjectSettings() : structuredClone(this.#project);
613
+ if (!this.#persist) cloned.#projectShellPathSource = this.#projectShellPathSource;
614
+ cloned.#configFiles = [...this.#configFiles];
615
+ cloned.#configOverlay = structuredClone(this.#configOverlay);
616
+ cloned.#overlayShellPathSource = this.#overlayShellPathSource;
617
+ cloned.#overrides = this.#buildOriginalOverrides();
618
+ cloned.#rebuildMerged();
619
+ cloned.#fireAllHooks();
620
+ return cloned;
621
+ }
622
+
623
+ /**
624
+ * Re-scope this instance to a new working directory *in place*: reload the
625
+ * project layer (`.claude/settings.yml` etc.) from `cwd`, re-resolve
626
+ * path-scoped settings against it, and re-fire side-effect hooks (theme,
627
+ * symbols, tab width, …). Global settings and runtime overrides are preserved.
628
+ *
629
+ * Unlike {@link cloneForCwd}, this mutates the live instance, so every holder
630
+ * (the `settings` proxy, the active session, controllers) observes the new
631
+ * project scope without swapping references — used when the process changes
632
+ * directory mid-run (`/move`, cross-project resume). No-op when `cwd` is
633
+ * already the current scope.
634
+ */
635
+ async reloadForCwd(cwd: string): Promise<void> {
636
+ const normalized = path.normalize(cwd);
637
+ if (normalized === this.#cwd) return;
638
+ await this.flush();
639
+ this.#restoreRuntimeModelRoleOverrides();
640
+ const prevModelRoles = this.get("modelRoles");
641
+ this.#cwd = normalized;
642
+ if (this.#persist) {
643
+ this.#project = await this.#loadProjectSettings();
644
+ }
645
+ this.#rebuildMerged();
646
+ this.#fireEffectiveSettingChanged("modelRoles", this.get("modelRoles"), prevModelRoles);
647
+ this.#fireAllHooks();
648
+ }
649
+
650
+ // ─────────────────────────────────────────────────────────────────────────
651
+ // Accessors
652
+ // ─────────────────────────────────────────────────────────────────────────
653
+
654
+ getStorage(): AgentStorage | null {
655
+ return this.#storage;
656
+ }
657
+
658
+ getCwd(): string {
659
+ return this.#cwd;
660
+ }
661
+
662
+ getAgentDir(): string {
663
+ return this.#agentDir;
664
+ }
665
+
666
+ getPlansDirectory(): string {
667
+ return path.join(this.#agentDir, "plans");
668
+ }
669
+
670
+ /**
671
+ * Get shell configuration based on settings.
672
+ */
673
+ getShellConfig() {
674
+ const shell = this.get("shellPath");
675
+ let configSource = this.#configPath ?? path.join(this.#agentDir, MAIN_CONFIG_FILENAMES[0]);
676
+ if (Object.hasOwn(this.#project, "shellPath")) {
677
+ configSource = this.#projectShellPathSource ?? "the active project configuration";
678
+ }
679
+ if (Object.hasOwn(this.#configOverlay, "shellPath")) {
680
+ configSource = this.#overlayShellPathSource ?? "the active config overlay";
681
+ }
682
+ if (Object.hasOwn(this.#overrides, "shellPath")) {
683
+ configSource = "the runtime settings override";
684
+ }
685
+ return procmgr.getShellConfig(shell, { configSource });
686
+ }
687
+
688
+ /**
689
+ * Get all settings in a group with full type safety.
690
+ */
691
+ getGroup<G extends GroupPrefix>(prefix: G): GroupTypeMap[G] {
692
+ const result: Record<string, unknown> = {};
693
+ for (const key of Object.keys(SETTINGS_SCHEMA) as SettingPath[]) {
694
+ if (key.startsWith(`${prefix}.`)) {
695
+ const suffix = key.slice(prefix.length + 1);
696
+ result[suffix] = this.get(key);
697
+ }
698
+ }
699
+ return result as unknown as GroupTypeMap[G];
700
+ }
701
+
702
+ /**
703
+ * Get the edit variant for a specific model.
704
+ * Returns "patch", "replace", "hashline", "apply_patch", or null (use global default).
705
+ */
706
+ getEditVariantForModel(model: string | undefined): EditMode | null {
707
+ if (!model) return null;
708
+ const variants = this.#getEditVariantEntries();
709
+ if (variants.length === 0) return null;
710
+
711
+ const modelLower = model.toLowerCase();
712
+
713
+ for (let i = 0; i < variants.length; i++) {
714
+ const variant = variants[i];
715
+ if (modelLower.includes(variant.patternLower)) {
716
+ return variant.mode;
717
+ }
718
+ }
719
+ return null;
720
+ }
721
+
722
+ #getEditVariantEntries(): readonly EditVariantEntry[] {
723
+ if (this.#editVariantCache !== undefined) return this.#editVariantCache;
724
+
725
+ const value = getByPath(this.#merged, ["edit", "modelVariants"]);
726
+ if (!isRecord(value)) {
727
+ this.#editVariantCache = [];
728
+ return this.#editVariantCache;
729
+ }
730
+
731
+ const variants: EditVariantEntry[] = [];
732
+ for (const pattern in value) {
733
+ if (!Object.hasOwn(value, pattern)) continue;
734
+ const rawMode = value[pattern];
735
+ if (typeof rawMode !== "string") continue;
736
+ const mode = normalizeEditMode(rawMode);
737
+ if (mode) {
738
+ variants.push({ patternLower: pattern.toLowerCase(), mode });
739
+ }
740
+ }
741
+
742
+ this.#editVariantCache = variants;
743
+ return variants;
744
+ }
745
+
746
+ /**
747
+ * Get bash interceptor rules (typed accessor for complex array config).
748
+ */
749
+ getBashInterceptorRules(): BashInterceptorRule[] {
750
+ return this.get("bashInterceptor.patterns");
751
+ }
752
+
753
+ #modelRolesFromLayer(layer: RawSettings): Record<string, string> {
754
+ const value = getByPath(layer, ["modelRoles"]);
755
+ if (!isRecord(value)) return {};
756
+
757
+ const roles: Record<string, string> = {};
758
+ for (const role in value) {
759
+ if (!Object.hasOwn(value, role)) continue;
760
+ const modelId = modelRoleValueFromUnknown(value[role]);
761
+ if (modelId !== undefined) {
762
+ roles[role] = modelId;
763
+ }
764
+ }
765
+ return roles;
766
+ }
767
+
768
+ #modelRoleLayerOwns(layer: RawSettings, role: ModelRole | string): boolean {
769
+ const value = getByPath(layer, ["modelRoles"]);
770
+ if (!isRecord(value)) return false;
771
+ return Object.hasOwn(value, role);
772
+ }
773
+
774
+ /**
775
+ * Set the full `modelRoles` map on the runtime override layer without
776
+ * routing through the public {@link override} method. Internal callers
777
+ * (project edits, global fallback updates) use this so they can control
778
+ * capture invalidation independently of the whole-map replacement
779
+ * semantics that `override("modelRoles", …)` carries.
780
+ */
781
+ #setRuntimeModelRoleOverrides(next: Record<string, string>): void {
782
+ const prev = this.get("modelRoles");
783
+ setByPath(this.#overrides, ["modelRoles"], next);
784
+ this.#rebuildMerged();
785
+ this.#fireEffectiveSettingChanged("modelRoles", this.get("modelRoles"), prev);
786
+ }
787
+
788
+ #updateRuntimeModelRoleOverride(role: ModelRole | string, modelId: string | undefined): void {
789
+ const runtimeOverrides = getByPath(this.#overrides, ["modelRoles"]);
790
+ if (!isRecord(runtimeOverrides) || !Object.hasOwn(runtimeOverrides, role)) return;
791
+
792
+ const nextRuntimeOverride = this.#modelRolesFromLayer(this.#overrides);
793
+ if (modelId === undefined) {
794
+ delete nextRuntimeOverride[role];
795
+ } else {
796
+ nextRuntimeOverride[role] = modelId;
797
+ }
798
+ this.#setRuntimeModelRoleOverrides(nextRuntimeOverride);
799
+ }
800
+
801
+ /**
802
+ * Capture the original process-wide override for `role` the first time a
803
+ * project edit temporarily replaces it, so the original can be restored on
804
+ * cwd changes. Subsequent edits in the same cwd must not overwrite the
805
+ * first captured value.
806
+ */
807
+ #captureRuntimeModelRoleOverride(role: ModelRole | string): void {
808
+ if (this.#savedRuntimeModelRoleOverrides.has(role)) return;
809
+ const runtimeOverrides = getByPath(this.#overrides, ["modelRoles"]);
810
+ if (!isRecord(runtimeOverrides) || !Object.hasOwn(runtimeOverrides, role)) return;
811
+ this.#savedRuntimeModelRoleOverrides.set(role, this.#modelRolesFromLayer(this.#overrides)[role]);
812
+ }
813
+
814
+ /**
815
+ * Restore original process-wide model-role overrides that were temporarily
816
+ * replaced by project edits, mutating `#overrides` in place without
817
+ * rebuilding. All remaining captures are valid because superseding
818
+ * operations (late `overrideModelRoles`, global-mode `setModelRole`,
819
+ * whole-map `override`/`clearOverride`) invalidate the affected captures
820
+ * at the point of supersession. Caller is responsible for `#rebuildMerged()`.
821
+ */
822
+ #restoreRuntimeModelRoleOverrides(): void {
823
+ if (this.#savedRuntimeModelRoleOverrides.size === 0) return;
824
+ const runtimeRoles = getByPath(this.#overrides, ["modelRoles"]);
825
+ if (!isRecord(runtimeRoles)) {
826
+ this.#savedRuntimeModelRoleOverrides.clear();
827
+ return;
828
+ }
829
+ for (const [role, originalValue] of this.#savedRuntimeModelRoleOverrides) {
830
+ if (originalValue === undefined) {
831
+ delete runtimeRoles[role];
832
+ } else {
833
+ runtimeRoles[role] = originalValue;
834
+ }
835
+ }
836
+ this.#savedRuntimeModelRoleOverrides.clear();
837
+ }
838
+
839
+ /**
840
+ * Produce a deep copy of `#overrides` with original process-wide model-role
841
+ * overrides restored, for use by {@link cloneForCwd}. All remaining
842
+ * captures are valid (see {@link #restoreRuntimeModelRoleOverrides}).
843
+ * Does not mutate the current instance's `#overrides`.
844
+ */
845
+ #buildOriginalOverrides(): RawSettings {
846
+ if (this.#savedRuntimeModelRoleOverrides.size === 0) {
847
+ return structuredClone(this.#overrides);
848
+ }
849
+ const overrides = structuredClone(this.#overrides);
850
+ const runtimeRoles = getByPath(overrides, ["modelRoles"]);
851
+ if (!isRecord(runtimeRoles)) return overrides;
852
+ for (const [role, originalValue] of this.#savedRuntimeModelRoleOverrides) {
853
+ if (originalValue === undefined) {
854
+ delete runtimeRoles[role];
855
+ } else {
856
+ runtimeRoles[role] = originalValue;
857
+ }
858
+ }
859
+ return overrides;
860
+ }
861
+
862
+ #setProjectModelRoleValue(role: ModelRole | string, modelId: string | null): void {
863
+ const prev = this.get("modelRoles");
864
+ const projectRoles = getByPath(this.#project, ["modelRoles"]);
865
+ const current: Record<string, unknown> = isRecord(projectRoles) ? { ...projectRoles } : {};
866
+ current[role] = modelId;
867
+ setByPath(this.#project, ["modelRoles"], current);
868
+ this.#modifiedProjectModelRoles.add(role);
869
+ this.#rebuildMerged();
870
+ this.#fireEffectiveSettingChanged("modelRoles", this.get("modelRoles"), prev);
871
+ this.#queueProjectSave();
872
+ }
873
+
874
+ /**
875
+ * Set a model role (helper for modelRoles record). Passing `undefined`
876
+ * clears the role from the persisted record and any runtime override.
877
+ *
878
+ * In project storage mode, when a project edit has temporarily replaced
879
+ * the process-wide runtime override for `role` and that override is still
880
+ * active (the runtime slot currently matches the project value), the
881
+ * global-layer write must not rewrite that runtime slot — otherwise the
882
+ * global fallback would immediately shadow the still-configured project
883
+ * role. The global layer is still persisted; only the runtime override is
884
+ * left untouched. The guard is precise so that a later clear, a late
885
+ * `overrideModelRoles`, or a storage-mode transition does not leave a
886
+ * stale skip in place.
887
+ */
888
+ setModelRole(role: ModelRole | string, modelId: string | undefined): void {
889
+ const prev = this.get("modelRoles");
890
+ const current = this.#modelRolesFromLayer(this.#global);
891
+ if (modelId === undefined) {
892
+ delete current[role];
893
+ } else {
894
+ current[role] = modelId;
895
+ }
896
+ // Persist per-role rather than marking the whole `modelRoles` path
897
+ // modified: #saveNow merges only the changed role into the re-read
898
+ // file, so a concurrent external edit to a sibling role is not
899
+ // clobbered by this process's stale in-memory snapshot.
900
+ setByPath(this.#global, ["modelRoles"], current);
901
+ this.#modifiedGlobalModelRoles.add(role);
902
+ this.#rebuildMerged();
903
+ this.#queueSave();
904
+ this.#fireEffectiveSettingChanged("modelRoles", this.get("modelRoles"), prev);
905
+ if (this.isProjectModelRoleRuntimeOverrideActive(role)) {
906
+ return;
907
+ }
908
+ this.#savedRuntimeModelRoleOverrides.delete(role);
909
+ this.#updateRuntimeModelRoleOverride(role, modelId);
910
+ }
911
+
912
+ /**
913
+ * Whether `role`'s runtime override slot currently holds the temporary
914
+ * project-scoped value installed by a prior `setProjectModelRole`. Returns
915
+ * `false` when storage is not project-mode, no capture exists, or the
916
+ * project role was cleared. With explicit provenance invalidation, a
917
+ * surviving capture implies no external supersession occurred.
918
+ */
919
+ isProjectModelRoleRuntimeOverrideActive(role: ModelRole | string): boolean {
920
+ if (this.get("modelRoleStorage") !== "project") return false;
921
+ if (!this.#savedRuntimeModelRoleOverrides.has(role)) return false;
922
+ return !!this.getProjectModelRole(role);
923
+ }
924
+ /**
925
+ * Set a model role in the current project's settings layer.
926
+ */
927
+ setProjectModelRole(role: ModelRole | string, modelId: string): void {
928
+ this.#setProjectModelRoleValue(role, modelId);
929
+ this.#captureRuntimeModelRoleOverride(role);
930
+ this.#updateRuntimeModelRoleOverride(role, modelId);
931
+ }
932
+ /**
933
+ * Clear a model role from the current project's settings layer.
934
+ */
935
+ clearProjectModelRole(role: ModelRole | string): void {
936
+ this.#setProjectModelRoleValue(role, null);
937
+ this.#captureRuntimeModelRoleOverride(role);
938
+ this.#updateRuntimeModelRoleOverride(role, undefined);
939
+ }
940
+
941
+ /**
942
+ * Get a model role (helper for modelRoles record).
943
+ */
944
+ getModelRole(role: ModelRole | string): string | undefined {
945
+ const roles: unknown = this.get("modelRoles");
946
+ if (!isRecord(roles)) return undefined;
947
+ return modelRoleValueFromUnknown(roles[role]);
948
+ }
949
+ /**
950
+ * Get a model role from only the global settings layer.
951
+ */
952
+ getGlobalModelRole(role: ModelRole | string): string | undefined {
953
+ const modelId = this.#modelRolesFromLayer(this.#global)[role];
954
+ return modelId || undefined;
955
+ }
956
+
957
+ /**
958
+ * Get a model role from only the current project settings layer.
959
+ */
960
+ getProjectModelRole(role: ModelRole | string): string | undefined {
961
+ const modelId = this.#modelRolesFromLayer(this.#project)[role];
962
+ return modelId || undefined;
963
+ }
964
+
965
+ /**
966
+ * Report which layer actually supplies the effective model role across
967
+ * full merge precedence (runtime override → config overlay → project →
968
+ * global → default). Unlike {@link getModelRoleSource}, this accounts
969
+ * for runtime and config-overlay layers and detects ownership by key
970
+ * presence rather than normalized value, so a `null` tombstone in the
971
+ * overlay or runtime layer correctly blocks lower layers. The project
972
+ * layer is checked through {@link #projectSettingsForMerge} because a
973
+ * project null is a cleared value (falls back to global), not a
974
+ * tombstone.
975
+ */
976
+ getModelRoleProvenance(role: ModelRole | string): "runtime" | "overlay" | "project" | "global" | "default" {
977
+ if (this.#modelRoleLayerOwns(this.#overrides, role)) return "runtime";
978
+ if (this.#modelRoleLayerOwns(this.#configOverlay, role)) return "overlay";
979
+ if (this.#modelRoleLayerOwns(this.#projectSettingsForMerge(), role)) return "project";
980
+ if (this.#modelRoleLayerOwns(this.#global, role)) return "global";
981
+ return "default";
982
+ }
983
+
984
+ /**
985
+ * Get the persisted layer supplying a model role (project/global/default only).
986
+ */
987
+ getModelRoleSource(role: ModelRole | string): "project" | "global" | "default" {
988
+ if (this.getProjectModelRole(role)) return "project";
989
+ if (this.getGlobalModelRole(role)) return "global";
990
+ return "default";
991
+ }
992
+
993
+ /**
994
+ * Get all model roles (helper for modelRoles record).
995
+ */
996
+ getModelRoles(): ReadOnlyDict<string> {
997
+ const roles: unknown = this.get("modelRoles");
998
+ if (!isRecord(roles)) return {};
999
+
1000
+ const normalized: Record<string, string> = {};
1001
+ for (const role in roles) {
1002
+ if (!Object.hasOwn(roles, role)) continue;
1003
+ const modelId = modelRoleValueFromUnknown(roles[role]);
1004
+ if (modelId !== undefined) {
1005
+ normalized[role] = modelId;
1006
+ }
1007
+ }
1008
+ return normalized;
1009
+ }
1010
+
1011
+ /*
1012
+ * Override model roles (helper for modelRoles record).
1013
+ */
1014
+ overrideModelRoles(roles: ReadOnlyDict<string>): void {
1015
+ const next = this.#modelRolesFromLayer(this.#overrides);
1016
+ for (const [role, modelId] of Object.entries(roles)) {
1017
+ if (modelId) {
1018
+ next[role] = modelId;
1019
+ this.#savedRuntimeModelRoleOverrides.delete(role);
1020
+ }
1021
+ }
1022
+ this.#setRuntimeModelRoleOverrides(next);
1023
+ }
1024
+
1025
+ /**
1026
+ * Set disabled providers (for compatibility with discovery system).
1027
+ */
1028
+ setDisabledProviders(ids: string[]): void {
1029
+ this.set("disabledProviders", ids);
1030
+ }
1031
+
1032
+ // ─────────────────────────────────────────────────────────────────────────
1033
+ // Loading
1034
+ // ─────────────────────────────────────────────────────────────────────────
1035
+
1036
+ async #load(): Promise<Settings> {
1037
+ // Project settings discovery is independent of the persist chain, while
1038
+ // the persist steps themselves remain sequential. Wait for both branches
1039
+ // to settle so simultaneous failures produce one catchable error without
1040
+ // abandoning the other rejection.
1041
+ const [globalResult, projectResult] = await Promise.allSettled([
1042
+ this.#persist ? this.#loadGlobalSettings() : Promise.resolve(),
1043
+ this.#loadProjectSettings(),
1044
+ ]);
1045
+ if (globalResult.status === "rejected") throw globalResult.reason;
1046
+ if (projectResult.status === "rejected") throw projectResult.reason;
1047
+
1048
+ this.#project = projectResult.value;
1049
+ this.#configOverlay = await this.#loadConfigOverlays();
1050
+
1051
+ // Build merged view (global → project → overrides; project wins over global)
1052
+ this.#rebuildMerged();
1053
+ this.#fireAllHooks();
1054
+ return this;
1055
+ }
1056
+ async #loadGlobalSettings(): Promise<void> {
1057
+ this.#storage = await AgentStorage.open(getAgentDbPath(this.#agentDir));
1058
+ const existingConfig = await this.#loadExistingMainYaml();
1059
+ if (existingConfig) {
1060
+ this.#global = existingConfig;
1061
+ } else {
1062
+ await this.#migrateFromLegacy();
1063
+ this.#global = await this.#loadYaml(this.#configPath!);
1064
+ }
1065
+ await this.#seedLastChangelogVersionMarker();
1066
+ }
1067
+
1068
+ async #loadReadOnly(): Promise<Settings> {
1069
+ const [globalResult, projectResult] = await Promise.allSettled([
1070
+ this.#loadExistingMainYaml(),
1071
+ this.#loadProjectSettings(),
1072
+ ]);
1073
+ if (globalResult.status === "rejected") throw globalResult.reason;
1074
+ if (projectResult.status === "rejected") throw projectResult.reason;
1075
+ if (globalResult.value) {
1076
+ this.#global = globalResult.value;
1077
+ }
1078
+
1079
+ this.#project = projectResult.value;
1080
+ this.#configOverlay = await this.#loadConfigOverlays();
1081
+ this.#rebuildMerged();
1082
+ return this;
1083
+ }
1084
+
1085
+ async #loadYaml(filePath: string): Promise<RawSettings> {
1086
+ const loaded = await this.#loadYamlIfPresentForStartup(filePath);
1087
+ return loaded ?? {};
1088
+ }
1089
+
1090
+ async #loadYamlIfPresent(filePath: string): Promise<YamlLoadResult> {
1091
+ let content: string;
1092
+ try {
1093
+ content = await fs.promises.readFile(filePath, "utf8");
1094
+ } catch (error) {
1095
+ if (isEnoent(error)) return { kind: "missing" };
1096
+ return { kind: "unreadable", error };
1097
+ }
1098
+
1099
+ let parsed: unknown;
1100
+ try {
1101
+ parsed = YAML.parse(content);
1102
+ } catch (error) {
1103
+ return { kind: "invalid", error };
1104
+ }
1105
+ if (parsed === null || parsed === undefined) {
1106
+ return { kind: "loaded", settings: {} };
1107
+ }
1108
+ if (typeof parsed !== "object" || Array.isArray(parsed)) {
1109
+ return {
1110
+ kind: "invalid",
1111
+ error: new Error("Settings YAML must contain a mapping at the document root"),
1112
+ };
1113
+ }
1114
+ return { kind: "loaded", settings: this.#migrateRawSettings(parsed as RawSettings) };
1115
+ }
1116
+
1117
+ async #resolveYamlWritePath(filePath: string): Promise<string> {
1118
+ const quarantinedTarget = this.#quarantinedYamlTargets.get(filePath);
1119
+ if (quarantinedTarget) return quarantinedTarget;
1120
+ try {
1121
+ return await fs.promises.realpath(filePath);
1122
+ } catch (error) {
1123
+ if (!isEnoent(error)) throw error;
1124
+ }
1125
+
1126
+ // realpath fails for a dangling symlink. Resolve its immediate target so
1127
+ // recreating a quarantined config repairs the target without replacing
1128
+ // the user-managed link.
1129
+ try {
1130
+ const stat = await fs.promises.lstat(filePath);
1131
+ if (stat.isSymbolicLink()) {
1132
+ const target = await fs.promises.readlink(filePath);
1133
+ return path.resolve(path.dirname(filePath), target);
1134
+ }
1135
+ } catch (error) {
1136
+ if (!isEnoent(error)) throw error;
1137
+ }
1138
+ return path.resolve(filePath);
1139
+ }
1140
+
1141
+ async #withYamlWriteLock<T>(filePath: string, fn: (writePath: string) => Promise<T>): Promise<T> {
1142
+ const writePath = await this.#resolveYamlWritePath(filePath);
1143
+ return await withFileLock(writePath, async () => fn(writePath));
1144
+ }
1145
+
1146
+ async #loadYamlIfPresentForStartup(filePath: string): Promise<RawSettings | null> {
1147
+ const result = await this.#loadYamlIfPresent(filePath);
1148
+ if (result.kind !== "invalid" || !this.#persist) {
1149
+ return this.#unwrapYamlLoadResult(filePath, result);
1150
+ }
1151
+ return await this.#withYamlWriteLock(filePath, async writePath =>
1152
+ this.#loadYamlIfPresentForWriteLocked(filePath, writePath, true),
1153
+ );
1154
+ }
1155
+
1156
+ /**
1157
+ * Read a YAML settings file while its write lock is held. Invalid files are
1158
+ * moved aside before reporting failure, so a later write can never truncate
1159
+ * the only copy of the user's configuration.
1160
+ */
1161
+ async #loadYamlIfPresentForWriteLocked(
1162
+ filePath: string,
1163
+ writePath: string,
1164
+ rejectMissing = false,
1165
+ ): Promise<RawSettings | null> {
1166
+ let result = await this.#loadYamlIfPresent(writePath);
1167
+ if (result.kind === "missing" && rejectMissing) {
1168
+ throw new Error(
1169
+ `Settings config was invalid before locking and is now missing: ${filePath}; another process may have moved it aside`,
1170
+ );
1171
+ }
1172
+ if (result.kind === "invalid") {
1173
+ result = await this.#quarantineInvalidYamlLocked(writePath, result);
1174
+ this.#quarantinedYamlTargets.set(filePath, writePath);
1175
+ }
1176
+ return this.#unwrapYamlLoadResult(filePath, result);
1177
+ }
1178
+
1179
+ async #quarantineInvalidYamlLocked(
1180
+ filePath: string,
1181
+ result: Extract<YamlLoadResult, { kind: "invalid" }>,
1182
+ ): Promise<Extract<YamlLoadResult, { kind: "invalid" }>> {
1183
+ const backupPath = `${filePath}.broken-${Date.now()}-${process.pid}-${randomUUID()}`;
1184
+ try {
1185
+ await fs.promises.rename(filePath, backupPath);
1186
+ } catch (error) {
1187
+ throw new Error(
1188
+ `Settings config is invalid and could not be moved aside: ${filePath}; refusing to overwrite it: ${String(error)}`,
1189
+ );
1190
+ }
1191
+ logger.warn("Settings: moved invalid config aside", {
1192
+ path: filePath,
1193
+ backupPath,
1194
+ error: String(result.error),
1195
+ });
1196
+ return { ...result, backupPath };
1197
+ }
1198
+
1199
+ #unwrapYamlLoadResult(filePath: string, result: YamlLoadResult): RawSettings | null {
1200
+ switch (result.kind) {
1201
+ case "missing":
1202
+ return null;
1203
+ case "loaded":
1204
+ return result.settings;
1205
+ case "invalid":
1206
+ throw new Error(
1207
+ `Settings config is invalid: ${filePath}${result.backupPath ? ` (moved to ${result.backupPath})` : ""}: ${String(result.error)}`,
1208
+ );
1209
+ case "unreadable":
1210
+ throw new Error(`Failed to read settings config ${filePath}: ${String(result.error)}`);
1211
+ }
1212
+ }
1213
+
1214
+ async #loadExistingMainYaml(): Promise<RawSettings | null> {
1215
+ if (!this.#configPath) return null;
1216
+ for (const filename of MAIN_CONFIG_FILENAMES) {
1217
+ const configPath = path.join(this.#agentDir, filename);
1218
+ const loaded = await this.#loadYamlIfPresentForStartup(configPath);
1219
+ if (loaded) {
1220
+ this.#configPath = configPath;
1221
+ return loaded;
1222
+ }
1223
+ }
1224
+ this.#configPath = path.join(this.#agentDir, MAIN_CONFIG_FILENAMES[0]);
1225
+ return null;
1226
+ }
1227
+
1228
+ async #loadProjectSettings(): Promise<RawSettings> {
1229
+ this.#projectShellPathSource = undefined;
1230
+ let merged: RawSettings = {};
1231
+ try {
1232
+ const result = await loadCapability(settingsCapability.id, { cwd: this.#cwd });
1233
+ for (const item of result.items as SettingsCapabilityItem[]) {
1234
+ if (item.level === "project") {
1235
+ merged = this.#deepMerge(merged, item.data as RawSettings);
1236
+ if (Object.hasOwn(item.data, "shellPath")) this.#projectShellPathSource = item.path;
1237
+ }
1238
+ }
1239
+ } catch {
1240
+ this.#projectShellPathSource = undefined;
1241
+ // Capability discovery is best-effort; the native project config below
1242
+ // remains authoritative for its model-role layer and must not be hidden.
1243
+ }
1244
+ const projectConfigPath = path.join(this.#cwd, ".omp", "config.yml");
1245
+ const nativeProject = await this.#loadYaml(projectConfigPath);
1246
+ this.#projectFileSettings = structuredClone(nativeProject);
1247
+ const nativeModelRoles = getByPath(nativeProject, ["modelRoles"]);
1248
+ if (nativeModelRoles !== undefined) {
1249
+ merged = this.#deepMerge(merged, { modelRoles: nativeModelRoles });
1250
+ }
1251
+ return this.#migrateRawSettings(merged);
1252
+ }
1253
+
1254
+ async #loadConfigOverlays(): Promise<RawSettings> {
1255
+ this.#overlayShellPathSource = undefined;
1256
+ let merged: RawSettings = {};
1257
+ for (const filePath of this.#configFiles) {
1258
+ const overlay = await this.#loadOverlayYaml(filePath);
1259
+ merged = this.#deepMerge(merged, overlay);
1260
+ if (Object.hasOwn(overlay, "shellPath")) this.#overlayShellPathSource = filePath;
1261
+ }
1262
+ return merged;
1263
+ }
1264
+
1265
+ /**
1266
+ * Strict loader for explicit `--config` overlays: unlike `#loadYaml`,
1267
+ * missing or malformed files are hard errors so a typo'd path cannot
1268
+ * silently fall back to the persistent settings.
1269
+ */
1270
+ async #loadOverlayYaml(filePath: string): Promise<RawSettings> {
1271
+ let content: string;
1272
+ try {
1273
+ content = await Bun.file(filePath).text();
1274
+ } catch (error) {
1275
+ throw new Error(
1276
+ isEnoent(error)
1277
+ ? `Config overlay not found: ${filePath}`
1278
+ : `Failed to read config overlay ${filePath}: ${String(error)}`,
1279
+ );
1280
+ }
1281
+ let parsed: unknown;
1282
+ try {
1283
+ parsed = YAML.parse(content);
1284
+ } catch (error) {
1285
+ throw new Error(`Failed to parse config overlay ${filePath}: ${String(error)}`);
1286
+ }
1287
+ if (parsed === null || parsed === undefined) return {};
1288
+ if (typeof parsed !== "object" || Array.isArray(parsed)) {
1289
+ throw new Error(`Config overlay must be a YAML mapping: ${filePath}`);
1290
+ }
1291
+ return this.#migrateRawSettings(parsed as RawSettings);
1292
+ }
1293
+
1294
+ async #migrateFromLegacy(): Promise<void> {
1295
+ if (!this.#configPath) return;
1296
+
1297
+ let settings: RawSettings = {};
1298
+ let migrated = false;
1299
+
1300
+ // 1. Migrate from settings.json
1301
+ const settingsJsonPath = path.join(this.#agentDir, "settings.json");
1302
+ try {
1303
+ const parsed: unknown = JSONC.parse(await Bun.file(settingsJsonPath).text());
1304
+ if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
1305
+ settings = this.#deepMerge(settings, this.#migrateRawSettings(parsed as RawSettings));
1306
+ migrated = true;
1307
+ try {
1308
+ fs.renameSync(settingsJsonPath, `${settingsJsonPath}.bak`);
1309
+ } catch {}
1310
+ }
1311
+ } catch {}
1312
+
1313
+ // 2. Migrate from agent.db
1314
+ try {
1315
+ const dbSettings = this.#storage?.getSettings();
1316
+ if (dbSettings) {
1317
+ settings = this.#deepMerge(settings, this.#migrateRawSettings(dbSettings as RawSettings));
1318
+ migrated = true;
1319
+ }
1320
+ } catch {}
1321
+
1322
+ // 3. Write merged settings
1323
+ if (migrated && Object.keys(settings).length > 0) {
1324
+ try {
1325
+ await this.#writeYamlAtomically(this.#configPath, settings);
1326
+ logger.debug("Settings: migrated to config.yml", { path: this.#configPath });
1327
+ } catch {}
1328
+ }
1329
+ }
1330
+
1331
+ /** Apply schema migrations to raw settings */
1332
+ #migrateRawSettings(raw: RawSettings): RawSettings {
1333
+ // queueMode -> steeringMode
1334
+ if ("queueMode" in raw && !("steeringMode" in raw)) {
1335
+ raw.steeringMode = raw.queueMode;
1336
+ delete raw.queueMode;
1337
+ }
1338
+
1339
+ // lastChangelogVersion moved out of config.yml into the
1340
+ // <agentDir>/last-changelog-version marker file so version bumps no
1341
+ // longer dirty user-tracked configs. Capture for marker seeding (see
1342
+ // #seedLastChangelogVersionMarker), then strip the key — the next
1343
+ // config save drops it from disk.
1344
+ if (typeof raw.lastChangelogVersion === "string") {
1345
+ this.#legacyLastChangelogVersion ??= raw.lastChangelogVersion;
1346
+ }
1347
+ delete raw.lastChangelogVersion;
1348
+
1349
+ // collapseChangelog (boolean) -> startup.changelogMode (enum). Preserve
1350
+ // every explicit legacy choice while giving new installs the schema's
1351
+ // "summary" default: true -> summary, false -> expanded. A separately
1352
+ // configured new mode always wins.
1353
+ const startupObj = isRecord(raw.startup) ? (raw.startup as Record<string, unknown>) : undefined;
1354
+ const legacyCollapseChangelog = typeof raw.collapseChangelog === "boolean" ? raw.collapseChangelog : undefined;
1355
+ const flatChangelogMode = raw["startup.changelogMode"];
1356
+ const normalizedFlatChangelogMode =
1357
+ flatChangelogMode === "summary" || flatChangelogMode === "expanded" || flatChangelogMode === "hidden"
1358
+ ? flatChangelogMode
1359
+ : undefined;
1360
+ if (legacyCollapseChangelog !== undefined || normalizedFlatChangelogMode !== undefined) {
1361
+ if (!startupObj) {
1362
+ raw.startup = {};
1363
+ }
1364
+ const target = raw.startup as Record<string, unknown>;
1365
+ if (target.changelogMode === undefined) {
1366
+ target.changelogMode =
1367
+ normalizedFlatChangelogMode ??
1368
+ (legacyCollapseChangelog !== undefined ? (legacyCollapseChangelog ? "summary" : "expanded") : undefined);
1369
+ }
1370
+ }
1371
+ delete raw.collapseChangelog;
1372
+ delete raw["startup.changelogMode"];
1373
+
1374
+ // ask.timeout: ms -> seconds (if value > 1000, it's old ms format)
1375
+ if (raw.ask && typeof (raw.ask as Record<string, unknown>).timeout === "number") {
1376
+ const oldValue = (raw.ask as Record<string, unknown>).timeout as number;
1377
+ if (oldValue > 1000) {
1378
+ (raw.ask as Record<string, unknown>).timeout = Math.round(oldValue / 1000);
1379
+ }
1380
+ }
1381
+
1382
+ // Migrate old flat "theme" string to nested theme.dark/theme.light
1383
+ if (typeof raw.theme === "string") {
1384
+ const oldTheme = raw.theme;
1385
+ if (oldTheme === "light" || oldTheme === "dark") {
1386
+ // Built-in defaults — just remove, let new defaults apply
1387
+ delete raw.theme;
1388
+ } else {
1389
+ // Custom theme — detect luminance to place in correct slot
1390
+ const slot = isLightTheme(oldTheme) ? "light" : "dark";
1391
+ raw.theme = { [slot]: oldTheme };
1392
+ }
1393
+ }
1394
+
1395
+ // inspect_image.enabled (boolean) -> inspect_image.mode (enum). Explicit
1396
+ // user choices are preserved: true -> "on", false -> "off". Configs with
1397
+ // no legacy key get the new "auto" default, which hides the tool for
1398
+ // models with native image input. Handles nested and quoted-dotted
1399
+ // ("inspect_image.enabled") sources; the target is always the nested
1400
+ // form, which is the only shape the resolver reads.
1401
+ const inspectImageObj = isRecord(raw.inspect_image) ? (raw.inspect_image as Record<string, unknown>) : undefined;
1402
+ const legacyEnabled =
1403
+ typeof inspectImageObj?.enabled === "boolean"
1404
+ ? inspectImageObj.enabled
1405
+ : typeof raw["inspect_image.enabled"] === "boolean"
1406
+ ? (raw["inspect_image.enabled"] as boolean)
1407
+ : undefined;
1408
+ if (legacyEnabled !== undefined) {
1409
+ if (!inspectImageObj) {
1410
+ raw.inspect_image = {};
1411
+ }
1412
+ const target = raw.inspect_image as Record<string, unknown>;
1413
+ const flatMode = raw["inspect_image.mode"];
1414
+ if (target.mode === undefined) {
1415
+ // A quoted-dotted explicit mode wins over the legacy boolean but
1416
+ // must be normalized into the nested form the resolver reads.
1417
+ target.mode =
1418
+ typeof flatMode === "string" && (INSPECT_IMAGE_MODES as readonly string[]).includes(flatMode)
1419
+ ? flatMode
1420
+ : legacyEnabled
1421
+ ? "on"
1422
+ : "off";
1423
+ }
1424
+ delete target.enabled;
1425
+ delete raw["inspect_image.enabled"];
1426
+ delete raw["inspect_image.mode"];
1427
+ }
1428
+
1429
+ // task.isolation.enabled (boolean) -> task.isolation.mode (enum)
1430
+ const taskObj = raw.task as Record<string, unknown> | undefined;
1431
+ const isolationObj = taskObj?.isolation as Record<string, unknown> | undefined;
1432
+ if (isolationObj && "enabled" in isolationObj) {
1433
+ if (typeof isolationObj.enabled === "boolean") {
1434
+ isolationObj.mode = isolationObj.enabled ? "auto" : "none";
1435
+ }
1436
+ delete isolationObj.enabled;
1437
+ }
1438
+
1439
+ // task.simple: removed — the task tool no longer accepts a per-call
1440
+ // schema (workflows drive structured output via eval agent()) and the
1441
+ // batch/context shape is gated by task.batch instead.
1442
+ if (taskObj && "simple" in taskObj) {
1443
+ delete taskObj.simple;
1444
+ }
1445
+
1446
+ // task.eager / todo.eager: boolean -> enum (default | preferred | always).
1447
+ // `true` reproduced the previous "on" behavior, which is now `always`.
1448
+ if (taskObj && typeof taskObj.eager === "boolean") {
1449
+ taskObj.eager = taskObj.eager ? "always" : "default";
1450
+ }
1451
+ const todoObj = raw.todo as Record<string, unknown> | undefined;
1452
+ if (todoObj && typeof todoObj.eager === "boolean") {
1453
+ todoObj.eager = todoObj.eager ? "always" : "default";
1454
+ }
1455
+
1456
+ // task.isolation.mode: legacy values from before the pi-iso PAL refactor.
1457
+ // `worktree` was git worktree → now lives under `rcopy`. `fuse-overlay`
1458
+ // and `fuse-projfs` are now the platform-named `overlayfs` / `projfs`
1459
+ // kinds; the PAL falls back internally when the chosen one isn't
1460
+ // available, so we don't need the old TS-side platform guards.
1461
+ if (isolationObj && typeof isolationObj.mode === "string") {
1462
+ const legacy: Record<string, string> = {
1463
+ worktree: "rcopy",
1464
+ "fuse-overlay": "overlayfs",
1465
+ "fuse-projfs": "projfs",
1466
+ };
1467
+ const mapped = legacy[isolationObj.mode as string];
1468
+ if (mapped !== undefined) {
1469
+ isolationObj.mode = mapped;
1470
+ }
1471
+ }
1472
+
1473
+ // edit.mode: removed "atom" and "vim" variants map back to "hashline"
1474
+ const editObj = raw.edit as Record<string, unknown> | undefined;
1475
+ if (editObj) {
1476
+ if (editObj.mode === "atom" || editObj.mode === "vim") {
1477
+ editObj.mode = "hashline";
1478
+ }
1479
+ const modelVariants = editObj.modelVariants as Record<string, unknown> | undefined;
1480
+ if (modelVariants && typeof modelVariants === "object" && !Array.isArray(modelVariants)) {
1481
+ for (const [pattern, variant] of Object.entries(modelVariants)) {
1482
+ if (variant === "atom" || variant === "vim") {
1483
+ modelVariants[pattern] = "hashline";
1484
+ }
1485
+ }
1486
+ }
1487
+ }
1488
+ if (raw["edit.mode"] === "atom" || raw["edit.mode"] === "vim") {
1489
+ raw["edit.mode"] = "hashline";
1490
+ }
1491
+
1492
+ // compaction.strategy: removed local-model shake-summary mode; plain shake
1493
+ // keeps the same mechanical artifact-backed reduction without background CPU.
1494
+ const compactionObj = raw.compaction as Record<string, unknown> | undefined;
1495
+ if (compactionObj?.strategy === "shake-summary") {
1496
+ compactionObj.strategy = "shake";
1497
+ }
1498
+ if (raw["compaction.strategy"] === "shake-summary") {
1499
+ raw["compaction.strategy"] = "shake";
1500
+ }
1501
+
1502
+ // snapcompact.systemPrompt: boolean -> scoped enum.
1503
+ const snapcompactObj = raw.snapcompact as Record<string, unknown> | undefined;
1504
+ if (snapcompactObj && typeof snapcompactObj.systemPrompt === "boolean") {
1505
+ snapcompactObj.systemPrompt = snapcompactObj.systemPrompt ? "all" : "none";
1506
+ }
1507
+ if (typeof raw["snapcompact.systemPrompt"] === "boolean") {
1508
+ raw["snapcompact.systemPrompt"] = raw["snapcompact.systemPrompt"] ? "all" : "none";
1509
+ }
1510
+
1511
+ // inlineToolDescriptors: boolean -> enum (auto | on | off). The old
1512
+ // `true`/`false` mapped directly onto inline-on/inline-off, so preserve
1513
+ // the user's explicit choice; new installs get the `auto` default that
1514
+ // turns it on only for Gemini models.
1515
+ if (typeof raw.inlineToolDescriptors === "boolean") {
1516
+ raw.inlineToolDescriptors = raw.inlineToolDescriptors ? "on" : "off";
1517
+ }
1518
+
1519
+ // statusLine: rename "plan_mode" segment to "mode"
1520
+ const statusLineObj = raw.statusLine as Record<string, unknown> | undefined;
1521
+ if (statusLineObj) {
1522
+ for (const key of ["leftSegments", "rightSegments"] as const) {
1523
+ const segments = statusLineObj[key];
1524
+ if (Array.isArray(segments)) {
1525
+ statusLineObj[key] = segments.map(seg => (seg === "plan_mode" ? "mode" : seg));
1526
+ }
1527
+ }
1528
+ const segmentOptions = statusLineObj.segmentOptions as Record<string, unknown> | undefined;
1529
+ if (segmentOptions && "plan_mode" in segmentOptions && !("mode" in segmentOptions)) {
1530
+ segmentOptions.mode = segmentOptions.plan_mode;
1531
+ delete segmentOptions.plan_mode;
1532
+ }
1533
+ }
1534
+
1535
+ // providers.parallelFetch (boolean) replaced by the providers.fetch reader
1536
+ // priority enum. The new default ("auto") supersedes both old values —
1537
+ // Parallel is now a deep fallback in the auto chain rather than the first
1538
+ // choice — so drop the legacy key (flat and nested) and let the enum
1539
+ // default apply.
1540
+ const providersObj = raw.providers as Record<string, unknown> | undefined;
1541
+ if (providersObj && "parallelFetch" in providersObj) {
1542
+ delete providersObj.parallelFetch;
1543
+ }
1544
+ delete raw["providers.parallelFetch"];
1545
+
1546
+ // codexResets.autoRedeem: boolean -> tri-state enum.
1547
+ // Existing explicit false keeps the old "do not run" behavior; missing
1548
+ // config now falls through to the new "unset" default, which asks before
1549
+ // the first eligible spend.
1550
+ const codexResetsObj = raw.codexResets as Record<string, unknown> | undefined;
1551
+ if (codexResetsObj && typeof codexResetsObj.autoRedeem === "boolean") {
1552
+ codexResetsObj.autoRedeem = codexResetsObj.autoRedeem ? "yes" : "no";
1553
+ }
1554
+ if (typeof raw["codexResets.autoRedeem"] === "boolean") {
1555
+ raw["codexResets.autoRedeem"] = raw["codexResets.autoRedeem"] ? "yes" : "no";
1556
+ }
1557
+
1558
+ // Map legacy `memories.enabled` boolean to the explicit `memory.backend`
1559
+ // enum if the latter hasn't been set yet. Idempotent: subsequent
1560
+ // migrations are no-ops once memory.backend is materialised.
1561
+ const memoryBackendObj = raw.memory as Record<string, unknown> | undefined;
1562
+ const memoryBackendSet = memoryBackendObj && typeof memoryBackendObj.backend === "string";
1563
+ const memoriesObj = raw.memories as Record<string, unknown> | undefined;
1564
+ if (!memoryBackendSet && memoriesObj && typeof memoriesObj.enabled === "boolean") {
1565
+ const next = memoriesObj.enabled ? "local" : "off";
1566
+ const memoryRoot = (memoryBackendObj ?? {}) as Record<string, unknown>;
1567
+ memoryRoot.backend = next;
1568
+ raw.memory = memoryRoot;
1569
+ }
1570
+
1571
+ // Rename the legacy local `mnemosyne` memory backend to `mnemopi`.
1572
+ // - `memory.backend: "mnemosyne"` now selects the renamed backend.
1573
+ // - the top-level `mnemosyne` settings object becomes `mnemopi`.
1574
+ // Idempotent: skips the object move once `mnemopi` is materialised.
1575
+ if (memoryBackendObj && memoryBackendObj.backend === "mnemosyne") {
1576
+ memoryBackendObj.backend = "mnemopi";
1577
+ }
1578
+ if ("mnemosyne" in raw && !("mnemopi" in raw)) {
1579
+ raw.mnemopi = raw.mnemosyne;
1580
+ delete raw.mnemosyne;
1581
+ }
1582
+
1583
+ // hindsight: dynamicBankId/agentName -> scoping enum + bankId
1584
+ // - dynamicBankId=true → scoping="per-project" (closest semantic match;
1585
+ // the legacy `agent::project::channel::user` tuple was per-project in
1586
+ // practice — the channel/user env vars were rarely set).
1587
+ // - hindsight.agentName was only used as the agent slot in the legacy
1588
+ // dynamic tuple; if the user customised it we surface it as the new
1589
+ // bankId base when no explicit bankId is set.
1590
+ const hindsightObj = raw.hindsight as Record<string, unknown> | undefined;
1591
+ if (hindsightObj) {
1592
+ if ("dynamicBankId" in hindsightObj) {
1593
+ if (!("scoping" in hindsightObj) && hindsightObj.dynamicBankId === true) {
1594
+ hindsightObj.scoping = "per-project";
1595
+ }
1596
+ delete hindsightObj.dynamicBankId;
1597
+ }
1598
+ if ("agentName" in hindsightObj) {
1599
+ const agentName = hindsightObj.agentName;
1600
+ if (
1601
+ !("bankId" in hindsightObj) &&
1602
+ typeof agentName === "string" &&
1603
+ agentName.trim().length > 0 &&
1604
+ agentName !== "omp"
1605
+ ) {
1606
+ hindsightObj.bankId = agentName;
1607
+ }
1608
+ delete hindsightObj.agentName;
1609
+ }
1610
+ }
1611
+
1612
+ // power.preventIdleSleep / power.preventSystemSleep / power.declareUserActive
1613
+ // / power.preventDisplaySleep (four booleans) → power.sleepPrevention enum.
1614
+ // The enum is cumulative: each level adds the flags of all lower levels.
1615
+ // Migration picks the highest level whose condition is met, scanning from
1616
+ // most to least aggressive so a single enum value captures the old state.
1617
+ if (
1618
+ !("sleepPrevention" in ((raw.power as Record<string, unknown>) ?? {})) &&
1619
+ raw["power.sleepPrevention"] === undefined
1620
+ ) {
1621
+ const powerObj = raw.power as Record<string, unknown> | undefined;
1622
+ const getFlag = (key: string): boolean | undefined => {
1623
+ const nested = powerObj?.[key];
1624
+ const flat = raw[`power.${key}`];
1625
+ const value = nested ?? flat;
1626
+ return typeof value === "boolean" ? value : undefined;
1627
+ };
1628
+ const idle = getFlag("preventIdleSleep");
1629
+ const system = getFlag("preventSystemSleep");
1630
+ const user = getFlag("declareUserActive");
1631
+ const display = getFlag("preventDisplaySleep");
1632
+ const anySet = idle !== undefined || system !== undefined || user !== undefined || display !== undefined;
1633
+ if (anySet) {
1634
+ const mode = system || user ? "system" : display ? "display" : idle !== false ? "idle" : "off";
1635
+ const powerRoot = (powerObj ?? {}) as Record<string, unknown>;
1636
+ powerRoot.sleepPrevention = mode;
1637
+ raw.power = powerRoot;
1638
+ }
1639
+ // Clean up old keys (nested + flat)
1640
+ if (powerObj) {
1641
+ delete powerObj.preventIdleSleep;
1642
+ delete powerObj.preventSystemSleep;
1643
+ delete powerObj.declareUserActive;
1644
+ delete powerObj.preventDisplaySleep;
1645
+ }
1646
+ delete raw["power.preventIdleSleep"];
1647
+ delete raw["power.preventSystemSleep"];
1648
+ delete raw["power.declareUserActive"];
1649
+ delete raw["power.preventDisplaySleep"];
1650
+ }
1651
+
1652
+ // Migration for renamed settings grep.* and glob.* from search.* and find.*:
1653
+ // 1. Nested settings: find -> glob, search -> grep (per-property merge to avoid clobbering)
1654
+ const ensureRawObject = (key: "glob" | "grep"): Record<string, unknown> => {
1655
+ const current = raw[key];
1656
+ if (isRecord(current)) {
1657
+ return current;
1658
+ }
1659
+ const created: Record<string, unknown> = {};
1660
+ raw[key] = created;
1661
+ return created;
1662
+ };
1663
+
1664
+ if ("find" in raw) {
1665
+ const findObj = raw.find;
1666
+ if (isRecord(findObj)) {
1667
+ const globObj = ensureRawObject("glob");
1668
+ const findKeys: Array<"enabled"> = ["enabled"];
1669
+ for (const key of findKeys) {
1670
+ if (key in findObj && !(key in globObj)) {
1671
+ globObj[key] = findObj[key];
1672
+ }
1673
+ }
1674
+ }
1675
+ delete raw.find;
1676
+ }
1677
+
1678
+ if ("search" in raw) {
1679
+ const searchObj = raw.search;
1680
+ if (isRecord(searchObj)) {
1681
+ const grepObj = ensureRawObject("grep");
1682
+ const searchKeys: Array<"enabled" | "contextBefore" | "contextAfter"> = [
1683
+ "enabled",
1684
+ "contextBefore",
1685
+ "contextAfter",
1686
+ ];
1687
+ for (const key of searchKeys) {
1688
+ if (key in searchObj && !(key in grepObj)) {
1689
+ grepObj[key] = searchObj[key];
1690
+ }
1691
+ }
1692
+ }
1693
+ delete raw.search;
1694
+ }
1695
+
1696
+ // 2. Flat settings keys: map them to the proper nested target so get/set resolves them correctly
1697
+ if ("find.enabled" in raw) {
1698
+ const globObj = ensureRawObject("glob");
1699
+ if (!("enabled" in globObj)) {
1700
+ globObj.enabled = raw["find.enabled"];
1701
+ }
1702
+ delete raw["find.enabled"];
1703
+ }
1704
+ if ("search.enabled" in raw) {
1705
+ const grepObj = ensureRawObject("grep");
1706
+ if (!("enabled" in grepObj)) {
1707
+ grepObj.enabled = raw["search.enabled"];
1708
+ }
1709
+ delete raw["search.enabled"];
1710
+ }
1711
+ if ("search.contextBefore" in raw) {
1712
+ const grepObj = ensureRawObject("grep");
1713
+ if (!("contextBefore" in grepObj)) {
1714
+ grepObj.contextBefore = raw["search.contextBefore"];
1715
+ }
1716
+ delete raw["search.contextBefore"];
1717
+ }
1718
+ if ("search.contextAfter" in raw) {
1719
+ const grepObj = ensureRawObject("grep");
1720
+ if (!("contextAfter" in grepObj)) {
1721
+ grepObj.contextAfter = raw["search.contextAfter"];
1722
+ }
1723
+ delete raw["search.contextAfter"];
1724
+ }
1725
+
1726
+ // Also clean up any empty nested objects we might have created or left behind
1727
+ if (raw.glob && typeof raw.glob === "object" && Object.keys(raw.glob).length === 0) {
1728
+ delete raw.glob;
1729
+ }
1730
+ if (raw.grep && typeof raw.grep === "object" && Object.keys(raw.grep).length === 0) {
1731
+ delete raw.grep;
1732
+ }
1733
+ // readHashLines: removed. Hashline anchors are now driven solely by
1734
+ // edit.mode === "hashline"; the separate read toggle only ever produced
1735
+ // the incoherent "hashline edits without addressable anchors" state.
1736
+ delete raw.readHashLines;
1737
+
1738
+ // serviceTier (single enum with scoped openai-only/claude-only sentinels)
1739
+ // → per-family tier.openai/tier.anthropic/tier.google; serviceTierSubagent
1740
+ // → tier.subagent; serviceTierAdvisor → tier.advisor. `fastModeScope` is
1741
+ // dropped — per-family scoping is now expressed by the three tier settings.
1742
+ const tierObj = isRecord(raw.tier) ? raw.tier : {};
1743
+ let tierTouched = false;
1744
+ const setTier = (family: string, value: unknown): void => {
1745
+ if (value !== undefined && !(family in tierObj)) {
1746
+ tierObj[family] = value;
1747
+ tierTouched = true;
1748
+ }
1749
+ };
1750
+ if (typeof raw.serviceTier === "string") {
1751
+ switch (raw.serviceTier) {
1752
+ case "priority":
1753
+ setTier("openai", "priority");
1754
+ setTier("anthropic", "priority");
1755
+ setTier("google", "priority");
1756
+ break;
1757
+ case "openai-only":
1758
+ setTier("openai", "priority");
1759
+ break;
1760
+ case "claude-only":
1761
+ setTier("anthropic", "priority");
1762
+ break;
1763
+ case "auto":
1764
+ case "default":
1765
+ case "flex":
1766
+ case "scale":
1767
+ setTier("openai", raw.serviceTier);
1768
+ break;
1769
+ }
1770
+ delete raw.serviceTier;
1771
+ }
1772
+ const mapInheritTier = (value: unknown): unknown =>
1773
+ value === "openai-only" || value === "claude-only" ? "priority" : value;
1774
+ if ("serviceTierSubagent" in raw) {
1775
+ setTier("subagent", mapInheritTier(raw.serviceTierSubagent));
1776
+ delete raw.serviceTierSubagent;
1777
+ }
1778
+ if ("serviceTierAdvisor" in raw) {
1779
+ setTier("advisor", mapInheritTier(raw.serviceTierAdvisor));
1780
+ delete raw.serviceTierAdvisor;
1781
+ }
1782
+ if (tierTouched) raw.tier = tierObj;
1783
+ delete raw.fastModeScope;
1784
+
1785
+ // v17 renames that used to nest under a boolean parent path:
1786
+ // dev.autoqa.consent -> dev.autoqaConsent
1787
+ // todo.reminders.max -> todo.remindersMax
1788
+ migrateNestedLeafRename(
1789
+ raw,
1790
+ "dev",
1791
+ "autoqa",
1792
+ "consent",
1793
+ "autoqaConsent",
1794
+ value => value === "unset" || value === "granted" || value === "denied",
1795
+ );
1796
+ migrateNestedLeafRename(
1797
+ raw,
1798
+ "todo",
1799
+ "reminders",
1800
+ "max",
1801
+ "remindersMax",
1802
+ value => typeof value === "number" && Number.isFinite(value),
1803
+ );
1804
+
1805
+ // BM25 tool discovery removal: tools.discoveryMode / tools.essentialOverride /
1806
+ // mcp.discoveryMode / mcp.discoveryDefaultServers are gone with no
1807
+ // replacement (`tools.xdev` stays at its own default). Dead keys are
1808
+ // deleted so they stop lingering in config.yml.
1809
+ const toolsObj = raw.tools as Record<string, unknown> | undefined;
1810
+ if (toolsObj) {
1811
+ delete toolsObj.discoveryMode;
1812
+ delete toolsObj.essentialOverride;
1813
+ }
1814
+ delete raw["tools.discoveryMode"];
1815
+ delete raw["tools.essentialOverride"];
1816
+ const mcpObj = raw.mcp as Record<string, unknown> | undefined;
1817
+ if (mcpObj) {
1818
+ delete mcpObj.discoveryMode;
1819
+ delete mcpObj.discoveryDefaultServers;
1820
+ }
1821
+ delete raw["mcp.discoveryMode"];
1822
+ delete raw["mcp.discoveryDefaultServers"];
1823
+
1824
+ // providers.webSearch / providers.image (single preferred provider) →
1825
+ // providers.webSearchOrder / providers.imageOrder (priority lists). A
1826
+ // concrete legacy choice becomes the head of the new list with every
1827
+ // remaining provider appended in its built-in order, so the old
1828
+ // preference stays #1 and the fallback chain is written out explicitly.
1829
+ // "auto" (or an unknown id) just drops the key — the default chain.
1830
+ const providerPrefsObj = raw.providers as Record<string, unknown> | undefined;
1831
+ const migrateProviderPreference = (
1832
+ legacyKey: string,
1833
+ orderKey: string,
1834
+ expand: (value: string) => string[] | undefined,
1835
+ ): void => {
1836
+ const flatLegacyKey = `providers.${legacyKey}`;
1837
+ const legacy = providerPrefsObj?.[legacyKey] ?? raw[flatLegacyKey];
1838
+ if (legacy === undefined) return;
1839
+ const existingOrder = providerPrefsObj?.[orderKey] ?? raw[`providers.${orderKey}`];
1840
+ const orderAlreadySet = Array.isArray(existingOrder) && existingOrder.length > 0;
1841
+ if (!orderAlreadySet && typeof legacy === "string") {
1842
+ const expanded = expand(legacy);
1843
+ if (expanded) {
1844
+ const root = providerPrefsObj ?? {};
1845
+ root[orderKey] = expanded;
1846
+ raw.providers = root;
1847
+ }
1848
+ }
1849
+ if (providerPrefsObj) delete providerPrefsObj[legacyKey];
1850
+ delete raw[flatLegacyKey];
1851
+ };
1852
+ migrateProviderPreference("webSearch", "webSearchOrder", value =>
1853
+ value !== "auto" && isSearchProviderId(value)
1854
+ ? [value, ...SEARCH_PROVIDER_ORDER.filter(id => id !== value)]
1855
+ : undefined,
1856
+ );
1857
+ migrateProviderPreference("image", "imageOrder", value =>
1858
+ value !== "auto" && isImageProviderId(value)
1859
+ ? [value, ...AUTO_IMAGE_PROVIDER_ORDER.filter(id => id !== value)]
1860
+ : undefined,
1861
+ );
1862
+
1863
+ return raw;
1864
+ }
1865
+
1866
+ /**
1867
+ * One-time migration: seed the last-changelog-version marker file from the
1868
+ * legacy config.yml key. An existing marker always wins — it is the newer
1869
+ * source of truth.
1870
+ */
1871
+ async #seedLastChangelogVersionMarker(): Promise<void> {
1872
+ const legacy = this.#legacyLastChangelogVersion;
1873
+ if (!legacy) return;
1874
+ const markerPath = getLastChangelogVersionPath(this.#agentDir);
1875
+ try {
1876
+ if ((await Bun.file(markerPath).text()).trim()) return;
1877
+ } catch (error) {
1878
+ if (!isEnoent(error)) return;
1879
+ }
1880
+ try {
1881
+ await Bun.write(markerPath, legacy);
1882
+ } catch (error) {
1883
+ logger.warn("Settings: failed to seed last-changelog-version marker", { error: String(error) });
1884
+ }
1885
+ }
1886
+
1887
+ // ─────────────────────────────────────────────────────────────────────────
1888
+ // Saving
1889
+ // ─────────────────────────────────────────────────────────────────────────
1890
+
1891
+ async #writeYamlAtomically(filePath: string, settings: RawSettings): Promise<void> {
1892
+ const tempPath = `${filePath}.${process.pid}.${randomUUID()}.tmp`;
1893
+ let removeTemp = false;
1894
+ try {
1895
+ const handle = await fs.promises.open(tempPath, "wx", 0o600);
1896
+ removeTemp = true;
1897
+ try {
1898
+ await handle.writeFile(YAML.stringify(settings, null, 2), "utf8");
1899
+ await handle.sync();
1900
+ } finally {
1901
+ await handle.close();
1902
+ }
1903
+ try {
1904
+ await fs.promises.rename(tempPath, filePath);
1905
+ } catch (error) {
1906
+ if (!hasFsCode(error, "EPERM")) throw error;
1907
+ await this.#replaceYamlAfterEperm(tempPath, filePath, error);
1908
+ }
1909
+ removeTemp = false;
1910
+ } finally {
1911
+ if (removeTemp) {
1912
+ await fs.promises.rm(tempPath, { force: true }).catch(() => {});
1913
+ }
1914
+ }
1915
+ }
1916
+ async #replaceYamlAfterEperm(tempPath: string, filePath: string, renameError: unknown): Promise<void> {
1917
+ const backupPath = `${filePath}.${process.pid}.${randomUUID()}.bak`;
1918
+ try {
1919
+ await fs.promises.rename(filePath, backupPath);
1920
+ } catch (error) {
1921
+ if (isEnoent(error)) {
1922
+ await fs.promises.rename(tempPath, filePath);
1923
+ return;
1924
+ }
1925
+ throw renameError;
1926
+ }
1927
+
1928
+ try {
1929
+ await fs.promises.rename(tempPath, filePath);
1930
+ } catch (replaceError) {
1931
+ try {
1932
+ await fs.promises.rename(backupPath, filePath);
1933
+ } catch (rollbackError) {
1934
+ throw new Error(
1935
+ `Failed to replace settings file after EPERM (original: ${toError(renameError).message}; retry: ${
1936
+ toError(replaceError).message
1937
+ }; rollback: ${toError(rollbackError).message})`,
1938
+ { cause: toError(renameError) },
1939
+ );
1940
+ }
1941
+ throw replaceError;
1942
+ }
1943
+
1944
+ try {
1945
+ await fs.promises.rm(backupPath);
1946
+ } catch (error) {
1947
+ if (!isEnoent(error)) {
1948
+ logger.warn("Settings: failed to remove atomic-write backup", {
1949
+ path: filePath,
1950
+ backupPath,
1951
+ error: toError(error).message,
1952
+ });
1953
+ }
1954
+ }
1955
+ }
1956
+
1957
+ #queueSave(): void {
1958
+ if (!this.#persist || !this.#configPath) return;
1959
+
1960
+ // Debounce: wait 100ms for more changes
1961
+ clearTimeout(this.#saveTimer);
1962
+ this.#saveTimer = setTimeout(() => {
1963
+ this.#saveTimer = undefined;
1964
+ const previousSave = this.#savePromise;
1965
+ const savePromise = previousSave ? previousSave.then(() => this.#saveNow()) : this.#saveNow();
1966
+ this.#savePromise = savePromise;
1967
+ savePromise
1968
+ .catch(err => {
1969
+ logger.warn("Settings: background save failed", { error: String(err) });
1970
+ })
1971
+ .finally(() => {
1972
+ if (this.#savePromise === savePromise) {
1973
+ this.#savePromise = undefined;
1974
+ }
1975
+ });
1976
+ }, 100);
1977
+ }
1978
+
1979
+ async #saveNow(): Promise<void> {
1980
+ if (this.#savesCancelled || !this.#persist || !this.#configPath) return;
1981
+ if (this.#modified.size === 0 && this.#modifiedGlobalModelRoles.size === 0) return;
1982
+
1983
+ const configPath = this.#configPath;
1984
+ const modifiedPaths = [...this.#modified];
1985
+ const modifiedModelRoles = [...this.#modifiedGlobalModelRoles];
1986
+ const globalRolesAtStart = this.#modelRolesFromLayer(this.#global);
1987
+ this.#modified.clear();
1988
+ this.#modifiedGlobalModelRoles.clear();
1989
+
1990
+ try {
1991
+ await this.#withYamlWriteLock(configPath, async writePath => {
1992
+ // Re-read to preserve external changes. If this instance moved a
1993
+ // malformed file aside, recover from its last in-memory state
1994
+ // rather than recreating the config from only the pending path.
1995
+ const loaded = await this.#loadYamlIfPresentForWriteLocked(configPath, writePath);
1996
+ const current =
1997
+ loaded ?? (this.#quarantinedYamlTargets.has(configPath) ? structuredClone(this.#global) : {});
1998
+
1999
+ // Apply only our modified whole-value paths
2000
+ for (const modPath of modifiedPaths) {
2001
+ const segments = modPath.split(".");
2002
+ const value = getByPath(this.#global, segments);
2003
+ setByPath(current, segments, value);
2004
+ }
2005
+
2006
+ // Merge only the model roles captured by this save. Then retain
2007
+ // any role changed while the async read/lock was pending before
2008
+ // replacing #global, so the follow-up save still sees its value.
2009
+ const latestGlobalRoles = this.#modelRolesFromLayer(this.#global);
2010
+ const rolesToPreserve = new Set(this.#modifiedGlobalModelRoles);
2011
+ for (const role in globalRolesAtStart) {
2012
+ if (globalRolesAtStart[role] !== latestGlobalRoles[role]) {
2013
+ rolesToPreserve.add(role);
2014
+ }
2015
+ }
2016
+ for (const role in latestGlobalRoles) {
2017
+ if (globalRolesAtStart[role] !== latestGlobalRoles[role]) {
2018
+ rolesToPreserve.add(role);
2019
+ }
2020
+ }
2021
+ if (modifiedModelRoles.length > 0 || rolesToPreserve.size > 0) {
2022
+ const currentRoles = getByPath(current, ["modelRoles"]);
2023
+ const mergedRoles: Record<string, unknown> = isRecord(currentRoles) ? { ...currentRoles } : {};
2024
+ for (const role of modifiedModelRoles) {
2025
+ if (Object.hasOwn(globalRolesAtStart, role)) {
2026
+ mergedRoles[role] = globalRolesAtStart[role];
2027
+ } else {
2028
+ delete mergedRoles[role];
2029
+ }
2030
+ }
2031
+ for (const role of rolesToPreserve) {
2032
+ if (Object.hasOwn(latestGlobalRoles, role)) {
2033
+ mergedRoles[role] = latestGlobalRoles[role];
2034
+ } else {
2035
+ delete mergedRoles[role];
2036
+ }
2037
+ }
2038
+ setByPath(current, ["modelRoles"], mergedRoles);
2039
+ }
2040
+
2041
+ // Update our global with any external changes we preserved
2042
+ this.#global = current;
2043
+ await this.#writeYamlAtomically(writePath, this.#global);
2044
+ this.#quarantinedYamlTargets.delete(configPath);
2045
+ // These pending roles were included in this write. Remove each
2046
+ // only if no newer local change arrived while the write was in flight.
2047
+ const globalRolesAfterWrite = this.#modelRolesFromLayer(this.#global);
2048
+ for (const role of rolesToPreserve) {
2049
+ if (latestGlobalRoles[role] === globalRolesAfterWrite[role]) {
2050
+ this.#modifiedGlobalModelRoles.delete(role);
2051
+ }
2052
+ }
2053
+ });
2054
+ } catch (error) {
2055
+ logger.warn("Settings: save failed", { error: String(error) });
2056
+ // Re-add failed paths for retry
2057
+ for (const p of modifiedPaths) {
2058
+ this.#modified.add(p);
2059
+ }
2060
+ for (const role of modifiedModelRoles) {
2061
+ this.#modifiedGlobalModelRoles.add(role);
2062
+ }
2063
+ this.#rebuildMerged();
2064
+ throw error;
2065
+ }
2066
+
2067
+ this.#rebuildMerged();
2068
+ }
2069
+ #queueProjectSave(): void {
2070
+ if (!this.#persist) return;
2071
+
2072
+ clearTimeout(this.#projectSaveTimer);
2073
+ this.#projectSaveTimer = setTimeout(() => {
2074
+ this.#projectSaveTimer = undefined;
2075
+ const savePromise = this.#saveProjectNow();
2076
+ this.#projectSavePromise = savePromise;
2077
+ savePromise
2078
+ .catch(err => {
2079
+ logger.warn("Settings: background project save failed", { error: String(err) });
2080
+ })
2081
+ .finally(() => {
2082
+ if (this.#projectSavePromise === savePromise) {
2083
+ this.#projectSavePromise = undefined;
2084
+ }
2085
+ });
2086
+ }, 100);
2087
+ }
2088
+
2089
+ async #saveProjectNow(): Promise<void> {
2090
+ if (this.#savesCancelled || !this.#persist || this.#modifiedProjectModelRoles.size === 0) return;
2091
+
2092
+ const projectConfigPath = path.join(this.#cwd, ".omp", "config.yml");
2093
+ const modifiedModelRoles = [...this.#modifiedProjectModelRoles];
2094
+ this.#modifiedProjectModelRoles.clear();
2095
+
2096
+ try {
2097
+ await fs.promises.mkdir(path.dirname(projectConfigPath), { recursive: true });
2098
+ await this.#withYamlWriteLock(projectConfigPath, async writePath => {
2099
+ const loaded = await this.#loadYamlIfPresentForWriteLocked(projectConfigPath, writePath);
2100
+ const projectSettings =
2101
+ loaded ??
2102
+ (this.#quarantinedYamlTargets.has(projectConfigPath) ? structuredClone(this.#projectFileSettings) : {});
2103
+
2104
+ const projectRoles = getByPath(this.#project, ["modelRoles"]);
2105
+ for (const role of modifiedModelRoles) {
2106
+ const value = isRecord(projectRoles) ? projectRoles[role] : undefined;
2107
+ setByPath(projectSettings, ["modelRoles", role], value);
2108
+ }
2109
+
2110
+ await this.#writeYamlAtomically(writePath, projectSettings);
2111
+ this.#projectFileSettings = structuredClone(projectSettings);
2112
+ this.#quarantinedYamlTargets.delete(projectConfigPath);
2113
+ });
2114
+ invalidateCapabilityFsCache(projectConfigPath);
2115
+ } catch (error) {
2116
+ for (const role of modifiedModelRoles) {
2117
+ this.#modifiedProjectModelRoles.add(role);
2118
+ }
2119
+ throw error;
2120
+ }
2121
+
2122
+ this.#rebuildMerged();
2123
+ }
2124
+
2125
+ // ─────────────────────────────────────────────────────────────────────────
2126
+ // Utilities
2127
+ // ─────────────────────────────────────────────────────────────────────────
2128
+
2129
+ #projectSettingsForMerge(): RawSettings {
2130
+ const projectRoles = getByPath(this.#project, ["modelRoles"]);
2131
+ if (!isRecord(projectRoles)) return this.#project;
2132
+
2133
+ let filteredRoles: Record<string, unknown> | undefined;
2134
+ for (const role in projectRoles) {
2135
+ if (!Object.hasOwn(projectRoles, role) || modelRoleValueFromUnknown(projectRoles[role]) !== undefined)
2136
+ continue;
2137
+ filteredRoles ??= { ...projectRoles };
2138
+ delete filteredRoles[role];
2139
+ }
2140
+ return filteredRoles ? { ...this.#project, modelRoles: filteredRoles } : this.#project;
2141
+ }
2142
+
2143
+ #rebuildMerged(): void {
2144
+ this.#merged = this.#deepMerge(this.#deepMerge({}, this.#global), this.#projectSettingsForMerge());
2145
+ this.#merged = this.#deepMerge(this.#merged, this.#configOverlay);
2146
+ this.#merged = this.#deepMerge(this.#merged, this.#overrides);
2147
+ this.#resolvedCache.clear();
2148
+ this.#editVariantCache = undefined;
2149
+ }
2150
+
2151
+ #fireAllHooks(): void {
2152
+ for (const key of Object.keys(SETTING_HOOKS) as SettingPath[]) {
2153
+ const hook = SETTING_HOOKS[key];
2154
+ if (hook) {
2155
+ const value = this.get(key);
2156
+ hook(value, value);
2157
+ }
2158
+ }
2159
+ }
2160
+
2161
+ #deepMerge(base: RawSettings, overrides: RawSettings): RawSettings {
2162
+ const result = { ...base };
2163
+ for (const key of Object.keys(overrides)) {
2164
+ const override = overrides[key];
2165
+ const baseVal = base[key];
2166
+
2167
+ if (override === undefined) continue;
2168
+
2169
+ if (
2170
+ typeof override === "object" &&
2171
+ override !== null &&
2172
+ !Array.isArray(override) &&
2173
+ typeof baseVal === "object" &&
2174
+ baseVal !== null &&
2175
+ !Array.isArray(baseVal)
2176
+ ) {
2177
+ result[key] = this.#deepMerge(baseVal as RawSettings, override as RawSettings);
2178
+ } else {
2179
+ result[key] = override;
2180
+ }
2181
+ }
2182
+ return result;
2183
+ }
2184
+ }
2185
+
2186
+ // ═══════════════════════════════════════════════════════════════════════════
2187
+ // Setting Hooks
2188
+ // ═══════════════════════════════════════════════════════════════════════════
2189
+
2190
+ type SettingHook<P extends SettingPath> = (value: SettingValue<P>, prev: SettingValue<P>) => void;
2191
+
2192
+ /**
2193
+ * Minimal change-notification primitive backing the exported `on*Changed`
2194
+ * subscriptions. Holds a listener set, hands out unsubscribe closures, and
2195
+ * isolates errors so a single throwing listener can't abort the rest or bubble
2196
+ * out of `Settings.set()`.
2197
+ *
2198
+ * @typeParam A - argument tuple forwarded to each listener on `fire`.
2199
+ */
2200
+ class SettingSignal<A extends unknown[] = []> {
2201
+ #listeners = new Set<(...args: A) => void>();
2202
+
2203
+ constructor(private readonly label: string) {}
2204
+
2205
+ /** Subscribe `cb`; returns an unsubscribe function. */
2206
+ on(cb: (...args: A) => void): () => void {
2207
+ this.#listeners.add(cb);
2208
+ return () => {
2209
+ this.#listeners.delete(cb);
2210
+ };
2211
+ }
2212
+
2213
+ /**
2214
+ * Invoke every listener with `args`. Iterates a snapshot so a listener may
2215
+ * (un)subscribe mid-fire without re-entrancy — the Hindsight backend
2216
+ * re-registers the fresh state's listener on every rebuild — and wraps each
2217
+ * call so a throwing listener is logged and skipped instead of aborting the
2218
+ * rest.
2219
+ */
2220
+ fire(...args: A): void {
2221
+ for (const cb of [...this.#listeners]) {
2222
+ try {
2223
+ cb(...args);
2224
+ } catch (err) {
2225
+ logger.warn(`Settings: ${this.label} hook failed`, { error: String(err) });
2226
+ }
2227
+ }
2228
+ }
2229
+ }
2230
+
2231
+ const SETTING_HOOKS: Partial<Record<SettingPath, SettingHook<any>>> = {
2232
+ "theme.dark": value => {
2233
+ if (typeof value === "string") {
2234
+ setAutoThemeMapping("dark", value);
2235
+ }
2236
+ },
2237
+ "theme.light": value => {
2238
+ if (typeof value === "string") {
2239
+ setAutoThemeMapping("light", value);
2240
+ }
2241
+ },
2242
+ symbolPreset: value => {
2243
+ if (typeof value === "string" && (value === "unicode" || value === "nerd" || value === "ascii")) {
2244
+ setSymbolPreset(value).catch(err => {
2245
+ logger.warn("Settings: symbolPreset hook failed", { preset: value, error: String(err) });
2246
+ });
2247
+ }
2248
+ },
2249
+ colorBlindMode: value => {
2250
+ if (typeof value === "boolean") {
2251
+ setColorBlindMode(value).catch(err => {
2252
+ logger.warn("Settings: colorBlindMode hook failed", { enabled: value, error: String(err) });
2253
+ });
2254
+ }
2255
+ },
2256
+ "provider.appendOnlyContext": value => {
2257
+ if (typeof value === "string") {
2258
+ appendOnlyModeSignal.fire(value);
2259
+ }
2260
+ },
2261
+ "providers.maxInFlightRequests": value => {
2262
+ configureProviderMaxInFlightRequests(validateProviderMaxInFlightRequests(value));
2263
+ },
2264
+ "secrets.enabled": value => {
2265
+ configureCredentialRedaction(value === true);
2266
+ },
2267
+ "hindsight.bankId": () => hindsightScopeSignal.fire(),
2268
+ "hindsight.bankIdPrefix": () => hindsightScopeSignal.fire(),
2269
+ "hindsight.scoping": () => hindsightScopeSignal.fire(),
2270
+ "worktree.base": value => {
2271
+ const dir = typeof value === "string" && value.trim() ? value : undefined;
2272
+ // Always call so an unset/empty value clears a previously-applied override.
2273
+ // setWorktreesDir expands `~`, rejects relative paths, and returns the
2274
+ // applied absolute path (or undefined when cleared/rejected).
2275
+ if (dir && !setWorktreesDir(dir)) {
2276
+ logger.warn("Settings: worktree.base must be an absolute or ~-relative path; ignoring", { value: dir });
2277
+ } else if (!dir) {
2278
+ setWorktreesDir(undefined);
2279
+ }
2280
+ },
2281
+ };
2282
+ /** Fires when `provider.appendOnlyContext` changes at runtime. */
2283
+ const appendOnlyModeSignal = new SettingSignal<[value: string]>("provider.appendOnlyContext");
2284
+
2285
+ /**
2286
+ * Subscribe to append-only mode setting changes.
2287
+ * Returns an unsubscribe function. Multiple sessions (main + subagents)
2288
+ * can register independently without overwriting each other.
2289
+ */
2290
+ export const onAppendOnlyModeChanged = (cb: (value: string) => void) => appendOnlyModeSignal.on(cb);
2291
+
2292
+ /** Fires when any model role changes at runtime. */
2293
+ const modelRolesSignal = new SettingSignal("modelRoles");
2294
+
2295
+ /** Subscribe to model role changes. Returns an unsubscribe function. */
2296
+ export const onModelRolesChanged: (cb: () => void) => () => void = modelRolesSignal.on.bind(modelRolesSignal);
2297
+
2298
+ /** Fires when `statusLine.sessionAccent` changes at runtime. */
2299
+ const statusLineSessionAccentSignal = new SettingSignal("statusLine.sessionAccent");
2300
+
2301
+ /**
2302
+ * Subscribe to session-accent setting changes.
2303
+ * Returns an unsubscribe function. Callers should re-read settings in the callback.
2304
+ */
2305
+ export const onStatusLineSessionAccentChanged = (cb: () => void) => statusLineSessionAccentSignal.on(cb);
2306
+
2307
+ /** Fires when any `hindsight.bankId` / `bankIdPrefix` / `scoping` value changes. */
2308
+ const hindsightScopeSignal = new SettingSignal("hindsight scope");
2309
+
2310
+ /**
2311
+ * Subscribe to changes in the Hindsight bank-scoping settings. Lets the
2312
+ * Hindsight backend rebuild the active `HindsightSessionState` when the
2313
+ * operator switches `hindsight.bankId`, `hindsight.bankIdPrefix`, or
2314
+ * `hindsight.scoping` mid-session so subsequent retain/recall calls land in
2315
+ * the new bank instead of the one selected at session start.
2316
+ *
2317
+ * Returns an unsubscribe function. The callback receives no arguments — the
2318
+ * caller is expected to re-read the relevant settings via `Settings.get`.
2319
+ */
2320
+ export const onHindsightScopeChanged = (cb: () => void) => hindsightScopeSignal.on(cb);
2321
+
2322
+ // ═══════════════════════════════════════════════════════════════════════════
2323
+ // Global Singleton
2324
+ // ═══════════════════════════════════════════════════════════════════════════
2325
+
2326
+ /**
2327
+ * Weak registry of every constructed instance so `resetSettingsForTest` can
2328
+ * disarm stray background saves on isolated instances too. WeakRefs never
2329
+ * retain instances; the set is cleared on every test reset.
2330
+ */
2331
+ const liveSettingsInstances = new Set<WeakRef<Settings>>();
2332
+
2333
+ let globalInstance: Settings | null = null;
2334
+ let globalInstancePromise: Promise<Settings> | null = null;
2335
+ let boundSettingsInstance: Settings | null = null;
2336
+ let boundSettingsMethods = new Map<PropertyKey, unknown>();
2337
+
2338
+ function clearBoundSettingsMethods(): void {
2339
+ boundSettingsInstance = null;
2340
+ boundSettingsMethods = new Map<PropertyKey, unknown>();
2341
+ }
2342
+
2343
+ export function isSettingsInitialized(): boolean {
2344
+ return globalInstance !== null;
2345
+ }
2346
+
2347
+ /**
2348
+ * Reset the global singleton for testing.
2349
+ * @internal
2350
+ */
2351
+ export function resetSettingsForTest(): void {
2352
+ // Disarm every constructed instance's debounced saves — including isolated
2353
+ // (non-singleton) instances: an armed timer or chained in-flight save on a
2354
+ // dropped instance fires mid-way through the NEXT test and races its file
2355
+ // locks/spies (cross-file pollution).
2356
+ for (const ref of liveSettingsInstances) {
2357
+ ref.deref()?.cancelPendingSaves();
2358
+ }
2359
+ liveSettingsInstances.clear();
2360
+ globalInstance = null;
2361
+ globalInstancePromise = null;
2362
+ clearBoundSettingsMethods();
2363
+ configureProviderMaxInFlightRequests(undefined);
2364
+ configureCredentialRedaction(false);
2365
+ }
2366
+
2367
+ /**
2368
+ * The global settings singleton.
2369
+ * Must call `Settings.init()` before using.
2370
+ */
2371
+ export const settings = new Proxy({} as Settings, {
2372
+ get(_target, prop) {
2373
+ if (!globalInstance) {
2374
+ throw new Error("Settings not initialized. Call Settings.init() first.");
2375
+ }
2376
+ if (boundSettingsInstance !== globalInstance) {
2377
+ clearBoundSettingsMethods();
2378
+ boundSettingsInstance = globalInstance;
2379
+ }
2380
+ const value = (globalInstance as unknown as Record<PropertyKey, unknown>)[prop];
2381
+ if (typeof value === "function") {
2382
+ const cached = boundSettingsMethods.get(prop);
2383
+ if (cached) return cached;
2384
+ const bound = value.bind(globalInstance);
2385
+ boundSettingsMethods.set(prop, bound);
2386
+ return bound;
2387
+ }
2388
+ return value;
2389
+ },
2390
+ });
2391
+
2392
+ // ═══════════════════════════════════════════════════════════════════════════
2393
+ // Helpers
2394
+ // ═══════════════════════════════════════════════════════════════════════════