@oh-my-pi/pi-coding-agent 17.2.3 → 17.2.5

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 (375) hide show
  1. package/CHANGELOG.md +92 -0
  2. package/dist/{CHANGELOG-c5hpqt9r.md → CHANGELOG-q2w43aw3.md} +92 -0
  3. package/dist/cli.js +3979 -3548
  4. package/dist/{template-8vdv6xb4.js → template-c2hyaytt.js} +8 -26
  5. package/dist/types/advisor/advise-tool.d.ts +6 -0
  6. package/dist/types/advisor/runtime.d.ts +4 -4
  7. package/dist/types/capability/mcp.d.ts +9 -0
  8. package/dist/types/cli/args.d.ts +4 -1
  9. package/dist/types/cli/browser-relay-cli.d.ts +15 -0
  10. package/dist/types/cli/command-help.d.ts +99 -0
  11. package/dist/types/cli/help-extra.d.ts +2 -0
  12. package/dist/types/cli/models-cli.d.ts +1 -1
  13. package/dist/types/cli/thinking-levels.d.ts +5 -0
  14. package/dist/types/cli/update-cli.d.ts +13 -0
  15. package/dist/types/commands/browser-relay.d.ts +38 -0
  16. package/dist/types/commands/commit.d.ts +3 -0
  17. package/dist/types/commands/complete.d.ts +1 -1
  18. package/dist/types/commands/completions.d.ts +6 -0
  19. package/dist/types/commands/grep.d.ts +3 -0
  20. package/dist/types/commands/join.d.ts +4 -0
  21. package/dist/types/commands/launch-help.d.ts +171 -0
  22. package/dist/types/commands/launch.d.ts +5 -1
  23. package/dist/types/commands/models.d.ts +3 -0
  24. package/dist/types/commands/say.d.ts +10 -0
  25. package/dist/types/commands/worktree.d.ts +3 -0
  26. package/dist/types/config/keybindings.d.ts +6 -1
  27. package/dist/types/config/model-discovery.d.ts +2 -2
  28. package/dist/types/config/models-config-schema-bundle.d.ts +3 -0
  29. package/dist/types/config/models-config-schema.d.ts +2 -0
  30. package/dist/types/config/models-config.d.ts +1 -0
  31. package/dist/types/config/service-tier.d.ts +2 -0
  32. package/dist/types/config/settings-schema.d.ts +70 -22
  33. package/dist/types/cursor-bridge-tools.d.ts +1 -1
  34. package/dist/types/cursor.d.ts +7 -4
  35. package/dist/types/discovery/helpers.d.ts +6 -0
  36. package/dist/types/discovery/omp-extension-roots.d.ts +21 -2
  37. package/dist/types/edit/index.d.ts +3 -3
  38. package/dist/types/edit/modes/replace.d.ts +22 -15
  39. package/dist/types/edit/renderer.d.ts +1 -1
  40. package/dist/types/eval/py/kernel.d.ts +3 -1
  41. package/dist/types/eval/py/spawn-options.d.ts +12 -16
  42. package/dist/types/extensibility/extensions/loader.d.ts +6 -2
  43. package/dist/types/extensibility/legacy-pi-ai-shim.d.ts +8 -1
  44. package/dist/types/extensibility/legacy-pi-coding-agent-shim.d.ts +1 -1
  45. package/dist/types/extensibility/plugins/legacy-pi-compat.d.ts +2 -0
  46. package/dist/types/extensibility/plugins/marketplace/registry.d.ts +2 -2
  47. package/dist/types/launch/broker.d.ts +1 -1
  48. package/dist/types/launch/client.d.ts +6 -3
  49. package/dist/types/launch/ensure.d.ts +8 -0
  50. package/dist/types/launch/paths.d.ts +3 -2
  51. package/dist/types/launch/protocol.d.ts +2 -2
  52. package/dist/types/lsp/client.d.ts +2 -0
  53. package/dist/types/lsp/mux/daemon.d.ts +15 -0
  54. package/dist/types/lsp/mux/protocol.d.ts +57 -0
  55. package/dist/types/lsp/mux/server.d.ts +18 -0
  56. package/dist/types/lsp/types.d.ts +22 -2
  57. package/dist/types/main.d.ts +2 -0
  58. package/dist/types/mcp/request-id.d.ts +19 -0
  59. package/dist/types/mcp/types.d.ts +14 -0
  60. package/dist/types/memory-backend/index.d.ts +1 -0
  61. package/dist/types/memory-backend/messages.d.ts +15 -0
  62. package/dist/types/mnemopi/embed-client.d.ts +1 -1
  63. package/dist/types/mnemopi/state.d.ts +0 -23
  64. package/dist/types/modes/components/assistant-message.d.ts +2 -0
  65. package/dist/types/modes/components/custom-editor.d.ts +2 -1
  66. package/dist/types/modes/components/model-browser.d.ts +7 -5
  67. package/dist/types/modes/components/model-picker.d.ts +9 -3
  68. package/dist/types/modes/components/read-tool-group.d.ts +11 -0
  69. package/dist/types/modes/components/stripped-tool-calls-placeholder.d.ts +12 -0
  70. package/dist/types/modes/components/tool-execution.d.ts +2 -0
  71. package/dist/types/modes/controllers/input-controller.d.ts +1 -0
  72. package/dist/types/modes/interactive-mode.d.ts +1 -0
  73. package/dist/types/modes/types.d.ts +2 -1
  74. package/dist/types/modes/workflow.d.ts +2 -1
  75. package/dist/types/registry/agent-lifecycle.d.ts +14 -5
  76. package/dist/types/registry/agent-registry.d.ts +4 -3
  77. package/dist/types/sdk.d.ts +4 -2
  78. package/dist/types/secrets/index.d.ts +7 -9
  79. package/dist/types/session/agent-session-types.d.ts +3 -1
  80. package/dist/types/session/agent-session.d.ts +6 -1
  81. package/dist/types/session/indexed-session-storage.d.ts +1 -0
  82. package/dist/types/session/model-controls.d.ts +0 -1
  83. package/dist/types/session/session-advisors.d.ts +8 -1
  84. package/dist/types/session/session-context.d.ts +2 -1
  85. package/dist/types/session/session-maintenance.d.ts +2 -0
  86. package/dist/types/session/session-manager.d.ts +16 -5
  87. package/dist/types/session/session-stats.d.ts +10 -0
  88. package/dist/types/session/session-storage.d.ts +15 -3
  89. package/dist/types/system-prompt.d.ts +6 -1
  90. package/dist/types/task/index.d.ts +2 -1
  91. package/dist/types/task/spawn-policy.d.ts +5 -0
  92. package/dist/types/thinking.d.ts +1 -7
  93. package/dist/types/tools/ast-grep.d.ts +1 -1
  94. package/dist/types/tools/browser/registry.d.ts +2 -1
  95. package/dist/types/tools/browser/relay/bridge.d.ts +35 -0
  96. package/dist/types/tools/browser/relay/daemon.d.ts +16 -0
  97. package/dist/types/tools/browser/relay/kind.d.ts +19 -0
  98. package/dist/types/tools/browser/relay/protocol.d.ts +97 -0
  99. package/dist/types/tools/browser/relay/server.d.ts +35 -0
  100. package/dist/types/tools/browser/render.d.ts +1 -0
  101. package/dist/types/tools/browser.d.ts +3 -0
  102. package/dist/types/tools/computer/exposure.d.ts +9 -7
  103. package/dist/types/tools/computer/protocol.d.ts +70 -17
  104. package/dist/types/tools/computer/supervisor.d.ts +23 -10
  105. package/dist/types/tools/computer/worker-entry.d.ts +1 -0
  106. package/dist/types/tools/computer/worker.d.ts +42 -7
  107. package/dist/types/tools/computer-renderer.d.ts +7 -5
  108. package/dist/types/tools/computer.d.ts +25 -44
  109. package/dist/types/tools/context.d.ts +3 -0
  110. package/dist/types/tools/default-renderer.d.ts +3 -0
  111. package/dist/types/tools/glob.d.ts +1 -1
  112. package/dist/types/tools/grep.d.ts +1 -1
  113. package/dist/types/tools/render-utils.d.ts +2 -0
  114. package/dist/types/tools/{browser/run-cancellation.d.ts → run-scope.d.ts} +8 -8
  115. package/dist/types/tools/tool-timeouts.d.ts +5 -0
  116. package/dist/types/tools/xdev.d.ts +8 -0
  117. package/dist/types/utils/clipboard.d.ts +2 -2
  118. package/dist/types/web/kagi.d.ts +1 -0
  119. package/dist/types/web/search/index.d.ts +1 -1
  120. package/dist/types/web/search/providers/anthropic.d.ts +1 -0
  121. package/dist/types/web/search/providers/base.d.ts +2 -0
  122. package/dist/types/web/search/providers/brave.d.ts +1 -0
  123. package/dist/types/web/search/providers/browser-page.d.ts +1 -0
  124. package/dist/types/web/search/providers/codex.d.ts +1 -0
  125. package/dist/types/web/search/providers/exa.d.ts +1 -0
  126. package/dist/types/web/search/providers/firecrawl.d.ts +1 -0
  127. package/dist/types/web/search/providers/gemini.d.ts +1 -0
  128. package/dist/types/web/search/providers/jina.d.ts +1 -0
  129. package/dist/types/web/search/providers/kagi.d.ts +1 -0
  130. package/dist/types/web/search/providers/kimi.d.ts +1 -0
  131. package/dist/types/web/search/providers/parallel.d.ts +1 -0
  132. package/dist/types/web/search/providers/perplexity.d.ts +1 -0
  133. package/dist/types/web/search/providers/searxng.d.ts +1 -0
  134. package/dist/types/web/search/providers/tavily.d.ts +1 -0
  135. package/dist/types/web/search/providers/tinyfish.d.ts +1 -0
  136. package/dist/types/web/search/providers/utils.d.ts +6 -9
  137. package/dist/types/web/search/providers/zai.d.ts +1 -0
  138. package/dist/types/web/search/types.d.ts +4 -0
  139. package/package.json +12 -12
  140. package/scripts/legacy-pi-virtual-module.ts +12 -2
  141. package/src/advisor/advise-tool.ts +18 -0
  142. package/src/advisor/runtime.ts +6 -6
  143. package/src/capability/mcp.ts +7 -0
  144. package/src/cli/args.ts +7 -87
  145. package/src/cli/browser-relay-cli.ts +119 -0
  146. package/src/cli/command-help.ts +103 -0
  147. package/src/cli/completion-gen.ts +5 -5
  148. package/src/cli/flag-tables.ts +9 -0
  149. package/src/cli/help-extra.ts +89 -0
  150. package/src/cli/models-cli.ts +13 -16
  151. package/src/cli/setup-cli.ts +13 -21
  152. package/src/cli/thinking-levels.ts +7 -0
  153. package/src/cli/update-cli.ts +26 -7
  154. package/src/cli-commands.ts +170 -33
  155. package/src/cli.ts +17 -5
  156. package/src/collab/host.ts +1 -1
  157. package/src/commands/acp.ts +3 -1
  158. package/src/commands/agents.ts +3 -2
  159. package/src/commands/auth-broker.ts +3 -2
  160. package/src/commands/auth-gateway.ts +3 -2
  161. package/src/commands/bench.ts +2 -3
  162. package/src/commands/browser-relay.ts +53 -0
  163. package/src/commands/cleanse.ts +2 -2
  164. package/src/commands/commit.ts +3 -2
  165. package/src/commands/complete.ts +2 -1
  166. package/src/commands/completions.ts +3 -2
  167. package/src/commands/config.ts +3 -2
  168. package/src/commands/dry-balance.ts +2 -2
  169. package/src/commands/gallery.ts +3 -2
  170. package/src/commands/gc.ts +3 -2
  171. package/src/commands/grep.ts +3 -2
  172. package/src/commands/grievances.ts +3 -2
  173. package/src/commands/install.ts +2 -2
  174. package/src/commands/join.ts +3 -2
  175. package/src/commands/launch-help.ts +116 -0
  176. package/src/commands/launch.ts +7 -191
  177. package/src/commands/models.ts +3 -2
  178. package/src/commands/plugin.ts +3 -2
  179. package/src/commands/read.ts +3 -2
  180. package/src/commands/say.ts +3 -2
  181. package/src/commands/setup.ts +3 -2
  182. package/src/commands/shell.ts +3 -2
  183. package/src/commands/ssh.ts +3 -2
  184. package/src/commands/stats.ts +3 -2
  185. package/src/commands/tiny-models.ts +2 -2
  186. package/src/commands/token.ts +2 -2
  187. package/src/commands/ttsr.ts +2 -2
  188. package/src/commands/update.ts +3 -2
  189. package/src/commands/usage.ts +3 -2
  190. package/src/commands/web-search.ts +3 -2
  191. package/src/commands/worktree.ts +3 -2
  192. package/src/config/keybindings.ts +19 -9
  193. package/src/config/mcp-schema.json +5 -0
  194. package/src/config/model-discovery.ts +30 -13
  195. package/src/config/model-registry.ts +16 -6
  196. package/src/config/models-config-schema-bundle.ts +9 -0
  197. package/src/config/resolve-config-value.ts +2 -1
  198. package/src/config/service-tier.ts +5 -0
  199. package/src/config/settings-schema.ts +73 -21
  200. package/src/cursor-bridge-tools.ts +1 -1
  201. package/src/cursor.ts +10 -14
  202. package/src/discovery/builtin.ts +10 -0
  203. package/src/discovery/helpers.ts +10 -0
  204. package/src/discovery/mcp-json.ts +11 -1
  205. package/src/discovery/omp-extension-roots.ts +79 -20
  206. package/src/discovery/omp-plugins.ts +10 -1
  207. package/src/edit/diff.ts +17 -8
  208. package/src/edit/index.ts +20 -7
  209. package/src/edit/modes/replace.ts +93 -40
  210. package/src/edit/renderer.ts +1 -1
  211. package/src/edit/streaming.ts +14 -17
  212. package/src/eval/py/kernel.ts +2 -1
  213. package/src/eval/py/spawn-options.ts +24 -29
  214. package/src/export/html/template.js +8 -26
  215. package/src/extensibility/extensions/loader.ts +66 -28
  216. package/src/extensibility/extensions/wrapper.ts +1 -0
  217. package/src/extensibility/legacy-pi-ai-shim.ts +22 -1
  218. package/src/extensibility/legacy-pi-coding-agent-shim.ts +7 -6
  219. package/src/extensibility/plugins/legacy-pi-compat.ts +124 -57
  220. package/src/extensibility/plugins/marketplace/registry.ts +4 -8
  221. package/src/extensibility/skills.ts +11 -0
  222. package/src/extensibility/utils.ts +10 -2
  223. package/src/launch/broker.ts +1 -1
  224. package/src/launch/client.ts +32 -13
  225. package/src/launch/ensure.ts +73 -0
  226. package/src/launch/paths.ts +3 -6
  227. package/src/launch/protocol.ts +2 -2
  228. package/src/lsp/client.ts +23 -2
  229. package/src/lsp/index.ts +8 -0
  230. package/src/lsp/mux/daemon.ts +348 -0
  231. package/src/lsp/mux/protocol.ts +96 -0
  232. package/src/lsp/mux/server.ts +797 -0
  233. package/src/lsp/types.ts +28 -2
  234. package/src/main.ts +31 -24
  235. package/src/mcp/config.ts +1 -0
  236. package/src/mcp/oauth-flow.ts +11 -1
  237. package/src/mcp/request-id.ts +24 -0
  238. package/src/mcp/transports/http.ts +4 -2
  239. package/src/mcp/transports/sse.ts +4 -2
  240. package/src/mcp/transports/stdio.ts +4 -2
  241. package/src/mcp/types.ts +15 -0
  242. package/src/memory-backend/index.ts +1 -0
  243. package/src/memory-backend/messages.ts +19 -0
  244. package/src/mnemopi/embed-client.ts +49 -2
  245. package/src/mnemopi/state.ts +57 -23
  246. package/src/modes/components/agent-hub.ts +1 -1
  247. package/src/modes/components/assistant-message.ts +11 -0
  248. package/src/modes/components/chat-transcript-builder.ts +4 -0
  249. package/src/modes/components/custom-editor.ts +10 -1
  250. package/src/modes/components/model-browser.ts +25 -17
  251. package/src/modes/components/model-picker.ts +10 -6
  252. package/src/modes/components/read-tool-group.ts +36 -0
  253. package/src/modes/components/status-line/component.ts +1 -1
  254. package/src/modes/components/stripped-tool-calls-placeholder.ts +35 -0
  255. package/src/modes/components/tool-execution.ts +85 -20
  256. package/src/modes/components/tree-selector.ts +46 -48
  257. package/src/modes/controllers/command-controller.ts +2 -2
  258. package/src/modes/controllers/event-controller.ts +199 -10
  259. package/src/modes/controllers/input-controller.ts +39 -1
  260. package/src/modes/controllers/mcp-command-controller.ts +11 -5
  261. package/src/modes/controllers/selector-controller.ts +47 -6
  262. package/src/modes/interactive-mode.ts +2 -0
  263. package/src/modes/types.ts +6 -1
  264. package/src/modes/utils/hotkeys-markdown.ts +1 -0
  265. package/src/modes/utils/interactive-context-helpers.ts +1 -0
  266. package/src/modes/utils/ui-helpers.ts +6 -10
  267. package/src/modes/workflow.ts +8 -2
  268. package/src/prompts/agents/init.md +1 -1
  269. package/src/prompts/system/plan-mode-active.md +2 -2
  270. package/src/prompts/system/system-prompt.md +26 -52
  271. package/src/prompts/system/workflow-notice.md +3 -3
  272. package/src/prompts/tools/ast-grep.md +1 -1
  273. package/src/prompts/tools/bash.md +1 -1
  274. package/src/prompts/tools/browser.md +1 -0
  275. package/src/prompts/tools/computer.md +21 -21
  276. package/src/prompts/tools/glob.md +1 -1
  277. package/src/prompts/tools/grep.md +1 -1
  278. package/src/prompts/tools/replace.md +5 -4
  279. package/src/prompts/tools/task.md +4 -4
  280. package/src/registry/agent-lifecycle.ts +36 -11
  281. package/src/registry/agent-registry.ts +12 -5
  282. package/src/sdk.ts +102 -35
  283. package/src/secrets/index.ts +16 -18
  284. package/src/session/agent-session-types.ts +3 -1
  285. package/src/session/agent-session.ts +23 -2
  286. package/src/session/agent-storage.ts +6 -4
  287. package/src/session/history-storage.ts +4 -2
  288. package/src/session/indexed-session-storage.ts +9 -0
  289. package/src/session/messages.ts +31 -7
  290. package/src/session/model-controls.ts +0 -1
  291. package/src/session/prewalk.ts +24 -2
  292. package/src/session/session-advisors.ts +21 -3
  293. package/src/session/session-context.ts +16 -12
  294. package/src/session/session-dump-format.ts +1 -1
  295. package/src/session/session-maintenance.ts +41 -4
  296. package/src/session/session-manager.ts +181 -94
  297. package/src/session/session-paths.ts +118 -55
  298. package/src/session/session-stats.ts +48 -3
  299. package/src/session/session-storage.ts +30 -22
  300. package/src/session/session-tools.ts +25 -23
  301. package/src/session/stream-guards.ts +1 -1
  302. package/src/slash-commands/builtin-registry.ts +29 -26
  303. package/src/system-prompt.ts +14 -4
  304. package/src/task/executor.ts +8 -1
  305. package/src/task/index.ts +23 -8
  306. package/src/task/spawn-policy.ts +14 -0
  307. package/src/thinking.ts +2 -8
  308. package/src/tiny/text.ts +12 -0
  309. package/src/tools/ast-grep.ts +10 -4
  310. package/src/tools/browser/cmux/cmux-tab.ts +5 -10
  311. package/src/tools/browser/cmux/rpc.ts +2 -8
  312. package/src/tools/browser/registry.ts +53 -2
  313. package/src/tools/browser/relay/bridge.ts +945 -0
  314. package/src/tools/browser/relay/daemon.ts +117 -0
  315. package/src/tools/browser/relay/extension-assets/background.js.txt +242 -0
  316. package/src/tools/browser/relay/extension-assets/manifest.json.txt +14 -0
  317. package/src/tools/browser/relay/extension-assets/options.html.txt +53 -0
  318. package/src/tools/browser/relay/extension-assets/options.js.txt +23 -0
  319. package/src/tools/browser/relay/kind.ts +41 -0
  320. package/src/tools/browser/relay/protocol.ts +54 -0
  321. package/src/tools/browser/relay/server.ts +141 -0
  322. package/src/tools/browser/render.ts +4 -1
  323. package/src/tools/browser/shared-daemon.ts +7 -58
  324. package/src/tools/browser/tab-supervisor.ts +5 -4
  325. package/src/tools/browser/tab-worker.ts +33 -12
  326. package/src/tools/browser.ts +26 -1
  327. package/src/tools/computer/exposure.ts +11 -35
  328. package/src/tools/computer/protocol.ts +51 -10
  329. package/src/tools/computer/supervisor.ts +227 -124
  330. package/src/tools/computer/worker-entry.ts +11 -6
  331. package/src/tools/computer/worker.ts +695 -81
  332. package/src/tools/computer-renderer.ts +104 -65
  333. package/src/tools/computer.ts +134 -428
  334. package/src/tools/context.ts +3 -0
  335. package/src/tools/default-renderer.ts +19 -4
  336. package/src/tools/glob.ts +9 -2
  337. package/src/tools/grep.ts +12 -6
  338. package/src/tools/index.ts +10 -6
  339. package/src/tools/inspect-image.ts +32 -6
  340. package/src/tools/render-utils.ts +2 -0
  341. package/src/tools/{browser/run-cancellation.ts → run-scope.ts} +11 -11
  342. package/src/tools/tool-timeouts.ts +1 -0
  343. package/src/tools/xdev.ts +29 -2
  344. package/src/utils/clipboard.ts +40 -14
  345. package/src/utils/command-args.ts +26 -28
  346. package/src/utils/tool-choice.ts +0 -13
  347. package/src/web/kagi.ts +2 -1
  348. package/src/web/search/index.ts +18 -2
  349. package/src/web/search/providers/anthropic.ts +7 -2
  350. package/src/web/search/providers/base.ts +2 -0
  351. package/src/web/search/providers/brave.ts +3 -1
  352. package/src/web/search/providers/browser-page.ts +6 -4
  353. package/src/web/search/providers/codex.ts +4 -1
  354. package/src/web/search/providers/duckduckgo.ts +2 -1
  355. package/src/web/search/providers/ecosia.ts +2 -1
  356. package/src/web/search/providers/exa.ts +4 -2
  357. package/src/web/search/providers/firecrawl.ts +3 -1
  358. package/src/web/search/providers/gemini.ts +8 -2
  359. package/src/web/search/providers/google.ts +2 -1
  360. package/src/web/search/providers/jina.ts +12 -2
  361. package/src/web/search/providers/kagi.ts +3 -0
  362. package/src/web/search/providers/kimi.ts +5 -1
  363. package/src/web/search/providers/mojeek.ts +2 -1
  364. package/src/web/search/providers/parallel.ts +5 -1
  365. package/src/web/search/providers/perplexity.ts +6 -3
  366. package/src/web/search/providers/public.ts +1 -1
  367. package/src/web/search/providers/searxng.ts +11 -5
  368. package/src/web/search/providers/startpage.ts +10 -4
  369. package/src/web/search/providers/synthetic.ts +11 -5
  370. package/src/web/search/providers/tavily.ts +3 -1
  371. package/src/web/search/providers/tinyfish.ts +3 -1
  372. package/src/web/search/providers/utils.ts +12 -10
  373. package/src/web/search/providers/xai.ts +2 -2
  374. package/src/web/search/providers/zai.ts +18 -3
  375. package/src/web/search/types.ts +6 -0
@@ -14,13 +14,25 @@ export interface SessionStorageStat {
14
14
 
15
15
  export interface SessionStorageWriter {
16
16
  /**
17
- * Append one newline-terminated line. File storage batches same-turn appends
18
- * until its microtask boundary; memory storage updates in-body; indexed
19
- * backends queue in call order.
17
+ * Append one newline-terminated line.
18
+ *
19
+ * File and memory storage apply the line synchronously before the returned
20
+ * promise settles, so a software crash after `append` returns (or after a
21
+ * fire-and-forget call begins) still sees the entry on disk / in body. No
22
+ * `fsync` — power loss may still drop the last page. Indexed backends update
23
+ * the local index immediately and queue the remote publish in call order.
20
24
  *
21
25
  * `line` MUST include the trailing newline.
22
26
  */
23
27
  append(line: string): Promise<void>;
28
+ /**
29
+ * Synchronous append when the backend can apply the line before return.
30
+ * File and memory implement this so {@link SessionManager} can latch the
31
+ * first write failure before the appending call returns (surfaced by a later
32
+ * flushSync/close/next append — the turn loop does not throw from append).
33
+ * Indexed backends update the local index immediately and queue remote I/O.
34
+ */
35
+ appendSync?(line: string): void;
24
36
  /** Resolve once all queued appends complete. No fsync. */
25
37
  flush(): Promise<void>;
26
38
  /** Drain synchronously flushable queued work when the backend supports it. No fsync. */
@@ -93,8 +105,6 @@ class FileSessionStorageWriter implements SessionStorageWriter {
93
105
  #closed = false;
94
106
  #error: Error | undefined;
95
107
  #onError: ((err: Error) => void) | undefined;
96
- #pending = "";
97
- #flushScheduled = false;
98
108
 
99
109
  constructor(fpath: string, options?: { flags?: "a" | "w"; onError?: (err: Error) => void }) {
100
110
  this.#onError = options?.onError;
@@ -129,34 +139,29 @@ class FileSessionStorageWriter implements SessionStorageWriter {
129
139
  }
130
140
  }
131
141
 
132
- #flushPendingNow(): void {
133
- this.#flushScheduled = false;
134
- if (this.#pending.length === 0) return;
135
- const pending = this.#pending;
136
- this.#pending = "";
142
+ appendSync(line: string): void {
143
+ if (this.#closed) throw new Error("Writer closed");
144
+ if (this.#error) throw this.#error;
145
+ // Write in-body so software crash after the call still sees the entry.
146
+ // Microtask batching used to leave completed transcript lines only in
147
+ // memory until the next event-loop turn; process crash then lost every
148
+ // post-checkpoint event. flush/flushSync remain no-op drains (no fsync).
137
149
  try {
138
- this.#writeNow(pending);
150
+ this.#writeNow(line);
139
151
  } catch (err) {
140
- this.#recordError(err);
152
+ throw this.#recordError(err);
141
153
  }
142
154
  }
143
155
 
144
156
  async append(line: string): Promise<void> {
145
- if (this.#closed) throw new Error("Writer closed");
146
- if (this.#error) throw this.#error;
147
- this.#pending += line;
148
- if (this.#flushScheduled) return;
149
- this.#flushScheduled = true;
150
- queueMicrotask(() => this.#flushPendingNow());
157
+ this.appendSync(line);
151
158
  }
152
159
 
153
160
  async flush(): Promise<void> {
154
- this.#flushPendingNow();
155
161
  if (this.#error) throw this.#error;
156
162
  }
157
163
 
158
164
  flushSync(): void {
159
- this.#flushPendingNow();
160
165
  if (this.#error) throw this.#error;
161
166
  }
162
167
 
@@ -166,7 +171,6 @@ class FileSessionStorageWriter implements SessionStorageWriter {
166
171
 
167
172
  async close(): Promise<void> {
168
173
  if (this.#closed) return;
169
- this.#flushPendingNow();
170
174
  this.#closed = true;
171
175
  // Unregister from finalization - we're closing properly
172
176
  writerRegistry.unregister(this);
@@ -487,7 +491,7 @@ class MemorySessionStorageWriter implements SessionStorageWriter {
487
491
  return error;
488
492
  }
489
493
 
490
- async append(line: string): Promise<void> {
494
+ appendSync(line: string): void {
491
495
  if (this.#closed) throw new Error("Writer closed");
492
496
  if (this.#error) throw this.#error;
493
497
  try {
@@ -498,6 +502,10 @@ class MemorySessionStorageWriter implements SessionStorageWriter {
498
502
  }
499
503
  }
500
504
 
505
+ async append(line: string): Promise<void> {
506
+ this.appendSync(line);
507
+ }
508
+
501
509
  async flush(): Promise<void> {
502
510
  if (this.#error) throw this.#error;
503
511
  }
@@ -574,24 +574,28 @@ export class SessionTools {
574
574
  /** Applies an enabled tool set and reconciles its `xd://` partition. */
575
575
  async applyActiveToolsByName(toolNames: string[]): Promise<void> {
576
576
  toolNames = normalizeToolNames(toolNames);
577
+ let builtInWriteAvailable = this.#builtInToolNames.has("write");
578
+ if (toolNames.includes("write") && !builtInWriteAvailable) {
579
+ builtInWriteAvailable = (await this.#ensureWriteRegistered?.()) === true;
580
+ if (builtInWriteAvailable) this.#builtInToolNames.add("write");
581
+ }
577
582
  const selectedTools = toolNames.flatMap(name => {
578
583
  const tool = this.#toolRegistry.get(name);
579
584
  return tool ? [{ name, tool }] : [];
580
585
  });
581
586
  const xdevReadAvailable = this.#builtInToolNames.has("read") && selectedTools.some(({ name }) => name === "read");
587
+ const xdevWriteAvailable = builtInWriteAvailable && selectedTools.some(({ name }) => name === "write");
582
588
  const isPresentationPinned = (name: string): boolean =>
583
589
  this.#presentationPinnedToolNames?.has(name) === true || this.#runtimeSelectedToolNames?.has(name) === true;
584
590
  const mountCandidates = selectedTools.filter(
585
591
  ({ name, tool }) =>
586
- this.#xdev !== undefined && xdevReadAvailable && !isPresentationPinned(name) && isMountableUnderXdev(tool),
592
+ this.#xdev !== undefined &&
593
+ xdevReadAvailable &&
594
+ xdevWriteAvailable &&
595
+ !isPresentationPinned(name) &&
596
+ isMountableUnderXdev(tool),
587
597
  );
588
-
589
- let builtInWriteAvailable = this.#builtInToolNames.has("write");
590
- if (mountCandidates.length > 0 && !builtInWriteAvailable) {
591
- builtInWriteAvailable = (await this.#ensureWriteRegistered?.()) === true;
592
- if (builtInWriteAvailable) this.#builtInToolNames.add("write");
593
- }
594
- const mountNames = builtInWriteAvailable ? new Set(mountCandidates.map(({ name }) => name)) : new Set<string>();
598
+ const mountNames = new Set(mountCandidates.map(({ name }) => name));
595
599
  const tools: AgentTool[] = [];
596
600
  const validToolNames: string[] = [];
597
601
  for (const { name, tool } of selectedTools) {
@@ -831,23 +835,21 @@ export class SessionTools {
831
835
 
832
836
  /** Rediscovers reloadable skills and refreshes prompt metadata. */
833
837
  async refreshSkills(): Promise<void> {
834
- if (!this.#skillsReloadable) {
835
- return;
836
- }
837
-
838
838
  resetCapabilities();
839
- const skillsSettings = this.#host.settings.getGroup("skills");
840
- const discovered = await loadSkills({
841
- ...skillsSettings,
842
- cwd: this.#host.sessionManager.getCwd(),
843
- disabledExtensions: this.#host.settings.get("disabledExtensions") ?? [],
844
- });
845
- this.#skills = discovered.skills;
846
- this.#skillWarnings = discovered.warnings;
847
- this.#skillsSettings = skillsSettings;
839
+ if (this.#skillsReloadable) {
840
+ const skillsSettings = this.#host.settings.getGroup("skills");
841
+ const discovered = await loadSkills({
842
+ ...skillsSettings,
843
+ cwd: this.#host.sessionManager.getCwd(),
844
+ disabledExtensions: this.#host.settings.get("disabledExtensions") ?? [],
845
+ });
846
+ this.#skills = discovered.skills;
847
+ this.#skillWarnings = discovered.warnings;
848
+ this.#skillsSettings = skillsSettings;
848
849
 
849
- if (this.#host.agentKind() === "main") {
850
- setActiveSkills(this.#skills);
850
+ if (this.#host.agentKind() === "main") {
851
+ setActiveSkills(this.#skills);
852
+ }
851
853
  }
852
854
  await this.refreshBaseSystemPrompt();
853
855
  this.#host.notifyCommandMetadataChanged();
@@ -164,7 +164,7 @@ export class StreamingEditGuard {
164
164
  const toolCall = messageContent[contentIndex] as ToolCall;
165
165
  if (toolCall.name !== "edit") return undefined;
166
166
  const args = toolCall.arguments;
167
- if (!args || typeof args !== "object" || Array.isArray(args) || "old_text" in args || "new_text" in args) {
167
+ if (!args || typeof args !== "object" || Array.isArray(args) || "old_string" in args || "new_string" in args) {
168
168
  return undefined;
169
169
  }
170
170
  const filePath = typeof args.path === "string" ? args.path : undefined;
@@ -32,7 +32,7 @@ import {
32
32
  MarketplaceManager,
33
33
  } from "../extensibility/plugins/marketplace";
34
34
  import { readMCPConfigFile } from "../mcp/config-writer";
35
- import { resolveMemoryBackend } from "../memory-backend";
35
+ import { memoryStatsUnavailableMessage, resolveMemoryBackend } from "../memory-backend";
36
36
  import { runPauseScreen } from "../modes/components/pause-screen";
37
37
  import { collectMcpServerNames, MCPCommandController } from "../modes/controllers/mcp-command-controller";
38
38
  import { describeLoopLimitRuntime } from "../modes/loop-limit";
@@ -102,39 +102,42 @@ function formatFastModeStatus(session: AgentSession): string {
102
102
  }
103
103
 
104
104
  /** Detailed, session-effective `/computer status` diagnostics. */
105
- function formatComputerUseStatus(session: AgentSession): string {
105
+ async function formatComputerUseStatus(session: AgentSession): Promise<string> {
106
106
  const enabled = session.settings.get("computer.enabled");
107
107
  const active = session.getEnabledToolNames().includes("computer");
108
108
  const model = session.model;
109
109
  const modelName = model ? formatModelString(model) : "none";
110
- const exposure = !enabled || !active ? "not exposed" : computerExposureMode(model);
111
- const toolState = active ? "active" : enabled ? "unavailable" : "inactive";
110
+ const exposure = !enabled
111
+ ? "not exposed (disabled)"
112
+ : !active
113
+ ? "not exposed (tool inactive)"
114
+ : computerExposureMode(model);
112
115
  const configured = {
113
- backend: session.settings.get("computer.backend"),
114
116
  display: session.settings.get("computer.display"),
115
117
  maxWidth: session.settings.get("computer.maxWidth"),
116
118
  maxHeight: session.settings.get("computer.maxHeight"),
117
119
  };
118
- const computerTool = session.getToolByName("computer") as Pick<ComputerTool, "effectiveConfiguration"> | undefined;
119
- const effective = computerTool?.effectiveConfiguration ?? configured;
120
- const configurationChanged =
121
- effective.backend !== configured.backend ||
122
- effective.display !== configured.display ||
123
- effective.maxWidth !== configured.maxWidth ||
124
- effective.maxHeight !== configured.maxHeight;
120
+ const computerTool = active
121
+ ? (session.getToolByName("computer") as Pick<ComputerTool, "capabilities"> | undefined)
122
+ : undefined;
123
+ const capabilities = await computerTool?.capabilities();
124
+ const capabilityStatus = capabilities
125
+ ? [
126
+ `backend=${capabilities.backend}${capabilities.displayServer ? `/${capabilities.displayServer}` : ""}`,
127
+ `capture=${capabilities.capture} (${capabilities.capturePermission})`,
128
+ `input=${capabilities.input} (${capabilities.inputPermission})`,
129
+ `ax=${capabilities.ax} (${capabilities.axPermission})`,
130
+ `backgroundWindowInput=${capabilities.backgroundWindowInput}`,
131
+ `deliveryModes=${capabilities.deliveryModes.join(",") || "none"}`,
132
+ ].join(", ")
133
+ : "session not started";
125
134
  return [
126
135
  `Computer use: ${enabled ? "enabled" : "disabled"}`,
127
- `tool: ${toolState}`,
128
- `backend: ${effective.backend}`,
129
- `display: ${effective.display}`,
130
- `capture: ${effective.maxWidth}×${effective.maxHeight}`,
131
- ...(configurationChanged
132
- ? [
133
- `next-session settings: backend=${configured.backend}, display=${configured.display}, capture=${configured.maxWidth}×${configured.maxHeight}`,
134
- ]
135
- : []),
136
- `model: ${modelName}`,
136
+ `tool: ${active ? "active" : "inactive"}`,
137
137
  `exposure: ${exposure}`,
138
+ `model: ${modelName}`,
139
+ `configured: display=${configured.display}, maxWidth=${configured.maxWidth}, maxHeight=${configured.maxHeight}`,
140
+ `capabilities: ${capabilityStatus}`,
138
141
  ].join(" · ");
139
142
  }
140
143
 
@@ -151,7 +154,7 @@ async function applyComputerUseToggle(session: AgentSession, enable: boolean): P
151
154
  }
152
155
  session.settings.override("computer.enabled", enable);
153
156
  return enable
154
- ? `Computer use enabled for this session. ${formatComputerUseStatus(session)}`
157
+ ? `Computer use enabled for this session. ${await formatComputerUseStatus(session)}`
155
158
  : "Computer use disabled for this session.";
156
159
  }
157
160
 
@@ -673,7 +676,7 @@ const BUILTIN_SLASH_COMMAND_REGISTRY: ReadonlyArray<SlashCommandSpec> = [
673
676
  handle: async (command, runtime) => {
674
677
  const arg = command.args.trim().toLowerCase();
675
678
  if (arg === "status") {
676
- await runtime.output(formatComputerUseStatus(runtime.session));
679
+ await runtime.output(await formatComputerUseStatus(runtime.session));
677
680
  return commandConsumed();
678
681
  }
679
682
  if (!arg || arg === "toggle" || arg === "on" || arg === "off") {
@@ -686,7 +689,7 @@ const BUILTIN_SLASH_COMMAND_REGISTRY: ReadonlyArray<SlashCommandSpec> = [
686
689
  handleTui: async (command, runtime) => {
687
690
  const arg = command.args.trim().toLowerCase();
688
691
  if (arg === "status") {
689
- runtime.ctx.showStatus(formatComputerUseStatus(runtime.ctx.session));
692
+ runtime.ctx.showStatus(await formatComputerUseStatus(runtime.ctx.session));
690
693
  runtime.ctx.editor.setText("");
691
694
  return;
692
695
  }
@@ -1947,7 +1950,7 @@ const BUILTIN_SLASH_COMMAND_REGISTRY: ReadonlyArray<SlashCommandSpec> = [
1947
1950
  case "diagnose": {
1948
1951
  const hook = verb === "stats" ? backend.stats : backend.diagnose;
1949
1952
  const payload = await hook?.(runtime.settings.getAgentDir(), runtime.cwd, runtime.session);
1950
- await runtime.output(payload ?? `Memory ${verb} is not available for the ${backend.id} backend.`);
1953
+ await runtime.output(payload ?? memoryStatsUnavailableMessage(backend.id, verb));
1951
1954
  return commandConsumed();
1952
1955
  }
1953
1956
  case "mm":
@@ -332,6 +332,8 @@ export async function resolvePromptInput(input: string | undefined, description:
332
332
  export interface LoadContextFilesOptions {
333
333
  /** Working directory to start walking up from. Default: getProjectDir() */
334
334
  cwd?: string;
335
+ /** Disabled extension IDs to honor instead of the process-global settings. */
336
+ disabledExtensions?: string[];
335
337
  }
336
338
 
337
339
  function dedupeExactContextFiles(
@@ -356,7 +358,10 @@ export async function loadProjectContextFiles(
356
358
  ): Promise<Array<{ path: string; content: string; depth?: number }>> {
357
359
  const resolvedCwd = options.cwd ?? getProjectDir();
358
360
 
359
- const result = await loadCapability(contextFileCapability.id, { cwd: resolvedCwd });
361
+ const result = await loadCapability(contextFileCapability.id, {
362
+ cwd: resolvedCwd,
363
+ disabledExtensions: options.disabledExtensions,
364
+ });
360
365
 
361
366
  // Materialize ContextFile items, expanding any `@path/to/file` includes
362
367
  // in their content. The expansion uses the file's own directory as the
@@ -493,7 +498,8 @@ export interface BuildSystemPromptOptions {
493
498
  /**
494
499
  * Whether provider-native tool calling is active (no owned/in-band syntax).
495
500
  * When true and `inlineToolDescriptors` is false, the inventory renders as a
496
- * compact tool-name list; otherwise it renders full `# Tool:` sections. Default: true
501
+ * compact tool-name list; otherwise it renders the full Harmony-style
502
+ * `namespace functions { … }` catalog. Default: true
497
503
  */
498
504
  nativeTools?: boolean;
499
505
  /** Skills settings for discovery. */
@@ -520,6 +526,9 @@ export interface BuildSystemPromptOptions {
520
526
  taskMaxConcurrency?: number;
521
527
  /** Whether IRC-backed parallel coordination can be included in delegation policy. */
522
528
  taskIrcEnabled?: boolean;
529
+ /** Whether the read-only `scout` subagent is spawnable (not disabled, allowed by spawn policy). Defaults to true. */
530
+ scoutAvailable?: boolean;
531
+
523
532
  /** Rules with alwaysApply=true — their full content is injected into the prompt. */
524
533
  alwaysApplyRules?: AlwaysApplyRule[];
525
534
  /** Whether secret obfuscation is active. When true, explains the redaction format in the prompt. */
@@ -594,6 +603,7 @@ export async function buildSystemPrompt(options: BuildSystemPromptOptions = {}):
594
603
  taskIrcEnabled = false,
595
604
  secretsEnabled = false,
596
605
  workspaceTree: providedWorkspaceTree,
606
+ scoutAvailable = true,
597
607
  memoryRootEnabled = false,
598
608
  securityEnabled = false,
599
609
  model,
@@ -788,7 +798,7 @@ export async function buildSystemPrompt(options: BuildSystemPromptOptions = {}):
788
798
 
789
799
  // List mode shows a compact tool-name list; it only applies when descriptors
790
800
  // stay in provider-native tool schemas AND native tool calling is active.
791
- // Otherwise render full `# Tool:` sections inline in the system prompt.
801
+ // Otherwise render the full functions-namespace catalog in the system prompt.
792
802
  const toolListMode = !inlineToolDescriptors && nativeTools;
793
803
  // Build tool descriptions for system prompt rendering.
794
804
  const toolPromptNames = new Map<string, string>(toolNames.map(name => [name, tools?.get(name)?.wireName ?? name]));
@@ -821,7 +831,6 @@ export async function buildSystemPrompt(options: BuildSystemPromptOptions = {}):
821
831
  examples: meta?.examples,
822
832
  };
823
833
  }),
824
- model ?? "",
825
834
  );
826
835
 
827
836
  // Filter skills for the rendered system prompt:
@@ -874,6 +883,7 @@ export async function buildSystemPrompt(options: BuildSystemPromptOptions = {}):
874
883
  eagerTasksAlways,
875
884
  taskBatch,
876
885
  MAX_CONCURRENCY: normalizeConcurrencyLimit(taskMaxConcurrency),
886
+ scoutAvailable,
877
887
  taskIrcEnabled,
878
888
  secretsEnabled,
879
889
  hasMemoryRoot: memoryRootEnabled,
@@ -2364,7 +2364,14 @@ export async function finalizeSubagentLifecycle(args: {
2364
2364
  const resumableAbort =
2365
2365
  args.abortKind === "budget" && args.keepAlive && !args.isolated && args.reviveSession !== null;
2366
2366
  if (args.aborted && !resumableAbort) {
2367
- if (ref && ownsRef) registry.setStatus(args.id, "aborted", ref);
2367
+ if (ref && ownsRef) {
2368
+ // Terminal hard kill: mark `aborted` and detach the session before
2369
+ // disposing so the ref satisfies the AgentRef invariant (session null
2370
+ // when aborted) — ensureLive/hub focus must treat it as terminal, never
2371
+ // route into the disposed session.
2372
+ registry.setStatus(args.id, "aborted", ref);
2373
+ registry.detachSession(args.id, ref);
2374
+ }
2368
2375
  await disposeSession();
2369
2376
  return;
2370
2377
  }
package/src/task/index.ts CHANGED
@@ -27,7 +27,7 @@ import { TASK_EFFORTS, type TaskEffort } from "../thinking";
27
27
  import { truncateForPrompt } from "../tools/approval";
28
28
  import { isIrcEnabled } from "../tools/hub";
29
29
  import { formatBytes, formatDuration } from "../tools/render-utils";
30
- import { resolveSpawnPolicy } from "./spawn-policy";
30
+ import { isScoutSpawnable, resolveSpawnPolicy } from "./spawn-policy";
31
31
  import {
32
32
  type AgentDefinition,
33
33
  type AgentProgress,
@@ -188,8 +188,10 @@ function renderDescription(options: TaskDescriptionOptions): string {
188
188
  readOnly: isReadOnlyAgent(agent),
189
189
  blocking: agent.blocking === true,
190
190
  }));
191
+ const scoutAvailable = isScoutSpawnable(options.disabledAgents, options.parentSpawns);
191
192
  return prompt.render(taskDescriptionTemplate, {
192
193
  agents: renderedAgents,
194
+ scoutAvailable,
193
195
  spawningDisabled,
194
196
  defaultAgent: spawnPolicy.defaultAgent,
195
197
  isolationEnabled: options.isolationEnabled,
@@ -398,15 +400,19 @@ const GENERIC_SPAWN_AGENTS: ReadonlySet<string> = new Set(["task", "sonic"]);
398
400
  * (DepthCapacity: it currently has the `task` tool). `agentNames` are the
399
401
  * per-item resolved agent types. Returns undefined when no nudge applies.
400
402
  */
401
- export function buildSpecializationAdvisory(agentNames: string[], depthCapacity: boolean): string | undefined {
403
+ export function buildSpecializationAdvisory(
404
+ agentNames: string[],
405
+ depthCapacity: boolean,
406
+ scoutAvailable = true,
407
+ ): string | undefined {
402
408
  if (!depthCapacity) return undefined;
403
409
  const generics = agentNames.filter(name => GENERIC_SPAWN_AGENTS.has(name));
404
410
  if (generics.length < 2) return undefined;
405
- return (
406
- `Tip: this call spawned ${generics.length} generic \`${generics[0]}\` workers. ` +
407
- `Check the agent list for a closer specialist type — e.g. read-only research belongs on ` +
408
- `\`agent: "scout"\`, which runs on a faster model.`
409
- );
411
+ const specialist = scoutAvailable
412
+ ? `Check the agent list for a closer specialist type — e.g. read-only research belongs on ` +
413
+ `\`agent: "scout"\`, which runs on a faster model.`
414
+ : `Check the agent list for a closer specialist type.`;
415
+ return `Tip: this call spawned ${generics.length} generic \`${generics[0]}\` workers. ${specialist}`;
410
416
  }
411
417
 
412
418
  /**
@@ -443,10 +449,11 @@ export function composeSpawnAdvisory(args: {
443
449
  depthCapacity: boolean;
444
450
  ircEnabled: boolean;
445
451
  willRunAsync: boolean;
452
+ scoutAvailable?: boolean;
446
453
  }): string | undefined {
447
454
  return (
448
455
  [
449
- buildSpecializationAdvisory(args.agents, args.depthCapacity),
456
+ buildSpecializationAdvisory(args.agents, args.depthCapacity, args.scoutAvailable),
450
457
  args.willRunAsync ? buildCoordinationAdvisory(args.items, args.depthCapacity, args.ircEnabled) : undefined,
451
458
  ]
452
459
  .filter(Boolean)
@@ -751,6 +758,10 @@ export class TaskTool implements AgentTool<TaskToolSchemaInstance, TaskToolDetai
751
758
  depthCapacity,
752
759
  ircEnabled,
753
760
  willRunAsync: false,
761
+ scoutAvailable: isScoutSpawnable(
762
+ this.session.settings.get("task.disabledAgents") as string[] | undefined,
763
+ this.session.getSessionSpawns?.() ?? "*",
764
+ ),
754
765
  });
755
766
  const result = await this.#executeSyncFanout(
756
767
  toolCallId,
@@ -784,6 +795,10 @@ export class TaskTool implements AgentTool<TaskToolSchemaInstance, TaskToolDetai
784
795
  depthCapacity,
785
796
  ircEnabled,
786
797
  willRunAsync: asyncItems.length > 0,
798
+ scoutAvailable: isScoutSpawnable(
799
+ this.session.settings.get("task.disabledAgents") as string[] | undefined,
800
+ this.session.getSessionSpawns?.() ?? "*",
801
+ ),
787
802
  });
788
803
  // Returns a fresh result (copied content array, copied text part) rather
789
804
  // than mutating the caller's — task results are short-lived here, but an
@@ -56,3 +56,17 @@ export function resolveSpawnPolicy(parentSpawns: string | boolean | null | undef
56
56
  allowedPromptText: allowedAgents.map(agent => `\`${agent}\``).join(", "),
57
57
  };
58
58
  }
59
+
60
+ /**
61
+ * Whether the `scout` agent is spawnable in a session: not disabled via
62
+ * `task.disabledAgents`, and permitted by the session spawn policy.
63
+ */
64
+ export function isScoutSpawnable(
65
+ disabledAgents: readonly string[] | undefined,
66
+ spawns: string | boolean | null | undefined,
67
+ ): boolean {
68
+ if (disabledAgents?.includes("scout")) return false;
69
+ const policy = resolveSpawnPolicy(spawns);
70
+ if (!policy.enabled) return false;
71
+ return policy.allowedAgents === null || policy.allowedAgents.includes("scout");
72
+ }
package/src/thinking.ts CHANGED
@@ -3,6 +3,8 @@ import { Effort, type Model, THINKING_EFFORTS } from "@oh-my-pi/pi-ai";
3
3
  import { clampThinkingLevelForModel, getSupportedEfforts } from "@oh-my-pi/pi-catalog/model-thinking";
4
4
  import { modelsAreEqual } from "@oh-my-pi/pi-catalog/models";
5
5
 
6
+ export { CLI_THINKING_LEVELS } from "./cli/thinking-levels";
7
+
6
8
  /**
7
9
  * Metadata used to render thinking selector values in the coding-agent UI.
8
10
  */
@@ -209,14 +211,6 @@ export function getConfiguredThinkingLevelMetadata(level: ConfiguredThinkingLeve
209
211
  return level === AUTO_THINKING ? AUTO_THINKING_METADATA : getThinkingLevelMetadata(level);
210
212
  }
211
213
 
212
- /**
213
- * Thinking selectors accepted by the `--thinking` CLI flag, in display order:
214
- * `off`, every concrete effort (`minimal`..`max`), then `auto`. Single source
215
- * for the flag's `options` list, shell completions, and the "invalid level"
216
- * warning so all three stay in sync.
217
- */
218
- export const CLI_THINKING_LEVELS: readonly string[] = [ThinkingLevel.Off, ...THINKING_EFFORTS, AUTO_THINKING];
219
-
220
214
  /**
221
215
  * Parses a `--thinking` CLI value. Accepts every {@link parseConfiguredThinkingLevel}
222
216
  * selector (`off`, `auto`, `minimal`..`max`) but rejects
package/src/tiny/text.ts CHANGED
@@ -142,6 +142,17 @@ export function isLowSignalTitleInput(message: string): boolean {
142
142
  */
143
143
  export const NO_TITLE_SENTINEL = "none";
144
144
 
145
+ /**
146
+ * Upper bounds on an accepted title. Titling is a 3-7 word task, so any output
147
+ * past these limits is a model that ignored the task and answered the user's
148
+ * message instead — its whole reply must not become the session title (issue
149
+ * #7303). Rejecting (return null) lets the caller defer titling to the next
150
+ * user turn; truncating would keep half an assistant blob, which is still an
151
+ * assistant blob.
152
+ */
153
+ const MAX_TITLE_CHARS = 80;
154
+ const MAX_TITLE_WORDS = 12;
155
+
145
156
  export function normalizeGeneratedTitle(value: string | null | undefined, sourceText?: string): string | null {
146
157
  const firstLine = value?.trim().split(/\r?\n/, 1)[0]?.trim();
147
158
  if (!firstLine) return null;
@@ -154,6 +165,7 @@ export function normalizeGeneratedTitle(value: string | null | undefined, source
154
165
  .replace(/[.!?]$/, "")
155
166
  .trim();
156
167
  if (!title || title.toLowerCase() === NO_TITLE_SENTINEL) return null;
168
+ if (title.length > MAX_TITLE_CHARS || (title.match(TITLE_WORD)?.length ?? 0) > MAX_TITLE_WORDS) return null;
157
169
  return sourceText === undefined ? title : reconcileTitleCasing(title, sourceText);
158
170
  }
159
171
 
@@ -11,6 +11,7 @@ import { recordFileSnapshot, recordSeenLinesFromBody } from "../edit/file-snapsh
11
11
  import type { RenderResultOptions } from "../extensibility/custom-tools/types";
12
12
  import type { Theme } from "../modes/theme/theme";
13
13
  import astGrepDescription from "../prompts/tools/ast-grep.md" with { type: "text" };
14
+ import { isScoutSpawnable } from "../task/spawn-policy";
14
15
  import { Ellipsis, fileHyperlink, renderStatusLine, renderTreeList, truncateToWidth } from "../tui";
15
16
  import { resolveFileDisplayMode } from "../utils/file-display-mode";
16
17
  import type { ToolSession } from ".";
@@ -150,7 +151,14 @@ export class AstGrepTool implements AgentTool<typeof astGrepSchema, AstGrepToolD
150
151
  readonly approval = "read" as const;
151
152
  readonly label = "AST Grep";
152
153
  readonly summary = "Search code with AST patterns (structural grep)";
153
- readonly description: string;
154
+ get description(): string {
155
+ return prompt.render(astGrepDescription, {
156
+ scoutAvailable: isScoutSpawnable(
157
+ this.session.settings.get("task.disabledAgents") as string[] | undefined,
158
+ this.session.getSessionSpawns?.() ?? "*",
159
+ ),
160
+ });
161
+ }
154
162
  readonly parameters = astGrepSchema;
155
163
  readonly strict = true;
156
164
 
@@ -178,9 +186,7 @@ export class AstGrepTool implements AgentTool<typeof astGrepSchema, AstGrepToolD
178
186
  ];
179
187
  readonly loadMode = "discoverable";
180
188
 
181
- constructor(private readonly session: ToolSession) {
182
- this.description = prompt.render(astGrepDescription);
183
- }
189
+ constructor(private readonly session: ToolSession) {}
184
190
 
185
191
  async execute(
186
192
  _toolCallId: string,
@@ -8,16 +8,11 @@ import { resizeImage } from "../../../utils/image-resize";
8
8
  import type { ToolSession } from "../../index";
9
9
  import { resolveToCwd } from "../../path-utils";
10
10
  import { formatScreenshot } from "../../render-utils";
11
+ import { bindRunFacade, resolvePredicateTimeout, type WaitPredicateOptions, waitForRun } from "../../run-scope";
11
12
  import { ToolAbortError, ToolError, throwIfAborted } from "../../tool-errors";
12
13
  import { type AriaSnapshotOptions, assertSelectorString, buildAriaSnapshotScript } from "../aria/aria-snapshot";
13
14
  import { DEFAULT_VIEWPORT } from "../launch";
14
15
  import { extractReadableFromHtml, type ReadableFormat } from "../readable";
15
- import {
16
- bindBrowserRunFacade,
17
- resolvePredicateTimeout,
18
- type WaitPredicateOptions,
19
- waitForBrowserRun,
20
- } from "../run-cancellation";
21
16
  import { cloneSafe, RunOutput } from "../run-output";
22
17
  import type { Observation, ReadyInfo, RunResultOk, ScreenshotResult, SessionSnapshot } from "../tab-protocol";
23
18
  import {
@@ -1345,16 +1340,16 @@ export async function runCmuxCode(tab: CmuxTab, opts: RunCmuxCodeOptions): Promi
1345
1340
  // Keep both inside try so a concurrent in-process eval/browser run surfaces as
1346
1341
  // a rejected promise the supervisor can report, never an unhandled rejection.
1347
1342
  runtime.setCwd(opts.snapshot.cwd);
1348
- const runTab = bindBrowserRunFacade(tab, signal);
1343
+ const runTab = bindRunFacade(tab, signal);
1349
1344
  runtime.setRunScope({
1350
- page: bindBrowserRunFacade(tab.page, signal),
1351
- browser: bindBrowserRunFacade(tab.browser, signal),
1345
+ page: bindRunFacade(tab.page, signal),
1346
+ browser: bindRunFacade(tab.browser, signal),
1352
1347
  tab: runTab,
1353
1348
  assert: (cond: unknown, text?: string): void => {
1354
1349
  if (!cond) throw new ToolError(text ?? "Assertion failed");
1355
1350
  },
1356
1351
  wait: (msOrPredicate: number | (() => unknown), waitOpts?: WaitPredicateOptions): Promise<unknown> =>
1357
- waitForBrowserRun(
1352
+ waitForRun(
1358
1353
  msOrPredicate,
1359
1354
  signal,
1360
1355
  typeof msOrPredicate === "number"
@@ -1,3 +1,4 @@
1
+ import { parseFlag } from "@oh-my-pi/pi-utils";
1
2
  import { ToolError } from "../../tool-errors";
2
3
  import type { Observation, ObservationEntry } from "../tab-protocol";
3
4
 
@@ -174,13 +175,6 @@ export function mapWaitUntil(waitUntil: string | undefined): "interactive" | "co
174
175
  return waitUntil === "domcontentloaded" ? "interactive" : "complete";
175
176
  }
176
177
 
177
- const TRUTHY_ENV_VALUES = new Set(["1", "Y", "y", "TRUE", "true", "YES", "yes", "ON", "on"]);
178
-
179
- function resolveCmuxEnabled(envValue: string | undefined, settingEnabled: boolean): boolean {
180
- if (!envValue) return settingEnabled;
181
- return TRUTHY_ENV_VALUES.has(envValue);
182
- }
183
-
184
178
  export interface ResolveCmuxKindOptions {
185
179
  surface?: string;
186
180
  settingEnabled?: boolean;
@@ -190,7 +184,7 @@ export function resolveCmuxKind(
190
184
  options?: ResolveCmuxKindOptions | null,
191
185
  env: Record<string, string | undefined> = process.env,
192
186
  ): CmuxKind | null {
193
- if (!resolveCmuxEnabled(env.PI_BROWSER_CMUX, options?.settingEnabled ?? true)) {
187
+ if (!parseFlag(env.PI_BROWSER_CMUX, options?.settingEnabled ?? true)) {
194
188
  return null;
195
189
  }
196
190
  const socketPath = env.CMUX_SOCKET_PATH;