@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
package/src/sdk.ts ADDED
@@ -0,0 +1,3820 @@
1
+ import {
2
+ Agent,
3
+ type AgentEvent,
4
+ type AgentMessage,
5
+ type AgentOptions,
6
+ type AgentTelemetryConfig,
7
+ type AgentTool,
8
+ AppendOnlyContextManager,
9
+ filterProviderReplayMessages,
10
+ type ThinkingLevel,
11
+ } from "@oh-my-pi/pi-agent-core";
12
+ import type {
13
+ Context,
14
+ CredentialDisabledEvent,
15
+ Effort,
16
+ Message,
17
+ Model,
18
+ ModelUsageHealth,
19
+ ProviderSessionState,
20
+ ServiceTier,
21
+ SimpleStreamOptions,
22
+ } from "@oh-my-pi/pi-ai";
23
+ import { resolveApiKeyOnce } from "@oh-my-pi/pi-ai/auth-retry";
24
+ import type { Dialect } from "@oh-my-pi/pi-ai/dialect";
25
+ import {
26
+ getOpenAICodexTransportDetails,
27
+ prewarmOpenAICodexResponses,
28
+ } from "@oh-my-pi/pi-ai/providers/openai-codex-responses";
29
+ import { FALLBACK_DIALECT, preferredDialect } from "@oh-my-pi/pi-catalog/identity";
30
+ import type { Component } from "@oh-my-pi/pi-tui";
31
+ import { $env, $flag, getAgentDir, getProjectDir, logger, postmortem, prompt, Snowflake } from "@oh-my-pi/pi-utils";
32
+ import { INTENT_FIELD } from "@oh-my-pi/pi-wire";
33
+ import {
34
+ discoverAdvisorConfigs,
35
+ discoverWatchdogFiles,
36
+ formatActiveRepoWatchdogPrompt,
37
+ formatAdvisorContextPrompt,
38
+ loadAdvisorTranscriptCosts,
39
+ } from "./advisor";
40
+ import { AsyncJobManager } from "./async";
41
+ import { AutoLearnController, buildAutoLearnInstructions } from "./autolearn/controller";
42
+ import { createAutoresearchExtension } from "./autoresearch";
43
+ import { loadCapability } from "./capability";
44
+ import { type Rule, ruleCapability, setActiveRules } from "./capability/rule";
45
+ import { bucketRules } from "./capability/rule-buckets";
46
+ import { shouldEnableAppendOnlyContext } from "./config/append-only-context-mode";
47
+ import { shouldInlineToolDescriptors } from "./config/inline-tool-descriptors-mode";
48
+ import { isAuthenticated, kNoAuth, ModelRegistry } from "./config/model-registry";
49
+ import {
50
+ formatModelSelectorValue,
51
+ formatModelString,
52
+ formatModelStringWithRouting,
53
+ getModelMatchPreferences,
54
+ parseModelPattern,
55
+ parseModelString,
56
+ pickDefaultAvailableModel,
57
+ resolveAllowedModels,
58
+ resolveCliModel,
59
+ resolveConfiguredModelPatterns,
60
+ resolveModelRoleValue,
61
+ } from "./config/model-resolver";
62
+ import { loadPromptTemplates as loadPromptTemplatesInternal, type PromptTemplate } from "./config/prompt-templates";
63
+ import { applyProviderGlobalsFromSettings } from "./config/provider-globals";
64
+ import { buildServiceTierByFamily } from "./config/service-tier";
65
+ import { Settings, type SkillsSettings } from "./config/settings";
66
+ import { CursorExecHandlers, type CursorMcpResourceAdapter } from "./cursor";
67
+ import { createBridgeEditTool, createBridgeGrepFactory } from "./cursor-bridge-tools";
68
+ import "./discovery";
69
+ import { initializeWithSettings } from "./discovery";
70
+ import { withOmpExtensionRootScope } from "./discovery/omp-extension-roots";
71
+ import { disposeAllJuliaKernelSessions, disposeJuliaKernelSessionsByOwner } from "./eval/jl/executor";
72
+ import { disposeVmContextsByOwner } from "./eval/js/context-manager";
73
+ import { disposeAllKernelSessions, disposeKernelSessionsByOwner } from "./eval/py/executor";
74
+ import { disposeAllRubyKernelSessions, disposeRubyKernelSessionsByOwner } from "./eval/rb/executor";
75
+ import { defaultEvalSessionId } from "./eval/session-id";
76
+ import {
77
+ type CustomCommandsLoadResult,
78
+ type LoadedCustomCommand,
79
+ loadCustomCommands as loadCustomCommandsInternal,
80
+ } from "./extensibility/custom-commands";
81
+ import { discoverCustomToolPaths, loadCustomTools, type ToolPathWithSource } from "./extensibility/custom-tools";
82
+ import type { CustomTool, CustomToolContext, CustomToolSessionEvent } from "./extensibility/custom-tools/types";
83
+ import {
84
+ discoverAndLoadExtensions,
85
+ discoverExtensionPaths,
86
+ type ExtensionContext,
87
+ type ExtensionFactory,
88
+ ExtensionRunner,
89
+ ExtensionToolWrapper,
90
+ type ExtensionUIContext,
91
+ type LoadExtensionsResult,
92
+ loadExtensionFromFactory,
93
+ loadExtensions,
94
+ type ToolDefinition,
95
+ wrapRegisteredTools,
96
+ } from "./extensibility/extensions";
97
+ import {
98
+ loadSkills as loadSkillsInternal,
99
+ type Skill,
100
+ type SkillWarning,
101
+ setActiveSkills,
102
+ } from "./extensibility/skills";
103
+ import { type FileSlashCommand, loadSlashCommands as loadSlashCommandsInternal } from "./extensibility/slash-commands";
104
+ import type { HindsightSessionState } from "./hindsight/state";
105
+ import { LocalProtocolHandler, type LocalProtocolOptions } from "./internal-urls";
106
+ import { setSharedLspEnabled } from "./lsp/client";
107
+ import { LSP_STARTUP_EVENT_CHANNEL, type LspStartupEvent } from "./lsp/startup-events";
108
+ import {
109
+ deduplicateMCPToolsByName,
110
+ discoverAndLoadMCPTools,
111
+ type MCPLoadResult,
112
+ MCPManager,
113
+ MCPToolCache,
114
+ type MCPToolsLoadResult,
115
+ parseMCPToolName,
116
+ } from "./mcp";
117
+ import { MCP_CONNECTION_STATUS_EVENT_CHANNEL, type McpConnectionStatusEvent } from "./mcp/startup-events";
118
+ import { createSessionMemoryRuntimeContext, resolveMemoryBackend } from "./memory-backend";
119
+ import { MEMORY_BACKEND_TOOL_NAMES } from "./memory-backend/tool-names";
120
+ import type { MnemopiSessionState } from "./mnemopi/state";
121
+ import mcpXdevGuidanceTemplate from "./prompts/system/mcp-xdev-guidance.md" with { type: "text" };
122
+ import lateDiagnosticTemplate from "./prompts/tools/lsp-late-diagnostic.md" with { type: "text" };
123
+ import { AgentLifecycleManager } from "./registry/agent-lifecycle";
124
+ import { type AgentRef, AgentRegistry, MAIN_AGENT_ID } from "./registry/agent-registry";
125
+ import {
126
+ buildSecretObfuscator,
127
+ deobfuscateSessionContext,
128
+ deobfuscateToolArguments,
129
+ obfuscateMessages,
130
+ obfuscateProviderContext,
131
+ type SecretObfuscator,
132
+ } from "./secrets";
133
+ import { AgentSession, type InitialRetryFallbackState, type PlanYolo, type Prewalk } from "./session/agent-session";
134
+ import { discoverAuthStorage as discoverAuthStorageFromConfig } from "./session/auth-broker-config";
135
+ import type { AuthStorage } from "./session/auth-storage";
136
+ import { createInterruptedTurnAbortMessage } from "./session/exit-diagnostics";
137
+ import {
138
+ type CustomMessage,
139
+ convertToLlm,
140
+ LSP_LATE_DIAGNOSTIC_MESSAGE_TYPE,
141
+ replaceLlmImagesWithText,
142
+ USER_INTERRUPT_LABEL,
143
+ wrapSteeringForModel,
144
+ } from "./session/messages";
145
+ import { clampProviderContextImages } from "./session/provider-image-budget";
146
+ import {
147
+ expandDefaultRetryFallbackChains,
148
+ findRetryFallbackCandidates,
149
+ type RetryFallbackResolutionContext,
150
+ resolveRetryFallbackChainKey,
151
+ } from "./session/retry-fallback-chains";
152
+ import { getRestorableSessionModels } from "./session/session-context";
153
+ import { SessionManager } from "./session/session-manager";
154
+ import { collectMountedMCPToolRoutes, projectMountedMCPXdevGuidance } from "./session/session-tools";
155
+ import { createSettingsAwareStreamFn } from "./session/settings-stream-fn";
156
+ import { SnapcompactInlineTransformer } from "./session/snapcompact-inline";
157
+ import { createSnapcompactSavingsRecorder } from "./session/snapcompact-savings-journal";
158
+ import { closeAllConnections } from "./ssh/connection-manager";
159
+ import { unmountAll } from "./ssh/sshfs-mount";
160
+ import {
161
+ type BuildSystemPromptResult,
162
+ buildSystemPrompt as buildSystemPromptInternal,
163
+ loadProjectContextFiles as loadContextFilesInternal,
164
+ projectSystemPromptToolMetadata,
165
+ } from "./system-prompt";
166
+ import { AgentOutputManager } from "./task/output-manager";
167
+ import { wrapStreamFnWithProviderConcurrency } from "./task/provider-concurrency";
168
+ import { isScoutSpawnable } from "./task/spawn-policy";
169
+ import type { StructuredSubagentSchemaMode } from "./task/types";
170
+ import {
171
+ AUTO_THINKING,
172
+ type ConfiguredThinkingLevel,
173
+ concreteThinkingLevel,
174
+ parseConfiguredThinkingLevel,
175
+ parseThinkingLevel,
176
+ resolveProvisionalAutoLevel,
177
+ resolveThinkingLevelForModel,
178
+ shouldDisableReasoning,
179
+ toReasoningEffort,
180
+ } from "./thinking";
181
+ import {
182
+ BashTool,
183
+ BUILTIN_TOOLS,
184
+ createTools,
185
+ createVibeTools,
186
+ type DeferredDiagnosticsEntry,
187
+ defaultLoadModeForToolName,
188
+ discoverStartupLspServers,
189
+ EditTool,
190
+ EvalTool,
191
+ GlobTool,
192
+ GrepTool,
193
+ getSearchTools,
194
+ HIDDEN_TOOLS,
195
+ isMountableUnderXdev,
196
+ type LspStartupServerInfo,
197
+ listXdevTools,
198
+ ReadTool,
199
+ releaseComputerSessionsForOwner,
200
+ resolveMountedXdevExecutable,
201
+ type Tool,
202
+ type ToolSession,
203
+ WebSearchTool,
204
+ WriteTool,
205
+ warmupLspServers,
206
+ xdevDocsAll,
207
+ xdevEntries,
208
+ } from "./tools";
209
+ import { isMCPToolName, normalizeToolNames } from "./tools/builtin-names";
210
+ import { ToolContextStore } from "./tools/context";
211
+ import { isIrcEnabled } from "./tools/hub";
212
+ import { getImageGenTools } from "./tools/image-gen";
213
+ import { wrapToolWithMetaNotice } from "./tools/output-meta";
214
+ import { isAutoQaEnabled } from "./tools/report-tool-issue";
215
+ import { queueResolveHandler } from "./tools/resolve";
216
+ import { USER_TODO_EDIT_CUSTOM_TYPE } from "./tools/todo";
217
+ import { ttsTool } from "./tools/tts";
218
+ import { resolveActiveRepoContext } from "./utils/active-repo-context";
219
+ import { EventBus } from "./utils/event-bus";
220
+ import { buildNamedToolChoice } from "./utils/tool-choice";
221
+ import { VibeSessionRegistry } from "./vibe/runtime";
222
+ import { buildWorkspaceTree, type WorkspaceTree } from "./workspace-tree";
223
+
224
+ type McpNotificationEntry = {
225
+ serverName: string;
226
+ uri: string;
227
+ };
228
+
229
+ type LateDiagnosticsDetails = {
230
+ files: Array<{ path: string; summary: string; errored: boolean; messages: string[] }>;
231
+ };
232
+
233
+ function buildLateDiagnosticsBatchMessage(
234
+ entries: DeferredDiagnosticsEntry[],
235
+ ): CustomMessage<LateDiagnosticsDetails> | null {
236
+ if (entries.length === 0) return null;
237
+ const files = entries.map(entry => ({
238
+ path: entry.path,
239
+ summary: entry.summary,
240
+ messages: entry.messages,
241
+ errored: entry.errored,
242
+ }));
243
+ const details: LateDiagnosticsDetails = {
244
+ files: files.map(file => ({
245
+ path: file.path,
246
+ summary: file.summary,
247
+ errored: file.errored,
248
+ messages: file.messages,
249
+ })),
250
+ };
251
+ return {
252
+ role: "custom",
253
+ customType: LSP_LATE_DIAGNOSTIC_MESSAGE_TYPE,
254
+ content: prompt.render(lateDiagnosticTemplate, {
255
+ multiple: files.length > 1,
256
+ files,
257
+ }),
258
+ display: true,
259
+ attribution: "agent",
260
+ details,
261
+ timestamp: Date.now(),
262
+ };
263
+ }
264
+
265
+ function buildMcpNotificationBatchMessage(entries: McpNotificationEntry[]): AgentMessage | null {
266
+ const resources: McpNotificationEntry[] = [];
267
+ const seen = new Set<string>();
268
+ for (const entry of entries) {
269
+ const key = `${entry.serverName}\0${entry.uri}`;
270
+ if (seen.has(key)) continue;
271
+ seen.add(key);
272
+ resources.push(entry);
273
+ }
274
+ if (resources.length === 0) return null;
275
+ const lines = [`[MCP notification] ${resources.length} resource(s) updated:`];
276
+ for (const resource of resources) {
277
+ lines.push(`- server="${resource.serverName}" uri=${resource.uri}`);
278
+ }
279
+ lines.push('Use read(path="mcp://<uri>") to inspect if relevant.');
280
+ return {
281
+ role: "user",
282
+ content: [{ type: "text", text: lines.join("\n") }],
283
+ attribution: "agent",
284
+ timestamp: Date.now(),
285
+ };
286
+ }
287
+
288
+ function createPendingMCPTool(name: string): Tool {
289
+ const parsed = parseMCPToolName(name);
290
+ const serverName = parsed?.serverName;
291
+ const mcpToolName = parsed?.toolName ?? name;
292
+ const label = serverName ? `${serverName}/${mcpToolName}` : name;
293
+ const message = serverName
294
+ ? `MCP server "${serverName}" is still connecting; tool "${name}" is not yet available. Retry after the MCP connection completes.`
295
+ : `MCP discovery is still in progress; tool "${name}" is not yet available. Retry after MCP connection completes.`;
296
+ const tool: Tool & { mcpServerName?: string; mcpToolName?: string } = {
297
+ name,
298
+ label,
299
+ description: `Pending MCP tool. ${message}`,
300
+ parameters: {
301
+ type: "object",
302
+ properties: {},
303
+ additionalProperties: true,
304
+ },
305
+ approval: "write",
306
+ intent: "omit",
307
+ mcpServerName: serverName,
308
+ mcpToolName,
309
+ async execute() {
310
+ return {
311
+ content: [{ type: "text", text: message }],
312
+ details: { serverName, mcpToolName, isError: true },
313
+ isError: true,
314
+ };
315
+ },
316
+ };
317
+ return tool;
318
+ }
319
+
320
+ function collectPendingMCPToolNames(explicitToolNames: readonly string[] | undefined): string[] {
321
+ const names = new Set<string>();
322
+ for (const name of explicitToolNames ?? []) {
323
+ const normalized = name.toLowerCase();
324
+ if (isMCPToolName(normalized)) names.add(normalized);
325
+ }
326
+ return [...names];
327
+ }
328
+
329
+ function logMCPLoadErrors(errors: MCPLoadResult["errors"]): void {
330
+ for (const [serverName, error] of errors) {
331
+ logger.error("MCP tool load failed", { path: `mcp:${serverName}`, error });
332
+ }
333
+ }
334
+
335
+ function applyMCPEnvironment(result: { exaApiKeys: string[] }): void {
336
+ if (result.exaApiKeys.length > 0 && !$env.EXA_API_KEY) {
337
+ Bun.env.EXA_API_KEY = result.exaApiKeys[0];
338
+ }
339
+ }
340
+
341
+ // Types
342
+ export interface CreateAgentSessionOptions {
343
+ /** Working directory for project-local discovery. Default: getProjectDir() */
344
+ cwd?: string;
345
+ /** Additional workspace directories beyond cwd (multi-root), absolute or cwd-relative. */
346
+ additionalDirectories?: string[];
347
+ /** Global config directory. Default: ~/.omp/agent */
348
+ agentDir?: string;
349
+ /** Spawns to allow. Default: "*" */
350
+ spawns?: string;
351
+
352
+ /** Auth storage for credentials. Default: discoverAuthStorage(agentDir) */
353
+ authStorage?: AuthStorage;
354
+ /** Model registry. Default: discoverModels(authStorage, agentDir) */
355
+ modelRegistry?: ModelRegistry;
356
+ /**
357
+ * Request credential resolver. Defaults to the model registry's normal
358
+ * session-affine resolver. Security scans use this narrow seam to keep one
359
+ * durable OAuth row pinned for the operation without changing ordinary
360
+ * provider routing.
361
+ */
362
+ getApiKey?: AgentOptions["getApiKey"];
363
+
364
+ /** Model to use. Default: from settings, else first available */
365
+ model?: Model;
366
+ /** Raw model pattern(s) (e.g. from --model CLI flag) to resolve after extensions load.
367
+ * Used when model lookup is deferred because extension-provided models aren't registered yet. */
368
+ modelPattern?: string | string[];
369
+ /** Authenticated fallback selector for deferred subagent model patterns. */
370
+ modelPatternAuthFallback?: string;
371
+ /** Role name used to install retry fallbacks after deferred subagent patterns resolve. */
372
+ modelPatternFallbackRole?: string;
373
+ /** Validated default retry chain to install when a deferred singleton pattern resolves. */
374
+ modelPatternDefaultFallbackChain?: string[];
375
+ /** Thinking selector. Default: from settings, else unset */
376
+ thinkingLevel?: ConfiguredThinkingLevel;
377
+ /** Hard ceiling on the session's thinking effort (e.g. a task spawn's `task.maxEffort`-capped hint); retry-fallback recovery re-clamps to it. */
378
+ thinkingLevelCeiling?: Effort;
379
+ /** OpenAI service-tier override for this session. `null` omits `service_tier`. */
380
+ openAIServiceTier?: ServiceTier | null;
381
+ /** Models available for cycling (Ctrl+P in interactive mode) */
382
+ scopedModels?: Array<{ model: Model; thinkingLevel?: ThinkingLevel }>;
383
+ /** Prewalk from the starting model to a fast/cheap target at the first edit/write once the todo list exists. */
384
+ prewalk?: Prewalk;
385
+ /** Force read-only plan mode at start, auto-approve on the model's first resolve call, then switch to execute. */
386
+ planYolo?: PlanYolo;
387
+
388
+ /** Provider-facing system prompt override. Replaces the fully rendered default blocks. */
389
+ systemPrompt?: string | string[] | ((defaultPrompt: string[]) => string | string[]);
390
+ /** Already-loaded custom prompt text rendered through the bundled custom system prompt template. */
391
+ customSystemPrompt?: string;
392
+ /** Already-loaded text appended through the bundled system prompt templates. */
393
+ appendSystemPrompt?: string;
394
+ /**
395
+ * Already-loaded title-generation system prompt override (typically
396
+ * {@link discoverTitleSystemPromptFile} → {@link resolvePromptInput}). When
397
+ * set, every automatic session-title generation path on this session — the
398
+ * first-input title and the replan-driven refresh — uses this prompt
399
+ * instead of the bundled default. Refresh on cwd change via
400
+ * {@link AgentSession.setTitleSystemPrompt}.
401
+ */
402
+ titleSystemPrompt?: string;
403
+ /** Optional provider-facing session identifier for prompt caches and sticky auth selection.
404
+ * Keeps persisted session files isolated while reusing provider-side caches. */
405
+ providerSessionId?: string;
406
+ /** Optional provider-facing prompt cache key, distinct from request lineage. */
407
+ providerPromptCacheKey?: string;
408
+ /** Whether `providerPromptCacheKey` is caller-pinned or inherited from a full fork. */
409
+ providerPromptCacheKeySource?: "explicit" | "fork";
410
+ /** Absolute wall-clock deadline in Unix epoch milliseconds. */
411
+ deadline?: number;
412
+
413
+ /** Custom tools to register (in addition to built-in tools). Accepts both CustomTool and ToolDefinition. */
414
+ customTools?: (CustomTool | ToolDefinition)[];
415
+ /** Inline extensions (merged with discovery). */
416
+ extensions?: ExtensionFactory[];
417
+ /** Additional extension paths to load (merged with discovery). */
418
+ additionalExtensionPaths?: string[];
419
+ /** Disable extension discovery (explicit paths still load). */
420
+ disableExtensionDiscovery?: boolean;
421
+ /**
422
+ * Pre-loaded extensions (skips file discovery and the per-session factory
423
+ * call). Used by the CLI when extensions are loaded early to parse custom
424
+ * flags — the same process owns the returned instances, so reusing them is
425
+ * safe.
426
+ *
427
+ * NEVER pass this across session boundaries (e.g. parent → subagent).
428
+ * `Extension` instances close over a parent-bound `ExtensionAPI` (cwd,
429
+ * eventBus, runtime), and reusing them would route tools/handlers/commands
430
+ * back through the parent. For subagents, forward
431
+ * {@link preloadedExtensionPaths} instead.
432
+ *
433
+ * @internal
434
+ */
435
+ preloadedExtensions?: LoadExtensionsResult;
436
+ /**
437
+ * Pre-discovered extension source paths. When provided, the filesystem-scan
438
+ * inside `discoverExtensionPaths()` is skipped — the session still calls
439
+ * `loadExtensions()` itself so each `Extension` is bound to THIS session's
440
+ * `ExtensionAPI` (cwd, eventBus, runtime).
441
+ *
442
+ * This is the safe pass-through for parent → subagent forwarding.
443
+ */
444
+ preloadedExtensionPaths?: string[];
445
+ /**
446
+ * Pre-discovered custom-tool source paths from `.omp/tools/`, `.claude/tools/`,
447
+ * plugins, etc. When provided, the filesystem-scan inside
448
+ * `discoverCustomToolPaths()` is skipped — subagents inherit the parent's
449
+ * scan result and call `loadCustomTools()` themselves so each session binds
450
+ * tools to its OWN `CustomToolAPI` (cwd, exec, pushPendingAction, UI).
451
+ *
452
+ * Forwarding the loaded `LoadedCustomTool[]` instances directly would reuse
453
+ * the parent's session-bound API and route tool execution back through the
454
+ * parent — wrong for isolated tasks and for pending-action routing.
455
+ */
456
+ preloadedCustomToolPaths?: ToolPathWithSource[];
457
+
458
+ /** Shared event bus for tool/extension communication. Default: creates new bus. */
459
+ eventBus?: EventBus;
460
+
461
+ /** Skills. Default: discovered from multiple locations */
462
+ skills?: Skill[];
463
+ /** Rules. Default: discovered from multiple locations */
464
+ rules?: Rule[];
465
+ /** Context files (AGENTS.md content). Default: discovered walking up from cwd */
466
+ contextFiles?: Array<{ path: string; content: string }>;
467
+ /** Pre-built workspace tree (skips re-scanning; passed by parents to subagents). */
468
+ workspaceTree?: WorkspaceTree;
469
+ /** Prompt templates. Default: discovered from cwd/.omp/prompts/ + agentDir/prompts/ */
470
+ promptTemplates?: PromptTemplate[];
471
+ /** File-based slash commands. Default: discovered from commands/ directories */
472
+ slashCommands?: FileSlashCommand[];
473
+
474
+ /**
475
+ * Enable MCP capabilities. `false` skips MCP discovery and ignores
476
+ * `mcpManager`, preventing process-global or inherited MCP access. Default:
477
+ * true.
478
+ */
479
+ enableMCP?: boolean;
480
+ /** Existing MCP manager to reuse when MCP is enabled (skips discovery, propagates to toolSession). */
481
+ mcpManager?: MCPManager;
482
+
483
+ /** Enable LSP integration (tool, formatting, diagnostics, warmup). Default: true */
484
+ enableLsp?: boolean;
485
+ /** Restrict LSP to navigation and diagnostics even when enabled. Defaults to true for restricted sessions. */
486
+ lspReadOnly?: boolean;
487
+ /** Whether this invocation may expose IRC. `false` removes it even for subagents. */
488
+ enableIrc?: boolean;
489
+ /** Skip subprocess-kernel availability checks and prelude warmup */
490
+ skipPythonPreflight?: boolean;
491
+ /** Tool names explicitly requested (enables disabled-by-default tools) */
492
+ toolNames?: string[];
493
+ /** Limit the session to explicitly supplied tool names, without discovered extras. */
494
+ restrictToolNames?: boolean;
495
+ /**
496
+ * Permit only caller-supplied SDK custom tools inside a restricted session.
497
+ * They must still be named in {@link toolNames}; discovered extensions, MCP,
498
+ * and ambient custom tools remain disabled. Default: false.
499
+ */
500
+ allowRestrictedCustomTools?: boolean;
501
+
502
+ /** Output schema for structured completion (subagents). */
503
+ outputSchema?: unknown;
504
+ /** Enforcement policy for {@link outputSchema}; defaults to legacy permissive behavior. */
505
+ outputSchemaMode?: StructuredSubagentSchemaMode;
506
+ /** Whether to include the yield tool by default */
507
+ requireYieldTool?: boolean;
508
+ /** Task recursion depth (for subagent sessions). Default: 0 */
509
+ taskDepth?: number;
510
+ /** Parent Hindsight state to alias for subagent memory tools. */
511
+ parentHindsightSessionState?: HindsightSessionState;
512
+ /** Parent Mnemopi state to alias for subagent memory tools. */
513
+ parentMnemopiSessionState?: MnemopiSessionState;
514
+ /** Pre-allocated agent identity for IRC routing. Default: "Main" for top-level, parentTaskPrefix-derived for sub. */
515
+ agentId?: string;
516
+ /** Display name for the agent in IRC. Default: "main" or "sub". */
517
+ agentDisplayName?: string;
518
+ /** Optional shared agent registry for IRC routing. Default: AgentRegistry.global(). */
519
+ agentRegistry?: AgentRegistry;
520
+ /**
521
+ * Registry generation authorized for this creation. `null` requires the id
522
+ * to be absent; an AgentRef allows a parked revival to reuse only that ref.
523
+ * Undefined preserves legacy unconditional registration for external SDK callers.
524
+ * @internal
525
+ */
526
+ expectedAgentRef?: AgentRef | null;
527
+ /** Parent task ID prefix for nested artifact naming (e.g., "Extensions") */
528
+ parentTaskPrefix?: string;
529
+ /**
530
+ * Registry id of the spawning agent, recorded as this subagent's parent in
531
+ * the agent registry. Distinct from `parentTaskPrefix`, which is this agent's
532
+ * own artifact/output-id prefix (the executor passes the child's own id
533
+ * there, so it must never double as the parent link). Undefined for the
534
+ * top-level "Main" session, which has no parent.
535
+ */
536
+ parentAgentId?: string;
537
+ /** Inherited eval executor session id for subagents sharing parent eval state. */
538
+ parentEvalSessionId?: string;
539
+
540
+ /** Session manager. Default: session stored under the configured agentDir sessions root */
541
+ sessionManager?: SessionManager;
542
+
543
+ /** Override local:// protocol options for subagent local:// sharing. Default: uses the session's own artifacts dir and session ID. */
544
+ localProtocolOptions?: LocalProtocolOptions;
545
+
546
+ /** Settings instance. Default: Settings.init({ cwd, agentDir }) */
547
+ settings?: Settings;
548
+ /**
549
+ * Legacy alias for `settings`. Older Pi extensions pass SettingsManager.create(...)
550
+ * through this field; accept it so their SDK calls keep the configured settings.
551
+ */
552
+ settingsManager?: Settings | Promise<Settings>;
553
+
554
+ /** Whether UI is available (enables interactive tools like ask). Default: false */
555
+ hasUI?: boolean;
556
+ /**
557
+ * Defer `confirm` reserve-policy fallback until AgentSession prompt-time UI is configured.
558
+ * ACP uses this while capabilities are negotiated without enabling UI-only tools.
559
+ */
560
+ deferUsageReserveConfirmation?: boolean;
561
+
562
+ /**
563
+ * Opt-in OpenTelemetry instrumentation forwarded to the underlying Agent.
564
+ * Passing `{}` enables the loop's GenAI-semantic-convention spans. See
565
+ * {@link AgentTelemetryConfig} for the full surface (hooks, content capture,
566
+ * cost estimator, agent identity).
567
+ *
568
+ * Safe to enable without an OTEL SDK registered in the host: the
569
+ * `@opentelemetry/api` package returns a no-op tracer in that case.
570
+ */
571
+ telemetry?: AgentTelemetryConfig;
572
+
573
+ /**
574
+ * Fired once, when the agent loop hands its first request to the provider
575
+ * transport (i.e. the `streamFn` wrapper is first invoked). Used to measure
576
+ * subagent launch latency — the boundary between "session built" and "model
577
+ * call dispatched". This is the loop's dispatch point, slightly before the
578
+ * actual provider HTTP call (per-request prep, identical across all
579
+ * requests, follows it), which is the right granularity for launch timing.
580
+ */
581
+ onFirstChatDispatch?: () => void;
582
+
583
+ /** Whether to auto-approve all tool calls (--auto-approve CLI flag). Default: false */
584
+ autoApprove?: boolean;
585
+ }
586
+
587
+ /** Result from createAgentSession */
588
+ export interface CreateAgentSessionResult {
589
+ /** The created session */
590
+ session: AgentSession;
591
+ /** Extensions result (loaded extensions + runtime) */
592
+ extensionsResult: LoadExtensionsResult;
593
+ /** Update tool UI context (interactive mode) */
594
+ setToolUIContext: (uiContext: ExtensionUIContext, hasUI: boolean) => void;
595
+ /** MCP manager for server lifecycle management (undefined if MCP disabled) */
596
+ mcpManager?: MCPManager;
597
+ /** Warning if session was restored with a different model than saved */
598
+ modelFallbackMessage?: string;
599
+ /** LSP servers detected for startup; warmup may continue in the background */
600
+ lspServers?: LspStartupServerInfo[];
601
+ /** Shared event bus for tool/extension communication */
602
+ eventBus: EventBus;
603
+ }
604
+
605
+ export type DialectFormat = "auto" | "native" | Dialect;
606
+
607
+ export function resolveDialect(
608
+ format: DialectFormat,
609
+ model: (Pick<Model, "supportsTools"> & Partial<Pick<Model, "id">>) | undefined,
610
+ ): Dialect | undefined {
611
+ if (format === "native") return undefined;
612
+ if (format === "auto") {
613
+ if (model?.supportsTools !== false) return undefined;
614
+ if (!model.id) return "glm";
615
+ const preferred = preferredDialect(model.id);
616
+ return preferred === FALLBACK_DIALECT ? "glm" : preferred;
617
+ }
618
+ return format;
619
+ }
620
+
621
+ // Re-exports
622
+
623
+ export type { PromptTemplate } from "./config/prompt-templates";
624
+ export { Settings, type SkillsSettings } from "./config/settings";
625
+ export type { CustomCommand, CustomCommandFactory } from "./extensibility/custom-commands/types";
626
+ export type { CustomTool, CustomToolFactory } from "./extensibility/custom-tools/types";
627
+ export type * from "./extensibility/extensions";
628
+ export type { Skill } from "./extensibility/skills";
629
+ export type { FileSlashCommand } from "./extensibility/slash-commands";
630
+ export type { MCPManager, MCPServerConfig, MCPServerConnection, MCPToolsLoadResult } from "./mcp";
631
+ // Agent registry: pass a private instance per `createAgentSession` when
632
+ // embedding several concurrent top-level sessions in one process (the default
633
+ // global registry admits only one "Main" per process generation).
634
+ export { type AgentRef, AgentRegistry, MAIN_AGENT_ID } from "./registry/agent-registry";
635
+ export type { Tool } from "./tools";
636
+ export { buildDirectoryTree, buildWorkspaceTree, type DirectoryTree, type WorkspaceTree } from "./workspace-tree";
637
+
638
+ export {
639
+ // Individual tool classes (for custom usage)
640
+ BashTool,
641
+ // Tool classes and factories
642
+ BUILTIN_TOOLS,
643
+ createTools,
644
+ EditTool,
645
+ EvalTool,
646
+ GlobTool,
647
+ GrepTool,
648
+ HIDDEN_TOOLS,
649
+ ReadTool,
650
+ type ToolSession,
651
+ WebSearchTool,
652
+ WriteTool,
653
+ };
654
+
655
+ // Helper Functions
656
+
657
+ // Discovery Functions
658
+
659
+ /**
660
+ * Create an AuthStorage instance.
661
+ *
662
+ * Default: local SQLite store at `<agentDir>/agent.db`.
663
+ *
664
+ * Broker mode: when `OMP_AUTH_BROKER_URL` is set, credentials are pulled from
665
+ * a remote auth-broker over the wire. Refresh tokens never leave the broker;
666
+ * the client receives access tokens with `refresh = "__remote__"` and calls
667
+ * back into the broker through the {@link AuthStorageOptions.refreshOAuthCredential}
668
+ * override to re-mint access tokens when needed.
669
+ *
670
+ * Delegates to {@link ./session/auth-broker-config} so the TUI and the catalog
671
+ * generator share the same credential-discovery logic.
672
+ */
673
+ export async function discoverAuthStorage(agentDir: string = getAgentDir()): Promise<AuthStorage> {
674
+ return discoverAuthStorageFromConfig(agentDir);
675
+ }
676
+
677
+ /**
678
+ * Discover extensions from cwd.
679
+ */
680
+ export async function discoverExtensions(cwd?: string): Promise<LoadExtensionsResult> {
681
+ const resolvedCwd = cwd ?? getProjectDir();
682
+
683
+ return discoverAndLoadExtensions([], resolvedCwd);
684
+ }
685
+
686
+ /**
687
+ * Path-only counterpart of {@link loadSessionExtensions}: the FS-heavy scan
688
+ * without the per-session module load. Subagents reuse the parent's path list
689
+ * (cached on {@link ToolSession.extensionPaths}) and rebuild Extension
690
+ * instances themselves so each session's `ExtensionAPI` (cwd, eventBus,
691
+ * runtime) is its own.
692
+ */
693
+ export async function discoverSessionExtensionPaths(
694
+ options: Pick<CreateAgentSessionOptions, "disableExtensionDiscovery" | "additionalExtensionPaths">,
695
+ cwd: string,
696
+ settings: Settings,
697
+ ): Promise<string[]> {
698
+ const configuredPaths = options.disableExtensionDiscovery
699
+ ? (options.additionalExtensionPaths ?? [])
700
+ : [...(options.additionalExtensionPaths ?? []), ...(settings.get("extensions") ?? [])];
701
+ const disabledExtensionIds = options.disableExtensionDiscovery
702
+ ? undefined
703
+ : (settings.get("disabledExtensions") ?? []);
704
+ return discoverExtensionPaths(configuredPaths, cwd, disabledExtensionIds, {
705
+ ambient: !options.disableExtensionDiscovery,
706
+ });
707
+ }
708
+
709
+ /**
710
+ * Load the discovered/configured extensions for a session — everything {@link
711
+ * createAgentSession} would load except the inline factory extensions it appends
712
+ * itself. Extracted so the CLI can resolve extension-registered flags (and thus
713
+ * classify `@file` arguments extension-aware) *before* a session — and its
714
+ * terminal breadcrumb — is created, then hand the result back through
715
+ * {@link CreateAgentSessionOptions.preloadedExtensions} so the work is not
716
+ * repeated. Keep this the single source of the discovery branch logic.
717
+ */
718
+ export async function loadSessionExtensions(
719
+ options: Pick<CreateAgentSessionOptions, "disableExtensionDiscovery" | "additionalExtensionPaths">,
720
+ cwd: string,
721
+ settings: Settings,
722
+ eventBus: EventBus,
723
+ ): Promise<LoadExtensionsResult> {
724
+ const paths = await discoverSessionExtensionPaths(options, cwd, settings);
725
+ const result = await logger.time("loadExtensions", loadExtensions, paths, cwd, eventBus);
726
+ for (const { path, error } of result.errors) {
727
+ logger.error("Failed to load extension", { path, error });
728
+ }
729
+ return result;
730
+ }
731
+
732
+ /**
733
+ * Load discovered/configured extensions and register their providers into
734
+ * `modelRegistry`, then discover the dynamic provider catalogs. One-shot CLIs
735
+ * (`omp bench`, dry-balance) build a bare {@link ModelRegistry} that only knows
736
+ * built-in catalog providers; without this, providers contributed by an
737
+ * extension (e.g. a custom OpenAI-compatible provider under
738
+ * `~/.omp/agent/extensions/`) never reach model resolution. Mirrors the
739
+ * session / `omp models` path: drain the queued provider registrations, then
740
+ * `refreshRuntimeProviders` so dynamically-discovered models exist before
741
+ * selectors are resolved.
742
+ */
743
+ export async function loadCliExtensionProviders(
744
+ modelRegistry: ModelRegistry,
745
+ settings: Settings,
746
+ cwd: string,
747
+ options: Pick<CreateAgentSessionOptions, "disableExtensionDiscovery" | "additionalExtensionPaths"> = {},
748
+ ): Promise<void> {
749
+ const eventBus = new EventBus();
750
+ const extensionsResult = await loadSessionExtensions(options, cwd, settings, eventBus);
751
+ const activeSources = extensionsResult.extensions.map(extension => extension.path);
752
+ modelRegistry.syncExtensionSources(activeSources);
753
+ for (const sourceId of new Set(activeSources)) {
754
+ modelRegistry.clearSourceRegistrations(sourceId);
755
+ }
756
+ for (const { name, config, sourceId } of extensionsResult.runtime.pendingProviderRegistrations) {
757
+ modelRegistry.registerProvider(name, config, sourceId);
758
+ }
759
+ extensionsResult.runtime.pendingProviderRegistrations = [];
760
+ await modelRegistry.refreshRuntimeProviders();
761
+ }
762
+
763
+ /**
764
+ * Discover skills from cwd and agentDir.
765
+ */
766
+ export async function discoverSkills(
767
+ cwd?: string,
768
+ _agentDir?: string,
769
+ settings?: SkillsSettings,
770
+ ): Promise<{ skills: Skill[]; warnings: SkillWarning[] }> {
771
+ return await loadSkillsInternal({
772
+ ...settings,
773
+ cwd: cwd ?? getProjectDir(),
774
+ });
775
+ }
776
+
777
+ /**
778
+ * Discover context files (AGENTS.md) walking up from cwd.
779
+ * Returns files sorted by depth (farther from cwd first, so closer files appear last/more prominent).
780
+ */
781
+ export async function discoverContextFiles(
782
+ cwd?: string,
783
+ _agentDir?: string,
784
+ disabledExtensions?: string[],
785
+ ): Promise<Array<{ path: string; content: string; depth?: number }>> {
786
+ return await loadContextFilesInternal({
787
+ cwd: cwd ?? getProjectDir(),
788
+ disabledExtensions,
789
+ });
790
+ }
791
+
792
+ /**
793
+ * Discover prompt templates from cwd and agentDir.
794
+ */
795
+ export async function discoverPromptTemplates(cwd?: string, agentDir?: string): Promise<PromptTemplate[]> {
796
+ return await loadPromptTemplatesInternal({
797
+ cwd: cwd ?? getProjectDir(),
798
+ agentDir: agentDir ?? getAgentDir(),
799
+ });
800
+ }
801
+
802
+ /**
803
+ * Discover file-based slash commands from commands/ directories.
804
+ */
805
+ export async function discoverSlashCommands(cwd?: string): Promise<FileSlashCommand[]> {
806
+ return loadSlashCommandsInternal({ cwd: cwd ?? getProjectDir() });
807
+ }
808
+
809
+ /**
810
+ * Discover custom commands (TypeScript slash commands) from cwd and agentDir.
811
+ */
812
+ export async function discoverCustomTSCommands(cwd?: string, agentDir?: string): Promise<CustomCommandsLoadResult> {
813
+ const resolvedCwd = cwd ?? getProjectDir();
814
+ const resolvedAgentDir = agentDir ?? getAgentDir();
815
+
816
+ return loadCustomCommandsInternal({
817
+ cwd: resolvedCwd,
818
+ agentDir: resolvedAgentDir,
819
+ });
820
+ }
821
+
822
+ /**
823
+ * Discover MCP servers from .mcp.json files.
824
+ * Returns the manager and loaded tools.
825
+ */
826
+ export async function discoverMCPServers(cwd?: string): Promise<MCPToolsLoadResult> {
827
+ const resolvedCwd = cwd ?? getProjectDir();
828
+ return discoverAndLoadMCPTools(resolvedCwd);
829
+ }
830
+
831
+ // API Key Helpers
832
+
833
+ // System Prompt
834
+
835
+ export interface BuildSystemPromptOptions {
836
+ tools?: Tool[];
837
+ skills?: Skill[];
838
+ contextFiles?: Array<{ path: string; content: string }>;
839
+ cwd?: string;
840
+ customPrompt?: string;
841
+ appendPrompt?: string;
842
+ inlineToolDescriptors?: boolean;
843
+ includeWorkspaceTree?: boolean;
844
+ /** Include the read-only security:// resource inventory entry. Default: false. */
845
+ securityEnabled?: boolean;
846
+ }
847
+
848
+ /**
849
+ * Build the default provider-facing system prompt blocks.
850
+ *
851
+ * The returned `systemPrompt` preserves the stable harness prompt and dynamic project context
852
+ * as separate entries so providers can cache prompt prefixes without concatenating blocks.
853
+ */
854
+ export async function buildSystemPrompt(options: BuildSystemPromptOptions = {}): Promise<BuildSystemPromptResult> {
855
+ const toolNames = options.tools?.map(tool => tool.name);
856
+ const toolMap = options.tools ? new Map(options.tools.map(tool => [tool.name, tool])) : undefined;
857
+ const promptTools = toolMap
858
+ ? projectSystemPromptToolMetadata(
859
+ toolMap,
860
+ options.inlineToolDescriptors ? { mode: "full" } : { mode: "compact", toolNames: toolNames ?? [] },
861
+ )
862
+ : undefined;
863
+ return await buildSystemPromptInternal({
864
+ cwd: options.cwd,
865
+ customPrompt: options.customPrompt,
866
+ skills: options.skills,
867
+ contextFiles: options.contextFiles,
868
+ appendSystemPrompt: options.appendPrompt,
869
+ inlineToolDescriptors: options.inlineToolDescriptors,
870
+ includeWorkspaceTree: options.includeWorkspaceTree,
871
+ securityEnabled: options.securityEnabled,
872
+ toolNames,
873
+ tools: promptTools,
874
+ });
875
+ }
876
+
877
+ // Internal Helpers
878
+
879
+ function createCustomToolContext(ctx: ExtensionContext): CustomToolContext {
880
+ return {
881
+ sessionManager: ctx.sessionManager,
882
+ modelRegistry: ctx.modelRegistry,
883
+ model: ctx.model,
884
+ isIdle: ctx.isIdle,
885
+ hasQueuedMessages: ctx.hasPendingMessages,
886
+ abort: ctx.abort,
887
+ localProtocolOptions: ctx.localProtocolOptions,
888
+ };
889
+ }
890
+
891
+ function isCustomTool(tool: CustomTool | ToolDefinition): tool is CustomTool {
892
+ // To distinguish, we mark converted tools with a hidden symbol property.
893
+ // If the tool doesn't have this marker, it's a CustomTool that needs conversion.
894
+ return !(tool as any).__isToolDefinition;
895
+ }
896
+
897
+ function isLegacyBuiltinToolDefinition(tool: CustomTool | ToolDefinition): boolean {
898
+ return !isCustomTool(tool) && "__ompLegacyBuiltinTool" in tool && tool.__ompLegacyBuiltinTool === true;
899
+ }
900
+
901
+ const TOOL_DEFINITION_MARKER = Symbol("__isToolDefinition");
902
+ /** Matches the truncation applied to per-server instructions inside `rebuildSystemPrompt`. */
903
+ const MAX_MCP_INSTRUCTIONS_LENGTH = 4000;
904
+
905
+ let sshCleanupRegistered = false;
906
+
907
+ async function cleanupSshResources(): Promise<void> {
908
+ const results = await Promise.allSettled([closeAllConnections(), unmountAll()]);
909
+ for (const result of results) {
910
+ if (result.status === "rejected") {
911
+ logger.warn("SSH cleanup failed", { error: String(result.reason) });
912
+ }
913
+ }
914
+ }
915
+
916
+ function registerSshCleanup(): void {
917
+ if (sshCleanupRegistered) return;
918
+ sshCleanupRegistered = true;
919
+ postmortem.register("ssh-cleanup", cleanupSshResources);
920
+ }
921
+
922
+ let evalCleanupRegistered = false;
923
+
924
+ function registerEvalCleanup(): void {
925
+ if (evalCleanupRegistered) return;
926
+ evalCleanupRegistered = true;
927
+ postmortem.register("python-cleanup", disposeAllKernelSessions);
928
+ postmortem.register("ruby-cleanup", disposeAllRubyKernelSessions);
929
+ postmortem.register("julia-cleanup", disposeAllJuliaKernelSessions);
930
+ }
931
+
932
+ export function customToolToDefinition(tool: CustomTool): ToolDefinition {
933
+ const definition: ToolDefinition & { [TOOL_DEFINITION_MARKER]: true } = {
934
+ name: tool.name,
935
+ label: tool.label,
936
+ description: tool.description,
937
+ parameters: tool.parameters,
938
+ hidden: tool.hidden,
939
+ loadMode: defaultLoadModeForToolName(tool.name, tool.loadMode),
940
+ deferrable: tool.deferrable,
941
+ approval: typeof tool.approval === "function" ? tool.approval.bind(tool) : tool.approval,
942
+ // Preserved through RegisteredToolAdapter so MCP-backed tools' explicit
943
+ // `strict: false` (#4336/#4340) survives the custom-tool → definition bridge.
944
+ strict: tool.strict,
945
+ mcpServerName: tool.mcpServerName,
946
+ mcpToolName: tool.mcpToolName,
947
+ execute: (toolCallId, params, signal, onUpdate, ctx) =>
948
+ tool.execute(toolCallId, params, onUpdate, createCustomToolContext(ctx), signal),
949
+ onSession: tool.onSession ? (event, ctx) => tool.onSession?.(event, createCustomToolContext(ctx)) : undefined,
950
+ renderCall: tool.renderCall,
951
+ renderResult: tool.renderResult
952
+ ? (result, options, theme): Component => {
953
+ const component = tool.renderResult?.(
954
+ result,
955
+ { expanded: options.expanded, isPartial: options.isPartial, spinnerFrame: options.spinnerFrame },
956
+ theme,
957
+ );
958
+ // Return empty component if undefined to match Component type requirement
959
+ return component ?? ({ render: () => [] } as unknown as Component);
960
+ }
961
+ : undefined,
962
+ [TOOL_DEFINITION_MARKER]: true,
963
+ };
964
+ return definition;
965
+ }
966
+
967
+ function createCustomToolsExtension(tools: CustomTool[]): ExtensionFactory {
968
+ const uniqueTools = deduplicateMCPToolsByName(tools);
969
+ return api => {
970
+ for (const tool of uniqueTools) {
971
+ api.registerTool(customToolToDefinition(tool));
972
+ }
973
+
974
+ const runOnSession = async (event: CustomToolSessionEvent, ctx: ExtensionContext) => {
975
+ for (const tool of uniqueTools) {
976
+ if (!tool.onSession) continue;
977
+ try {
978
+ await tool.onSession(event, createCustomToolContext(ctx));
979
+ } catch (err) {
980
+ logger.warn("Custom tool onSession error", { tool: tool.name, error: String(err) });
981
+ }
982
+ }
983
+ };
984
+
985
+ api.on("session_start", async (_event, ctx) =>
986
+ runOnSession({ reason: "start", previousSessionFile: undefined }, ctx),
987
+ );
988
+ api.on("session_switch", async (event, ctx) =>
989
+ runOnSession({ reason: "switch", previousSessionFile: event.previousSessionFile }, ctx),
990
+ );
991
+ api.on("session_branch", async (event, ctx) =>
992
+ runOnSession({ reason: "branch", previousSessionFile: event.previousSessionFile }, ctx),
993
+ );
994
+ api.on("session_tree", async (_event, ctx) =>
995
+ runOnSession({ reason: "tree", previousSessionFile: undefined }, ctx),
996
+ );
997
+ api.on("session_shutdown", async (_event, ctx) =>
998
+ runOnSession({ reason: "shutdown", previousSessionFile: undefined }, ctx),
999
+ );
1000
+ api.on("auto_compaction_start", async (event, ctx) =>
1001
+ runOnSession({ reason: "auto_compaction_start", trigger: event.reason, action: event.action }, ctx),
1002
+ );
1003
+ api.on("auto_compaction_end", async (event, ctx) =>
1004
+ runOnSession(
1005
+ {
1006
+ reason: "auto_compaction_end",
1007
+ action: event.action,
1008
+ result: event.result,
1009
+ aborted: event.aborted,
1010
+ willRetry: event.willRetry,
1011
+ errorMessage: event.errorMessage,
1012
+ },
1013
+ ctx,
1014
+ ),
1015
+ );
1016
+ api.on("auto_retry_start", async (event, ctx) =>
1017
+ runOnSession(
1018
+ {
1019
+ reason: "auto_retry_start",
1020
+ attempt: event.attempt,
1021
+ maxAttempts: event.maxAttempts,
1022
+ delayMs: event.delayMs,
1023
+ errorMessage: event.errorMessage,
1024
+ errorId: event.errorId,
1025
+ },
1026
+ ctx,
1027
+ ),
1028
+ );
1029
+ api.on("auto_retry_end", async (event, ctx) =>
1030
+ runOnSession(
1031
+ {
1032
+ reason: "auto_retry_end",
1033
+ success: event.success,
1034
+ attempt: event.attempt,
1035
+ finalError: event.finalError,
1036
+ recoveredErrors: event.recoveredErrors,
1037
+ },
1038
+ ctx,
1039
+ ),
1040
+ );
1041
+ api.on("ttsr_triggered", async (event, ctx) =>
1042
+ runOnSession({ reason: "ttsr_triggered", rules: event.rules }, ctx),
1043
+ );
1044
+ api.on("todo_reminder", async (event, ctx) =>
1045
+ runOnSession(
1046
+ {
1047
+ reason: "todo_reminder",
1048
+ todos: event.todos,
1049
+ attempt: event.attempt,
1050
+ maxAttempts: event.maxAttempts,
1051
+ },
1052
+ ctx,
1053
+ ),
1054
+ );
1055
+ };
1056
+ }
1057
+
1058
+ // Factory
1059
+
1060
+ /**
1061
+ * Build LoadedCustomCommand entries for all MCP prompts across connected servers.
1062
+ * These are re-created whenever prompts change (setOnPromptsChanged callback).
1063
+ */
1064
+ function buildMCPPromptCommands(manager: MCPManager): LoadedCustomCommand[] {
1065
+ const commands: LoadedCustomCommand[] = [];
1066
+ for (const serverName of manager.getConnectedServers()) {
1067
+ const prompts = manager.getServerPrompts(serverName);
1068
+ if (!prompts?.length) continue;
1069
+ for (const prompt of prompts) {
1070
+ const commandName = `${serverName}:${prompt.name}`;
1071
+ commands.push({
1072
+ path: `mcp:${commandName}`,
1073
+ resolvedPath: `mcp:${commandName}`,
1074
+ source: "bundled",
1075
+ command: {
1076
+ name: commandName,
1077
+ description: prompt.description ?? `MCP prompt from ${serverName}`,
1078
+ async execute(args: string[]) {
1079
+ const promptArgs: Record<string, string> = {};
1080
+ for (const arg of args) {
1081
+ const eqIdx = arg.indexOf("=");
1082
+ if (eqIdx > 0) {
1083
+ promptArgs[arg.slice(0, eqIdx)] = arg.slice(eqIdx + 1);
1084
+ }
1085
+ }
1086
+ const result = await manager.executePrompt(serverName, prompt.name, promptArgs);
1087
+ if (!result) return "";
1088
+ const parts: string[] = [];
1089
+ for (const msg of result.messages) {
1090
+ const contentItems = Array.isArray(msg.content) ? msg.content : [msg.content];
1091
+ for (const item of contentItems) {
1092
+ if (item.type === "text") {
1093
+ parts.push(item.text);
1094
+ } else if (item.type === "resource") {
1095
+ const resource = item.resource;
1096
+ if (resource.text) parts.push(resource.text);
1097
+ }
1098
+ }
1099
+ }
1100
+ return parts.join("\n\n");
1101
+ },
1102
+ },
1103
+ });
1104
+ }
1105
+ }
1106
+ return commands;
1107
+ }
1108
+
1109
+ /** Dependencies used to construct an isolated auto-learn capture agent. */
1110
+ export interface AutoLearnCaptureRunnerOptions {
1111
+ sourceAgent: Agent;
1112
+ captureTools: AgentTool[];
1113
+ createAgent: (options: AgentOptions) => Agent;
1114
+ onPayload?: SimpleStreamOptions["onPayload"];
1115
+ onResponse?: SimpleStreamOptions["onResponse"];
1116
+ createSessionId?: () => string;
1117
+ }
1118
+
1119
+ /** Build a private capture runner over a detached message snapshot and provider session. */
1120
+ export function createAutoLearnCaptureRunner(
1121
+ options: AutoLearnCaptureRunnerOptions,
1122
+ ): (content: string, signal?: AbortSignal) => Promise<void> {
1123
+ return async (content, signal) => {
1124
+ if (options.captureTools.length === 0 || signal?.aborted) return;
1125
+ const captureModel = options.sourceAgent.state.model;
1126
+ if (!captureModel) return;
1127
+
1128
+ const captureSessionId = options.createSessionId?.() ?? Bun.randomUUIDv7();
1129
+ const captureProviderSessionState = new Map<string, ProviderSessionState>();
1130
+ const captureMessages = options.sourceAgent.state.messages.map((message): AgentMessage => {
1131
+ if (message.role === "assistant") {
1132
+ return { ...message, responseId: undefined, providerPayload: undefined };
1133
+ }
1134
+ if (message.role === "user" || message.role === "developer") {
1135
+ return { ...message, providerPayload: undefined };
1136
+ }
1137
+ return message;
1138
+ });
1139
+ const captureAgent = options.createAgent({
1140
+ initialState: {
1141
+ systemPrompt: [...options.sourceAgent.state.systemPrompt],
1142
+ model: captureModel,
1143
+ thinkingLevel: options.sourceAgent.state.thinkingLevel,
1144
+ disableReasoning: options.sourceAgent.state.disableReasoning,
1145
+ tools: options.captureTools,
1146
+ messages: captureMessages,
1147
+ },
1148
+ sessionId: captureSessionId,
1149
+ promptCacheKey: captureSessionId,
1150
+ providerSessionState: captureProviderSessionState,
1151
+ getApiKey: requestModel => options.sourceAgent.getApiKey?.(requestModel),
1152
+ onPayload: options.onPayload,
1153
+ onResponse: options.onResponse,
1154
+ });
1155
+ captureAgent.setMetadataResolver(provider => options.sourceAgent.metadataForProvider(provider));
1156
+ const captureMessage: CustomMessage = {
1157
+ role: "custom",
1158
+ customType: "autolearn-nudge",
1159
+ content,
1160
+ display: false,
1161
+ attribution: "agent",
1162
+ timestamp: Date.now(),
1163
+ };
1164
+ const abortCapture = () => captureAgent.abort(signal?.reason);
1165
+ signal?.addEventListener("abort", abortCapture, { once: true });
1166
+ try {
1167
+ if (signal?.aborted) {
1168
+ abortCapture();
1169
+ return;
1170
+ }
1171
+ await captureAgent.prompt(captureMessage);
1172
+ } catch (error) {
1173
+ if (!signal?.aborted) throw error;
1174
+ } finally {
1175
+ signal?.removeEventListener("abort", abortCapture);
1176
+ for (const [providerKey, state] of captureProviderSessionState) {
1177
+ try {
1178
+ state.close();
1179
+ } catch (error) {
1180
+ logger.warn("Failed to close auto-learn capture provider state", {
1181
+ providerKey,
1182
+ error: String(error),
1183
+ });
1184
+ }
1185
+ }
1186
+ captureProviderSessionState.clear();
1187
+ }
1188
+ };
1189
+ }
1190
+ /**
1191
+ * Create an AgentSession with the specified options.
1192
+ *
1193
+ * @example
1194
+ * ```typescript
1195
+ * // Minimal - uses defaults
1196
+ * const { session } = await createAgentSession();
1197
+ *
1198
+ * // With explicit model
1199
+ * import { getModel } from '@oh-my-pi/pi-ai';
1200
+ * const { session } = await createAgentSession({
1201
+ * model: getModel('anthropic', 'claude-opus-4-5'),
1202
+ * thinkingLevel: 'high',
1203
+ * });
1204
+ *
1205
+ * // Continue previous session
1206
+ * const { session, modelFallbackMessage } = await createAgentSession({
1207
+ * continueSession: true,
1208
+ * });
1209
+ *
1210
+ * // Full control
1211
+ * const { session } = await createAgentSession({
1212
+ * model: myModel,
1213
+ * getApiKey: async () => Bun.env.MY_KEY,
1214
+ * systemPrompt: ['You are helpful.'],
1215
+ * tools: codingTools({ cwd: getProjectDir() }),
1216
+ * skills: [],
1217
+ * sessionManager: SessionManager.inMemory(),
1218
+ * });
1219
+ * ```
1220
+ */
1221
+ export async function createAgentSession(options: CreateAgentSessionOptions = {}): Promise<CreateAgentSessionResult> {
1222
+ const rootMode = options.disableExtensionDiscovery ? "explicit-only" : "merge";
1223
+ return await withOmpExtensionRootScope(options.additionalExtensionPaths ?? [], rootMode, () =>
1224
+ createAgentSessionScoped(options),
1225
+ );
1226
+ }
1227
+
1228
+ async function createAgentSessionScoped(options: CreateAgentSessionOptions): Promise<CreateAgentSessionResult> {
1229
+ const cwd = options.cwd ?? getProjectDir();
1230
+ const agentDir = options.agentDir ?? getAgentDir();
1231
+ const eventBus = options.eventBus ?? new EventBus();
1232
+
1233
+ registerSshCleanup();
1234
+ registerEvalCleanup();
1235
+
1236
+ // Pin authStorage to modelRegistry.authStorage: ModelRegistry.getApiKey() routes refresh
1237
+ // failures through that instance, so any divergent storage handed to the bridge / mcpManager
1238
+ // / session would silently miss credential_disabled events.
1239
+ const modelRegistry =
1240
+ options.modelRegistry ??
1241
+ new ModelRegistry(options.authStorage ?? (await logger.time("discoverModels", discoverAuthStorage, agentDir)));
1242
+ // Track whether we internally created the authStorage so we can close it
1243
+ // if construction fails before the session takes ownership.
1244
+ const ownsAuthStorage = !options.authStorage && !options.modelRegistry;
1245
+ const authStorage = modelRegistry.authStorage;
1246
+ if (options.authStorage && options.authStorage !== authStorage) {
1247
+ throw new Error(
1248
+ "options.authStorage and options.modelRegistry.authStorage must be the same instance when both are provided",
1249
+ );
1250
+ }
1251
+ // Subscribe before any getApiKey() call so startup model probes can't fire a
1252
+ // credential_disabled event past us. An embedder's constructor handler makes the
1253
+ // listener set non-empty from construction, which defeats AuthStorage's no-listener
1254
+ // buffer — so we can't rely on it to catch startup events for the extension runner.
1255
+ const startupCredentialDisabledEvents: CredentialDisabledEvent[] = [];
1256
+ let credentialDisabledTarget: ExtensionRunner | undefined;
1257
+ const unsubscribeCredentialDisabled: (() => void) | undefined = authStorage.onCredentialDisabled(event => {
1258
+ if (credentialDisabledTarget) {
1259
+ // Discard return: any handler error is routed through runner.onError listeners.
1260
+ void credentialDisabledTarget.emitCredentialDisabled(event);
1261
+ } else {
1262
+ startupCredentialDisabledEvents.push(event);
1263
+ }
1264
+ });
1265
+ const settings = await (options.settings ??
1266
+ options.settingsManager ??
1267
+ logger.time("settings", Settings.init, { cwd, agentDir }));
1268
+ logger.time("initializeWithSettings", initializeWithSettings, settings);
1269
+ if (!options.modelRegistry) {
1270
+ modelRegistry.refreshInBackground();
1271
+ }
1272
+ // Kick off workspace tree discovery early. The native workspace scan returns
1273
+ // both the rendered-tree input and the AGENTS.md directory-context index, so
1274
+ // startup does not perform a second recursive filesystem search. Subagents
1275
+ // inherit the parent's resolved values via options.
1276
+ const STARTUP_SCAN_DEADLINE_MS = 5000;
1277
+ const includeWorkspaceTree = settings.get("includeWorkspaceTree") ?? false;
1278
+ const workspaceTreePromise: Promise<WorkspaceTree> = options.workspaceTree
1279
+ ? Promise.resolve(options.workspaceTree)
1280
+ : includeWorkspaceTree
1281
+ ? logger.time("buildWorkspaceTree", () => buildWorkspaceTree(cwd, { timeoutMs: STARTUP_SCAN_DEADLINE_MS }))
1282
+ : Promise.resolve({ rootPath: cwd, rendered: "", truncated: false, totalLines: 0, agentsMdFiles: [] });
1283
+ workspaceTreePromise.catch(() => {});
1284
+
1285
+ // Independent discoveries that depend only on cwd/agentDir — kicked off in parallel and awaited
1286
+ // at their respective consumer sites. Their work can overlap with model resolution, secret loading,
1287
+ // session-context build, tool creation, MCP discovery, and extension discovery.
1288
+ const contextFilesPromise = options.contextFiles
1289
+ ? Promise.resolve(options.contextFiles)
1290
+ : logger.time("discoverContextFiles", discoverContextFiles, cwd, agentDir);
1291
+ contextFilesPromise.catch(() => {});
1292
+ const resolveRepoContext = async (repoCwd: string) => {
1293
+ try {
1294
+ return await resolveActiveRepoContext(repoCwd);
1295
+ } catch (err) {
1296
+ logger.debug("Failed to resolve active repo context", { err: String(err) });
1297
+ return null;
1298
+ }
1299
+ };
1300
+ const activeRepoContextPromise = logger.time("resolveActiveRepoContext", resolveRepoContext, cwd);
1301
+ activeRepoContextPromise.catch(() => {});
1302
+ const watchdogFilesPromise = logger.time("discoverWatchdogFiles", () => discoverWatchdogFiles(cwd, agentDir));
1303
+ watchdogFilesPromise.catch(() => {});
1304
+ const advisorConfigsPromise = logger.time("discoverAdvisorConfigs", () => discoverAdvisorConfigs(cwd, agentDir));
1305
+ advisorConfigsPromise.catch(() => {});
1306
+ const promptTemplatesPromise = options.promptTemplates
1307
+ ? Promise.resolve(options.promptTemplates)
1308
+ : logger.time("discoverPromptTemplates", discoverPromptTemplates, cwd, agentDir);
1309
+ promptTemplatesPromise.catch(() => {});
1310
+ const slashCommandsPromise = options.slashCommands
1311
+ ? Promise.resolve(options.slashCommands)
1312
+ : logger.time("discoverSlashCommands", discoverSlashCommands, cwd);
1313
+ slashCommandsPromise.catch(() => {});
1314
+ const skillsSettings = settings.getGroup("skills");
1315
+ const disabledExtensionIds = settings.get("disabledExtensions") ?? [];
1316
+ const discoveredSkillsPromise =
1317
+ options.skills === undefined
1318
+ ? logger.time("discoverSkills", discoverSkills, cwd, agentDir, {
1319
+ ...skillsSettings,
1320
+ disabledExtensions: disabledExtensionIds,
1321
+ })
1322
+ : undefined;
1323
+ discoveredSkillsPromise?.catch(() => {});
1324
+
1325
+ // Initialize provider preferences from settings
1326
+ applyProviderGlobalsFromSettings(settings);
1327
+
1328
+ const sessionManager =
1329
+ options.sessionManager ??
1330
+ logger.time("sessionManager", () =>
1331
+ SessionManager.create(cwd, SessionManager.getDefaultSessionDir(cwd, agentDir)),
1332
+ );
1333
+ const configuredDirs = options.additionalDirectories
1334
+ ? options.additionalDirectories
1335
+ : settings.get("workspace.additionalDirectories");
1336
+ if (configuredDirs.length > 0) {
1337
+ // Merge with any roots restored from the session header (resume/fork), not replace.
1338
+ const existing = sessionManager.getAdditionalDirectories();
1339
+ const merged = [...new Set([...existing, ...configuredDirs])];
1340
+ await sessionManager.setAdditionalDirectories(merged);
1341
+ }
1342
+ const providerSessionId = options.providerSessionId ?? sessionManager.getSessionId();
1343
+ const forkCacheShapeChanged =
1344
+ options.model !== undefined ||
1345
+ options.modelPattern !== undefined ||
1346
+ options.thinkingLevel !== undefined ||
1347
+ options.systemPrompt !== undefined ||
1348
+ options.customSystemPrompt !== undefined ||
1349
+ options.appendSystemPrompt !== undefined ||
1350
+ options.toolNames !== undefined ||
1351
+ options.customTools !== undefined;
1352
+ const inheritedPromptCacheKey = forkCacheShapeChanged
1353
+ ? undefined
1354
+ : sessionManager.getHeader()?.providerPromptCacheKey;
1355
+ const providerPromptCacheKey = options.providerPromptCacheKey ?? inheritedPromptCacheKey;
1356
+ const providerPromptCacheKeySource =
1357
+ options.providerPromptCacheKey !== undefined
1358
+ ? (options.providerPromptCacheKeySource ?? "explicit")
1359
+ : providerPromptCacheKey !== undefined
1360
+ ? "fork"
1361
+ : undefined;
1362
+ // Startup model *selection* only needs to know whether auth is configured for
1363
+ // a candidate's provider — never the resolved key bytes. Use the synchronous,
1364
+ // side-effect-free probe (`hasConfiguredAuth`): it refreshes no OAuth tokens,
1365
+ // executes no `!command` keys, and issues no auth-broker requests. Resolving the
1366
+ // real key here (`getApiKey`) blocks resume on those network paths — a slow or
1367
+ // unreachable OAuth/broker endpoint stalls startup for the full ~10s refresh
1368
+ // timeout per candidate (observed as a hang in `restoreSessionModel`). The real
1369
+ // key is resolved lazily per request via ModelRegistry.resolver.
1370
+ const hasModelAuth = (candidate: Model): boolean => modelRegistry.hasConfiguredAuth(candidate);
1371
+
1372
+ // Load and create secret obfuscator early so resumed session state and prompt warnings
1373
+ // reflect actual loaded secrets, not just the setting toggle.
1374
+ const obfuscator: SecretObfuscator | undefined = settings.get("secrets.enabled")
1375
+ ? await buildSecretObfuscator(cwd, agentDir, options.agentDir)
1376
+ : undefined;
1377
+ const secretsEnabled = obfuscator?.hasSecrets() === true;
1378
+
1379
+ // An abnormal process exit after a non-terminal message tail is durable
1380
+ // evidence that the old process can no longer finish that turn. Preserve the
1381
+ // partial transcript and append one terminal aborted assistant record before
1382
+ // rebuilding runtime context. The helper is idempotent once that record exists.
1383
+ let existingBranch = logger.time("getSessionBranch", () => sessionManager.getBranch());
1384
+ const interruptedTurnAbort = createInterruptedTurnAbortMessage(existingBranch);
1385
+ if (interruptedTurnAbort) {
1386
+ sessionManager.appendMessage(interruptedTurnAbort);
1387
+ existingBranch = logger.time("getRecoveredSessionBranch", () => sessionManager.getBranch());
1388
+ }
1389
+ let existingSession = logger.time("loadSessionContext", () =>
1390
+ deobfuscateSessionContext(sessionManager.buildSessionContext(), obfuscator),
1391
+ );
1392
+ const hasExistingSession = existingBranch.length > 0;
1393
+ const hasThinkingEntry = existingBranch.some(entry => entry.type === "thinking_level_change");
1394
+ const hasServiceTierEntry = existingBranch.some(entry => entry.type === "service_tier_change");
1395
+
1396
+ const deferredModelPatterns = Array.isArray(options.modelPattern)
1397
+ ? options.modelPattern.map(pattern => pattern.trim()).filter(Boolean)
1398
+ : options.modelPattern?.trim()
1399
+ ? [options.modelPattern.trim()]
1400
+ : [];
1401
+ const hasExplicitModel = options.model !== undefined || deferredModelPatterns.length > 0;
1402
+ const modelMatchPreferences = getModelMatchPreferences(settings);
1403
+ const allowedModels = await logger.time("resolveAllowedModels", () =>
1404
+ resolveAllowedModels(modelRegistry, settings, modelMatchPreferences),
1405
+ );
1406
+ let defaultRoleSpec = logger.time("resolveDefaultModelRole", () =>
1407
+ resolveModelRoleValue(settings.getModelRole("default"), allowedModels, {
1408
+ settings,
1409
+ matchPreferences: modelMatchPreferences,
1410
+ }),
1411
+ );
1412
+ let model = options.model;
1413
+ let modelFallbackMessage: string | undefined;
1414
+ let initialRetryFallback: InitialRetryFallbackState | undefined;
1415
+ // Identify session model strings to restore in fallback order. We do an
1416
+ // initial pass here so model-dependent setup (thinking-level resolution,
1417
+ // host preconnect) can use the restored model; extension-registered
1418
+ // providers aren't visible yet, so we retry the preferred candidates once
1419
+ // extensions register below.
1420
+ const sessionModelStrings =
1421
+ !hasExplicitModel && hasExistingSession
1422
+ ? getRestorableSessionModels(existingSession.models, sessionManager.getLastModelChangeRole())
1423
+ : [];
1424
+ let restoredSessionModelIndex = -1;
1425
+ let restoredSessionThinkingLevel: ConfiguredThinkingLevel | undefined;
1426
+ if (!hasExplicitModel && !model && sessionModelStrings.length > 0) {
1427
+ logger.time("restoreSessionModel", () => {
1428
+ let failedSessionModel: string | undefined;
1429
+ for (let i = 0; i < sessionModelStrings.length; i++) {
1430
+ const sessionModelStr = sessionModelStrings[i];
1431
+ const parsedModel = parseModelString(sessionModelStr, {
1432
+ allowMaxSuffix: true,
1433
+ allowAutoAlias: true,
1434
+ isLiteralModelId: (provider, id) => modelRegistry.find(provider, id) !== undefined,
1435
+ });
1436
+ if (!parsedModel) {
1437
+ failedSessionModel ??= sessionModelStr;
1438
+ continue;
1439
+ }
1440
+
1441
+ const restoredModel = modelRegistry.find(parsedModel.provider, parsedModel.id);
1442
+ if (restoredModel && hasModelAuth(restoredModel)) {
1443
+ model = restoredModel;
1444
+ restoredSessionModelIndex = i;
1445
+ restoredSessionThinkingLevel = parsedModel.thinkingLevel;
1446
+ break;
1447
+ }
1448
+ failedSessionModel ??= sessionModelStr;
1449
+ }
1450
+ if (failedSessionModel) {
1451
+ modelFallbackMessage = `Could not restore model ${failedSessionModel}`;
1452
+ }
1453
+ });
1454
+ }
1455
+
1456
+ // If still no model, try settings default.
1457
+ // Skip settings fallback when an explicit model was requested.
1458
+ if (!hasExplicitModel && !model && defaultRoleSpec.model) {
1459
+ const settingsDefaultModel = defaultRoleSpec.model;
1460
+ logger.time("resolveSettingsDefaultModel", () => {
1461
+ // defaultRoleSpec.model already comes from modelRegistry.getAvailable(),
1462
+ // so re-validating auth here just repeats the expensive lookup path.
1463
+ model = settingsDefaultModel;
1464
+ });
1465
+ }
1466
+
1467
+ const taskDepth = options.taskDepth ?? 0;
1468
+
1469
+ // Resolves the session/agent thinking level using the same precedence we
1470
+ // apply at startup: explicit option → persisted session entry → restored
1471
+ // model selector suffix → default role's explicit selector → selected
1472
+ // model's defaultLevel → global settings default. Run again after extension
1473
+ // role reclaim so the final model's own defaults aren't masked by an earlier
1474
+ // fallback model's.
1475
+ const pickInitialThinkingLevel = (selectedModel: Model | undefined): ConfiguredThinkingLevel | undefined => {
1476
+ let level = options.thinkingLevel;
1477
+ if (level === undefined && hasExistingSession && hasThinkingEntry) {
1478
+ level =
1479
+ parseConfiguredThinkingLevel(existingSession.configuredThinkingLevel) ??
1480
+ parseThinkingLevel(existingSession.thinkingLevel);
1481
+ }
1482
+ if (level === undefined && !hasThinkingEntry && restoredSessionThinkingLevel !== undefined) {
1483
+ level = restoredSessionThinkingLevel;
1484
+ }
1485
+ if (level === undefined && !hasExplicitModel && !hasThinkingEntry && defaultRoleSpec.explicitThinkingLevel) {
1486
+ level = defaultRoleSpec.thinkingLevel;
1487
+ }
1488
+ if (level === undefined && selectedModel?.thinking?.defaultLevel !== undefined) {
1489
+ level = selectedModel.thinking.defaultLevel;
1490
+ }
1491
+ if (level === undefined) {
1492
+ level = parseConfiguredThinkingLevel(settings.get("defaultThinkingLevel"));
1493
+ }
1494
+ return level;
1495
+ };
1496
+ let thinkingLevel = pickInitialThinkingLevel(model);
1497
+ let autoThinking = thinkingLevel === AUTO_THINKING;
1498
+ // Concrete level the agent/session start with. With `auto` this is the
1499
+ // provisional level shown until the first per-turn classification resolves;
1500
+ // `auto` itself stays a session-only concept handled by AgentSession.
1501
+ let effectiveThinkingLevel: ThinkingLevel | undefined = concreteThinkingLevel(thinkingLevel);
1502
+ if (model) {
1503
+ const resolvedModel = model;
1504
+ effectiveThinkingLevel = logger.time("resolveThinkingLevelForModel", () =>
1505
+ autoThinking
1506
+ ? resolveProvisionalAutoLevel(resolvedModel)
1507
+ : resolveThinkingLevelForModel(resolvedModel, effectiveThinkingLevel),
1508
+ );
1509
+ // Fire-and-forget TLS+H2 handshake to the model's host so it overlaps
1510
+ // with the rest of session setup (extension/skill load, tool registry,
1511
+ // system prompt build). Without this, the first `fetch(...)` pays the
1512
+ // full handshake serially — 100–300 ms transcontinental for
1513
+ // api.anthropic.com from a residential IP. Every mode benefits
1514
+ // (interactive, print, rpc, acp).
1515
+ preconnectModelHost(model.baseUrl);
1516
+ }
1517
+
1518
+ let skills: Skill[];
1519
+ let skillWarnings: SkillWarning[];
1520
+ if (options.skills !== undefined) {
1521
+ skills = options.skills;
1522
+ skillWarnings = [];
1523
+ } else {
1524
+ const discovered = await (discoveredSkillsPromise ?? Promise.resolve({ skills: [], warnings: [] }));
1525
+ skills = discovered.skills;
1526
+ skillWarnings = discovered.warnings;
1527
+ }
1528
+
1529
+ // Discover rules and bucket them in one pass to avoid repeated scans over large rule sets.
1530
+ const { ttsrManager, rulebookRules, alwaysApplyRules, allRules } = await logger.time(
1531
+ "discoverTtsrRules",
1532
+ async () => {
1533
+ const { TtsrManager } = await import("./export/ttsr");
1534
+ const ttsrSettings = settings.getGroup("ttsr");
1535
+ const ttsrManager = new TtsrManager(ttsrSettings);
1536
+ const rulesResult =
1537
+ options.rules !== undefined
1538
+ ? { items: options.rules, warnings: undefined }
1539
+ : await loadCapability<Rule>(ruleCapability.id, { cwd });
1540
+ const { rulebookRules, alwaysApplyRules } = bucketRules(rulesResult.items, ttsrManager, {
1541
+ builtinRules: ttsrSettings.builtinRules,
1542
+ disabledRules: ttsrSettings.disabledRules,
1543
+ });
1544
+ if (existingSession.injectedTtsrRules.length > 0) {
1545
+ ttsrManager.restoreInjected(existingSession.injectedTtsrRules);
1546
+ }
1547
+ return { ttsrManager, rulebookRules, alwaysApplyRules, allRules: rulesResult.items };
1548
+ },
1549
+ );
1550
+
1551
+ // Resolve contextFiles up-front (it's needed before tool creation). The
1552
+ // workspace tree scan is slow on large repos and we MUST NOT block startup on
1553
+ // it. On timeout we forward `undefined` to ToolSession; buildSystemPromptInternal
1554
+ // will re-race the same promise through its own withDeadline path. Background
1555
+ // work continues so caches still warm.
1556
+ const raceWithDeadline = async <T>(name: string, work: Promise<T>): Promise<T | undefined> => {
1557
+ let timedOut = false;
1558
+ const result = await Promise.race([
1559
+ work,
1560
+ Bun.sleep(STARTUP_SCAN_DEADLINE_MS).then(() => {
1561
+ timedOut = true;
1562
+ return undefined;
1563
+ }),
1564
+ ]);
1565
+ if (timedOut) {
1566
+ logger.warn("Startup scan exceeded deadline; deferring to system prompt fallback", {
1567
+ name,
1568
+ timeoutMs: STARTUP_SCAN_DEADLINE_MS,
1569
+ cwd,
1570
+ });
1571
+ }
1572
+ return result;
1573
+ };
1574
+ const [initialContextFiles, resolvedWorkspaceTree, watchdogFiles, initialActiveRepoContext, discoveredAdvisors] =
1575
+ await Promise.all([
1576
+ contextFilesPromise,
1577
+ raceWithDeadline("buildWorkspaceTree", workspaceTreePromise),
1578
+ watchdogFilesPromise,
1579
+ activeRepoContextPromise,
1580
+ advisorConfigsPromise,
1581
+ ]);
1582
+ let contextFiles = initialContextFiles;
1583
+
1584
+ let agent: Agent;
1585
+ let session!: AgentSession;
1586
+ let hasSession = false;
1587
+ let hasRegistered = false;
1588
+ const restrictToolNames = options.restrictToolNames === true;
1589
+ const enableLsp = options.enableLsp ?? !restrictToolNames;
1590
+ const lspReadOnly = options.lspReadOnly ?? restrictToolNames;
1591
+ const asyncMaxJobs = Math.min(100, Math.max(1, settings.get("async.maxJobs") ?? 100));
1592
+ // Only the first top-level session in a process owns an AsyncJobManager.
1593
+ // Subagents inherit the parent's manager via `AsyncJobManager.instance()`
1594
+ // (set below), and any additional top-level session spun up in-process
1595
+ // (e.g. the agent-creation architect in `agent-dashboard.ts`) must share
1596
+ // the live singleton — otherwise its dispose path would clobber the
1597
+ // owning session's manager and break the `task`/`bash` async paths
1598
+ // (issue #1923). The `instance()` guard means later sessions also skip
1599
+ // constructing an orphaned manager that nothing would ever route to.
1600
+ // Delivery is owner-routed: every AgentSession registers its own sink
1601
+ // (see session/async-job-delivery.ts), so the manager takes no default
1602
+ // onJobComplete here.
1603
+ const asyncJobManager =
1604
+ !options.parentTaskPrefix && !AsyncJobManager.instance()
1605
+ ? new AsyncJobManager({ maxRunningJobs: asyncMaxJobs })
1606
+ : undefined;
1607
+
1608
+ const scopedAsyncJobManager = asyncJobManager ?? (options.parentTaskPrefix ? AsyncJobManager.instance() : undefined);
1609
+
1610
+ const agentRegistry = options.agentRegistry ?? AgentRegistry.global();
1611
+ const resolvedAgentId = options.agentId ?? options.parentTaskPrefix ?? MAIN_AGENT_ID;
1612
+ const resolvedAgentDisplayName =
1613
+ options.agentDisplayName ?? ((options.taskDepth ?? 0) > 0 || options.parentTaskPrefix ? "sub" : "main");
1614
+ const agentKind = (options.taskDepth ?? 0) > 0 || options.parentTaskPrefix ? ("sub" as const) : ("main" as const);
1615
+ let registeredAgentRef: AgentRef | undefined;
1616
+ /**
1617
+ * Forget the agent ref on teardown — unless it is a retained terminal ref.
1618
+ * Parking disposes the session but keeps the ref addressable (history://,
1619
+ * revive); a hard kill leaves it as a terminal `aborted` tombstone. Both are
1620
+ * detached (session === null) by the time dispose runs, per the AgentRef
1621
+ * invariant, so preserving them never keeps a disposed session reachable — an
1622
+ * aborted ref that still holds a live session is a bug and is unregistered
1623
+ * rather than handed to ensureLive. Only process teardown / a plain release
1624
+ * unregisters.
1625
+ */
1626
+ const unregisterUnlessParked = (): void => {
1627
+ const ref = registeredAgentRef;
1628
+ if (!ref || agentRegistry.get(resolvedAgentId) !== ref) return;
1629
+ if (ref.status === "parked" || (ref.status === "aborted" && !ref.session)) return;
1630
+ if (AgentLifecycleManager.global().isParking(resolvedAgentId, ref)) return;
1631
+ agentRegistry.unregister(resolvedAgentId, ref);
1632
+ };
1633
+ const evalKernelOwnerId = `agent-session:${Snowflake.next()}`;
1634
+
1635
+ try {
1636
+ const getActiveModelString = (): string | undefined => {
1637
+ const activeModel = agent?.state.model;
1638
+ if (activeModel) return formatModelString(activeModel);
1639
+ if (model) return formatModelString(model);
1640
+ return undefined;
1641
+ };
1642
+ // Per-path mutation counter shared across edit/write tools. Late-diagnostics
1643
+ // entries capture it at fetch time and are dropped at injection if a newer
1644
+ // mutation (any tool) bumped it in the meantime.
1645
+ const fileMutationVersions = new Map<string, number>();
1646
+ const disposeCallbacks = new Set<() => void>();
1647
+ const activeToolNames = new Set<string>();
1648
+ const toolRegistry = new Map<string, Tool>();
1649
+ const setActiveToolNames = (names: Iterable<string>): void => {
1650
+ activeToolNames.clear();
1651
+ for (const name of names) {
1652
+ activeToolNames.add(name);
1653
+ }
1654
+ };
1655
+ const toolSession: ToolSession = {
1656
+ get cwd() {
1657
+ return sessionManager.getCwd();
1658
+ },
1659
+ isToolActive: name => activeToolNames.has(name),
1660
+ setActiveToolNames,
1661
+ toolRegistry,
1662
+ hasUI: options.hasUI ?? false,
1663
+ getApiKey: options.getApiKey,
1664
+ get additionalDirectories() {
1665
+ return sessionManager.getAdditionalDirectories();
1666
+ },
1667
+ enableLsp,
1668
+ lspReadOnly,
1669
+ enableIrc: restrictToolNames ? false : options.enableIrc,
1670
+ restrictToolNames,
1671
+ get hasEditTool() {
1672
+ const requestedToolNames = options.toolNames ? normalizeToolNames(options.toolNames) : undefined;
1673
+ return restrictToolNames
1674
+ ? requestedToolNames?.includes("edit") === true
1675
+ : !requestedToolNames || requestedToolNames.includes("edit");
1676
+ },
1677
+ skipPythonPreflight: options.skipPythonPreflight,
1678
+ contextFiles,
1679
+ workspaceTree: resolvedWorkspaceTree,
1680
+ get skills() {
1681
+ return session?.skills ?? skills;
1682
+ },
1683
+ refreshSkills: () => session.refreshSkills(),
1684
+ rules: allRules,
1685
+ eventBus,
1686
+ outputSchema: options.outputSchema,
1687
+ outputSchemaMode: options.outputSchemaMode,
1688
+ requireYieldTool: options.requireYieldTool,
1689
+ prewalkArmed: options.prewalk !== undefined,
1690
+ taskDepth: options.taskDepth ?? 0,
1691
+ getSessionFile: () => sessionManager.getSessionFile() ?? null,
1692
+ sessionManager,
1693
+ getEvalKernelOwnerId: () => evalKernelOwnerId,
1694
+ getEvalSessionId: () =>
1695
+ session?.getEvalSessionId() ?? options.parentEvalSessionId ?? defaultEvalSessionId(toolSession),
1696
+ assertEvalExecutionAllowed: () => session?.assertEvalExecutionAllowed(),
1697
+ trackEvalExecution: (execution, abortController) =>
1698
+ session ? session.trackEvalExecution(execution, abortController) : execution,
1699
+ getSessionId: () => sessionManager.getSessionId?.() ?? null,
1700
+ isDisposed: () => session?.isDisposed ?? false,
1701
+ getHindsightSessionState: () => session?.getHindsightSessionState(),
1702
+ getMnemopiSessionState: () => session?.getMnemopiSessionState(),
1703
+ getAgentId: () => resolvedAgentId,
1704
+ getToolByName: name => session?.getToolByName(name),
1705
+ agentRegistry,
1706
+ // The global lifecycle releases through AgentRegistry.global(); wiring it
1707
+ // onto a caller-supplied registry would report a cancel while releasing an
1708
+ // unrelated global ref. With no lifecycle, hub cancel falls back to
1709
+ // dispose + unregister on the session's own registry.
1710
+ agentLifecycle: options.agentRegistry ? undefined : () => AgentLifecycleManager.global(),
1711
+ getSessionSpawns: () => options.spawns ?? "*",
1712
+ getModelString: () => (hasExplicitModel && model ? formatModelString(model) : undefined),
1713
+ getActiveModelString,
1714
+ getActiveModel: () => agent?.state.model ?? model,
1715
+ getInspectImageModeOverride: () => session?.getInspectImageModeOverride(),
1716
+ getServiceTierByFamily: () => session?.serviceTierByFamily,
1717
+ getImageAttachments: () => session?.getImageAttachments() ?? [],
1718
+ getPlanModeState: () => session?.getPlanModeState(),
1719
+ getPlanReferencePath: () => session?.getPlanReferencePath() ?? "local://PLAN.md",
1720
+ getGoalModeState: () => session?.getGoalModeState(),
1721
+ getGoalRuntime: () => session?.goalRuntime,
1722
+ getUsageStatistics: () => sessionManager.getUsageStatistics(),
1723
+ getTurnBudget: () => sessionManager.getTurnBudget(),
1724
+ recordEvalSubagentUsage: output => sessionManager.recordEvalSubagentOutput(output),
1725
+ getClientBridge: () => session?.clientBridge,
1726
+ queueDeferredDiagnostics: entry => session?.yieldQueue.enqueue(LSP_LATE_DIAGNOSTIC_MESSAGE_TYPE, entry),
1727
+ queueLaunchCompletion: notification =>
1728
+ session?.queueLaunchCompletion(notification) ??
1729
+ Promise.reject(new Error("Session unavailable for launch completion delivery")),
1730
+ registerDisposeCallback: callback => {
1731
+ disposeCallbacks.add(callback);
1732
+ return () => disposeCallbacks.delete(callback);
1733
+ },
1734
+ registerSessionChangeCallback: callback => session?.registerSessionChangeCallback(callback),
1735
+ bumpFileMutationVersion: path => {
1736
+ const next = (fileMutationVersions.get(path) ?? 0) + 1;
1737
+ fileMutationVersions.set(path, next);
1738
+ return next;
1739
+ },
1740
+ getFileMutationVersion: path => fileMutationVersions.get(path) ?? 0,
1741
+ getTodoPhases: () => session.getTodoPhases(),
1742
+ setTodoPhases: phases => session.setTodoPhases(phases),
1743
+ getCheckpointState: () => session.getCheckpointState(),
1744
+ setCheckpointState: state => session.setCheckpointState(state ?? undefined),
1745
+ getLastCompletedRewind: () => session.getLastCompletedRewind(),
1746
+ getToolChoiceQueue: () => session.toolChoiceQueue,
1747
+ buildToolChoice: name => {
1748
+ const m = session.model;
1749
+ return m ? buildNamedToolChoice(name, m) : undefined;
1750
+ },
1751
+ steer: msg =>
1752
+ session.agent.steer({
1753
+ role: "custom",
1754
+ customType: msg.customType,
1755
+ content: msg.content,
1756
+ display: false,
1757
+ details: msg.details,
1758
+ attribution: "agent",
1759
+ timestamp: Date.now(),
1760
+ }),
1761
+ peekQueueInvoker: () => session.peekQueueInvoker(),
1762
+ peekPendingInvoker: () => session.peekPendingInvoker(),
1763
+ clearPendingInvokers: () => session.clearPendingInvokers(),
1764
+ peekPlanProposalHandler: () => session.peekPlanProposalHandler(),
1765
+ setPlanProposalHandler: handler => session.setPlanProposalHandler(handler),
1766
+ allocateOutputArtifact: async toolType => {
1767
+ try {
1768
+ return await sessionManager.allocateArtifactPath(toolType);
1769
+ } catch {
1770
+ return {};
1771
+ }
1772
+ },
1773
+ getArtifactManager: () => sessionManager.getArtifactManager(),
1774
+ settings,
1775
+ authStorage,
1776
+ modelRegistry,
1777
+ getTelemetry: () => agent?.telemetry,
1778
+ // Subagents inherit the singleton (the parent's manager) so their bash/task
1779
+ // completions still flow into the spawning conversation's yieldQueue.
1780
+ // Secondary in-process top-level sessions (no parentTaskPrefix, no
1781
+ // constructed manager because the singleton was already installed) leave
1782
+ // this undefined so tools and session job snapshots refuse async work
1783
+ // instead of silently routing into the owning session (issue #1923).
1784
+ asyncJobManager: scopedAsyncJobManager,
1785
+ };
1786
+
1787
+ // Wire process-wide internal URL singletons owned by their real classes.
1788
+ // Top-level sessions install the active snapshots; subagents inherit them.
1789
+ // Artifact and agent-output URLs resolve via `AgentRegistry.global()` —
1790
+ // the protocol handlers walk each ref's `sessionManager.getArtifactsDir()`,
1791
+ // which collapses to the parent's dir for subagents (they adopt the
1792
+ // parent's ArtifactManager) so one lookup hits everything.
1793
+ const getArtifactsDir = () => sessionManager.getArtifactsDir();
1794
+ if (!options.parentTaskPrefix) {
1795
+ setActiveSkills(skills);
1796
+ // Include TTSR rules so `rule://<name>` can resolve them too. They are
1797
+ // registered with the manager and bucketed out before rulebook/always,
1798
+ // so without this a TTSR-only rule (e.g. a triggered builtin) is not
1799
+ // addressable and `rule://` reports "Available: none".
1800
+ setActiveRules([...rulebookRules, ...alwaysApplyRules, ...ttsrManager.getRules()]);
1801
+ if (asyncJobManager) AsyncJobManager.setInstance(asyncJobManager);
1802
+ }
1803
+ const localProtocolOptions = options.localProtocolOptions ?? {
1804
+ getArtifactsDir,
1805
+ getSessionId: () => sessionManager.getSessionId?.() ?? null,
1806
+ };
1807
+ if (options.localProtocolOptions && !options.parentTaskPrefix) {
1808
+ LocalProtocolHandler.setOverride(options.localProtocolOptions);
1809
+ }
1810
+ toolSession.getArtifactsDir = getArtifactsDir;
1811
+ toolSession.localProtocolOptions = localProtocolOptions;
1812
+ toolSession.agentOutputManager = new AgentOutputManager(
1813
+ getArtifactsDir,
1814
+ options.parentTaskPrefix ? { parentPrefix: options.parentTaskPrefix } : undefined,
1815
+ );
1816
+
1817
+ // Create built-in tools (already wrapped with meta notice formatting)
1818
+ await logger.time("createAllTools", createTools, toolSession, options.toolNames);
1819
+
1820
+ // Restricted sessions cannot inherit or discover MCP capabilities.
1821
+ const enableMCP = !restrictToolNames && (options.enableMCP ?? true);
1822
+ let mcpManager: MCPManager | undefined = enableMCP ? options.mcpManager : undefined;
1823
+ toolSession.mcpManager = mcpManager;
1824
+ toolSession.enableMCP = enableMCP;
1825
+ const deferMCPDiscoveryForUI = enableMCP && !mcpManager && options.hasUI === true;
1826
+ const customTools: CustomTool[] = [];
1827
+ let startDeferredMCPDiscovery: ((liveSession: AgentSession) => void) | undefined;
1828
+ const startupQuiet = settings.get("startup.quiet");
1829
+ const onMCPStatus = (event: McpConnectionStatusEvent) => {
1830
+ if (!options.hasUI || startupQuiet) return;
1831
+ if (event.type === "connecting" && event.serverNames.length === 0) return;
1832
+ eventBus.emit(MCP_CONNECTION_STATUS_EVENT_CHANNEL, event);
1833
+ };
1834
+ const mcpDiscoverOptions = {
1835
+ onStatus: onMCPStatus,
1836
+ enableProjectConfig: settings.get("mcp.enableProjectConfig") ?? true,
1837
+ // Always filter Exa - we have native integration
1838
+ filterExa: true,
1839
+ // Filter browser MCP servers when builtin browser tool is active
1840
+ filterBrowser: settings.get("browser.enabled") ?? false,
1841
+ };
1842
+ if (enableMCP && !mcpManager) {
1843
+ if (deferMCPDiscoveryForUI) {
1844
+ const cacheStorage = settings.getStorage();
1845
+ mcpManager = new MCPManager(cwd, cacheStorage ? new MCPToolCache(cacheStorage) : null);
1846
+ mcpManager.setAuthStorage(authStorage);
1847
+ toolSession.mcpManager = mcpManager;
1848
+
1849
+ if (settings.get("mcp.notifications")) {
1850
+ mcpManager.setNotificationsEnabled(true);
1851
+ }
1852
+
1853
+ const deferredMCPManager = mcpManager;
1854
+ startDeferredMCPDiscovery = liveSession => {
1855
+ void (async () => {
1856
+ try {
1857
+ const mcpResult = await logger.time("discoverAndLoadMCPTools", () =>
1858
+ deferredMCPManager.discoverAndConnect(mcpDiscoverOptions),
1859
+ );
1860
+ // The session can be torn down while servers are still connecting.
1861
+ // Don't resurrect tools on a disposed session, and don't leak the
1862
+ // transports/subprocesses the connect just spawned.
1863
+ if (liveSession.isDisposed) {
1864
+ await deferredMCPManager.disconnectAll();
1865
+ return;
1866
+ }
1867
+ applyMCPEnvironment(mcpResult);
1868
+ logMCPLoadErrors(mcpResult.errors);
1869
+ // Connected MCP tools are enabled and mounted under xd:// devices.
1870
+ await liveSession.refreshMCPTools(mcpResult.tools);
1871
+ } catch (error) {
1872
+ logger.error("MCP tool load failed", {
1873
+ path: ".mcp.json",
1874
+ error: error instanceof Error ? error.message : String(error),
1875
+ });
1876
+ }
1877
+ })();
1878
+ };
1879
+ } else {
1880
+ const mcpResult = await logger.time("discoverAndLoadMCPTools", discoverAndLoadMCPTools, cwd, {
1881
+ ...mcpDiscoverOptions,
1882
+ cacheStorage: settings.getStorage(),
1883
+ authStorage,
1884
+ });
1885
+ mcpManager = mcpResult.manager;
1886
+ toolSession.mcpManager = mcpManager;
1887
+
1888
+ if (settings.get("mcp.notifications")) {
1889
+ mcpManager.setNotificationsEnabled(true);
1890
+ }
1891
+ applyMCPEnvironment(mcpResult);
1892
+
1893
+ // Log MCP errors
1894
+ for (const { path, error } of mcpResult.errors) {
1895
+ logger.error("MCP tool load failed", { path, error });
1896
+ }
1897
+
1898
+ if (mcpResult.tools.length > 0) {
1899
+ // MCP tools are LoadedCustomTool, extract the tool property
1900
+ customTools.push(...mcpResult.tools.map(loaded => loaded.tool));
1901
+ }
1902
+ }
1903
+ }
1904
+ // Only top-level sessions own the global MCPManager. Subagents already
1905
+ // receive the parent's manager via `options.mcpManager`, and reassigning
1906
+ // the singleton to the same value is a no-op — keep the gate explicit
1907
+ // to mirror the AsyncJobManager ownership rule.
1908
+ if (mcpManager && !options.parentTaskPrefix) MCPManager.setInstance(mcpManager);
1909
+
1910
+ const builtInToolNames = [...toolRegistry.keys()];
1911
+ let customToolPaths: ToolPathWithSource[] = [];
1912
+ const inlineExtensions: ExtensionFactory[] = [];
1913
+ if (!restrictToolNames) {
1914
+ // Add image tools when generation is enabled and either no explicit tool
1915
+ // whitelist was given or it names `generate_image`. Unlike built-in tools
1916
+ // (filtered in `createTools`), custom tools are force-activated via
1917
+ // `alwaysInclude` below, so an explicit `--no-tools`/whitelist must be
1918
+ // honored here or image-gen would leak past every filter (issue #5305).
1919
+ const imageGenRequested = !options.toolNames || options.toolNames.includes("generate_image");
1920
+ if (settings.get("generate_image.enabled") && imageGenRequested) {
1921
+ const imageGenTools = await logger.time("getImageGenTools", () => getImageGenTools(modelRegistry, model));
1922
+ if (imageGenTools.length > 0) {
1923
+ customTools.push(...(imageGenTools as unknown as CustomTool[]));
1924
+ }
1925
+ }
1926
+
1927
+ if (settings.get("speechgen.enabled")) {
1928
+ customTools.push(ttsTool as unknown as CustomTool);
1929
+ }
1930
+
1931
+ // Add web search tools
1932
+ if (options.toolNames?.includes("web_search")) {
1933
+ customTools.push(...getSearchTools());
1934
+ }
1935
+
1936
+ // Discover custom tools from `.omp/tools/`, `.claude/tools/`, plugins, etc.
1937
+ // Subagents reuse the parent's scan via `preloadedCustomToolPaths` to skip
1938
+ // the FS walk, but ALWAYS re-call `loadCustomTools` here so factories bind
1939
+ // to THIS session's `CustomToolAPI` (cwd, exec, pushPendingAction, UI).
1940
+ // Forwarding the parent's `LoadedCustomTool[]` directly would route tool
1941
+ // execution back through the parent — wrong for isolated tasks and for
1942
+ // pending-action queueing.
1943
+ customToolPaths =
1944
+ options.preloadedCustomToolPaths ??
1945
+ (await logger.time("discoverCustomToolPaths", () => discoverCustomToolPaths([], cwd)));
1946
+ const customToolsLoadResult = await logger.time("loadCustomTools", () =>
1947
+ loadCustomTools(customToolPaths, cwd, builtInToolNames, action => queueResolveHandler(toolSession, action)),
1948
+ );
1949
+ for (const { path, error } of customToolsLoadResult.errors) {
1950
+ logger.error("Custom tool load failed", { path, error });
1951
+ }
1952
+ if (customToolsLoadResult.tools.length > 0) {
1953
+ customTools.push(...customToolsLoadResult.tools.map(loaded => loaded.tool));
1954
+ }
1955
+
1956
+ inlineExtensions.push(...(options.extensions ?? []));
1957
+ inlineExtensions.push(createAutoresearchExtension);
1958
+ if (customTools.length > 0) {
1959
+ inlineExtensions.push(createCustomToolsExtension(customTools));
1960
+ }
1961
+ }
1962
+ // Forward the path list (NOT the loaded tools) to subagents so they
1963
+ // re-bind under their own `CustomToolAPI` while skipping the FS scan.
1964
+ toolSession.customToolPaths = customToolPaths;
1965
+
1966
+ // Load extensions. Three paths:
1967
+ // 1. `preloadedExtensions` (CLI): caller already loaded — reuse the
1968
+ // Extension instances. Shallow-clone `extensions` so the inline
1969
+ // push below cannot mutate the caller's array. `runtime` is shared
1970
+ // so flag values set pre-creation flow into the live session.
1971
+ // 2. `preloadedExtensionPaths` (subagent): caller resolved paths;
1972
+ // skip the FS scan but always re-call `loadExtensions` here so
1973
+ // each `Extension` binds to THIS session's `ExtensionAPI`
1974
+ // (cwd, eventBus, runtime).
1975
+ // 3. No preload: run the full session discovery.
1976
+ // `disableExtensionDiscovery` is honored implicitly: a caller that set
1977
+ // the flag and pre-resolved the result already reflects that choice.
1978
+ let extensionPaths: string[];
1979
+ let extensionsResult: LoadExtensionsResult;
1980
+ if (restrictToolNames) {
1981
+ // Allocate a session runtime without evaluating caller-provided extension
1982
+ // instances, paths, or factories.
1983
+ extensionPaths = [];
1984
+ extensionsResult = await loadExtensions([], cwd, eventBus);
1985
+ } else if (options.preloadedExtensions) {
1986
+ extensionsResult = {
1987
+ ...options.preloadedExtensions,
1988
+ extensions: [...options.preloadedExtensions.extensions],
1989
+ };
1990
+ // Capture paths for downstream forwarding; filter inline-factory
1991
+ // entries (`<inline-N>`) — those are per-session, not source paths.
1992
+ extensionPaths = extensionsResult.extensions
1993
+ .map(ext => ext.resolvedPath)
1994
+ .filter(p => !p.startsWith("<inline"));
1995
+ } else if (options.preloadedExtensionPaths) {
1996
+ extensionPaths = options.preloadedExtensionPaths;
1997
+ extensionsResult = await logger.time("loadExtensions", loadExtensions, extensionPaths, cwd, eventBus);
1998
+ for (const { path, error } of extensionsResult.errors) {
1999
+ logger.error("Failed to load extension", { path, error });
2000
+ }
2001
+ } else {
2002
+ extensionPaths = await logger.time("discoverSessionExtensionPaths", () =>
2003
+ discoverSessionExtensionPaths(options, cwd, settings),
2004
+ );
2005
+ extensionsResult = await logger.time("loadExtensions", loadExtensions, extensionPaths, cwd, eventBus);
2006
+ for (const { path, error } of extensionsResult.errors) {
2007
+ logger.error("Failed to load extension", { path, error });
2008
+ }
2009
+ }
2010
+ // Forward the source-path list (NOT the loaded instances) so subagents
2011
+ // rebuild their own session-scoped extensions.
2012
+ toolSession.extensionPaths = extensionPaths;
2013
+
2014
+ // Load inline extensions from factories
2015
+ if (inlineExtensions.length > 0) {
2016
+ for (let i = 0; i < inlineExtensions.length; i++) {
2017
+ const factory = inlineExtensions[i];
2018
+ const loaded = await loadExtensionFromFactory(
2019
+ factory,
2020
+ cwd,
2021
+ eventBus,
2022
+ extensionsResult.runtime,
2023
+ `<inline-${i}>`,
2024
+ );
2025
+ extensionsResult.extensions.push(loaded);
2026
+ }
2027
+ }
2028
+
2029
+ // Process provider registrations queued during extension loading.
2030
+ // This must happen before the runner is created so that models registered by
2031
+ // extensions are available for model selection on session resume / fallback.
2032
+ if (!restrictToolNames) {
2033
+ const activeExtensionSources = extensionsResult.extensions.map(extension => extension.path);
2034
+ modelRegistry.syncExtensionSources(activeExtensionSources);
2035
+ for (const sourceId of new Set(activeExtensionSources)) {
2036
+ modelRegistry.clearSourceRegistrations(sourceId);
2037
+ }
2038
+ }
2039
+ if (extensionsResult.runtime.pendingProviderRegistrations.length > 0) {
2040
+ for (const { name, config, sourceId } of extensionsResult.runtime.pendingProviderRegistrations) {
2041
+ modelRegistry.registerProvider(name, config, sourceId);
2042
+ }
2043
+ extensionsResult.runtime.pendingProviderRegistrations = [];
2044
+ }
2045
+ // Hydrate cached runtime (extension) provider catalogs before model
2046
+ // resolution. Dynamic-only providers have no synchronous registration side
2047
+ // effect, so a cold --model/provider resume must see the same fresh SQLite
2048
+ // cache that `omp models find` uses before the online refresh continues in
2049
+ // the background.
2050
+ await modelRegistry.refreshRuntimeProviders("offline");
2051
+ // Continue runtime discovery in the background (cache-aware) so startup is
2052
+ // only blocked on local cache reads, not provider network fetches. Stash
2053
+ // the promise so the deferred `--model` retry below can await it instead
2054
+ // of starting a second concurrent discovery pass (the unfiltered
2055
+ // `refresh()` also covers runtime model managers).
2056
+ const runtimeDiscoveryPromise = modelRegistry.refreshRuntimeProviders().catch(error => {
2057
+ logger.warn("runtime provider discovery failed", {
2058
+ error: error instanceof Error ? error.message : String(error),
2059
+ });
2060
+ });
2061
+
2062
+ // Retry session-model candidates now that extension providers are
2063
+ // registered. The initial restore runs before extensions load, so a role
2064
+ // model supplied by an extension would have either fallen back to the
2065
+ // saved default (`restoredSessionModelIndex > 0`) or failed entirely
2066
+ // (`restoredSessionModelIndex === -1`, with the settings default or
2067
+ // downstream fallback filling `model`). Reclaim it here so resume
2068
+ // honors the last active role in either case.
2069
+ const sessionRetryLimit = restoredSessionModelIndex >= 0 ? restoredSessionModelIndex : sessionModelStrings.length;
2070
+ if (!hasExplicitModel && sessionRetryLimit > 0) {
2071
+ for (let i = 0; i < sessionRetryLimit; i++) {
2072
+ const sessionModelStr = sessionModelStrings[i];
2073
+ const parsedModel = parseModelString(sessionModelStr, {
2074
+ allowMaxSuffix: true,
2075
+ allowAutoAlias: true,
2076
+ isLiteralModelId: (provider, id) => modelRegistry.find(provider, id) !== undefined,
2077
+ });
2078
+ if (!parsedModel) continue;
2079
+ const restoredModel = modelRegistry.find(parsedModel.provider, parsedModel.id);
2080
+ if (restoredModel && hasModelAuth(restoredModel)) {
2081
+ model = restoredModel;
2082
+ modelFallbackMessage = undefined;
2083
+ restoredSessionModelIndex = i;
2084
+ restoredSessionThinkingLevel = parsedModel.thinkingLevel;
2085
+ // Recompute thinking-level from scratch against the reclaimed
2086
+ // model: any value derived from the earlier fallback model's
2087
+ // `thinking.defaultLevel` must not become sticky.
2088
+ thinkingLevel = pickInitialThinkingLevel(restoredModel);
2089
+ autoThinking = thinkingLevel === AUTO_THINKING;
2090
+ effectiveThinkingLevel = concreteThinkingLevel(thinkingLevel);
2091
+ effectiveThinkingLevel = logger.time("resolveThinkingLevelForModel", () =>
2092
+ autoThinking
2093
+ ? resolveProvisionalAutoLevel(restoredModel)
2094
+ : resolveThinkingLevelForModel(restoredModel, effectiveThinkingLevel),
2095
+ );
2096
+ preconnectModelHost(restoredModel.baseUrl);
2097
+ break;
2098
+ }
2099
+ }
2100
+ }
2101
+ // Resolve deferred --model/subagent patterns now that extension models are
2102
+ // registered. Use the same CLI resolver as the immediate path so bare role
2103
+ // names, exact model names, and provider selectors keep one precedence rule.
2104
+ if (!model && deferredModelPatterns.length > 0) {
2105
+ // Deferred `--model` patterns almost always failed at the immediate
2106
+ // path (main.ts:881) precisely because discovery-backed providers
2107
+ // hadn't populated yet. Await the in-flight runtime discovery
2108
+ // already kicked off above (stash + reuse avoids a second concurrent
2109
+ // `#refreshRuntimeDiscoveries` pass for the same runtime model
2110
+ // managers; it resolves instantly when no runtime managers are
2111
+ // registered). `refreshRuntimeProviders()` only covers runtime model
2112
+ // managers, not config-discovery providers (e.g. user-configured
2113
+ // ollama); fall back to a full cache-aware refresh only when the
2114
+ // runtime pass didn't surface a match AND config-discovery providers
2115
+ // exist to fetch from. By then runtime managers short-circuit on the
2116
+ // fresh cache written by the awaited pass, closing the double-fetch
2117
+ // window.
2118
+ await logger.time("resolveModelDiscoveryDeferredRetry", () => runtimeDiscoveryPromise);
2119
+ const matchPreferences = getModelMatchPreferences(settings);
2120
+ const runtimeResolved = deferredModelPatterns.some(pattern =>
2121
+ pattern.split(",").some(selector => {
2122
+ const trimmedSelector = selector.trim();
2123
+ if (!trimmedSelector) return false;
2124
+ const resolved = resolveCliModel({
2125
+ cliModel: trimmedSelector,
2126
+ modelRegistry,
2127
+ settings,
2128
+ preferences: matchPreferences,
2129
+ });
2130
+ return Boolean(
2131
+ resolved.model || (resolved.configuredPatterns && resolved.configuredPatterns.length > 0),
2132
+ );
2133
+ }),
2134
+ );
2135
+ if (!runtimeResolved && modelRegistry.getDiscoverableProviders().length > 0) {
2136
+ await logger.time("resolveModelDiscoveryFallbackNonRuntime", () =>
2137
+ modelRegistry.refresh("online-if-uncached"),
2138
+ );
2139
+ }
2140
+ const allModels = modelRegistry.getAll();
2141
+ const availableModels = modelRegistry.getAvailable();
2142
+ const expandedModelPatterns = deferredModelPatterns.flatMap(pattern =>
2143
+ pattern.split(",").flatMap(selector => {
2144
+ const trimmedSelector = selector.trim();
2145
+ if (!trimmedSelector) return [];
2146
+ const resolved = resolveCliModel({
2147
+ cliModel: trimmedSelector,
2148
+ modelRegistry,
2149
+ settings,
2150
+ preferences: matchPreferences,
2151
+ });
2152
+ if (resolved.configuredPatterns && resolved.configuredPatterns.length > 0) {
2153
+ const primaryPatterns: Array<{
2154
+ pattern: string;
2155
+ retryFallback: InitialRetryFallbackState | undefined;
2156
+ }> = resolved.configuredPatterns.map(pattern => ({
2157
+ pattern,
2158
+ retryFallback: undefined,
2159
+ }));
2160
+ if (!resolved.configuredRole || !settings.get("retry.modelFallback")) {
2161
+ return primaryPatterns;
2162
+ }
2163
+ const fallbackContext: RetryFallbackResolutionContext = {
2164
+ chains: expandDefaultRetryFallbackChains(settings.get("retry.fallbackChains"), [
2165
+ ...Object.keys(settings.getModelRoles()),
2166
+ resolved.configuredRole,
2167
+ ]),
2168
+ getModelRole: role => settings.getModelRole(role),
2169
+ modelLookup: modelRegistry,
2170
+ };
2171
+ const originalSelector = resolved.configuredPatterns[0];
2172
+ const availableOriginal = parseModelPattern(originalSelector, availableModels, matchPreferences);
2173
+ const originalModel =
2174
+ availableOriginal.model ?? parseModelPattern(originalSelector, allModels, matchPreferences).model;
2175
+ const chainKey = resolveRetryFallbackChainKey(
2176
+ fallbackContext,
2177
+ originalSelector,
2178
+ originalModel,
2179
+ resolved.configuredRole,
2180
+ );
2181
+ if (!chainKey) return primaryPatterns;
2182
+ const parsedOriginal = parseModelString(originalSelector, {
2183
+ allowMaxSuffix: true,
2184
+ allowAutoAlias: true,
2185
+ isLiteralModelId: (provider, id) => modelRegistry.find(provider, id) !== undefined,
2186
+ });
2187
+ const retryFallback: InitialRetryFallbackState = {
2188
+ role: chainKey,
2189
+ originalSelector,
2190
+ originalThinkingLevel: parsedOriginal?.thinkingLevel,
2191
+ };
2192
+ return [
2193
+ ...primaryPatterns,
2194
+ ...findRetryFallbackCandidates(fallbackContext, chainKey, originalSelector, originalModel, {
2195
+ allowMissingPrimary: true,
2196
+ }).map(candidate => ({ pattern: candidate.raw, retryFallback })),
2197
+ ];
2198
+ }
2199
+ if (resolved.model) {
2200
+ return [
2201
+ {
2202
+ pattern: formatModelSelectorValue(
2203
+ resolved.selector ?? formatModelStringWithRouting(resolved.model),
2204
+ resolved.thinkingLevel,
2205
+ ),
2206
+ retryFallback: undefined,
2207
+ },
2208
+ ];
2209
+ }
2210
+ return resolveConfiguredModelPatterns([trimmedSelector], settings).map(pattern => ({
2211
+ pattern,
2212
+ retryFallback: undefined,
2213
+ }));
2214
+ }),
2215
+ );
2216
+ const resolutionModels = expandedModelPatterns.some(
2217
+ ({ pattern }) => parseModelPattern(pattern, availableModels, matchPreferences).model,
2218
+ )
2219
+ ? availableModels
2220
+ : allModels;
2221
+ let usageFallbackTriggered = false;
2222
+ for (let patternIndex = 0; patternIndex < expandedModelPatterns.length; patternIndex += 1) {
2223
+ const { pattern, retryFallback } = expandedModelPatterns[patternIndex];
2224
+ const primary = parseModelPattern(pattern, resolutionModels, matchPreferences);
2225
+ if (!primary.model || (retryFallback && !hasModelAuth(primary.model))) continue;
2226
+ let hasUsageFallbackCandidate = false;
2227
+ for (
2228
+ let candidateIndex = patternIndex + 1;
2229
+ candidateIndex < expandedModelPatterns.length;
2230
+ candidateIndex += 1
2231
+ ) {
2232
+ const candidate = parseModelPattern(
2233
+ expandedModelPatterns[candidateIndex].pattern,
2234
+ resolutionModels,
2235
+ matchPreferences,
2236
+ );
2237
+ if (candidate.model && hasModelAuth(candidate.model)) {
2238
+ hasUsageFallbackCandidate = true;
2239
+ break;
2240
+ }
2241
+ }
2242
+ const usageReservePolicy = settings.get("retry.usageReservePolicy");
2243
+ const modelFallbackEnabled = settings.get("retry.modelFallback");
2244
+ if (
2245
+ ((modelFallbackEnabled && (hasUsageFallbackCandidate || usageFallbackTriggered)) ||
2246
+ usageReservePolicy === "fail-closed") &&
2247
+ settings.get("retry.usageAwareFallback")
2248
+ ) {
2249
+ let usageHealth: ModelUsageHealth | undefined;
2250
+ try {
2251
+ usageHealth = await modelRegistry.authStorage.getModelUsageHealth(primary.model.provider, {
2252
+ modelId: primary.model.id,
2253
+ baseUrl: primary.model.baseUrl,
2254
+ reserveFraction: settings.get("retry.usageReservePct") / 100,
2255
+ });
2256
+ } catch (error) {
2257
+ logger.debug("Usage-aware model preflight failed open", {
2258
+ provider: primary.model.provider,
2259
+ model: primary.model.id,
2260
+ error: String(error),
2261
+ });
2262
+ }
2263
+ if (usageHealth?.state === "depleted") {
2264
+ if (usageReservePolicy === "fail-closed") {
2265
+ throw new Error(
2266
+ `Usage depleted for ${primary.model.provider}/${primary.model.id}; reserve policy is fail-closed.`,
2267
+ );
2268
+ }
2269
+ if (modelFallbackEnabled) {
2270
+ usageFallbackTriggered = true;
2271
+ continue;
2272
+ }
2273
+ }
2274
+ if (usageHealth?.state === "reserve") {
2275
+ if (usageReservePolicy === "fail-closed") {
2276
+ throw new Error(
2277
+ `Usage reserve reached for ${primary.model.provider}/${primary.model.id}; reserve policy is fail-closed.`,
2278
+ );
2279
+ }
2280
+ if (
2281
+ modelFallbackEnabled &&
2282
+ (usageReservePolicy === "auto" || (!options.hasUI && !options.deferUsageReserveConfirmation))
2283
+ ) {
2284
+ usageFallbackTriggered = true;
2285
+ continue;
2286
+ }
2287
+ }
2288
+ }
2289
+ let selectedModel = primary.model;
2290
+ let selectedThinkingLevel = primary.thinkingLevel;
2291
+ let selectedExplicitThinkingLevel = primary.explicitThinkingLevel;
2292
+ // A chain entry without its own `:level` suffix inherits the
2293
+ // unavailable primary's configured thinking level, matching
2294
+ // runtime fallback-chain semantics.
2295
+ if (retryFallback && !selectedExplicitThinkingLevel && retryFallback.originalThinkingLevel !== undefined) {
2296
+ selectedThinkingLevel = retryFallback.originalThinkingLevel;
2297
+ selectedExplicitThinkingLevel = true;
2298
+ }
2299
+ let authFallbackUsed = false;
2300
+ if (options.modelPatternAuthFallback) {
2301
+ const primaryKey = await modelRegistry.getApiKey(primary.model);
2302
+ if (primaryKey !== kNoAuth && !isAuthenticated(primaryKey)) {
2303
+ const fallback = parseModelPattern(
2304
+ options.modelPatternAuthFallback,
2305
+ resolutionModels,
2306
+ matchPreferences,
2307
+ );
2308
+ if (fallback.model) {
2309
+ const fallbackKey = await modelRegistry.getApiKey(fallback.model);
2310
+ if (isAuthenticated(fallbackKey)) {
2311
+ selectedModel = fallback.model;
2312
+ selectedThinkingLevel = fallback.thinkingLevel;
2313
+ selectedExplicitThinkingLevel = fallback.explicitThinkingLevel;
2314
+ authFallbackUsed = true;
2315
+ }
2316
+ }
2317
+ }
2318
+ }
2319
+ if (!authFallbackUsed && options.modelPatternFallbackRole) {
2320
+ const primarySelector = formatModelSelectorValue(
2321
+ formatModelStringWithRouting(primary.model),
2322
+ primary.thinkingLevel,
2323
+ );
2324
+ const seenSelectors = new Set<string>([primarySelector]);
2325
+ const fallbackSelectors: string[] = [];
2326
+ for (const fallbackEntry of expandedModelPatterns.slice(patternIndex + 1)) {
2327
+ const fallback = parseModelPattern(fallbackEntry.pattern, resolutionModels, matchPreferences);
2328
+ if (!fallback.model) continue;
2329
+ const fallbackSelector = formatModelSelectorValue(
2330
+ formatModelStringWithRouting(fallback.model),
2331
+ fallback.thinkingLevel,
2332
+ );
2333
+ if (seenSelectors.has(fallbackSelector)) continue;
2334
+ seenSelectors.add(fallbackSelector);
2335
+ fallbackSelectors.push(fallbackSelector);
2336
+ }
2337
+ if (fallbackSelectors.length === 0) {
2338
+ for (const selector of options.modelPatternDefaultFallbackChain ?? []) {
2339
+ if (typeof selector !== "string" || seenSelectors.has(selector)) continue;
2340
+ seenSelectors.add(selector);
2341
+ fallbackSelectors.push(selector);
2342
+ }
2343
+ }
2344
+ if (fallbackSelectors.length > 0) {
2345
+ const modelRoles: Record<string, string> = {};
2346
+ const existingRoles = settings.getModelRoles();
2347
+ for (const role in existingRoles) {
2348
+ const selector = existingRoles[role];
2349
+ if (selector) {
2350
+ modelRoles[role] = selector;
2351
+ }
2352
+ }
2353
+ modelRoles[options.modelPatternFallbackRole] = primarySelector;
2354
+ settings.override("modelRoles", modelRoles);
2355
+ const fallbackChains: Record<string, string[]> = {
2356
+ [options.modelPatternFallbackRole]: fallbackSelectors,
2357
+ };
2358
+ const existingFallbackChains = settings.get("retry.fallbackChains");
2359
+ for (const role in existingFallbackChains) {
2360
+ if (role !== options.modelPatternFallbackRole) {
2361
+ fallbackChains[role] = existingFallbackChains[role];
2362
+ }
2363
+ }
2364
+ settings.override("retry.fallbackChains", fallbackChains);
2365
+ }
2366
+ }
2367
+ model = selectedModel;
2368
+ initialRetryFallback =
2369
+ retryFallback && usageFallbackTriggered ? { ...retryFallback, pinned: true } : retryFallback;
2370
+ modelFallbackMessage = undefined;
2371
+ if (selectedExplicitThinkingLevel) {
2372
+ restoredSessionThinkingLevel = selectedThinkingLevel;
2373
+ }
2374
+ thinkingLevel = pickInitialThinkingLevel(selectedModel);
2375
+ autoThinking = thinkingLevel === AUTO_THINKING;
2376
+ effectiveThinkingLevel = concreteThinkingLevel(thinkingLevel);
2377
+ effectiveThinkingLevel = logger.time("resolveThinkingLevelForModel", () =>
2378
+ autoThinking
2379
+ ? resolveProvisionalAutoLevel(selectedModel)
2380
+ : resolveThinkingLevelForModel(selectedModel, effectiveThinkingLevel),
2381
+ );
2382
+ preconnectModelHost(selectedModel.baseUrl);
2383
+ break;
2384
+ }
2385
+ if (!model) {
2386
+ const requested =
2387
+ deferredModelPatterns.length === 1
2388
+ ? `"${deferredModelPatterns[0]}"`
2389
+ : `one of ${deferredModelPatterns.map(pattern => `"${pattern}"`).join(", ")}`;
2390
+ modelFallbackMessage = `Model ${requested} not found`;
2391
+ }
2392
+ }
2393
+
2394
+ // Fall back to first available model with a valid API key, honoring the
2395
+ // path-scoped `enabledModels` allow-list when configured. Skip when the
2396
+ // user explicitly requested a model via --model that wasn't found.
2397
+ if (!model && deferredModelPatterns.length === 0) {
2398
+ // Retry the configured default role against the current catalog,
2399
+ // setting `model` (+ thinking level) when it resolves. Extension
2400
+ // factories register providers AFTER the early `defaultRoleSpec`
2401
+ // resolution, and configured discovery providers may still be
2402
+ // mid-discovery, so a role pointing at such a model (an openai-compat
2403
+ // plugin's `posthog/claude-opus-4-8`, a models.yml `openai-models-list`
2404
+ // endpoint) returned `undefined` there. Without this retry the
2405
+ // `pickDefaultAvailableModel` fallback below happily replaces the
2406
+ // user's configured default with a bundled provider's default whenever
2407
+ // a stray `OPENAI_API_KEY`/`ANTHROPIC_API_KEY` is in the environment.
2408
+ // (issues #3569, #6162)
2409
+ const tryResolveDefaultRole = async (): Promise<boolean> => {
2410
+ if (hasExplicitModel) return false;
2411
+ // Re-resolve the allowed set: extension factories and discovery
2412
+ // refreshes above may have registered models not visible earlier.
2413
+ const fallbackCandidates = await resolveAllowedModels(modelRegistry, settings, modelMatchPreferences);
2414
+ const reResolvedRoleSpec = resolveModelRoleValue(settings.getModelRole("default"), fallbackCandidates, {
2415
+ settings,
2416
+ matchPreferences: modelMatchPreferences,
2417
+ });
2418
+ if (!reResolvedRoleSpec.model) return false;
2419
+ defaultRoleSpec = reResolvedRoleSpec;
2420
+ const resolvedDefaultModel = reResolvedRoleSpec.model;
2421
+ model = resolvedDefaultModel;
2422
+ modelFallbackMessage = undefined;
2423
+ // Recompute the thinking level against the now-real model.
2424
+ // `pickInitialThinkingLevel` closes over `defaultRoleSpec`,
2425
+ // so the role's explicit selector (e.g. `:max`) now applies.
2426
+ thinkingLevel = pickInitialThinkingLevel(resolvedDefaultModel);
2427
+ autoThinking = thinkingLevel === AUTO_THINKING;
2428
+ effectiveThinkingLevel = concreteThinkingLevel(thinkingLevel);
2429
+ effectiveThinkingLevel = logger.time("resolveThinkingLevelForModel", () =>
2430
+ autoThinking
2431
+ ? resolveProvisionalAutoLevel(resolvedDefaultModel)
2432
+ : resolveThinkingLevelForModel(resolvedDefaultModel, effectiveThinkingLevel),
2433
+ );
2434
+ preconnectModelHost(resolvedDefaultModel.baseUrl);
2435
+ return true;
2436
+ };
2437
+
2438
+ await tryResolveDefaultRole();
2439
+
2440
+ if (!model) {
2441
+ const fallbackCandidates = await resolveAllowedModels(modelRegistry, settings, modelMatchPreferences);
2442
+ let pick = pickDefaultAvailableModel(fallbackCandidates.filter(hasModelAuth));
2443
+
2444
+ // Cold-cache discovery race (issues #6114, #6162): a discovery
2445
+ // provider (models.yml `openai-models-list`, LM Studio/Ollama/
2446
+ // llama.cpp, or an openai-compat proxy) ships no static models, so
2447
+ // the static+cached catalog resolved nothing above. Background
2448
+ // discovery in main.ts fires only AFTER createAgentSession returns,
2449
+ // so on a cache-cold boot the configured default stays unresolved
2450
+ // and `pick` silently degrades to an unrelated authed provider's
2451
+ // default (#6162) or "No models available" (#6114) — even though
2452
+ // `omp models` (which awaits discovery) lists the model. Await one
2453
+ // cache-aware discovery pass and retry when a default role is
2454
+ // configured (must win over `pick`) or nothing resolved at all.
2455
+ // The common path — role already resolved, or a `pick` with no
2456
+ // configured default — never pays for it.
2457
+ const defaultRoleConfigured = Boolean(settings.getModelRole("default"));
2458
+ if (
2459
+ !hasExplicitModel &&
2460
+ (defaultRoleConfigured || !pick) &&
2461
+ modelRegistry.getDiscoverableProviders().length > 0
2462
+ ) {
2463
+ await logger.time("resolveModelDiscoveryFallback", () => modelRegistry.refresh("online-if-uncached"));
2464
+ if (!(await tryResolveDefaultRole()) && !model) {
2465
+ const refreshedCandidates = await resolveAllowedModels(
2466
+ modelRegistry,
2467
+ settings,
2468
+ modelMatchPreferences,
2469
+ );
2470
+ pick = pickDefaultAvailableModel(refreshedCandidates.filter(hasModelAuth));
2471
+ }
2472
+ }
2473
+
2474
+ if (!model && pick) {
2475
+ model = pick;
2476
+ }
2477
+ }
2478
+ if (model) {
2479
+ if (modelFallbackMessage) {
2480
+ modelFallbackMessage += `. Using ${model.provider}/${model.id}`;
2481
+ }
2482
+ } else {
2483
+ const patterns = settings.get("enabledModels");
2484
+ modelFallbackMessage =
2485
+ patterns && patterns.length > 0
2486
+ ? `No model available matching enabledModels (${patterns.join(", ")}) with usable credentials. Configure auth for an allowed provider or adjust enabledModels.`
2487
+ : "No models available. Use /login or set an API key environment variable. Then use /model to select a model.";
2488
+ }
2489
+ }
2490
+
2491
+ if (model) {
2492
+ const selectedModel = model;
2493
+ const refreshedModel = await logger.time("refreshInitialModelMetadata", () =>
2494
+ modelRegistry.refreshSelectedModelMetadata(selectedModel),
2495
+ );
2496
+ if (refreshedModel !== selectedModel) {
2497
+ model = refreshedModel;
2498
+ thinkingLevel = pickInitialThinkingLevel(refreshedModel);
2499
+ autoThinking = thinkingLevel === AUTO_THINKING;
2500
+ effectiveThinkingLevel = concreteThinkingLevel(thinkingLevel);
2501
+ effectiveThinkingLevel = logger.time("resolveThinkingLevelForModel", () =>
2502
+ autoThinking
2503
+ ? resolveProvisionalAutoLevel(refreshedModel)
2504
+ : resolveThinkingLevelForModel(refreshedModel, effectiveThinkingLevel),
2505
+ );
2506
+ }
2507
+ }
2508
+
2509
+ // A first-turn user tail has no assistant metadata to copy. Once startup
2510
+ // has selected its final model, use that model to terminate the
2511
+ // interrupted turn before the live agent consumes the restored context.
2512
+ if (model) {
2513
+ const selectedModelAbort = createInterruptedTurnAbortMessage(existingBranch, {
2514
+ api: model.api,
2515
+ provider: model.provider,
2516
+ model: model.id,
2517
+ });
2518
+ if (selectedModelAbort) {
2519
+ sessionManager.appendMessage(selectedModelAbort);
2520
+ existingBranch = logger.time("getRecoveredUserTailBranch", () => sessionManager.getBranch());
2521
+ existingSession = logger.time("loadRecoveredUserTailContext", () =>
2522
+ deobfuscateSessionContext(sessionManager.buildSessionContext(), obfuscator),
2523
+ );
2524
+ }
2525
+ }
2526
+
2527
+ // Restricted sessions do not discover or evaluate custom command modules.
2528
+ const customCommandsResult: CustomCommandsLoadResult =
2529
+ options.disableExtensionDiscovery || restrictToolNames
2530
+ ? { commands: [], errors: [] }
2531
+ : await logger.time("discoverCustomCommands", loadCustomCommandsInternal, { cwd, agentDir });
2532
+ if (!options.disableExtensionDiscovery && !restrictToolNames) {
2533
+ for (const { path, error } of customCommandsResult.errors) {
2534
+ logger.error("Failed to load custom command", { path, error });
2535
+ }
2536
+ }
2537
+
2538
+ // The runner is created unconditionally — even with zero extensions loaded — because the
2539
+ // `ExtensionToolWrapper` installed below is the only place the per-tool approval gate runs.
2540
+ // A conditional runner means the approval system silently disappears for users with no
2541
+ // extensions, contradicting non-yolo `tools.approvalMode` settings without feedback.
2542
+ // (The builtin autoresearch extension is unconditionally loaded above, so this scenario
2543
+ // is unreachable; unconditional runner construction keeps that invariant explicit and
2544
+ // prevents future optional extensions from silently re-opening the hole.)
2545
+ const extensionRunner: ExtensionRunner = new ExtensionRunner(
2546
+ extensionsResult.extensions,
2547
+ extensionsResult.runtime,
2548
+ cwd,
2549
+ sessionManager,
2550
+ modelRegistry,
2551
+ () => (hasSession ? createSessionMemoryRuntimeContext(session, agentDir, cwd) : undefined),
2552
+ settings,
2553
+ localProtocolOptions,
2554
+ () => (hasSession ? session.getAsyncJobSnapshot() : null),
2555
+ );
2556
+
2557
+ credentialDisabledTarget = extensionRunner;
2558
+ for (const event of startupCredentialDisabledEvents.splice(0)) {
2559
+ // Discard return: any handler error is routed through runner.onError listeners.
2560
+ void extensionRunner.emitCredentialDisabled(event);
2561
+ }
2562
+
2563
+ const getSessionContext = () => ({
2564
+ sessionManager,
2565
+ modelRegistry,
2566
+ model: agent.state.model,
2567
+ isIdle: () => !session.isStreaming,
2568
+ hasQueuedMessages: () => session.queuedMessageCount > 0,
2569
+ abort: () => {
2570
+ session.abort({ reason: USER_INTERRUPT_LABEL });
2571
+ },
2572
+ settings,
2573
+ localProtocolOptions,
2574
+ autoApprove: options.autoApprove ?? false,
2575
+ });
2576
+ const toolContextStore = new ToolContextStore(getSessionContext);
2577
+ // Native built-in implementations backing same-tool `ctx.invokeTool`, so a tool that
2578
+ // re-registers a built-in (e.g. wrapping `write`) can delegate to the original — reaching the
2579
+ // unwrapped native execute, which inherits the caller's already-granted approval rather than
2580
+ // re-running the gate. Seeded from the xdev registry when present (it retains discoverable
2581
+ // built-ins like `browser` that xdev partitioning removes from the active tool array), else
2582
+ // from the built-in registry; captured before the ExtensionToolWrapper pass so the natives
2583
+ // stay unwrapped. The extension runner exposes it to re-registered tools via createContext.
2584
+ const nativeToolsByName = new Map<string, Tool>(toolSession.xdev?.tools ?? undefined);
2585
+
2586
+ const registeredTools = restrictToolNames ? [] : extensionRunner.getAllRegisteredTools();
2587
+ const sdkCustomTools =
2588
+ restrictToolNames && options.allowRestrictedCustomTools !== true
2589
+ ? []
2590
+ : (options.customTools?.filter(tool => !isLegacyBuiltinToolDefinition(tool)) ?? []);
2591
+ const allCustomTools = [
2592
+ ...registeredTools,
2593
+ ...sdkCustomTools.map(tool => {
2594
+ const definition = isCustomTool(tool) ? customToolToDefinition(tool) : tool;
2595
+ return { definition, extensionPath: "<sdk>" };
2596
+ }),
2597
+ ];
2598
+ // `wrapToolWithMetaNotice` runs the centralized large-output → artifact spill.
2599
+ // Built-in tools get it in `createTools`; extension, SDK-custom, image-gen,
2600
+ // TTS, and startup (non-deferred) MCP tools all funnel through here, so apply
2601
+ // it once at this adapter boundary (idempotent — a no-op if already wrapped).
2602
+ const wrappedExtensionTools: Tool[] = deduplicateMCPToolsByName(
2603
+ wrapRegisteredTools(allCustomTools, extensionRunner).map(wrapToolWithMetaNotice),
2604
+ );
2605
+
2606
+ // All built-in tools are active (conditional tools like git/ask return null from factory if disabled)
2607
+ const builtInRegistryToolNames = toolSession.xdev?.builtInNames ?? new Set(toolRegistry.keys());
2608
+ // Capture the native built-in implementations before extension re-registration replaces registry
2609
+ // entries and before the ExtensionToolWrapper pass below, so `ctx.invokeTool` reaches the
2610
+ // unwrapped native execute (inheriting the caller's already-granted approval, not re-gating).
2611
+ for (const [name, tool] of toolRegistry) {
2612
+ nativeToolsByName.set(name, tool);
2613
+ }
2614
+ if (!restrictToolNames && !toolRegistry.has("goal") && settings.get("goal.enabled")) {
2615
+ const goalTool = await logger.time("createTools:goal:session", HIDDEN_TOOLS.goal, toolSession);
2616
+ if (goalTool) {
2617
+ const wrapped = wrapToolWithMetaNotice(goalTool);
2618
+ toolRegistry.set(goalTool.name, wrapped);
2619
+ builtInRegistryToolNames.add(goalTool.name);
2620
+ nativeToolsByName.set(goalTool.name, wrapped);
2621
+ }
2622
+ }
2623
+ for (const tool of wrappedExtensionTools) {
2624
+ toolRegistry.set(tool.name, tool);
2625
+ builtInRegistryToolNames.delete(tool.name);
2626
+ }
2627
+ // Expose the native built-ins to same-tool `ctx.invokeTool` on re-registered tools. Set after
2628
+ // the override loop so the map holds the natives, not the extension replacements. The context
2629
+ // factory is the loop's own tool context, so a delegated native call sees ordinary session state.
2630
+ extensionRunner.setNativeToolResolver(name => {
2631
+ const tool = nativeToolsByName.get(name);
2632
+ return tool ? { tool, makeContext: () => toolContextStore.getContext() } : undefined;
2633
+ });
2634
+ if (deferMCPDiscoveryForUI && mcpManager) {
2635
+ for (const name of collectPendingMCPToolNames(options.toolNames)) {
2636
+ if (!toolRegistry.has(name)) {
2637
+ toolRegistry.set(name, createPendingMCPTool(name));
2638
+ }
2639
+ }
2640
+ }
2641
+
2642
+ // Wrap every tool with `ExtensionToolWrapper` so the per-tool approval gate runs on every
2643
+ // call site, regardless of whether any user extensions are loaded. See the runner-construction
2644
+ // comment above for the safety invariant this enforces.
2645
+ for (const tool of toolRegistry.values()) {
2646
+ toolRegistry.set(tool.name, new ExtensionToolWrapper(tool, extensionRunner));
2647
+ }
2648
+ // Cursor's own client owns file edits, so `edit` is not advertised to the
2649
+ // model (commit 8ba0498eb: full-file `write` is used instead). The exec
2650
+ // bridge is a different consumer: the server sends native `pi_edit`
2651
+ // frames regardless of the advertised catalog, and answering them needs
2652
+ // a real tool.
2653
+ //
2654
+ // It must be a `replace`-mode instance. `PiEditExecArgs` carries
2655
+ // `old_string`/`new_string` replacements, which is exactly `replace`'s schema and
2656
+ // nothing else's — under the default `hashline` mode the frame's args do
2657
+ // not match the tool's parameters at all. The registry instance follows
2658
+ // the session's configured mode, so the bridge builds its own.
2659
+ //
2660
+ // The grant is captured HERE, before the Cursor branch below deletes
2661
+ // `edit` from the registry, and independently of the session's provider:
2662
+ // a session that starts on another provider can switch to Cursor later,
2663
+ // and the roster is built once, at session creation. Reading the registry
2664
+ // at frame time would see the switched-to state, not the grant.
2665
+ const editWasGranted = toolRegistry.has("edit");
2666
+ // Built on first use rather than eagerly: a session that never reaches
2667
+ // Cursor never constructs it.
2668
+ let cursorBridgeEditTool: AgentTool | undefined;
2669
+ const getCursorBridgeEditTool = (): AgentTool | undefined => {
2670
+ // Only when the session actually granted `edit`. `createTools` omits
2671
+ // it entirely for a restricted tool set, and the bridge answers native
2672
+ // frames that arrive regardless of the advertised catalog — so
2673
+ // building one unconditionally would hand a read-only agent a
2674
+ // mutating tool it was denied (the issue #5680 escalation).
2675
+ if (!editWasGranted) return undefined;
2676
+ cursorBridgeEditTool ??= createBridgeEditTool(toolSession, extensionRunner);
2677
+ return cursorBridgeEditTool;
2678
+ };
2679
+ // Whether this session granted a file-writing tool. Same capture-early
2680
+ // reasoning, plus `write` may be auto-registered further down as an xdev
2681
+ // transport. The exec bridge answers native `delete` and
2682
+ // resource-download frames that mutate files without running a registry
2683
+ // tool, so it needs the grant as the session actually made it.
2684
+ const cursorCanMutateFiles = editWasGranted || toolRegistry.has("write");
2685
+ if (model?.provider === "cursor") {
2686
+ toolRegistry.delete("edit");
2687
+ builtInRegistryToolNames.delete("edit");
2688
+ }
2689
+
2690
+ let writeRegistration: Promise<boolean> | undefined;
2691
+ const ensureWriteRegistered = (): Promise<boolean> => {
2692
+ if (toolRegistry.has("write")) return Promise.resolve(builtInRegistryToolNames.has("write"));
2693
+ writeRegistration ??= (async () => {
2694
+ const writeTool = await logger.time("createTools:write:session", BUILTIN_TOOLS.write, toolSession);
2695
+ if (!writeTool || toolRegistry.has("write")) return builtInRegistryToolNames.has("write");
2696
+ const nativeWrite = wrapToolWithMetaNotice(writeTool);
2697
+ toolRegistry.set(writeTool.name, new ExtensionToolWrapper(nativeWrite, extensionRunner) as Tool);
2698
+ builtInRegistryToolNames.add(writeTool.name);
2699
+ nativeToolsByName.set(writeTool.name, nativeWrite);
2700
+ return true;
2701
+ })().finally(() => {
2702
+ writeRegistration = undefined;
2703
+ });
2704
+ return writeRegistration;
2705
+ };
2706
+
2707
+ // Existing staged/device paths need write registered before active-set assembly.
2708
+ // Deferred MCP also registers it now, but refresh activates it only after a server connects.
2709
+ // xd:// mounts never register write: xdev state only exists when the session
2710
+ // already granted a write tool (see createTools), so mounting rides that grant.
2711
+ const hasDeferrableTools = Array.from(toolRegistry.values()).some(tool => tool.deferrable === true);
2712
+ const planModeAvailable = settings.get("plan.enabled");
2713
+ if (!restrictToolNames && (hasDeferrableTools || planModeAvailable || deferMCPDiscoveryForUI)) {
2714
+ await ensureWriteRegistered();
2715
+ }
2716
+
2717
+ let cursorEventEmitter: ((event: AgentEvent) => void) | undefined;
2718
+ // Cursor and the agent loop may call a mounted device by its top-level
2719
+ // name. Resolve that name from the canonical map and apply the same
2720
+ // execution-only ACP decorator used by `write xd://<tool>`; docs and
2721
+ // renderer lookup continue to use the undecorated canonical instance.
2722
+ const resolveDeviceTool = (name: string): AgentTool | undefined => {
2723
+ const state = toolSession.xdev;
2724
+ if (!state) return undefined;
2725
+ return resolveMountedXdevExecutable(state, name);
2726
+ };
2727
+ // Cursor's resource frames ask what THIS client's servers advertise; only
2728
+ // live connections have any. Built once: the advisor bridges answer from
2729
+ // the same connections the primary does.
2730
+ const cursorMcpResources: CursorMcpResourceAdapter | undefined = mcpManager && {
2731
+ serverNames: () => mcpManager.getConnectedServers(),
2732
+ getServerResources: async name => {
2733
+ // The manager registers a server's tools before its background
2734
+ // resource load finishes, so a frame arriving in that window
2735
+ // would read an empty cache and report "advertises nothing".
2736
+ await mcpManager.ensureServerResources(name);
2737
+ return mcpManager.getServerResources(name);
2738
+ },
2739
+ readServerResource: (name, uri) => mcpManager.readServerResource(name, uri),
2740
+ };
2741
+ const cursorExecHandlers = new CursorExecHandlers({
2742
+ cwd,
2743
+ // The session's cwd moves (`/cd`, resume, branch restore) while this
2744
+ // bridge is built once at startup. Path-confining frames — the native
2745
+ // `delete` and a `download_path` resource read — resolve against
2746
+ // whichever of the two they are given, so without the live resolver the
2747
+ // primary would write into the workspace the session has left while
2748
+ // reporting success for the path the server asked about. The advisor
2749
+ // bridge already passes one.
2750
+ getCwd: () => sessionManager.getCwd(),
2751
+ tools: toolRegistry,
2752
+ getExecutableTool: resolveDeviceTool,
2753
+ // `pi_edit` needs the `replace`-mode instance specifically, and the
2754
+ // registry may still hold the session's own `edit` (any mode) when
2755
+ // this session did not start on Cursor.
2756
+ getEditReplaceTool: getCursorBridgeEditTool,
2757
+ getToolContext: () => toolContextStore.getContext(),
2758
+ mcpResources: cursorMcpResources,
2759
+ emitEvent: event => cursorEventEmitter?.(event),
2760
+ getTodoPhases: () => session.getTodoPhases(),
2761
+ setTodoPhases: phases => session.setTodoPhases(phases),
2762
+ persistTodoPhases: phases => sessionManager.appendCustomEntry(USER_TODO_EDIT_CUSTOM_TYPE, { phases }),
2763
+ // `pi_grep` carries its own context width and match cap, which the
2764
+ // shared grep instance fixed at construction cannot express. Gated on
2765
+ // the grant: the factory builds a fresh tool and `executeTool` prefers
2766
+ // it over the registry, so installing it unconditionally would let a
2767
+ // session without `grep` search anyway.
2768
+ createGrepTool: toolRegistry.has("grep") ? createBridgeGrepFactory(toolSession, extensionRunner) : undefined,
2769
+ // The native `delete` and resource-download frames mutate files
2770
+ // without running a registry tool, so this grant is the only thing
2771
+ // standing between a restricted session and a workspace write.
2772
+ allowDirectFileMutation: cursorCanMutateFiles,
2773
+ });
2774
+
2775
+ // Resolve the inline-descriptors setting against the session-start model.
2776
+ // `auto` enforces the per-model policy (inline for Gemini, off otherwise);
2777
+ // like the rest of the prune machinery this is fixed for the session, so a
2778
+ // mid-session model switch keeps the start-time decision.
2779
+ const inlineToolDescriptors = shouldInlineToolDescriptors(settings.get("inlineToolDescriptors"), model?.id);
2780
+ const eagerTasks = settings.get("task.eager") !== "default";
2781
+ const eagerTasksAlways = settings.get("task.eager") === "always";
2782
+ const intentField = $flag("PI_INTENT_TRACING", settings.get("tools.intentTracing")) ? INTENT_FIELD : undefined;
2783
+ const includeWorkspaceTree = settings.get("includeWorkspaceTree") ?? false;
2784
+ const rebuildSystemPrompt = async (
2785
+ toolNames: string[],
2786
+ tools: Map<string, AgentTool>,
2787
+ ): Promise<BuildSystemPromptResult> => {
2788
+ toolContextStore.setToolNames(toolNames);
2789
+ const promptCwd = sessionManager.getCwd();
2790
+ const activeRepoContext = hasSession
2791
+ ? await logger.time("resolveActiveRepoContext", resolveRepoContext, promptCwd)
2792
+ : initialActiveRepoContext;
2793
+ if (hasSession && options.contextFiles === undefined) {
2794
+ contextFiles = await logger.time("discoverContextFiles", discoverContextFiles, promptCwd, agentDir, [
2795
+ ...(settings.get("disabledExtensions") ?? []),
2796
+ ]);
2797
+ toolSession.contextFiles = contextFiles;
2798
+ session.setAdvisorContextPrompt(formatAdvisorContextPrompt(contextFiles));
2799
+ }
2800
+ const memoryBackend = restrictToolNames ? undefined : await resolveMemoryBackend(settings);
2801
+ const memoryInstructions = memoryBackend
2802
+ ? await memoryBackend.buildDeveloperInstructions(agentDir, settings, session)
2803
+ : undefined;
2804
+
2805
+ // Build combined append prompt: memory instructions + auto-learn guidance
2806
+ // + mounted MCP route guidance + optional MCP server instructions. For UI
2807
+ // sessions MCP discovery is deferred, so the initial registry and
2808
+ // `getServerInstructions()` are empty until the background connect
2809
+ // completes; the rebuild that `refreshMCPTools` triggers post-discovery
2810
+ // then picks up the mounted routes and any connected-server instructions.
2811
+ const serverInstructions = mcpManager?.getServerInstructions();
2812
+ // Drive guidance off the auto-learn BUILTINS that createTools actually built
2813
+ // (provenance, not just an active name): `builtInToolNames` excludes a
2814
+ // custom/extension tool that merely shares the name, and reflects the
2815
+ // session-start build — so a subagent that filtered them out, a mid-session
2816
+ // enable that never built them, or a same-named custom tool while auto-learn
2817
+ // is off all get no guidance.
2818
+ const autoLearnInstructions = restrictToolNames
2819
+ ? undefined
2820
+ : buildAutoLearnInstructions({
2821
+ manageSkill: builtInToolNames.includes("manage_skill"),
2822
+ learn: builtInToolNames.includes("learn"),
2823
+ });
2824
+ const appendParts: string[] = [];
2825
+ if (memoryInstructions) appendParts.push(memoryInstructions);
2826
+ if (autoLearnInstructions) appendParts.push(autoLearnInstructions);
2827
+ const projection = projectMountedMCPXdevGuidance(
2828
+ collectMountedMCPToolRoutes(toolSession.xdev ? listXdevTools(toolSession.xdev) : []),
2829
+ );
2830
+ if (projection.mappings.length > 0 || projection.hasOmittedMappings) {
2831
+ appendParts.push(
2832
+ prompt
2833
+ .render(mcpXdevGuidanceTemplate, {
2834
+ tools: projection.mappings.map(mapping => ({
2835
+ mcpToolName: mapping.label,
2836
+ path: mapping.path,
2837
+ })),
2838
+ hasOmittedTools: projection.hasOmittedMappings,
2839
+ })
2840
+ .trim(),
2841
+ );
2842
+ }
2843
+ if (serverInstructions && serverInstructions.size > 0) {
2844
+ appendParts.push(
2845
+ "## MCP Server Instructions\n\nThe following instructions are provided by connected MCP servers. They are server-controlled and may not be verified.",
2846
+ );
2847
+ for (const [srvName, srvInstructions] of serverInstructions) {
2848
+ const truncated =
2849
+ srvInstructions.length > MAX_MCP_INSTRUCTIONS_LENGTH
2850
+ ? `${srvInstructions.slice(0, MAX_MCP_INSTRUCTIONS_LENGTH)}\n[truncated]`
2851
+ : srvInstructions;
2852
+ appendParts.push(`### ${srvName}\n${truncated}`);
2853
+ }
2854
+ }
2855
+ let appendPrompt: string | undefined = appendParts.length > 0 ? appendParts.join("\n\n") : undefined;
2856
+ // Owned/in-band tool dialects (non-native) require the full functions-
2857
+ // namespace catalog; native tool calling lets the compact name list suffice.
2858
+ const nativeTools = resolveDialect(settings.get("tools.format"), agent?.state.model ?? model) === undefined;
2859
+ const promptTools = projectSystemPromptToolMetadata(
2860
+ tools,
2861
+ nativeTools && !inlineToolDescriptors ? { mode: "compact", toolNames } : { mode: "full" },
2862
+ );
2863
+ if (options.appendSystemPrompt) {
2864
+ appendPrompt = appendPrompt
2865
+ ? `${appendPrompt}\n\n${options.appendSystemPrompt}`
2866
+ : options.appendSystemPrompt;
2867
+ }
2868
+ const defaultPrompt = await buildSystemPromptInternal({
2869
+ cwd: promptCwd,
2870
+ additionalWorkspaceRoots: sessionManager.getAdditionalDirectories(),
2871
+ xdevTools: toolSession.xdev ? xdevEntries(toolSession.xdev) : [],
2872
+ xdevDocs: toolSession.xdev
2873
+ ? xdevDocsAll(toolSession.xdev, settings.get("tools.xdevDocs"), settings.get("tools.xdevInlineDevices"))
2874
+ : "",
2875
+ resolvedCustomPrompt: options.customSystemPrompt,
2876
+ skills: session?.skills ?? skills,
2877
+ contextFiles,
2878
+ tools: promptTools,
2879
+ toolNames,
2880
+ rules: rulebookRules,
2881
+ alwaysApplyRules,
2882
+ resolvedAppendSystemPrompt: appendPrompt,
2883
+ skillsSettings: settings.getGroup("skills"),
2884
+ inlineToolDescriptors,
2885
+ nativeTools,
2886
+ intentField,
2887
+ eagerTasks,
2888
+ eagerTasksAlways,
2889
+ taskBatch: settings.get("task.batch"),
2890
+ taskMaxConcurrency: settings.get("task.maxConcurrency"),
2891
+ scoutAvailable: isScoutSpawnable(
2892
+ settings.get("task.disabledAgents") as string[] | undefined,
2893
+ options.spawns ?? "*",
2894
+ ),
2895
+ taskIrcEnabled: !restrictToolNames && isIrcEnabled(settings, options.taskDepth ?? 0),
2896
+ autoQaEnabled: !restrictToolNames && isAutoQaEnabled(settings),
2897
+ secretsEnabled,
2898
+ workspaceTree: workspaceTreePromise,
2899
+ includeWorkspaceTree,
2900
+ memoryRootEnabled: memoryBackend?.id === "local",
2901
+ securityEnabled: settings.get("security.enabled"),
2902
+ model: getActiveModelString(),
2903
+ includeModelInPrompt: settings.get("includeModelInPrompt"),
2904
+ personality: agentKind === "sub" ? "none" : settings.get("personality"),
2905
+ renderMermaid: settings.get("tui.renderMermaid"),
2906
+ activeRepoContext,
2907
+ });
2908
+
2909
+ if (options.systemPrompt === undefined) {
2910
+ return defaultPrompt;
2911
+ }
2912
+ const customPrompt =
2913
+ typeof options.systemPrompt === "function"
2914
+ ? options.systemPrompt(defaultPrompt.systemPrompt)
2915
+ : options.systemPrompt;
2916
+ return {
2917
+ systemPrompt: typeof customPrompt === "string" ? [customPrompt] : customPrompt,
2918
+ };
2919
+ };
2920
+
2921
+ const toolNamesFromRegistry = Array.from(toolRegistry.keys());
2922
+ const explicitlyRequestedToolNames = options.toolNames ? normalizeToolNames(options.toolNames) : undefined;
2923
+ // When `requireYieldTool` is set, the subagent's prompts and idle-reminders demand a
2924
+ // `yield` call to terminate. The tool registry already includes `yield` (see
2925
+ // `createTools`), but an explicit `toolNames` list would otherwise drop it from the
2926
+ // active set — leaving the model unable to satisfy the contract. Mirror the same
2927
+ // invariant `parseAgentFields` enforces on frontmatter `tools`.
2928
+ if (
2929
+ options.requireYieldTool === true &&
2930
+ explicitlyRequestedToolNames &&
2931
+ !explicitlyRequestedToolNames.includes("yield")
2932
+ ) {
2933
+ explicitlyRequestedToolNames.push("yield");
2934
+ }
2935
+ // Auto-learn builtins are force-included into the registry by `createTools`
2936
+ // for enabled top-level sessions (tools/index.ts), but — like `yield` above —
2937
+ // an explicit `toolNames` list would otherwise drop them from the ACTIVE set,
2938
+ // leaving the nudge/guidance pointing at tools the model cannot call. Activate
2939
+ // exactly the builtins createTools built (`builtInToolNames` — provenance, so a
2940
+ // same-named custom/extension tool is never force-activated when auto-learn is
2941
+ // off) to keep guidance, controller, and the active set consistent.
2942
+ if (!restrictToolNames && explicitlyRequestedToolNames) {
2943
+ for (const name of ["manage_skill", "learn"]) {
2944
+ if (builtInToolNames.includes(name) && !explicitlyRequestedToolNames.includes(name)) {
2945
+ explicitlyRequestedToolNames.push(name);
2946
+ }
2947
+ }
2948
+ }
2949
+ // Checkpoint and rewind are a pair: `createTools` auto-includes the sister
2950
+ // tool in the registry, but an explicit `toolNames` list would otherwise
2951
+ // drop it from the ACTIVE set — leaving the agent able to checkpoint but
2952
+ // unable to rewind (or vice versa). Mirror the pairing here. Unlike the
2953
+ // manage_skill/learn mirror above, this is a safety pairing — it applies
2954
+ // to restricted sessions too.
2955
+ if (explicitlyRequestedToolNames) {
2956
+ if (builtInToolNames.includes("checkpoint") && !explicitlyRequestedToolNames.includes("rewind")) {
2957
+ explicitlyRequestedToolNames.push("rewind");
2958
+ } else if (builtInToolNames.includes("rewind") && !explicitlyRequestedToolNames.includes("checkpoint")) {
2959
+ explicitlyRequestedToolNames.push("checkpoint");
2960
+ }
2961
+ }
2962
+ const requestedToolNames = explicitlyRequestedToolNames ?? toolNamesFromRegistry;
2963
+ const normalizedRequested = requestedToolNames.filter(name => toolRegistry.has(name));
2964
+ const defaultInactiveToolNames = new Set(
2965
+ registeredTools.filter(tool => tool.definition.defaultInactive).map(tool => tool.definition.name),
2966
+ );
2967
+ const requestedActiveToolNames = normalizedRequested.filter(name => name !== "goal");
2968
+ const explicitlyRequestedToolNameSet = explicitlyRequestedToolNames
2969
+ ? new Set(explicitlyRequestedToolNames)
2970
+ : undefined;
2971
+ const xdevReadAvailable =
2972
+ builtInRegistryToolNames.has("read") &&
2973
+ (explicitlyRequestedToolNameSet === undefined || explicitlyRequestedToolNameSet.has("read"));
2974
+ const xdevWriteAvailable =
2975
+ builtInRegistryToolNames.has("write") &&
2976
+ (explicitlyRequestedToolNameSet === undefined || explicitlyRequestedToolNameSet.has("write"));
2977
+ const initialRequestedActiveToolNames = options.toolNames
2978
+ ? requestedActiveToolNames
2979
+ : requestedActiveToolNames.filter(name => !defaultInactiveToolNames.has(name));
2980
+ let initialToolNames = [...initialRequestedActiveToolNames];
2981
+
2982
+ // Custom tools and extension-registered tools are always included regardless of toolNames filter.
2983
+ // Restricted callers own the list, so never widen it with registered tools.
2984
+ const alwaysInclude: string[] = restrictToolNames
2985
+ ? []
2986
+ : [
2987
+ ...sdkCustomTools.map(t => (isCustomTool(t) ? t.name : t.name)),
2988
+ ...registeredTools.filter(t => !t.definition.defaultInactive).map(t => t.definition.name),
2989
+ ];
2990
+ for (const name of alwaysInclude) {
2991
+ if (toolRegistry.has(name) && !initialToolNames.includes(name)) {
2992
+ initialToolNames.push(name);
2993
+ }
2994
+ }
2995
+
2996
+ // Pre-register in the global agent registry BEFORE building the system prompt,
2997
+ // so that subagents launched in the same parallel batch can see each other in
2998
+ // their initial `# IRC Peers` block (rendered inside `rebuildSystemPrompt`).
2999
+ // The session reference is attached after construction below.
3000
+ const registrationInput = {
3001
+ id: resolvedAgentId,
3002
+ displayName: resolvedAgentDisplayName,
3003
+ kind: agentKind,
3004
+ parentId: options.parentAgentId,
3005
+ session: null,
3006
+ sessionFile: sessionManager.getSessionFile() ?? null,
3007
+ status: "running" as const,
3008
+ };
3009
+ registeredAgentRef =
3010
+ options.expectedAgentRef === undefined
3011
+ ? agentRegistry.register(registrationInput)
3012
+ : agentRegistry.registerIfAvailable(registrationInput, options.expectedAgentRef);
3013
+ if (!registeredAgentRef) {
3014
+ throw new Error(`Agent "${resolvedAgentId}" is already owned by another session generation.`);
3015
+ }
3016
+ // A reused parked ref remains parked until the new AgentSession is fully
3017
+ // constructed and attached. Startup failure therefore leaves it revivable.
3018
+ hasRegistered = options.expectedAgentRef === undefined || options.expectedAgentRef === null;
3019
+
3020
+ // Partition the initial enabled set for the xd:// transport. Tool instances
3021
+ // remain in the canonical map; only presentation names move between layers.
3022
+ // Mounting requires both transport halves in the granted set (`read xd://`
3023
+ // discovers, `write xd://<tool>` executes); a session without either keeps
3024
+ // every tool top-level instead of auto-granting the missing transport.
3025
+ if (toolSession.xdev) {
3026
+ const topLevelToolNames: string[] = [];
3027
+ const mountedNames: string[] = [];
3028
+ for (const name of initialToolNames) {
3029
+ const tool = toolRegistry.get(name);
3030
+ const explicitlyRequested = explicitlyRequestedToolNameSet?.has(name) === true;
3031
+ if (tool && xdevReadAvailable && xdevWriteAvailable && !explicitlyRequested && isMountableUnderXdev(tool))
3032
+ mountedNames.push(name);
3033
+ else topLevelToolNames.push(name);
3034
+ }
3035
+ toolSession.xdev.mountedNames.clear();
3036
+ for (const name of mountedNames) toolSession.xdev.mountedNames.add(name);
3037
+ initialToolNames = topLevelToolNames;
3038
+ if (mountedNames.length > 0 && !initialToolNames.includes("write")) initialToolNames.push("write");
3039
+ }
3040
+
3041
+ setActiveToolNames(initialToolNames);
3042
+ const { systemPrompt } = await logger.time(
3043
+ "buildSystemPrompt",
3044
+ rebuildSystemPrompt,
3045
+ initialToolNames,
3046
+ toolRegistry,
3047
+ );
3048
+
3049
+ const promptTemplates = await promptTemplatesPromise;
3050
+ toolSession.promptTemplates = promptTemplates;
3051
+
3052
+ const slashCommands = await slashCommandsPromise;
3053
+
3054
+ // Keep image blocks off the wire when they'd be rejected: either the user
3055
+ // disabled images (`images.blockImages`) or the active model has no vision
3056
+ // support. The latter covers switching from a vision model to a text-only
3057
+ // one mid-session — historical image blocks would otherwise be replayed to
3058
+ // a provider that 400s on them (#5400). Read both dynamically so a `/model`
3059
+ // switch or setting change takes effect on the next turn.
3060
+ const convertToLlmWithBlockImages = (messages: AgentMessage[]): Message[] => {
3061
+ const converted = convertToLlm(messages);
3062
+ if (settings.get("images.blockImages")) {
3063
+ return replaceLlmImagesWithText(converted, "Image reading is disabled.");
3064
+ }
3065
+ const activeModel = agent?.state.model ?? model;
3066
+ if (activeModel && !activeModel.input.includes("image")) {
3067
+ return replaceLlmImagesWithText(
3068
+ converted,
3069
+ "[image omitted: the active model does not support image input]",
3070
+ );
3071
+ }
3072
+ return converted;
3073
+ };
3074
+
3075
+ // Final convertToLlm: live provider replay drops API-level refusal errors,
3076
+ // then applies secret obfuscation to the remaining outbound context.
3077
+ const convertToLlmFinal = (messages: AgentMessage[]): Message[] => {
3078
+ const converted = filterProviderReplayMessages(convertToLlmWithBlockImages(messages));
3079
+ if (!obfuscator?.hasSecrets()) return converted;
3080
+ return obfuscateMessages(obfuscator, converted);
3081
+ };
3082
+
3083
+ const transformContext = async (messages: AgentMessage[], _signal?: AbortSignal) => {
3084
+ const withContext = await extensionRunner.emitContext(messages);
3085
+ return wrapSteeringForModel(withContext);
3086
+ };
3087
+ // Per-request provider-context transforms. Obfuscate FIRST so secrets are
3088
+ // redacted from text before snapcompact rasterizes it into PNG frames, then
3089
+ // clamp images to the active provider budget before the request is sent.
3090
+ const snapcompactSystemPromptMode = settings.get("snapcompact.systemPrompt");
3091
+ const snapcompactInline =
3092
+ snapcompactSystemPromptMode !== "none" || settings.get("snapcompact.toolResults")
3093
+ ? new SnapcompactInlineTransformer(
3094
+ {
3095
+ renderSystemPrompt: snapcompactSystemPromptMode,
3096
+ renderToolResults: settings.get("snapcompact.toolResults"),
3097
+ shape: settings.get("snapcompact.shape"),
3098
+ },
3099
+ // Journal the tokens each imaged tool result keeps off the wire
3100
+ // (frames never reach session.jsonl, so this is their only trace).
3101
+ createSnapcompactSavingsRecorder(() => sessionManager.getSessionFile() ?? null),
3102
+ )
3103
+ : undefined;
3104
+ const transformProviderContext = async (context: Context, transformModel: Model): Promise<Context> => {
3105
+ let transformed = obfuscator ? obfuscateProviderContext(obfuscator, context) : context;
3106
+ if (snapcompactInline) transformed = await snapcompactInline.transform(transformed, transformModel);
3107
+ return clampProviderContextImages(transformed, transformModel);
3108
+ };
3109
+ const onPayload = async (payload: unknown, model?: Model) => {
3110
+ return await extensionRunner.emitBeforeProviderRequest(payload, model);
3111
+ };
3112
+ const onResponse: SimpleStreamOptions["onResponse"] = async (response, model) => {
3113
+ await extensionRunner.emitAfterProviderResponse(response, model);
3114
+ };
3115
+
3116
+ const setToolUIContext = (uiContext: ExtensionUIContext, hasUI: boolean) => {
3117
+ toolContextStore.setUIContext(uiContext, hasUI);
3118
+ };
3119
+
3120
+ const initialTools = initialToolNames
3121
+ .map(name => toolRegistry.get(name))
3122
+ .filter((tool): tool is AgentTool => tool !== undefined);
3123
+ const autoLearnCaptureTools = initialTools.filter(tool => tool.name === "manage_skill" || tool.name === "learn");
3124
+
3125
+ const openaiWebsocketSetting = settings.get("providers.openaiWebsockets") ?? "off";
3126
+ const preferOpenAICodexWebsockets =
3127
+ openaiWebsocketSetting === "on" ? true : openaiWebsocketSetting === "off" ? false : undefined;
3128
+ const configuredServiceTierByFamily = hasServiceTierEntry
3129
+ ? (existingSession.serviceTier ?? {})
3130
+ : buildServiceTierByFamily(
3131
+ settings.get("tier.openai"),
3132
+ settings.get("tier.anthropic"),
3133
+ settings.get("tier.google"),
3134
+ );
3135
+ const initialServiceTierByFamily = { ...configuredServiceTierByFamily };
3136
+ if (options.openAIServiceTier === null) {
3137
+ delete initialServiceTierByFamily.openai;
3138
+ } else if (options.openAIServiceTier !== undefined) {
3139
+ initialServiceTierByFamily.openai = options.openAIServiceTier;
3140
+ }
3141
+
3142
+ // One-shot launch-latency marker: fired the first time the loop dispatches
3143
+ // a chat request to the provider transport. See onFirstChatDispatch.
3144
+ let notifyFirstChatDispatch = options.onFirstChatDispatch;
3145
+ // Shared, settings-aware stream wrapper used by the main agent, advisor,
3146
+ // and side-channel requests (`/btw`, `/omfg`, IRC auto-replies, handoff).
3147
+ // Keeps OpenRouter sticky-routing variants, antigravity endpoint routing,
3148
+ // in-flight caps, and the loop guard consistent across every provider call
3149
+ // the session drives. Wrapped in a per-provider concurrency limiter so
3150
+ // each LLM HTTP request — not the whole subagent lifecycle — holds the
3151
+ // slot, preventing the nested-spawn deadlock from issue #3749.
3152
+ const settingsAwareStreamFn = wrapStreamFnWithProviderConcurrency(
3153
+ settings,
3154
+ createSettingsAwareStreamFn(settings),
3155
+ );
3156
+ const transformToolCallArguments = (args: Record<string, unknown>): Record<string, unknown> => {
3157
+ let result = args;
3158
+ const maxTimeout = settings.get("tools.maxTimeout");
3159
+ if (maxTimeout > 0 && typeof result.timeout === "number") {
3160
+ result = { ...result, timeout: Math.min(result.timeout, maxTimeout) };
3161
+ }
3162
+ if (obfuscator?.hasSecrets()) {
3163
+ result = deobfuscateToolArguments(obfuscator, result);
3164
+ }
3165
+ return result;
3166
+ };
3167
+ const kimiApiFormatSetting = settings.get("providers.kimiApiFormat");
3168
+ const kimiApiFormat = kimiApiFormatSetting === "auto" ? undefined : kimiApiFormatSetting;
3169
+ agent = new Agent({
3170
+ initialState: {
3171
+ systemPrompt,
3172
+ model,
3173
+ thinkingLevel: toReasoningEffort(effectiveThinkingLevel),
3174
+ disableReasoning: shouldDisableReasoning(effectiveThinkingLevel),
3175
+ tools: initialTools,
3176
+ },
3177
+ cwd,
3178
+ // Live cwd: `/move` updates SessionManager (and process cwd) without
3179
+ // reconstructing the Agent, so a static cwd would strand GitLab Duo Agent
3180
+ // namespace/project discovery on the original repo's git remote. Re-read it
3181
+ // per turn from the SessionManager.
3182
+ cwdResolver: () => sessionManager.getCwd(),
3183
+ convertToLlm: convertToLlmFinal,
3184
+ onPayload,
3185
+ onResponse,
3186
+ sessionId: providerSessionId,
3187
+ promptCacheKey: providerPromptCacheKey,
3188
+ deadline: options.deadline,
3189
+ transformContext,
3190
+ transformProviderContext,
3191
+ steeringMode: settings.get("steeringMode") ?? "one-at-a-time",
3192
+ followUpMode: settings.get("followUpMode") ?? "one-at-a-time",
3193
+ interruptMode: settings.get("interruptMode") ?? "immediate",
3194
+ thinkingBudgets: settings.getGroup("thinkingBudgets"),
3195
+ temperature: settings.get("temperature") >= 0 ? settings.get("temperature") : undefined,
3196
+ topP: settings.get("topP") >= 0 ? settings.get("topP") : undefined,
3197
+ topK: settings.get("topK") >= 0 ? settings.get("topK") : undefined,
3198
+ minP: settings.get("minP") >= 0 ? settings.get("minP") : undefined,
3199
+ presencePenalty: settings.get("presencePenalty") >= 0 ? settings.get("presencePenalty") : undefined,
3200
+ repetitionPenalty: settings.get("repetitionPenalty") >= 0 ? settings.get("repetitionPenalty") : undefined,
3201
+ hideThinkingSummary: settings.get("omitThinking"),
3202
+ kimiApiFormat,
3203
+ preferWebsockets: preferOpenAICodexWebsockets,
3204
+ getToolContext: tc => toolContextStore.getContext(tc),
3205
+ getApiKey: options.getApiKey ?? (requestModel => modelRegistry.resolver(requestModel, agent.sessionId)),
3206
+ streamFn: (streamModel, context, streamOptions) => {
3207
+ if (notifyFirstChatDispatch) {
3208
+ const cb = notifyFirstChatDispatch;
3209
+ notifyFirstChatDispatch = undefined;
3210
+ try {
3211
+ cb();
3212
+ } catch (err) {
3213
+ logger.warn("onFirstChatDispatch hook threw", {
3214
+ error: err instanceof Error ? err.message : String(err),
3215
+ });
3216
+ }
3217
+ }
3218
+ return settingsAwareStreamFn(streamModel, context, streamOptions);
3219
+ },
3220
+ cursorExecHandlers,
3221
+ getCursorTools: () => (toolSession.xdev ? listXdevTools(toolSession.xdev) : []),
3222
+ transformToolCallArguments,
3223
+ resolveFallbackTool: resolveDeviceTool,
3224
+ intentTracing: !!intentField,
3225
+ pruneToolDescriptions: inlineToolDescriptors,
3226
+ dialect: resolveDialect(settings.get("tools.format"), model),
3227
+ abortOnFabricatedToolResult: settings.get("tools.abortOnFabricatedResult"),
3228
+ getToolChoice: () => session?.nextToolChoiceDirective(),
3229
+ onToolChoiceUnavailable: () => session?.toolChoiceQueue.reject("unavailable"),
3230
+ telemetry: options.telemetry,
3231
+ appendOnlyContext: model
3232
+ ? shouldEnableAppendOnlyContext(settings.get("provider.appendOnlyContext"), model)
3233
+ ? new AppendOnlyContextManager()
3234
+ : undefined
3235
+ : undefined,
3236
+ });
3237
+
3238
+ cursorEventEmitter = event => agent.emitExternalEvent(event);
3239
+
3240
+ // Restore messages if session has existing data
3241
+ if (hasExistingSession) {
3242
+ agent.replaceMessages(existingSession.messages);
3243
+ if (options.openAIServiceTier !== undefined) {
3244
+ sessionManager.appendServiceTierChange(
3245
+ Object.keys(initialServiceTierByFamily).length > 0 ? initialServiceTierByFamily : null,
3246
+ );
3247
+ }
3248
+ } else {
3249
+ // Save initial model, thinking level, and service tier for new sessions so they can be restored on resume.
3250
+ if (model) {
3251
+ sessionManager.appendModelChange(`${model.provider}/${model.id}`);
3252
+ }
3253
+ if (!autoThinking) {
3254
+ // Do not write the `auto` selector before the first turn resolves; auto
3255
+ // classification persists its concrete effort once a real user turn runs.
3256
+ sessionManager.appendThinkingLevelChange(effectiveThinkingLevel);
3257
+ }
3258
+ if (options.openAIServiceTier !== undefined || Object.keys(initialServiceTierByFamily).length > 0) {
3259
+ sessionManager.appendServiceTierChange(
3260
+ Object.keys(initialServiceTierByFamily).length > 0 ? initialServiceTierByFamily : null,
3261
+ );
3262
+ }
3263
+ }
3264
+
3265
+ // Full toolset for the advisor, built unconditionally so it can be toggled at
3266
+ // runtime. Bound to a DISTINCT ToolSession (its own `-advisor` session id +
3267
+ // agent id) so the advisor's tool state — snapshot, seen-lines, conflict, and
3268
+ // summary caches, all keyed on session identity — stays isolated from the
3269
+ // primary, while edit/bash/write stay fully functional: the advisor is a full
3270
+ // agent and its config's `tools` selects which of these it actually gets
3271
+ // (defaulting to read/grep/glob).
3272
+ const advisorToolSession: ToolSession = {
3273
+ ...toolSession,
3274
+ get cwd() {
3275
+ return sessionManager.getCwd();
3276
+ },
3277
+ hasEditTool: true,
3278
+ requireYieldTool: false,
3279
+ getSessionId: () => {
3280
+ const id = sessionManager.getSessionId?.();
3281
+ return id ? `${id}-advisor` : null;
3282
+ },
3283
+ queueLaunchCompletion: notification =>
3284
+ session?.queueLaunchCompletion(notification) ??
3285
+ Promise.reject(new Error("Session unavailable for launch completion delivery")),
3286
+ getAgentId: () => "advisor",
3287
+ // The primary's availability signals are wrong for advisors: their tool
3288
+ // slate is filtered separately at runtime (default read/grep/glob, no
3289
+ // write transport), so xd:// devices are unreachable and read must never
3290
+ // advertise inspect_image — images are inlined, and the provider
3291
+ // boundary handles text-only advisor models.
3292
+ xdev: undefined,
3293
+ isToolActive: name => name !== "inspect_image" && toolSession.isToolActive?.(name) === true,
3294
+ };
3295
+ const advisorToolBuilds: Array<Tool | null | Promise<Tool | null>> = [];
3296
+ for (const name in BUILTIN_TOOLS) {
3297
+ advisorToolBuilds.push(BUILTIN_TOOLS[name as keyof typeof BUILTIN_TOOLS](advisorToolSession));
3298
+ }
3299
+ const built = await Promise.all(advisorToolBuilds);
3300
+ // Wrapped like every registry tool: `ExtensionToolWrapper` is where the
3301
+ // approval mode, per-tool `tools.approval.<tool>` policies and
3302
+ // `autoApprove` are enforced. The advisor's loop and its Cursor exec
3303
+ // bridge both run these instances directly, so a raw one would execute a
3304
+ // `bash`/`write` the user configured as `ask` or `deny`. Meta-notice
3305
+ // first, matching the registry's wrap order.
3306
+ const advisorTools: Tool[] = built
3307
+ .filter((tool): tool is Tool => tool != null)
3308
+ .map(tool => new ExtensionToolWrapper(wrapToolWithMetaNotice(tool), extensionRunner) as Tool);
3309
+
3310
+ const advisorWatchdogPrompts = [...watchdogFiles];
3311
+ if (initialActiveRepoContext) {
3312
+ advisorWatchdogPrompts.push(formatActiveRepoWatchdogPrompt(initialActiveRepoContext));
3313
+ }
3314
+ const advisorWatchdogPrompt = advisorWatchdogPrompts.length > 0 ? advisorWatchdogPrompts.join("\n\n") : undefined;
3315
+ // Hand the advisor the same project context files (AGENTS.md, etc.) the
3316
+ // primary agent gets in its system prompt, so the read-only reviewer judges
3317
+ // against the user's standing project rules instead of advising blind.
3318
+ const advisorContextPrompt = formatAdvisorContextPrompt(contextFiles);
3319
+ // Owned only when this session created the manager; subagents receive a
3320
+ // parent's manager via `options.mcpManager` and MUST NOT disconnect it.
3321
+ const ownedMcpManager = options.mcpManager ? undefined : mcpManager;
3322
+ // A resumed session already has advisor turns on disk; without this the status
3323
+ // line would restart its `(adv)` total at zero for the rest of the session.
3324
+ const initialAdvisorCosts = await loadAdvisorTranscriptCosts(sessionManager.getSessionFile());
3325
+ session = new AgentSession({
3326
+ advisorWatchdogPrompt,
3327
+ advisorContextPrompt,
3328
+ advisorSharedInstructions: discoveredAdvisors.sharedInstructions,
3329
+ advisorConfigs: discoveredAdvisors.advisors,
3330
+ agent,
3331
+ pruneToolDescriptions: inlineToolDescriptors,
3332
+ thinkingLevel: autoThinking ? AUTO_THINKING : effectiveThinkingLevel,
3333
+ thinkingLevelCeiling: options.thinkingLevelCeiling,
3334
+ initialRetryFallback,
3335
+ prewalk: options.prewalk,
3336
+ planYolo: options.planYolo,
3337
+ serviceTierByFamily: initialServiceTierByFamily,
3338
+ sessionManager,
3339
+ initialAdvisorCosts,
3340
+ settings,
3341
+ autoApprove: options.autoApprove,
3342
+ scoutAllowedBySpawnPolicy: isScoutSpawnable(undefined, options.spawns ?? "*"),
3343
+ evalKernelOwnerId,
3344
+ // Defined only for top-level sessions (creation is gated above).
3345
+ // AgentSession uses this to decide whether it may dispose the global
3346
+ // AsyncJobManager on teardown; subagents inherit the parent's and
3347
+ // **MUST NOT** tear it down.
3348
+ ownedAsyncJobManager: asyncJobManager,
3349
+ asyncJobManager: scopedAsyncJobManager,
3350
+ scopedModels: options.scopedModels,
3351
+ promptTemplates,
3352
+ slashCommands,
3353
+ extensionRunner,
3354
+ customCommands: customCommandsResult.commands,
3355
+ skills,
3356
+ skillWarnings,
3357
+ skillsReloadable: options.skills === undefined,
3358
+ skillsSettings: settings.getGroup("skills"),
3359
+ modelRegistry,
3360
+ toolRegistry,
3361
+ memoryAgentDir: agentDir,
3362
+ memoryTaskDepth: taskDepth,
3363
+ createMemoryTools: restrictToolNames
3364
+ ? undefined
3365
+ : async () => {
3366
+ const tools = await Promise.all(
3367
+ MEMORY_BACKEND_TOOL_NAMES.map(name => BUILTIN_TOOLS[name](toolSession)),
3368
+ );
3369
+ return tools.filter((tool): tool is AgentTool => tool !== null);
3370
+ },
3371
+ createComputerTool: restrictToolNames
3372
+ ? undefined
3373
+ : async () => (await BUILTIN_TOOLS.computer(toolSession)) ?? null,
3374
+ createInspectImageTool: restrictToolNames
3375
+ ? undefined
3376
+ : async () => (await BUILTIN_TOOLS.inspect_image(toolSession)) ?? null,
3377
+ createVibeTools:
3378
+ (options.taskDepth ?? 0) === 0 && !options.parentTaskPrefix
3379
+ ? () => createVibeTools(toolSession)
3380
+ : undefined,
3381
+ builtInToolNames: builtInRegistryToolNames,
3382
+ transformContext,
3383
+ transformProviderContext,
3384
+ onPayload,
3385
+ onResponse,
3386
+ sideStreamFn: settingsAwareStreamFn,
3387
+ advisorStreamFn: settingsAwareStreamFn,
3388
+ preferWebsockets: preferOpenAICodexWebsockets,
3389
+ convertToLlm: convertToLlmFinal,
3390
+ rebuildSystemPrompt,
3391
+ getXdevToolEntries: () => (toolSession.xdev ? xdevEntries(toolSession.xdev) : []),
3392
+ xdev: toolSession.xdev,
3393
+ presentationPinnedToolNames: explicitlyRequestedToolNameSet,
3394
+ setActiveToolNames,
3395
+ ensureWriteRegistered,
3396
+ getMcpServerInstructions: mcpManager
3397
+ ? () => {
3398
+ const raw = mcpManager.getServerInstructions();
3399
+ if (!raw || raw.size === 0) return raw;
3400
+ const out = new Map<string, string>();
3401
+ for (const [name, text] of raw) {
3402
+ out.set(
3403
+ name,
3404
+ text.length > MAX_MCP_INSTRUCTIONS_LENGTH ? text.slice(0, MAX_MCP_INSTRUCTIONS_LENGTH) : text,
3405
+ );
3406
+ }
3407
+ return out;
3408
+ }
3409
+ : undefined,
3410
+ disconnectOwnedMcpManager: ownedMcpManager ? () => ownedMcpManager.disconnectAll() : undefined,
3411
+ ttsrManager,
3412
+ obfuscator,
3413
+ agentId: resolvedAgentId,
3414
+ agentKind,
3415
+ providerSessionId: options.providerSessionId,
3416
+ providerPromptCacheKeySource,
3417
+ parentEvalSessionId: options.parentEvalSessionId,
3418
+ advisorTools,
3419
+ // Same per-call `grep` seam the primary bridge gets, built against the
3420
+ // advisor's own tool session so a `pi_grep` frame's context width and
3421
+ // match cap are honored there too.
3422
+ advisorCreateGrepTool: createBridgeGrepFactory(advisorToolSession, extensionRunner),
3423
+ // Same `replace`-mode requirement as the primary bridge; the advisor
3424
+ // path gates it on the advisor's own `edit` grant.
3425
+ advisorCreateEditTool: () => createBridgeEditTool(advisorToolSession, extensionRunner),
3426
+ // The advisor's bridge tools are wrapped for approval, but the wrapper
3427
+ // reads the mode and per-tool policies only from the execute-time
3428
+ // context — the primary bridge passes the same store.
3429
+ advisorGetToolContext: () => toolContextStore.getContext(),
3430
+ // Same live connections the primary bridge reads; an advisor's
3431
+ // resource frame would otherwise report every server as empty.
3432
+ advisorMcpResources: cursorMcpResources,
3433
+ titleSystemPrompt: options.titleSystemPrompt,
3434
+ });
3435
+ hasSession = true;
3436
+ session.yieldQueue.register<McpNotificationEntry>("mcp-notification", {
3437
+ build: buildMcpNotificationBatchMessage,
3438
+ });
3439
+ session.yieldQueue.register<DeferredDiagnosticsEntry>(LSP_LATE_DIAGNOSTIC_MESSAGE_TYPE, {
3440
+ isStale: entry => entry.isStale(),
3441
+ build: buildLateDiagnosticsBatchMessage,
3442
+ });
3443
+
3444
+ // Attach the live session to the pre-registered ref so peers can route IRC
3445
+ // messages here. Refresh sessionFile in case it was unavailable at pre-register
3446
+ // time. The dispose wrapper below unregisters on teardown (unless parked).
3447
+ if (
3448
+ !registeredAgentRef ||
3449
+ !agentRegistry.attachSession(
3450
+ resolvedAgentId,
3451
+ session,
3452
+ sessionManager.getSessionFile() ?? null,
3453
+ registeredAgentRef,
3454
+ ) ||
3455
+ !agentRegistry.setStatus(resolvedAgentId, "running", registeredAgentRef)
3456
+ ) {
3457
+ throw new Error(`Agent "${resolvedAgentId}" was replaced during session initialization.`);
3458
+ }
3459
+ hasRegistered = true;
3460
+ // MCP notification bridge cleanup — assigned when the bridge is wired below,
3461
+ // invoked from the dispose wrapper AND registered as a postmortem so both
3462
+ // explicit-dispose (SDK embedders that reuse the process across sessions) and
3463
+ // process-exit paths tear the listener down. Nulled after use so the closure
3464
+ // graph (`extensionRunner`, `session`) can be GC'd instead of retained by the
3465
+ // process-global postmortem list.
3466
+ let unsubscribeMcpNotifications: (() => void) | undefined;
3467
+ let unregisterMcpPostmortem: (() => void) | undefined;
3468
+
3469
+ {
3470
+ const originalDispose = session.dispose.bind(session);
3471
+ session.dispose = async () => {
3472
+ try {
3473
+ // Reject new session work (eval starts) the moment disposal
3474
+ // begins — the lifecycle await below opens an async gap before
3475
+ // AgentSession.dispose() would otherwise set its guards.
3476
+ session.beginDispose();
3477
+ if (agentKind === "main") {
3478
+ // Top-level teardown owns the global agent lifecycle: park timers,
3479
+ // adopted subagent sessions, revivers. Tear it down while shared
3480
+ // resources (kernels, MCP, LSP) are still live. Subagent disposal
3481
+ // must NOT touch the global lifecycle.
3482
+ const vibeRegistry = VibeSessionRegistry.global();
3483
+ const vibeParentSession = {
3484
+ getAgentId: () => resolvedAgentId,
3485
+ getSessionId: () => sessionManager.getSessionId(),
3486
+ getSessionFile: () => sessionManager.getSessionFile() ?? null,
3487
+ sessionManager,
3488
+ asyncJobManager: scopedAsyncJobManager,
3489
+ settings,
3490
+ getActiveModelString,
3491
+ };
3492
+ await vibeRegistry.suspendScope(vibeRegistry.ownerScope(vibeParentSession), scopedAsyncJobManager);
3493
+ await AgentLifecycleManager.global().dispose();
3494
+ }
3495
+ await originalDispose();
3496
+ } finally {
3497
+ unregisterUnlessParked();
3498
+ unsubscribeCredentialDisabled?.();
3499
+ unsubscribeMcpNotifications?.();
3500
+ unregisterMcpPostmortem?.();
3501
+ for (const callback of disposeCallbacks) callback();
3502
+ disposeCallbacks.clear();
3503
+ // Drop refs so the process-global postmortem list doesn't retain
3504
+ // the bridge closure past explicit dispose.
3505
+ unsubscribeMcpNotifications = undefined;
3506
+ unregisterMcpPostmortem = undefined;
3507
+ }
3508
+ };
3509
+ }
3510
+
3511
+ if (model?.api === "openai-codex-responses") {
3512
+ // `.api` equality doesn't narrow the generic; the guard makes this cast sound.
3513
+ const codexModel = model as Model<"openai-codex-responses">;
3514
+ const codexTransport = getOpenAICodexTransportDetails(codexModel, {
3515
+ sessionId: providerSessionId,
3516
+ baseUrl: codexModel.baseUrl,
3517
+ preferWebsockets: preferOpenAICodexWebsockets,
3518
+ providerSessionState: session.providerSessionState,
3519
+ });
3520
+ if (codexTransport.websocketPreferred) {
3521
+ void (async () => {
3522
+ try {
3523
+ const codexPrewarmApiKey = options.getApiKey
3524
+ ? // `getApiKey` returns a value-or-promise union; unwrap the promise,
3525
+ // then resolve the result if it is itself an ApiKeyResolver.
3526
+ await resolveApiKeyOnce(await options.getApiKey(codexModel))
3527
+ : await modelRegistry.getApiKey(codexModel, providerSessionId);
3528
+ if (!codexPrewarmApiKey) return;
3529
+ await logger.time("prewarmOpenAICodexResponses", prewarmOpenAICodexResponses, codexModel, {
3530
+ apiKey: codexPrewarmApiKey,
3531
+ sessionId: providerSessionId,
3532
+ preferWebsockets: preferOpenAICodexWebsockets,
3533
+ providerSessionState: session.providerSessionState,
3534
+ });
3535
+ } catch (error) {
3536
+ const errorMessage = error instanceof Error ? error.message : String(error);
3537
+ logger.debug("Codex websocket prewarm failed", {
3538
+ error: errorMessage,
3539
+ provider: codexModel.provider,
3540
+ model: codexModel.id,
3541
+ });
3542
+ }
3543
+ })();
3544
+ }
3545
+ }
3546
+
3547
+ // Broker-shared language servers: one server per project, multiplexed
3548
+ // across omp instances by the LSP mux daemon. Session-level because the
3549
+ // flag lives in module state consulted on every client cold-start.
3550
+ setSharedLspEnabled(enableLsp && settings.get("lsp.shared"));
3551
+
3552
+ // Start LSP warmup in the background so startup does not block on language server initialization.
3553
+ // With `lsp.lazy` (the default) the warmup is skipped: recognized servers are still discovered and
3554
+ // surfaced in the UI as "available", but cold-start on first use — the lsp tool or an edit/write
3555
+ // touching a matching file type — through `getOrCreateClient`.
3556
+ // Print/script invocations (`hasUI=false`) skip it regardless: they don't render the warmup status
3557
+ // indicator AND typically finish before LSP servers would have stabilized — warming them just spends
3558
+ // CPU parsing big `initialize` responses concurrently with the LLM stream consumer, jittering
3559
+ // perceived latency.
3560
+ let lspServers: CreateAgentSessionResult["lspServers"];
3561
+ if (enableLsp && options.hasUI && settings.get("lsp.lazy")) {
3562
+ lspServers = discoverStartupLspServers(cwd, "available");
3563
+ } else if (enableLsp && options.hasUI) {
3564
+ lspServers = discoverStartupLspServers(cwd);
3565
+ if (lspServers.length > 0) {
3566
+ void (async () => {
3567
+ try {
3568
+ const result = await logger.time("warmupLspServers", warmupLspServers, cwd);
3569
+ const serversByName = new Map(result.servers.map(server => [server.name, server] as const));
3570
+ for (const server of lspServers ?? []) {
3571
+ const next = serversByName.get(server.name);
3572
+ if (!next) continue;
3573
+ server.status = next.status;
3574
+ server.fileTypes = next.fileTypes;
3575
+ server.error = next.error;
3576
+ }
3577
+ const event: LspStartupEvent = {
3578
+ type: "completed",
3579
+ servers: result.servers,
3580
+ };
3581
+ if (!startupQuiet) eventBus.emit(LSP_STARTUP_EVENT_CHANNEL, event);
3582
+ } catch (error) {
3583
+ const errorMessage = error instanceof Error ? error.message : String(error);
3584
+ logger.warn("LSP server warmup failed", { cwd, error: errorMessage });
3585
+ for (const server of lspServers ?? []) {
3586
+ server.status = "error";
3587
+ server.error = errorMessage;
3588
+ }
3589
+ const event: LspStartupEvent = {
3590
+ type: "failed",
3591
+ error: errorMessage,
3592
+ };
3593
+ if (!startupQuiet) eventBus.emit(LSP_STARTUP_EVENT_CHANNEL, event);
3594
+ }
3595
+ })();
3596
+ }
3597
+ }
3598
+
3599
+ const startMemoryBackend = async () => {
3600
+ const memoryBackend = await resolveMemoryBackend(settings);
3601
+ await memoryBackend.start({
3602
+ session,
3603
+ settings,
3604
+ modelRegistry,
3605
+ agentDir,
3606
+ taskDepth,
3607
+ parentHindsightSessionState: options.parentHindsightSessionState,
3608
+ parentMnemopiSessionState: options.parentMnemopiSessionState,
3609
+ });
3610
+ };
3611
+
3612
+ const runAutoLearnCapture = createAutoLearnCaptureRunner({
3613
+ sourceAgent: agent,
3614
+ captureTools: autoLearnCaptureTools,
3615
+ onPayload,
3616
+ onResponse,
3617
+ createAgent: captureOptions => {
3618
+ const captureModel = captureOptions.initialState?.model;
3619
+ const captureSessionId = captureOptions.sessionId;
3620
+ if (!captureModel || !captureSessionId) throw new Error("Auto-learn capture identity is incomplete");
3621
+ return new Agent({
3622
+ ...captureOptions,
3623
+ cwd: sessionManager.getCwd(),
3624
+ cwdResolver: () => sessionManager.getCwd(),
3625
+ convertToLlm: convertToLlmFinal,
3626
+ transformContext: async messages => wrapSteeringForModel(messages),
3627
+ transformProviderContext: async (context, transformModel) => {
3628
+ const transformed = obfuscator ? obfuscateProviderContext(obfuscator, context) : context;
3629
+ return clampProviderContextImages(transformed, transformModel);
3630
+ },
3631
+ thinkingBudgets: agent.thinkingBudgets,
3632
+ temperature: agent.temperature,
3633
+ topP: agent.topP,
3634
+ topK: agent.topK,
3635
+ minP: agent.minP,
3636
+ presencePenalty: agent.presencePenalty,
3637
+ repetitionPenalty: agent.repetitionPenalty,
3638
+ serviceTierResolver: agent.serviceTierResolver,
3639
+ hideThinkingSummary: agent.hideThinkingSummary,
3640
+ maxRetryDelayMs: agent.maxRetryDelayMs,
3641
+ kimiApiFormat,
3642
+ preferWebsockets: preferOpenAICodexWebsockets,
3643
+ getToolContext: toolCall => toolContextStore.getContext(toolCall),
3644
+ streamFn: settingsAwareStreamFn,
3645
+ transformToolCallArguments,
3646
+ resolveFallbackTool: resolveDeviceTool,
3647
+ intentTracing: !!intentField,
3648
+ pruneToolDescriptions: inlineToolDescriptors,
3649
+ dialect: resolveDialect(settings.get("tools.format"), captureModel),
3650
+ abortOnFabricatedToolResult: settings.get("tools.abortOnFabricatedResult"),
3651
+ appendOnlyContext: shouldEnableAppendOnlyContext(
3652
+ settings.get("provider.appendOnlyContext"),
3653
+ captureModel,
3654
+ )
3655
+ ? new AppendOnlyContextManager()
3656
+ : undefined,
3657
+ });
3658
+ },
3659
+ });
3660
+
3661
+ // Auto-learn can immediately trigger a private capture after the first real
3662
+ // stop. When a memory backend is selected, install that backend's
3663
+ // per-session state first so the capture turn's `learn` tool observes the
3664
+ // same initialized state as normal memory tools. Other sessions keep memory
3665
+ // startup in the background to preserve the existing startup profile.
3666
+ //
3667
+ // Gated on `autolearn.enabled` to match the tools: `createTools` builds the
3668
+ // `learn`/`manage_skill` registry ONCE at session start and no settings
3669
+ // change rebuilds it, so installing the controller while disabled would let a
3670
+ // mid-session enable fire a nudge pointing at tools the session never built.
3671
+ // Activation is therefore a session-start decision for BOTH the controller
3672
+ // and the tools; the fire-time re-check in `#onAgentEnd` still handles a
3673
+ // mid-session DISABLE. The subscription lives for the session's lifetime; the
3674
+ // reference is intentionally discarded (the listener retains it).
3675
+ if (!restrictToolNames) {
3676
+ if (settings.get("autolearn.enabled") && taskDepth === 0) {
3677
+ await logger.time("startMemoryStartupTask", startMemoryBackend);
3678
+ new AutoLearnController({
3679
+ session,
3680
+ settings,
3681
+ capture: content => session.runAutolearnCapture(signal => runAutoLearnCapture(content, signal)),
3682
+ });
3683
+ } else {
3684
+ void logger.time("startMemoryStartupTask", startMemoryBackend);
3685
+ }
3686
+ }
3687
+
3688
+ // MCP manager wiring has two ownership models:
3689
+ // * Single-slot callbacks (tools/prompts/resources changed) — exactly one
3690
+ // owner per manager. When reusing a parent's manager (subagent path,
3691
+ // see task/executor.ts), the parent already owns these slots so we
3692
+ // MUST NOT overwrite them. Guarded by `!options.mcpManager`.
3693
+ // * Notification listener — multi-listener by design. Every session with
3694
+ // an MCP manager (fresh OR reused) needs its own bridge to its own
3695
+ // `extensionRunner` so extensions loaded in that session receive frames.
3696
+ // Guarded only by `mcpManager` (see the second `if` below).
3697
+ if (mcpManager && !options.mcpManager) {
3698
+ mcpManager.setOnToolsChanged(async tools => {
3699
+ try {
3700
+ await session.refreshMCPTools(tools);
3701
+ } catch (error) {
3702
+ logger.warn("MCP tool refresh failed", {
3703
+ error: error instanceof Error ? error.message : String(error),
3704
+ });
3705
+ }
3706
+ });
3707
+ // Wire prompt refresh → rebuild MCP prompt slash commands
3708
+ mcpManager.setOnPromptsChanged(serverName => {
3709
+ const promptCommands = buildMCPPromptCommands(mcpManager);
3710
+ session.setMCPPromptCommands(promptCommands);
3711
+ logger.debug("MCP prompt commands refreshed", { path: `mcp:${serverName}` });
3712
+ });
3713
+ const notificationDebounceTimers = new Map<string, Timer>();
3714
+ const clearDebounceTimers = () => {
3715
+ for (const timer of notificationDebounceTimers.values()) clearTimeout(timer);
3716
+ notificationDebounceTimers.clear();
3717
+ };
3718
+ postmortem.register("mcp-notification-cleanup", clearDebounceTimers);
3719
+ mcpManager.setOnResourcesChanged((serverName, uri) => {
3720
+ logger.debug("MCP resources changed", { path: `mcp:${serverName}`, uri });
3721
+ if (!settings.get("mcp.notifications")) return;
3722
+ const debounceMs = settings.get("mcp.notificationDebounceMs");
3723
+ const key = `${serverName}:${uri}`;
3724
+ const existing = notificationDebounceTimers.get(key);
3725
+ if (existing) clearTimeout(existing);
3726
+ notificationDebounceTimers.set(
3727
+ key,
3728
+ setTimeout(() => {
3729
+ notificationDebounceTimers.delete(key);
3730
+ // Re-check: user may have disabled notifications during the debounce window
3731
+ if (!settings.get("mcp.notifications")) return;
3732
+ session.yieldQueue.enqueue<McpNotificationEntry>("mcp-notification", { serverName, uri });
3733
+ }, debounceMs),
3734
+ );
3735
+ });
3736
+ }
3737
+
3738
+ if (mcpManager) {
3739
+ // Bridge server-initiated notifications to this session's extension
3740
+ // handlers. Multi-listener registration: fresh-manager and reused-manager
3741
+ // sessions both install their own listener here, so a subagent's
3742
+ // extensions get frames even though the parent owns the single-slot
3743
+ // tool/prompt/resource callbacks above. MCPManager fires known
3744
+ // list/update refreshes internally, then invokes all registered
3745
+ // listeners with (server, method, params) for every frame (including
3746
+ // server-custom methods). Two-layer buffering protects the startup
3747
+ // race: MCPManager buffers frames received before the first
3748
+ // `addNotificationListener` subscriber (drains here); ExtensionRunner
3749
+ // buffers frames received before `initialize()` and drains them on
3750
+ // init. Both drop-oldest under pressure at cap 100.
3751
+ unsubscribeMcpNotifications = mcpManager.addNotificationListener((server, method, params) => {
3752
+ void extensionRunner.emitMcpNotification({ server, method, params });
3753
+ });
3754
+ // postmortem.register returns a cancel function; capture it so explicit
3755
+ // session.dispose can remove this from the global list (see finally above).
3756
+ unregisterMcpPostmortem = postmortem.register("mcp-notification-listener-cleanup", () =>
3757
+ unsubscribeMcpNotifications?.(),
3758
+ );
3759
+ }
3760
+
3761
+ startDeferredMCPDiscovery?.(session);
3762
+
3763
+ return {
3764
+ session,
3765
+ extensionsResult,
3766
+ setToolUIContext,
3767
+ mcpManager,
3768
+ modelFallbackMessage,
3769
+ lspServers,
3770
+ eventBus,
3771
+ };
3772
+ } catch (error) {
3773
+ // Release the subscription if the throw happened after install but before the
3774
+ // dispose-wrap took ownership. Idempotent with dispose() — Set.delete is a no-op
3775
+ // for already-removed listeners.
3776
+ unsubscribeCredentialDisabled?.();
3777
+ try {
3778
+ if (hasSession) {
3779
+ await session.dispose();
3780
+ if (hasRegistered) unregisterUnlessParked();
3781
+ } else {
3782
+ if (hasRegistered) unregisterUnlessParked();
3783
+ if (asyncJobManager) {
3784
+ if (AsyncJobManager.instance() === asyncJobManager) {
3785
+ AsyncJobManager.setInstance(undefined);
3786
+ }
3787
+ await asyncJobManager.dispose({ timeoutMs: 3_000 });
3788
+ }
3789
+ await releaseComputerSessionsForOwner(evalKernelOwnerId);
3790
+ await disposeKernelSessionsByOwner(evalKernelOwnerId);
3791
+ await disposeRubyKernelSessionsByOwner(evalKernelOwnerId);
3792
+ await disposeJuliaKernelSessionsByOwner(evalKernelOwnerId);
3793
+ await disposeVmContextsByOwner(evalKernelOwnerId);
3794
+ if (ownsAuthStorage) authStorage.close();
3795
+ }
3796
+ } catch (cleanupError) {
3797
+ logger.warn("Failed to clean up createAgentSession resources after startup error", {
3798
+ error: cleanupError instanceof Error ? cleanupError.message : String(cleanupError),
3799
+ });
3800
+ }
3801
+ throw error;
3802
+ }
3803
+ }
3804
+
3805
+ /**
3806
+ * Best-effort preconnect to the model's API host. Bun's `fetch.preconnect`
3807
+ * primes DNS + TCP + TLS + H2 so the first real request reuses the warm
3808
+ * connection. Errors are swallowed: preconnect is an optimization, never a
3809
+ * hard dependency.
3810
+ */
3811
+ function preconnectModelHost(baseUrl: string | undefined): void {
3812
+ if (!baseUrl) return;
3813
+ const preconnect = (globalThis.fetch as typeof fetch & { preconnect?: (url: string) => void }).preconnect;
3814
+ if (typeof preconnect !== "function") return;
3815
+ try {
3816
+ preconnect(baseUrl);
3817
+ } catch {
3818
+ // Best effort.
3819
+ }
3820
+ }