pi-agent-zh 17.1.8

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