@oh-my-pi/pi-coding-agent 17.0.9 → 17.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (349) hide show
  1. package/CHANGELOG.md +78 -0
  2. package/README.md +1 -1
  3. package/dist/cli.js +4685 -4614
  4. package/dist/types/async/job-manager.d.ts +37 -1
  5. package/dist/types/cli/args.d.ts +2 -0
  6. package/dist/types/cli/bench-cli.d.ts +41 -0
  7. package/dist/types/commands/bench.d.ts +15 -3
  8. package/dist/types/commands/launch.d.ts +4 -0
  9. package/dist/types/config/model-registry.d.ts +3 -1
  10. package/dist/types/config/models-config-schema.d.ts +18 -6
  11. package/dist/types/config/models-config.d.ts +14 -5
  12. package/dist/types/config/provider-globals.d.ts +2 -2
  13. package/dist/types/config/service-tier.d.ts +5 -1
  14. package/dist/types/config/settings-schema.d.ts +374 -47
  15. package/dist/types/config/settings.d.ts +7 -0
  16. package/dist/types/dap/client.d.ts +20 -0
  17. package/dist/types/exec/bash-executor.d.ts +38 -0
  18. package/dist/types/exec/direnv.d.ts +33 -0
  19. package/dist/types/export/html/args.d.ts +15 -0
  20. package/dist/types/export/html/index.d.ts +11 -26
  21. package/dist/types/export/html/web-palette.d.ts +55 -116
  22. package/dist/types/extensibility/extensions/loader.d.ts +3 -0
  23. package/dist/types/extensibility/extensions/types.d.ts +17 -2
  24. package/dist/types/extensibility/legacy-pi-coding-agent-shim.d.ts +3 -0
  25. package/dist/types/extensibility/plugins/legacy-pi-compat.d.ts +13 -0
  26. package/dist/types/live/attestation.d.ts +2 -0
  27. package/dist/types/live/controller.d.ts +48 -0
  28. package/dist/types/live/protocol.d.ts +85 -0
  29. package/dist/types/live/protocol.test.d.ts +1 -0
  30. package/dist/types/live/transport.d.ts +35 -0
  31. package/dist/types/live/visualizer.d.ts +30 -0
  32. package/dist/types/lsp/client.d.ts +16 -1
  33. package/dist/types/mcp/config-writer.test.d.ts +1 -0
  34. package/dist/types/mcp/oauth-discovery.d.ts +2 -0
  35. package/dist/types/mcp/smithery-auth.d.ts +1 -1
  36. package/dist/types/mcp/smithery-auth.test.d.ts +1 -0
  37. package/dist/types/memory-backend/tool-names.d.ts +2 -0
  38. package/dist/types/modes/components/assistant-message.d.ts +1 -0
  39. package/dist/types/modes/components/custom-message.d.ts +1 -1
  40. package/dist/types/modes/components/login-dialog.d.ts +2 -2
  41. package/dist/types/modes/components/message-frame.d.ts +8 -4
  42. package/dist/types/modes/components/plan-review-overlay.d.ts +26 -0
  43. package/dist/types/modes/components/session-account-selector.d.ts +11 -0
  44. package/dist/types/modes/components/settings-defs.d.ts +7 -1
  45. package/dist/types/modes/components/status-line/component.jj-cache.test.d.ts +1 -0
  46. package/dist/types/modes/components/tool-execution.d.ts +9 -1
  47. package/dist/types/modes/controllers/command-controller.d.ts +1 -1
  48. package/dist/types/modes/controllers/event-controller.d.ts +6 -1
  49. package/dist/types/modes/controllers/live-command-controller.d.ts +14 -0
  50. package/dist/types/modes/controllers/selector-controller.d.ts +1 -0
  51. package/dist/types/modes/interactive-mode.d.ts +6 -0
  52. package/dist/types/modes/theme/theme.d.ts +1 -1
  53. package/dist/types/modes/types.d.ts +4 -1
  54. package/dist/types/modes/utils/context-usage.d.ts +12 -2
  55. package/dist/types/plan-mode/plan-files.d.ts +10 -0
  56. package/dist/types/registry/agent-lifecycle.d.ts +21 -9
  57. package/dist/types/registry/agent-registry.d.ts +11 -4
  58. package/dist/types/sdk.d.ts +15 -1
  59. package/dist/types/secrets/index.d.ts +15 -2
  60. package/dist/types/secrets/obfuscator.d.ts +113 -15
  61. package/dist/types/session/acp-permission-gate.d.ts +19 -0
  62. package/dist/types/session/agent-session-events.d.ts +84 -0
  63. package/dist/types/session/agent-session-types.d.ts +321 -0
  64. package/dist/types/session/agent-session.d.ts +135 -753
  65. package/dist/types/session/async-job-delivery.d.ts +28 -0
  66. package/dist/types/session/auth-storage.d.ts +1 -1
  67. package/dist/types/session/bash-runner.d.ts +75 -0
  68. package/dist/types/session/blob-store.d.ts +11 -1
  69. package/dist/types/session/blob-store.test.d.ts +1 -0
  70. package/dist/types/session/checkpoint-entries.d.ts +28 -0
  71. package/dist/types/session/eval-runner.d.ts +51 -0
  72. package/dist/types/session/irc-bridge.d.ts +46 -0
  73. package/dist/types/session/messages.d.ts +24 -0
  74. package/dist/types/session/model-controls.d.ts +180 -0
  75. package/dist/types/session/prewalk.d.ts +46 -0
  76. package/dist/types/session/queued-messages.d.ts +22 -0
  77. package/dist/types/session/redis-session-storage.d.ts +1 -0
  78. package/dist/types/session/retry-fallback-chains.d.ts +74 -0
  79. package/dist/types/session/role-models.d.ts +15 -0
  80. package/dist/types/session/session-advisors.d.ts +228 -0
  81. package/dist/types/session/session-entries.d.ts +8 -0
  82. package/dist/types/session/session-handoff.d.ts +70 -0
  83. package/dist/types/session/session-history-format.d.ts +6 -1
  84. package/dist/types/session/session-maintenance.d.ts +246 -0
  85. package/dist/types/session/session-manager.d.ts +41 -0
  86. package/dist/types/session/session-memory.d.ts +56 -0
  87. package/dist/types/session/session-provider-boundary.d.ts +62 -0
  88. package/dist/types/session/session-stats.d.ts +48 -0
  89. package/dist/types/session/session-tools.d.ts +158 -0
  90. package/dist/types/session/session-workspace.d.ts +32 -0
  91. package/dist/types/session/stream-guards.d.ts +44 -0
  92. package/dist/types/session/streaming-output.d.ts +8 -0
  93. package/dist/types/session/todo-tracker.d.ts +56 -0
  94. package/dist/types/session/ttsr-coordinator.d.ts +51 -0
  95. package/dist/types/session/turn-recovery.d.ts +225 -0
  96. package/dist/types/slash-commands/helpers/session-pin.d.ts +9 -0
  97. package/dist/types/stt/index.d.ts +0 -2
  98. package/dist/types/stt/stt-controller.d.ts +7 -0
  99. package/dist/types/system-prompt.d.ts +2 -0
  100. package/dist/types/task/executor.d.ts +4 -1
  101. package/dist/types/task/output-manager.d.ts +3 -1
  102. package/dist/types/tiny/title-client.d.ts +10 -0
  103. package/dist/types/tools/approval.d.ts +1 -0
  104. package/dist/types/tools/browser/launch.d.ts +5 -0
  105. package/dist/types/tools/builtin-names.d.ts +1 -1
  106. package/dist/types/tools/computer/protocol.d.ts +43 -0
  107. package/dist/types/tools/computer/supervisor.d.ts +32 -0
  108. package/dist/types/tools/computer/worker-entry.d.ts +1 -0
  109. package/dist/types/tools/computer/worker.d.ts +15 -0
  110. package/dist/types/tools/computer-renderer.d.ts +22 -0
  111. package/dist/types/tools/computer.d.ts +71 -0
  112. package/dist/types/tools/context.d.ts +2 -0
  113. package/dist/types/tools/default-renderer.d.ts +21 -0
  114. package/dist/types/tools/image-gen.d.ts +4 -4
  115. package/dist/types/tools/image-providers.d.ts +38 -0
  116. package/dist/types/tools/index.d.ts +7 -0
  117. package/dist/types/tools/path-utils.d.ts +8 -0
  118. package/dist/types/tools/todo.d.ts +10 -4
  119. package/dist/types/tools/xdev.d.ts +5 -1
  120. package/dist/types/tts/streaming-player.d.ts +10 -29
  121. package/dist/types/tts/vocalizer.d.ts +5 -0
  122. package/dist/types/utils/jj.d.ts +29 -0
  123. package/dist/types/utils/lang-from-path.d.ts +1 -0
  124. package/dist/types/utils/title-generator.d.ts +30 -0
  125. package/dist/types/utils/tools-manager.d.ts +1 -2
  126. package/dist/types/vibe/runtime.d.ts +36 -8
  127. package/dist/types/web/search/index.d.ts +1 -1
  128. package/dist/types/web/search/provider.d.ts +15 -5
  129. package/dist/types/web/search/types.d.ts +94 -0
  130. package/package.json +12 -12
  131. package/scripts/generate-share-viewer.ts +4 -6
  132. package/src/advisor/__tests__/advisor.test.ts +643 -0
  133. package/src/advisor/runtime.ts +144 -43
  134. package/src/async/job-manager.ts +90 -2
  135. package/src/cli/args.ts +3 -0
  136. package/src/cli/auth-gateway-cli.ts +18 -3
  137. package/src/cli/bench-cli.ts +395 -29
  138. package/src/cli/flag-tables.ts +3 -0
  139. package/src/cli/gc-cli.ts +2 -2
  140. package/src/cli/setup-cli.ts +2 -14
  141. package/src/cli/usage-cli.ts +10 -1
  142. package/src/cli.ts +8 -0
  143. package/src/collab/host.ts +3 -2
  144. package/src/commands/bench.ts +18 -4
  145. package/src/commands/launch.ts +4 -0
  146. package/src/config/model-registry.ts +16 -2
  147. package/src/config/models-config-schema.ts +15 -4
  148. package/src/config/provider-globals.ts +9 -9
  149. package/src/config/service-tier.ts +26 -1
  150. package/src/config/settings-schema.ts +270 -51
  151. package/src/config/settings.ts +81 -2
  152. package/src/dap/client.ts +37 -4
  153. package/src/edit/index.ts +49 -12
  154. package/src/eval/executor-base.ts +1 -0
  155. package/src/eval/js/executor.ts +2 -0
  156. package/src/eval/py/executor.ts +103 -33
  157. package/src/exec/bash-executor.ts +90 -6
  158. package/src/exec/direnv.ts +145 -0
  159. package/src/export/html/args.ts +20 -0
  160. package/src/export/html/index.ts +45 -47
  161. package/src/export/html/template.css +19 -1
  162. package/src/export/html/template.html +6 -0
  163. package/src/export/html/template.js +21 -0
  164. package/src/export/html/tool-views.generated.js +31 -31
  165. package/src/export/html/web-palette.ts +116 -132
  166. package/src/export/share.ts +249 -49
  167. package/src/extensibility/extensions/loader.ts +29 -1
  168. package/src/extensibility/extensions/runner.ts +6 -0
  169. package/src/extensibility/extensions/types.ts +33 -2
  170. package/src/extensibility/extensions/wrapper.ts +68 -12
  171. package/src/extensibility/legacy-pi-coding-agent-shim.ts +7 -1
  172. package/src/extensibility/plugins/legacy-pi-compat.ts +19 -1
  173. package/src/hindsight/state.ts +64 -6
  174. package/src/internal-urls/local-protocol.ts +2 -1
  175. package/src/internal-urls/memory-protocol.ts +2 -2
  176. package/src/internal-urls/skill-protocol.ts +2 -2
  177. package/src/internal-urls/ssh-protocol.ts +2 -1
  178. package/src/internal-urls/vault-protocol.ts +2 -1
  179. package/src/live/attestation.ts +91 -0
  180. package/src/live/controller.ts +517 -0
  181. package/src/live/prompts/agent-final-message.md +3 -0
  182. package/src/live/prompts/live-instructions.md +23 -0
  183. package/src/live/protocol.test.ts +140 -0
  184. package/src/live/protocol.ts +233 -0
  185. package/src/live/transport.ts +422 -0
  186. package/src/live/visualizer.ts +214 -0
  187. package/src/lsp/client.ts +29 -9
  188. package/src/lsp/index.ts +19 -4
  189. package/src/main.ts +8 -1
  190. package/src/mcp/config-writer.test.ts +43 -0
  191. package/src/mcp/config-writer.ts +109 -82
  192. package/src/mcp/oauth-discovery.ts +10 -2
  193. package/src/mcp/smithery-auth.test.ts +29 -0
  194. package/src/mcp/smithery-auth.ts +3 -2
  195. package/src/memories/index.ts +40 -20
  196. package/src/memory-backend/tool-names.ts +2 -0
  197. package/src/mnemopi/backend.ts +24 -7
  198. package/src/modes/acp/acp-agent.ts +56 -10
  199. package/src/modes/acp/acp-event-mapper.ts +8 -1
  200. package/src/modes/components/agent-hub.ts +1 -1
  201. package/src/modes/components/assistant-message.ts +7 -2
  202. package/src/modes/components/custom-message.ts +4 -1
  203. package/src/modes/components/login-dialog.ts +14 -4
  204. package/src/modes/components/message-frame.ts +14 -8
  205. package/src/modes/components/plan-review-overlay.ts +350 -35
  206. package/src/modes/components/session-account-selector.ts +62 -0
  207. package/src/modes/components/settings-defs.ts +23 -1
  208. package/src/modes/components/settings-selector.ts +169 -1
  209. package/src/modes/components/status-line/component.jj-cache.test.ts +229 -0
  210. package/src/modes/components/status-line/component.ts +123 -2
  211. package/src/modes/components/tool-execution.ts +28 -112
  212. package/src/modes/controllers/command-controller.ts +56 -15
  213. package/src/modes/controllers/event-controller.ts +111 -16
  214. package/src/modes/controllers/extension-ui-controller.test.ts +1 -0
  215. package/src/modes/controllers/extension-ui-controller.ts +20 -4
  216. package/src/modes/controllers/input-controller.ts +47 -39
  217. package/src/modes/controllers/live-command-controller.ts +255 -0
  218. package/src/modes/controllers/mcp-command-controller.ts +10 -1
  219. package/src/modes/controllers/omfg-controller.ts +44 -40
  220. package/src/modes/controllers/selector-controller.ts +80 -10
  221. package/src/modes/controllers/session-focus-controller.ts +6 -1
  222. package/src/modes/interactive-mode.ts +176 -38
  223. package/src/modes/runtime-init.ts +2 -0
  224. package/src/modes/setup-wizard/scenes/web-search.ts +17 -9
  225. package/src/modes/theme/theme.ts +1 -1
  226. package/src/modes/types.ts +4 -1
  227. package/src/modes/utils/context-usage.ts +23 -7
  228. package/src/modes/utils/ui-helpers.ts +10 -5
  229. package/src/plan-mode/plan-files.ts +40 -0
  230. package/src/prompts/bench/cache-prefix-chunk.md +1 -0
  231. package/src/prompts/bench/cache-prefix.md +3 -0
  232. package/src/prompts/bench/cache-suffix.md +1 -0
  233. package/src/prompts/system/computer-safety.md +14 -0
  234. package/src/prompts/system/custom-system-prompt.md +1 -1
  235. package/src/prompts/system/project-prompt.md +8 -1
  236. package/src/prompts/system/subagent-async-pending.md +6 -0
  237. package/src/prompts/system/system-prompt.md +1 -1
  238. package/src/prompts/system/workflow-notice.md +48 -40
  239. package/src/prompts/system/xdev-mount-notice.md +4 -0
  240. package/src/prompts/tools/computer.md +26 -0
  241. package/src/prompts/tools/todo.md +3 -1
  242. package/src/registry/agent-lifecycle.ts +116 -46
  243. package/src/registry/agent-registry.ts +35 -8
  244. package/src/registry/persisted-agents.ts +26 -2
  245. package/src/sdk.ts +215 -136
  246. package/src/secrets/index.ts +127 -4
  247. package/src/secrets/obfuscator.ts +2328 -105
  248. package/src/session/acp-permission-gate.ts +165 -0
  249. package/src/session/agent-session-error-log.test.ts +1 -1
  250. package/src/session/agent-session-events.ts +66 -0
  251. package/src/session/agent-session-types.ts +343 -0
  252. package/src/session/agent-session.ts +1940 -12218
  253. package/src/session/artifacts.ts +6 -5
  254. package/src/session/async-job-delivery.ts +74 -0
  255. package/src/session/auth-storage.ts +1 -0
  256. package/src/session/bash-runner.ts +326 -0
  257. package/src/session/blob-store.test.ts +56 -0
  258. package/src/session/blob-store.ts +18 -2
  259. package/src/session/checkpoint-entries.ts +81 -0
  260. package/src/session/eval-runner.ts +217 -0
  261. package/src/session/indexed-session-storage.ts +9 -2
  262. package/src/session/irc-bridge.ts +203 -0
  263. package/src/session/messages.ts +266 -1
  264. package/src/session/model-controls.ts +714 -0
  265. package/src/session/prewalk.ts +252 -0
  266. package/src/session/queued-messages.ts +93 -0
  267. package/src/session/redis-session-storage.ts +51 -11
  268. package/src/session/retry-fallback-chains.ts +455 -0
  269. package/src/session/role-models.ts +85 -0
  270. package/src/session/session-advisors.ts +1679 -0
  271. package/src/session/session-context.ts +6 -3
  272. package/src/session/session-entries.ts +8 -0
  273. package/src/session/session-handoff.ts +308 -0
  274. package/src/session/session-history-format.ts +20 -9
  275. package/src/session/session-loader.ts +0 -46
  276. package/src/session/session-maintenance.ts +2983 -0
  277. package/src/session/session-manager.ts +390 -30
  278. package/src/session/session-memory.ts +222 -0
  279. package/src/session/session-provider-boundary.ts +307 -0
  280. package/src/session/session-stats.ts +293 -0
  281. package/src/session/session-tools.ts +980 -0
  282. package/src/session/session-workspace.ts +53 -0
  283. package/src/session/stream-guards.ts +417 -0
  284. package/src/session/streaming-output.ts +52 -5
  285. package/src/session/todo-tracker.ts +380 -0
  286. package/src/session/ttsr-coordinator.ts +496 -0
  287. package/src/session/turn-recovery.ts +1629 -0
  288. package/src/slash-commands/builtin-registry.ts +257 -20
  289. package/src/slash-commands/helpers/session-pin.ts +44 -0
  290. package/src/slash-commands/helpers/usage-report.ts +22 -3
  291. package/src/stt/downloader.ts +0 -2
  292. package/src/stt/index.ts +0 -2
  293. package/src/stt/stt-controller.ts +57 -146
  294. package/src/system-prompt.ts +45 -17
  295. package/src/task/executor.ts +285 -70
  296. package/src/task/index.ts +98 -105
  297. package/src/task/output-manager.ts +25 -3
  298. package/src/task/persisted-revive.ts +4 -3
  299. package/src/task/structured-subagent.ts +1 -0
  300. package/src/tiny/title-client.ts +22 -0
  301. package/src/tools/approval.ts +55 -10
  302. package/src/tools/bash-interactive.ts +116 -86
  303. package/src/tools/bash-skill-urls.ts +28 -2
  304. package/src/tools/bash.ts +339 -123
  305. package/src/tools/browser/launch.ts +9 -1
  306. package/src/tools/browser/tab-supervisor.ts +8 -5
  307. package/src/tools/browser.ts +94 -47
  308. package/src/tools/builtin-names.ts +1 -0
  309. package/src/tools/computer/protocol.ts +28 -0
  310. package/src/tools/computer/supervisor.ts +258 -0
  311. package/src/tools/computer/worker-entry.ts +25 -0
  312. package/src/tools/computer/worker.ts +135 -0
  313. package/src/tools/computer-renderer.ts +108 -0
  314. package/src/tools/computer.ts +433 -0
  315. package/src/tools/context.ts +2 -0
  316. package/src/tools/default-renderer.ts +139 -0
  317. package/src/tools/essential-tools.ts +1 -0
  318. package/src/tools/image-gen.ts +20 -28
  319. package/src/tools/image-providers.ts +50 -0
  320. package/src/tools/index.ts +14 -0
  321. package/src/tools/path-utils.ts +49 -2
  322. package/src/tools/read.ts +156 -88
  323. package/src/tools/renderers.ts +9 -1
  324. package/src/tools/todo.ts +101 -11
  325. package/src/tools/vibe.ts +1 -2
  326. package/src/tools/write.ts +51 -1
  327. package/src/tools/xdev.ts +121 -29
  328. package/src/tts/streaming-player.ts +82 -283
  329. package/src/tts/vocalizer.ts +18 -1
  330. package/src/utils/clipboard.ts +1 -30
  331. package/src/utils/jj.ts +125 -4
  332. package/src/utils/lang-from-path.ts +7 -0
  333. package/src/utils/mac-file-urls.applescript +37 -0
  334. package/src/utils/title-generator.ts +137 -1
  335. package/src/utils/tool-choice.ts +14 -0
  336. package/src/utils/tools-manager.ts +1 -19
  337. package/src/vibe/runtime.ts +852 -72
  338. package/src/web/search/index.ts +3 -6
  339. package/src/web/search/provider.ts +31 -16
  340. package/src/web/search/providers/xai.ts +7 -1
  341. package/src/web/search/types.ts +3 -0
  342. package/dist/types/stt/recorder.d.ts +0 -30
  343. package/dist/types/stt/transcriber.d.ts +0 -14
  344. package/dist/types/stt/wav.d.ts +0 -29
  345. package/dist/types/tts/player.d.ts +0 -32
  346. package/src/stt/recorder.ts +0 -551
  347. package/src/stt/transcriber.ts +0 -60
  348. package/src/stt/wav.ts +0 -173
  349. package/src/tts/player.ts +0 -137
@@ -49,7 +49,14 @@ import {
49
49
  } from "./conflict-detect";
50
50
  import { invalidateFsScanAfterWrite } from "./fs-cache-invalidation";
51
51
  import { type OutputMeta, outputMeta } from "./output-meta";
52
- import { formatPathRelativeToCwd, isInternalUrlPath, pathTargetsSsh, peelWriteUrlSelector } from "./path-utils";
52
+ import {
53
+ formatPathRelativeToCwd,
54
+ isInternalUrlPath,
55
+ pathTargetsSsh,
56
+ peelWriteUrlSelector,
57
+ probeLiteralPathExists,
58
+ splitPathAndSel,
59
+ } from "./path-utils";
53
60
  import { enforcePlanModeWrite, resolvePlanPath, unwrapHashlineHeaderPath } from "./plan-mode-guard";
54
61
  import {
55
62
  cachedRenderedString,
@@ -116,6 +123,43 @@ function assertWriteTargetAddressable(target: string, router: InternalUrlRouter)
116
123
  );
117
124
  }
118
125
 
126
+ /**
127
+ * Fail closed when a local write target looks like a mis-dispatched read.
128
+ *
129
+ * A read-only step that selects `write` instead of `read` passes the full read
130
+ * expression (`src/foo.tsx:1-260:raw`) as the target. Because a literal colon
131
+ * filename is legal on POSIX (issue #4618), that request otherwise resolves to
132
+ * filesystem creation and reports success, leaving a stray zero-byte file the
133
+ * model cannot recover from — the local analogue of the `xd://` near-miss guard
134
+ * ({@link assertWriteTargetAddressable}, issue #6123).
135
+ *
136
+ * Fires only on the high-confidence combination the report identifies: the tail
137
+ * parses as a read-tool selector, the literal target is missing, and no content
138
+ * was supplied. Non-empty content is the escape hatch — it is never blocked, so
139
+ * a deliberate write to a selector-shaped filename still succeeds. An existing
140
+ * literal path or an ambiguous stat (`"unknown"`: EACCES, transient I/O) also
141
+ * passes through so a real file is never shadowed by the guard.
142
+ */
143
+ function readSelectorForEmptyWrite(target: string, content: string): string | undefined {
144
+ if (content.length > 0) return undefined;
145
+ return splitPathAndSel(target).sel;
146
+ }
147
+
148
+ function throwReadSelectorMisfire(target: string, sel: string): never {
149
+ throw new ToolError(
150
+ `write target '${target}' ends with a read-tool selector ':${sel}' and no such file exists — refusing to create a literal file by that name. ` +
151
+ `If you meant to read it, use read({ path: "${target}" }). ` +
152
+ `If you truly intend to create this file, pass its contents in \`content\` (a non-empty write is never blocked).`,
153
+ );
154
+ }
155
+
156
+ async function assertNotReadSelectorMisfire(target: string, content: string, cwd: string): Promise<void> {
157
+ const sel = readSelectorForEmptyWrite(target, content);
158
+ if (sel === undefined) return;
159
+ if ((await probeLiteralPathExists(target, cwd)) !== "missing") return;
160
+ throwReadSelectorMisfire(target, sel);
161
+ }
162
+
119
163
  const BULK_DIRECTIVE_RE = /^#?(\d+)\s*[:=]\s*(@ours|@theirs|@base|@both)$/;
120
164
  /**
121
165
  * The head of a per-id directive line — `<id>:` / `<id>=` (optionally `#`-prefixed),
@@ -576,6 +620,11 @@ export class WriteTool implements AgentTool<typeof writeSchema, WriteToolDetails
576
620
  throw new ToolError(error instanceof Error ? error.message : String(error));
577
621
  }
578
622
  }
623
+ const writeTarget = `${resolvedArchivePath.archivePath}:${resolvedArchivePath.archiveSubPath}`;
624
+ const sel = readSelectorForEmptyWrite(writeTarget, content);
625
+ if (sel !== undefined && !entries.has(resolvedArchivePath.archiveSubPath)) {
626
+ throwReadSelectorMisfire(writeTarget, sel);
627
+ }
579
628
  entries.set(resolvedArchivePath.archiveSubPath, content);
580
629
 
581
630
  try {
@@ -1159,6 +1208,7 @@ export class WriteTool implements AgentTool<typeof writeSchema, WriteToolDetails
1159
1208
  return sqliteResult;
1160
1209
  }
1161
1210
 
1211
+ await assertNotReadSelectorMisfire(path, cleanContent, this.session.cwd);
1162
1212
  enforcePlanModeWrite(this.session, path, { op: "create" });
1163
1213
  const absolutePath = resolvePlanPath(this.session, path);
1164
1214
  const batchRequest = getLspBatchRequest(context?.toolCall);
package/src/tools/xdev.ts CHANGED
@@ -30,10 +30,11 @@ import { parseStreamingJson } from "@oh-my-pi/pi-utils";
30
30
  import type { RenderResultOptions } from "../extensibility/custom-tools/types";
31
31
  import { XD_URL_PREFIX } from "../internal-urls/xd-protocol";
32
32
  import type { Theme } from "../modes/theme/theme";
33
+ import { renderDefaultToolExecution } from "./default-renderer";
33
34
  import type { Tool } from "./index";
34
35
  import { replaceTabs } from "./render-utils";
35
36
  import type { ToolRenderer } from "./renderers";
36
- import { ToolError } from "./tool-errors";
37
+ import { renderError, ToolAbortError, ToolError } from "./tool-errors";
37
38
 
38
39
  /**
39
40
  * Discoverable built-ins that must stay top-level even when xdev mounting is
@@ -60,6 +61,9 @@ export const XDEV_KEEP_TOP_LEVEL: Record<string, true> = {
60
61
  */
61
62
  export const XDEV_TRANSPORT_TOOLS: Record<string, true> = { read: true, write: true };
62
63
 
64
+ /** Controls which mounted-device docs are inlined into the system prompt. */
65
+ export type XdevDocsMode = "inline" | "builtins" | "catalog";
66
+
63
67
  /**
64
68
  * Whether an enabled tool is presented under `xd://` (rather than top-level)
65
69
  * while the `xd://` transport is active. Discoverable tools mount unless they
@@ -170,6 +174,28 @@ function toolSummary(inst: Tool): string {
170
174
  return firstLine?.trim() ?? inst.label ?? inst.name;
171
175
  }
172
176
 
177
+ function promptCatalogSummary(inst: Tool, maxLength?: number): string {
178
+ const summary =
179
+ toolSummary(inst)
180
+ .split("\n")
181
+ .find(line => line.trim().length > 0)
182
+ ?.trim() ?? inst.name;
183
+ if (maxLength === undefined || summary.length <= maxLength) return summary;
184
+ return `${summary.slice(0, maxLength).trimEnd()}…`;
185
+ }
186
+
187
+ /** Compile the `tools.xdevInlineDevices` allowlist once per render, dropping
188
+ * non-string entries so malformed user config cannot break prompt builds. */
189
+ function compileInlineGlobs(patterns: readonly string[]): Bun.Glob[] {
190
+ if (!Array.isArray(patterns)) return [];
191
+ const globs: Bun.Glob[] = [];
192
+ for (const pattern of patterns) {
193
+ if (typeof pattern !== "string" || pattern.length === 0) continue;
194
+ globs.push(new Bun.Glob(pattern));
195
+ }
196
+ return globs;
197
+ }
198
+
173
199
  /** Decode the (possibly partially streamed) inner args JSON string into display args. */
174
200
  function decodeInnerArgs(raw: unknown): Record<string, unknown> {
175
201
  if (typeof raw !== "string" || raw.length === 0) return {};
@@ -230,7 +256,13 @@ export class XdevRegistry {
230
256
 
231
257
  /** `{name, summary}` pairs for prompt templates and /tools display. */
232
258
  entries(): Array<{ name: string; summary: string }> {
233
- return this.list().map(tool => ({ name: tool.name, summary: toolSummary(tool) }));
259
+ return this.list().map(tool => ({
260
+ name: tool.name,
261
+ summary: promptCatalogSummary(
262
+ tool,
263
+ this.#dynamic.has(tool.name) ? XdevRegistry.EXTERNAL_DESCRIPTION_CAP : undefined,
264
+ ),
265
+ }));
234
266
  }
235
267
 
236
268
  /** `read xd://` listing with one device per line. */
@@ -273,11 +305,16 @@ export class XdevRegistry {
273
305
  * Dynamic mounts embed at most {@link EXTERNAL_DESCRIPTION_CAP} description
274
306
  * chars (schema always intact); `read xd://<tool>` returns the full text.
275
307
  */
276
- docsAll(): string {
308
+ docsAll(mode: XdevDocsMode = "inline", inlinePatterns: readonly string[] = []): string {
277
309
  const sections: string[] = [];
278
310
  const overflow: Tool[] = [];
311
+ const inlineGlobs = compileInlineGlobs(inlinePatterns);
279
312
  let used = 0;
280
313
  for (const tool of this.list()) {
314
+ if (!this.#shouldInline(tool, mode, inlineGlobs)) {
315
+ overflow.push(tool);
316
+ continue;
317
+ }
281
318
  const descriptionCap = this.#dynamic.has(tool.name) ? XdevRegistry.EXTERNAL_DESCRIPTION_CAP : undefined;
282
319
  const docs = renderDocs(tool, "##", descriptionCap);
283
320
  if (docs.length > XdevRegistry.DOCS_PER_DEVICE_CAP || used + docs.length > XdevRegistry.DOCS_TOTAL_BUDGET) {
@@ -291,7 +328,10 @@ export class XdevRegistry {
291
328
  sections.push(
292
329
  [
293
330
  "## Additional devices (docs on demand)",
294
- ...overflow.map(tool => `- ${XD_URL_PREFIX}${tool.name} — ${toolSummary(tool)}`),
331
+ ...overflow.map(tool => {
332
+ const maxLength = this.#dynamic.has(tool.name) ? XdevRegistry.EXTERNAL_DESCRIPTION_CAP : undefined;
333
+ return `- ${XD_URL_PREFIX}${tool.name} — ${promptCatalogSummary(tool, maxLength)}`;
334
+ }),
295
335
  "",
296
336
  `Read ${XD_URL_PREFIX}<tool> for full docs + JSON schema before first use.`,
297
337
  ].join("\n"),
@@ -300,6 +340,31 @@ export class XdevRegistry {
300
340
  return sections.join("\n\n");
301
341
  }
302
342
 
343
+ /** Docs for selected mounted devices under the configured prompt-doc policy. */
344
+ docsFor(names: Iterable<string>, mode: XdevDocsMode, inlinePatterns: readonly string[] = []): string {
345
+ const sections: string[] = [];
346
+ const inlineGlobs = compileInlineGlobs(inlinePatterns);
347
+ let used = 0;
348
+ for (const name of names) {
349
+ const tool = this.get(name);
350
+ if (!tool || !this.#shouldInline(tool, mode, inlineGlobs)) continue;
351
+ const descriptionCap = this.#dynamic.has(tool.name) ? XdevRegistry.EXTERNAL_DESCRIPTION_CAP : undefined;
352
+ const docs = renderDocs(tool, "##", descriptionCap);
353
+ if (docs.length > XdevRegistry.DOCS_PER_DEVICE_CAP || used + docs.length > XdevRegistry.DOCS_TOTAL_BUDGET)
354
+ continue;
355
+ used += docs.length;
356
+ sections.push(docs);
357
+ }
358
+ return sections.join("\n\n");
359
+ }
360
+
361
+ #shouldInline(tool: Tool, mode: XdevDocsMode, inlineGlobs: readonly Bun.Glob[]): boolean {
362
+ return (
363
+ mode !== "catalog" &&
364
+ (mode === "inline" || this.#builtins.has(tool.name) || inlineGlobs.some(glob => glob.match(tool.name)))
365
+ );
366
+ }
367
+
303
368
  #resolve(name: string): Tool {
304
369
  const inst = this.get(name);
305
370
  if (!inst) {
@@ -325,28 +390,45 @@ export class XdevRegistry {
325
390
  onUpdate?: AgentToolUpdateCallback,
326
391
  context?: AgentToolContext,
327
392
  ): Promise<{ result: AgentToolResult<unknown>; xdev: XdevDispatch }> {
328
- const inst = this.#resolve(name);
393
+ let xdev: XdevDispatch = { tool: name, mode: "execute" };
394
+ try {
395
+ const inst = this.#resolve(name);
396
+
397
+ if (HELP_CONTENT_RE.test(content)) {
398
+ return {
399
+ result: { content: [{ type: "text", text: renderDocs(inst) }] },
400
+ xdev: { tool: name, mode: "help" },
401
+ };
402
+ }
329
403
 
330
- if (HELP_CONTENT_RE.test(content)) {
404
+ const validated = parseDeviceArgs(inst as AiTool, content, toolCallId, () => renderDocs(inst));
405
+ xdev = { ...xdev, args: validated };
406
+ const innerOnUpdate: AgentToolUpdateCallback | undefined = onUpdate
407
+ ? partial =>
408
+ onUpdate({
409
+ content: partial.content,
410
+ details: { xdev: { ...xdev, inner: partial.details } },
411
+ isError: partial.isError,
412
+ })
413
+ : undefined;
414
+ const result = await inst.execute(toolCallId, validated as never, signal, innerOnUpdate, context);
415
+ return { result, xdev: { ...xdev, inner: result.details } };
416
+ } catch (error) {
417
+ if (
418
+ error instanceof ToolAbortError ||
419
+ signal?.aborted ||
420
+ (error instanceof Error && error.name === "AbortError")
421
+ ) {
422
+ throw error;
423
+ }
331
424
  return {
332
- result: { content: [{ type: "text", text: renderDocs(inst) }] },
333
- xdev: { tool: name, mode: "help" },
425
+ result: {
426
+ content: [{ type: "text", text: renderError(error) }],
427
+ isError: true,
428
+ },
429
+ xdev,
334
430
  };
335
431
  }
336
-
337
- const validated = parseDeviceArgs(inst as AiTool, content, toolCallId, () => renderDocs(inst));
338
-
339
- const xdevBase: XdevDispatch = { tool: name, mode: "execute", args: validated };
340
- const innerOnUpdate: AgentToolUpdateCallback | undefined = onUpdate
341
- ? partial =>
342
- onUpdate({
343
- content: partial.content,
344
- details: { xdev: { ...xdevBase, inner: partial.details } },
345
- isError: partial.isError,
346
- })
347
- : undefined;
348
- const result = await inst.execute(toolCallId, validated as never, signal, innerOnUpdate, context);
349
- return { result, xdev: { ...xdevBase, inner: result.details } };
350
432
  }
351
433
  }
352
434
 
@@ -359,9 +441,8 @@ export class XdevRegistry {
359
441
  * map keyed by name. */
360
442
  function resolveDeviceRenderer(
361
443
  name: string,
362
- resolveMounted: ((name: string) => Tool | undefined) | undefined,
444
+ mounted: Tool | undefined,
363
445
  ): Pick<ToolRenderer, "renderCall" | "renderResult" | "mergeCallAndResult"> | undefined {
364
- const mounted = resolveMounted?.(name);
365
446
  if (mounted && (mounted.renderCall || mounted.renderResult)) {
366
447
  // A mounted AgentTool exposes the same renderCall/renderResult/mergeCallAndResult
367
448
  // surface as a static ToolRenderer; only the parameter generics differ, so unify
@@ -383,11 +464,13 @@ export function renderXdevCall(
383
464
  theme: Theme,
384
465
  resolveMounted?: (name: string) => Tool | undefined,
385
466
  ): Component | undefined {
386
- const renderer = resolveDeviceRenderer(name, resolveMounted);
467
+ const mounted = resolveMounted?.(name);
468
+ const renderer = resolveDeviceRenderer(name, mounted);
469
+ const args = decodeInnerArgs(content);
387
470
  if (renderer?.renderCall) {
388
- return renderer.renderCall(decodeInnerArgs(content), options, theme);
471
+ return renderer.renderCall(args, options, theme);
389
472
  }
390
- return new Text(theme.fg("toolTitle", theme.bold(`${XD_URL_PREFIX}${name}`)), 0, 0);
473
+ return renderDefaultToolExecution({ label: mounted?.label ?? name, args, options }, theme);
391
474
  }
392
475
 
393
476
  /** Forward an `xd://` dispatch result to the mounted tool's renderer. */
@@ -405,7 +488,8 @@ export function renderXdevResult(
405
488
  if (dispatch.mode === "help") {
406
489
  return text ? new Text(theme.fg("toolOutput", replaceTabs(text)), 0, 0) : undefined;
407
490
  }
408
- const renderer = resolveDeviceRenderer(dispatch.tool, resolveMounted);
491
+ const mounted = resolveMounted?.(dispatch.tool);
492
+ const renderer = resolveDeviceRenderer(dispatch.tool, mounted);
409
493
  const innerResult = { content: result.content, details: dispatch.inner, isError: result.isError };
410
494
  if (renderer?.renderResult) {
411
495
  const parts: Component[] = [];
@@ -424,5 +508,13 @@ export function renderXdevResult(
424
508
  return box;
425
509
  }
426
510
  }
427
- return text ? new Text(theme.fg("toolOutput", replaceTabs(text)), 0, 0) : undefined;
511
+ return renderDefaultToolExecution(
512
+ {
513
+ label: mounted?.label ?? dispatch.tool,
514
+ args: dispatch.args ?? {},
515
+ result: { output: text, isError: result.isError },
516
+ options,
517
+ },
518
+ theme,
519
+ );
428
520
  }