@draht/coding-agent 2026.3.14 → 2026.3.25

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 (345) hide show
  1. package/README.md +45 -30
  2. package/dist/bun/cli.d.ts +3 -0
  3. package/dist/bun/cli.d.ts.map +1 -0
  4. package/dist/bun/cli.js +7 -0
  5. package/dist/bun/cli.js.map +1 -0
  6. package/dist/bun/register-bedrock.d.ts +2 -0
  7. package/dist/bun/register-bedrock.d.ts.map +1 -0
  8. package/dist/bun/register-bedrock.js +4 -0
  9. package/dist/bun/register-bedrock.js.map +1 -0
  10. package/dist/cli/args.d.ts +1 -0
  11. package/dist/cli/args.d.ts.map +1 -1
  12. package/dist/cli/args.js +11 -6
  13. package/dist/cli/args.js.map +1 -1
  14. package/dist/cli/file-processor.d.ts.map +1 -1
  15. package/dist/cli/file-processor.js +4 -0
  16. package/dist/cli/file-processor.js.map +1 -1
  17. package/dist/cli/initial-message.d.ts +18 -0
  18. package/dist/cli/initial-message.d.ts.map +1 -0
  19. package/dist/cli/initial-message.js +22 -0
  20. package/dist/cli/initial-message.js.map +1 -0
  21. package/dist/cli/session-picker.d.ts.map +1 -1
  22. package/dist/cli/session-picker.js +2 -1
  23. package/dist/cli/session-picker.js.map +1 -1
  24. package/dist/cli.d.ts.map +1 -1
  25. package/dist/cli.js +1 -3
  26. package/dist/cli.js.map +1 -1
  27. package/dist/core/agent-session.d.ts +38 -5
  28. package/dist/core/agent-session.d.ts.map +1 -1
  29. package/dist/core/agent-session.js +201 -73
  30. package/dist/core/agent-session.js.map +1 -1
  31. package/dist/core/bash-executor.d.ts +6 -7
  32. package/dist/core/bash-executor.d.ts.map +1 -1
  33. package/dist/core/bash-executor.js +8 -107
  34. package/dist/core/bash-executor.js.map +1 -1
  35. package/dist/core/compaction/branch-summarization.d.ts.map +1 -1
  36. package/dist/core/compaction/branch-summarization.js +1 -0
  37. package/dist/core/compaction/branch-summarization.js.map +1 -1
  38. package/dist/core/compaction/compaction.d.ts.map +1 -1
  39. package/dist/core/compaction/compaction.js +2 -0
  40. package/dist/core/compaction/compaction.js.map +1 -1
  41. package/dist/core/exec.d.ts.map +1 -1
  42. package/dist/core/exec.js +7 -3
  43. package/dist/core/exec.js.map +1 -1
  44. package/dist/core/export-html/index.d.ts +2 -2
  45. package/dist/core/export-html/index.d.ts.map +1 -1
  46. package/dist/core/export-html/index.js +7 -6
  47. package/dist/core/export-html/index.js.map +1 -1
  48. package/dist/core/export-html/template.css +43 -13
  49. package/dist/core/export-html/template.html +1 -0
  50. package/dist/core/export-html/template.js +107 -0
  51. package/dist/core/export-html/tool-renderer.d.ts +2 -2
  52. package/dist/core/export-html/tool-renderer.d.ts.map +1 -1
  53. package/dist/core/export-html/tool-renderer.js +41 -16
  54. package/dist/core/export-html/tool-renderer.js.map +1 -1
  55. package/dist/core/extensions/index.d.ts +4 -3
  56. package/dist/core/extensions/index.d.ts.map +1 -1
  57. package/dist/core/extensions/index.js +1 -1
  58. package/dist/core/extensions/index.js.map +1 -1
  59. package/dist/core/extensions/loader.d.ts.map +1 -1
  60. package/dist/core/extensions/loader.js +16 -6
  61. package/dist/core/extensions/loader.js.map +1 -1
  62. package/dist/core/extensions/runner.d.ts +9 -9
  63. package/dist/core/extensions/runner.d.ts.map +1 -1
  64. package/dist/core/extensions/runner.js +89 -71
  65. package/dist/core/extensions/runner.js.map +1 -1
  66. package/dist/core/extensions/types.d.ts +49 -13
  67. package/dist/core/extensions/types.d.ts.map +1 -1
  68. package/dist/core/extensions/types.js.map +1 -1
  69. package/dist/core/extensions/wrapper.d.ts +4 -11
  70. package/dist/core/extensions/wrapper.d.ts.map +1 -1
  71. package/dist/core/extensions/wrapper.js +6 -86
  72. package/dist/core/extensions/wrapper.js.map +1 -1
  73. package/dist/core/footer-data-provider.d.ts +13 -1
  74. package/dist/core/footer-data-provider.d.ts.map +1 -1
  75. package/dist/core/footer-data-provider.js +155 -37
  76. package/dist/core/footer-data-provider.js.map +1 -1
  77. package/dist/core/index.d.ts +2 -1
  78. package/dist/core/index.d.ts.map +1 -1
  79. package/dist/core/index.js +2 -1
  80. package/dist/core/index.js.map +1 -1
  81. package/dist/core/keybindings.d.ts +270 -50
  82. package/dist/core/keybindings.d.ts.map +1 -1
  83. package/dist/core/keybindings.js +222 -134
  84. package/dist/core/keybindings.js.map +1 -1
  85. package/dist/core/model-registry.d.ts +1 -0
  86. package/dist/core/model-registry.d.ts.map +1 -1
  87. package/dist/core/model-registry.js +49 -23
  88. package/dist/core/model-registry.js.map +1 -1
  89. package/dist/core/model-resolver.d.ts +6 -0
  90. package/dist/core/model-resolver.d.ts.map +1 -1
  91. package/dist/core/model-resolver.js +41 -17
  92. package/dist/core/model-resolver.js.map +1 -1
  93. package/dist/core/output-guard.d.ts +6 -0
  94. package/dist/core/output-guard.d.ts.map +1 -0
  95. package/dist/core/output-guard.js +59 -0
  96. package/dist/core/output-guard.js.map +1 -0
  97. package/dist/core/package-manager.d.ts +22 -1
  98. package/dist/core/package-manager.d.ts.map +1 -1
  99. package/dist/core/package-manager.js +373 -53
  100. package/dist/core/package-manager.js.map +1 -1
  101. package/dist/core/prompt-templates.d.ts +2 -1
  102. package/dist/core/prompt-templates.d.ts.map +1 -1
  103. package/dist/core/prompt-templates.js +39 -39
  104. package/dist/core/prompt-templates.js.map +1 -1
  105. package/dist/core/resolve-config-value.d.ts.map +1 -1
  106. package/dist/core/resolve-config-value.js +43 -8
  107. package/dist/core/resolve-config-value.js.map +1 -1
  108. package/dist/core/resource-loader.d.ts +6 -7
  109. package/dist/core/resource-loader.d.ts.map +1 -1
  110. package/dist/core/resource-loader.js +141 -118
  111. package/dist/core/resource-loader.js.map +1 -1
  112. package/dist/core/sdk.d.ts +3 -3
  113. package/dist/core/sdk.d.ts.map +1 -1
  114. package/dist/core/sdk.js +4 -4
  115. package/dist/core/sdk.js.map +1 -1
  116. package/dist/core/session-manager.d.ts +6 -0
  117. package/dist/core/session-manager.d.ts.map +1 -1
  118. package/dist/core/session-manager.js +9 -10
  119. package/dist/core/session-manager.js.map +1 -1
  120. package/dist/core/settings-manager.d.ts +3 -0
  121. package/dist/core/settings-manager.d.ts.map +1 -1
  122. package/dist/core/settings-manager.js +8 -0
  123. package/dist/core/settings-manager.js.map +1 -1
  124. package/dist/core/skills.d.ts +5 -3
  125. package/dist/core/skills.d.ts.map +1 -1
  126. package/dist/core/skills.js +54 -9
  127. package/dist/core/skills.js.map +1 -1
  128. package/dist/core/slash-commands.d.ts +2 -3
  129. package/dist/core/slash-commands.d.ts.map +1 -1
  130. package/dist/core/slash-commands.js +3 -2
  131. package/dist/core/slash-commands.js.map +1 -1
  132. package/dist/core/source-info.d.ts +18 -0
  133. package/dist/core/source-info.d.ts.map +1 -0
  134. package/dist/core/source-info.js +19 -0
  135. package/dist/core/source-info.js.map +1 -0
  136. package/dist/core/system-prompt.d.ts.map +1 -1
  137. package/dist/core/system-prompt.js +17 -60
  138. package/dist/core/system-prompt.js.map +1 -1
  139. package/dist/core/tools/bash.d.ts +24 -6
  140. package/dist/core/tools/bash.d.ts.map +1 -1
  141. package/dist/core/tools/bash.js +210 -110
  142. package/dist/core/tools/bash.js.map +1 -1
  143. package/dist/core/tools/edit-diff.d.ts.map +1 -1
  144. package/dist/core/tools/edit-diff.js +1 -0
  145. package/dist/core/tools/edit-diff.js.map +1 -1
  146. package/dist/core/tools/edit.d.ts +14 -2
  147. package/dist/core/tools/edit.d.ts.map +1 -1
  148. package/dist/core/tools/edit.js +95 -23
  149. package/dist/core/tools/edit.js.map +1 -1
  150. package/dist/core/tools/file-mutation-queue.d.ts +6 -0
  151. package/dist/core/tools/file-mutation-queue.d.ts.map +1 -0
  152. package/dist/core/tools/file-mutation-queue.js +37 -0
  153. package/dist/core/tools/file-mutation-queue.js.map +1 -0
  154. package/dist/core/tools/find.d.ts +11 -4
  155. package/dist/core/tools/find.d.ts.map +1 -1
  156. package/dist/core/tools/find.js +82 -30
  157. package/dist/core/tools/find.js.map +1 -1
  158. package/dist/core/tools/grep.d.ts +15 -4
  159. package/dist/core/tools/grep.d.ts.map +1 -1
  160. package/dist/core/tools/grep.js +83 -29
  161. package/dist/core/tools/grep.js.map +1 -1
  162. package/dist/core/tools/index.d.ts +58 -19
  163. package/dist/core/tools/index.d.ts.map +1 -1
  164. package/dist/core/tools/index.js +51 -26
  165. package/dist/core/tools/index.js.map +1 -1
  166. package/dist/core/tools/ls.d.ts +9 -3
  167. package/dist/core/tools/ls.d.ts.map +1 -1
  168. package/dist/core/tools/ls.js +67 -13
  169. package/dist/core/tools/ls.js.map +1 -1
  170. package/dist/core/tools/read.d.ts +10 -3
  171. package/dist/core/tools/read.d.ts.map +1 -1
  172. package/dist/core/tools/read.js +110 -51
  173. package/dist/core/tools/read.js.map +1 -1
  174. package/dist/core/tools/render-utils.d.ts +21 -0
  175. package/dist/core/tools/render-utils.d.ts.map +1 -0
  176. package/dist/core/tools/render-utils.js +49 -0
  177. package/dist/core/tools/render-utils.js.map +1 -0
  178. package/dist/core/tools/tool-definition-wrapper.d.ts +14 -0
  179. package/dist/core/tools/tool-definition-wrapper.d.ts.map +1 -0
  180. package/dist/core/tools/tool-definition-wrapper.js +30 -0
  181. package/dist/core/tools/tool-definition-wrapper.js.map +1 -0
  182. package/dist/core/tools/write.d.ts +9 -3
  183. package/dist/core/tools/write.d.ts.map +1 -1
  184. package/dist/core/tools/write.js +168 -30
  185. package/dist/core/tools/write.js.map +1 -1
  186. package/dist/index.d.ts +5 -4
  187. package/dist/index.d.ts.map +1 -1
  188. package/dist/index.js +4 -3
  189. package/dist/index.js.map +1 -1
  190. package/dist/main.d.ts.map +1 -1
  191. package/dist/main.js +105 -226
  192. package/dist/main.js.map +1 -1
  193. package/dist/modes/interactive/components/bash-execution.d.ts +0 -1
  194. package/dist/modes/interactive/components/bash-execution.d.ts.map +1 -1
  195. package/dist/modes/interactive/components/bash-execution.js +22 -9
  196. package/dist/modes/interactive/components/bash-execution.js.map +1 -1
  197. package/dist/modes/interactive/components/bordered-loader.d.ts.map +1 -1
  198. package/dist/modes/interactive/components/bordered-loader.js +1 -1
  199. package/dist/modes/interactive/components/bordered-loader.js.map +1 -1
  200. package/dist/modes/interactive/components/branch-summary-message.d.ts.map +1 -1
  201. package/dist/modes/interactive/components/branch-summary-message.js +2 -2
  202. package/dist/modes/interactive/components/branch-summary-message.js.map +1 -1
  203. package/dist/modes/interactive/components/compaction-summary-message.d.ts.map +1 -1
  204. package/dist/modes/interactive/components/compaction-summary-message.js +2 -2
  205. package/dist/modes/interactive/components/compaction-summary-message.js.map +1 -1
  206. package/dist/modes/interactive/components/config-selector.d.ts.map +1 -1
  207. package/dist/modes/interactive/components/config-selector.js +8 -8
  208. package/dist/modes/interactive/components/config-selector.js.map +1 -1
  209. package/dist/modes/interactive/components/custom-editor.d.ts +3 -3
  210. package/dist/modes/interactive/components/custom-editor.d.ts.map +1 -1
  211. package/dist/modes/interactive/components/custom-editor.js +6 -6
  212. package/dist/modes/interactive/components/custom-editor.js.map +1 -1
  213. package/dist/modes/interactive/components/extension-editor.d.ts.map +1 -1
  214. package/dist/modes/interactive/components/extension-editor.js +9 -9
  215. package/dist/modes/interactive/components/extension-editor.js.map +1 -1
  216. package/dist/modes/interactive/components/extension-input.d.ts.map +1 -1
  217. package/dist/modes/interactive/components/extension-input.js +5 -5
  218. package/dist/modes/interactive/components/extension-input.js.map +1 -1
  219. package/dist/modes/interactive/components/extension-selector.d.ts.map +1 -1
  220. package/dist/modes/interactive/components/extension-selector.js +8 -8
  221. package/dist/modes/interactive/components/extension-selector.js.map +1 -1
  222. package/dist/modes/interactive/components/index.d.ts +1 -1
  223. package/dist/modes/interactive/components/index.d.ts.map +1 -1
  224. package/dist/modes/interactive/components/index.js +1 -1
  225. package/dist/modes/interactive/components/index.js.map +1 -1
  226. package/dist/modes/interactive/components/keybinding-hints.d.ts +3 -36
  227. package/dist/modes/interactive/components/keybinding-hints.d.ts.map +1 -1
  228. package/dist/modes/interactive/components/keybinding-hints.js +5 -44
  229. package/dist/modes/interactive/components/keybinding-hints.js.map +1 -1
  230. package/dist/modes/interactive/components/login-dialog.d.ts.map +1 -1
  231. package/dist/modes/interactive/components/login-dialog.js +6 -6
  232. package/dist/modes/interactive/components/login-dialog.js.map +1 -1
  233. package/dist/modes/interactive/components/model-selector.d.ts.map +1 -1
  234. package/dist/modes/interactive/components/model-selector.js +13 -9
  235. package/dist/modes/interactive/components/model-selector.js.map +1 -1
  236. package/dist/modes/interactive/components/oauth-selector.d.ts.map +1 -1
  237. package/dist/modes/interactive/components/oauth-selector.js +6 -6
  238. package/dist/modes/interactive/components/oauth-selector.js.map +1 -1
  239. package/dist/modes/interactive/components/scoped-models-selector.d.ts.map +1 -1
  240. package/dist/modes/interactive/components/scoped-models-selector.js +4 -4
  241. package/dist/modes/interactive/components/scoped-models-selector.js.map +1 -1
  242. package/dist/modes/interactive/components/session-selector.d.ts.map +1 -1
  243. package/dist/modes/interactive/components/session-selector.js +32 -35
  244. package/dist/modes/interactive/components/session-selector.js.map +1 -1
  245. package/dist/modes/interactive/components/settings-selector.d.ts.map +1 -1
  246. package/dist/modes/interactive/components/settings-selector.js +5 -1
  247. package/dist/modes/interactive/components/settings-selector.js.map +1 -1
  248. package/dist/modes/interactive/components/show-images-selector.d.ts.map +1 -1
  249. package/dist/modes/interactive/components/show-images-selector.js +5 -1
  250. package/dist/modes/interactive/components/show-images-selector.js.map +1 -1
  251. package/dist/modes/interactive/components/skill-invocation-message.d.ts.map +1 -1
  252. package/dist/modes/interactive/components/skill-invocation-message.js +2 -2
  253. package/dist/modes/interactive/components/skill-invocation-message.js.map +1 -1
  254. package/dist/modes/interactive/components/theme-selector.d.ts.map +1 -1
  255. package/dist/modes/interactive/components/theme-selector.js +5 -1
  256. package/dist/modes/interactive/components/theme-selector.js.map +1 -1
  257. package/dist/modes/interactive/components/thinking-selector.d.ts.map +1 -1
  258. package/dist/modes/interactive/components/thinking-selector.js +5 -1
  259. package/dist/modes/interactive/components/thinking-selector.js.map +1 -1
  260. package/dist/modes/interactive/components/tool-execution.d.ts +16 -34
  261. package/dist/modes/interactive/components/tool-execution.d.ts.map +1 -1
  262. package/dist/modes/interactive/components/tool-execution.js +128 -636
  263. package/dist/modes/interactive/components/tool-execution.js.map +1 -1
  264. package/dist/modes/interactive/components/tree-selector.d.ts.map +1 -1
  265. package/dist/modes/interactive/components/tree-selector.js +27 -16
  266. package/dist/modes/interactive/components/tree-selector.js.map +1 -1
  267. package/dist/modes/interactive/components/user-message-selector.d.ts.map +1 -1
  268. package/dist/modes/interactive/components/user-message-selector.js +6 -6
  269. package/dist/modes/interactive/components/user-message-selector.js.map +1 -1
  270. package/dist/modes/interactive/components/user-message.d.ts.map +1 -1
  271. package/dist/modes/interactive/components/user-message.js +2 -1
  272. package/dist/modes/interactive/components/user-message.js.map +1 -1
  273. package/dist/modes/interactive/interactive-mode.d.ts +7 -11
  274. package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
  275. package/dist/modes/interactive/interactive-mode.js +353 -214
  276. package/dist/modes/interactive/interactive-mode.js.map +1 -1
  277. package/dist/modes/interactive/theme/theme.d.ts +3 -0
  278. package/dist/modes/interactive/theme/theme.d.ts.map +1 -1
  279. package/dist/modes/interactive/theme/theme.js +63 -37
  280. package/dist/modes/interactive/theme/theme.js.map +1 -1
  281. package/dist/modes/print-mode.d.ts.map +1 -1
  282. package/dist/modes/print-mode.js +5 -11
  283. package/dist/modes/print-mode.js.map +1 -1
  284. package/dist/modes/rpc/rpc-mode.d.ts.map +1 -1
  285. package/dist/modes/rpc/rpc-mode.js +27 -17
  286. package/dist/modes/rpc/rpc-mode.js.map +1 -1
  287. package/dist/modes/rpc/rpc-types.d.ts +3 -4
  288. package/dist/modes/rpc/rpc-types.d.ts.map +1 -1
  289. package/dist/modes/rpc/rpc-types.js.map +1 -1
  290. package/dist/utils/child-process.d.ts +11 -0
  291. package/dist/utils/child-process.d.ts.map +1 -0
  292. package/dist/utils/child-process.js +78 -0
  293. package/dist/utils/child-process.js.map +1 -0
  294. package/dist/utils/clipboard-image.d.ts.map +1 -1
  295. package/dist/utils/clipboard-image.js +94 -11
  296. package/dist/utils/clipboard-image.js.map +1 -1
  297. package/dist/utils/clipboard-native.d.ts +1 -0
  298. package/dist/utils/clipboard-native.d.ts.map +1 -1
  299. package/dist/utils/clipboard-native.js.map +1 -1
  300. package/dist/utils/clipboard.d.ts +1 -1
  301. package/dist/utils/clipboard.d.ts.map +1 -1
  302. package/dist/utils/clipboard.js +27 -16
  303. package/dist/utils/clipboard.js.map +1 -1
  304. package/dist/utils/exif-orientation.d.ts +5 -0
  305. package/dist/utils/exif-orientation.d.ts.map +1 -0
  306. package/dist/utils/exif-orientation.js +158 -0
  307. package/dist/utils/exif-orientation.js.map +1 -0
  308. package/dist/utils/image-convert.d.ts.map +1 -1
  309. package/dist/utils/image-convert.js +5 -1
  310. package/dist/utils/image-convert.js.map +1 -1
  311. package/dist/utils/image-resize.d.ts +5 -5
  312. package/dist/utils/image-resize.d.ts.map +1 -1
  313. package/dist/utils/image-resize.js +51 -95
  314. package/dist/utils/image-resize.js.map +1 -1
  315. package/dist/utils/tools-manager.d.ts.map +1 -1
  316. package/dist/utils/tools-manager.js +5 -4
  317. package/dist/utils/tools-manager.js.map +1 -1
  318. package/docs/custom-provider.md +6 -2
  319. package/docs/extensions.md +108 -21
  320. package/docs/keybindings.md +103 -112
  321. package/docs/models.md +39 -1
  322. package/docs/packages.md +9 -0
  323. package/docs/providers.md +7 -0
  324. package/docs/rpc.md +15 -6
  325. package/docs/sdk.md +2 -2
  326. package/docs/settings.md +9 -0
  327. package/docs/terminal-setup.md +11 -0
  328. package/docs/tui.md +2 -2
  329. package/examples/extensions/README.md +2 -2
  330. package/examples/extensions/antigravity-image-gen.ts +5 -3
  331. package/examples/extensions/built-in-tool-renderer.ts +8 -8
  332. package/examples/extensions/commands.ts +3 -3
  333. package/examples/extensions/minimal-mode.ts +14 -14
  334. package/examples/extensions/question.ts +2 -2
  335. package/examples/extensions/questionnaire.ts +2 -2
  336. package/examples/extensions/subagent/index.ts +30 -8
  337. package/examples/extensions/titlebar-spinner.ts +2 -2
  338. package/examples/extensions/todo.ts +2 -2
  339. package/examples/extensions/tool-override.ts +9 -7
  340. package/examples/extensions/truncated-tool.ts +8 -5
  341. package/examples/sdk/04-skills.ts +8 -2
  342. package/examples/sdk/08-prompt-templates.ts +8 -2
  343. package/examples/sdk/12-full-control.ts +0 -1
  344. package/examples/sdk/README.md +1 -1
  345. package/package.json +4 -4
package/docs/rpc.md CHANGED
@@ -58,7 +58,7 @@ With images:
58
58
  {"type": "prompt", "message": "New instruction", "streamingBehavior": "steer"}
59
59
  ```
60
60
 
61
- - `"steer"`: Interrupt the agent mid-run. Message is delivered after current tool execution, remaining tools are skipped.
61
+ - `"steer"`: Queue the message while the agent is running. It is delivered after the current assistant turn finishes executing its tool calls, before the next LLM call.
62
62
  - `"followUp"`: Wait until the agent finishes. Message is delivered only when agent stops.
63
63
 
64
64
  If the agent is streaming and no `streamingBehavior` is specified, the command returns an error.
@@ -76,7 +76,7 @@ The `images` field is optional. Each image uses `ImageContent` format: `{"type":
76
76
 
77
77
  #### steer
78
78
 
79
- Queue a steering message to interrupt the agent mid-run. Delivered after current tool execution, remaining tools are skipped. Skill commands and prompt templates are expanded. Extension commands are not allowed (use `prompt` instead).
79
+ Queue a steering message while the agent is running. It is delivered after the current assistant turn finishes executing its tool calls, before the next LLM call. Skill commands and prompt templates are expanded. Extension commands are not allowed (use `prompt` instead).
80
80
 
81
81
  ```json
82
82
  {"type": "steer", "message": "Stop and do this instead"}
@@ -321,8 +321,8 @@ Control how steering messages (from `steer`) are delivered.
321
321
  ```
322
322
 
323
323
  Modes:
324
- - `"all"`: Deliver all steering messages at the next interruption point
325
- - `"one-at-a-time"`: Deliver one steering message per interruption (default)
324
+ - `"all"`: Deliver all steering messages after the current assistant turn finishes executing its tool calls
325
+ - `"one-at-a-time"`: Deliver one steering message per completed assistant turn (default)
326
326
 
327
327
  Response:
328
328
  ```json
@@ -494,7 +494,7 @@ Response:
494
494
 
495
495
  #### get_session_stats
496
496
 
497
- Get token usage and cost statistics.
497
+ Get token usage, cost statistics, and current context window usage.
498
498
 
499
499
  ```json
500
500
  {"type": "get_session_stats"}
@@ -521,11 +521,20 @@ Response:
521
521
  "cacheWrite": 5000,
522
522
  "total": 105000
523
523
  },
524
- "cost": 0.45
524
+ "cost": 0.45,
525
+ "contextUsage": {
526
+ "tokens": 60000,
527
+ "contextWindow": 200000,
528
+ "percent": 30
529
+ }
525
530
  }
526
531
  }
527
532
  ```
528
533
 
534
+ `tokens` contains assistant usage totals for the current session state. `contextUsage` contains the actual current context-window estimate used for compaction and footer display.
535
+
536
+ `contextUsage` is omitted when no model or context window is available. `contextUsage.tokens` and `contextUsage.percent` are `null` immediately after compaction until a fresh post-compaction assistant response provides valid usage data.
537
+
529
538
  #### export_html
530
539
 
531
540
  Export session to an HTML file.
package/docs/sdk.md CHANGED
@@ -78,7 +78,7 @@ interface AgentSession {
78
78
  prompt(text: string, options?: PromptOptions): Promise<void>;
79
79
 
80
80
  // Queue messages during streaming
81
- steer(text: string): Promise<void>; // Interrupt: delivered after current tool, skips remaining
81
+ steer(text: string): Promise<void>; // Queue for delivery after the current assistant turn finishes its tool calls
82
82
  followUp(text: string): Promise<void>; // Wait: delivered only when agent finishes
83
83
 
84
84
  // Subscribe to events (returns unsubscribe function)
@@ -150,7 +150,7 @@ await session.prompt("After you're done, also check X", { streamingBehavior: "fo
150
150
  For explicit queueing during streaming:
151
151
 
152
152
  ```typescript
153
- // Interrupt the agent (delivered after current tool, skips remaining tools)
153
+ // Queue a steering message for delivery after the current assistant turn finishes its tool calls
154
154
  await session.steer("New instruction");
155
155
 
156
156
  // Wait for agent to finish (delivered only when agent stops)
package/docs/settings.md CHANGED
@@ -117,6 +117,15 @@ When a provider requests a retry delay longer than `maxDelayMs` (e.g., Google's
117
117
  |---------|------|---------|-------------|
118
118
  | `shellPath` | string | - | Custom shell path (e.g., for Cygwin on Windows) |
119
119
  | `shellCommandPrefix` | string | - | Prefix for every bash command (e.g., `"shopt -s expand_aliases"`) |
120
+ | `npmCommand` | string[] | - | Command argv used for npm package lookup/install operations (e.g., `["mise", "exec", "node@20", "--", "npm"]`) |
121
+
122
+ ```json
123
+ {
124
+ "npmCommand": ["mise", "exec", "node@20", "--", "npm"]
125
+ }
126
+ ```
127
+
128
+ `npmCommand` is used for all npm package-manager operations, including `npm root -g`, installs, uninstalls, and `npm install` inside git packages. Use argv-style entries exactly as the process should be launched.
120
129
 
121
130
  ### Model Cycling
122
131
 
@@ -86,6 +86,17 @@ Add to `settings.json` (Ctrl+Shift+, or Settings → Open JSON file) to forward
86
86
 
87
87
  If you already have an `actions` array, add the objects to it. If the old fullscreen behavior persists, fully close and reopen Windows Terminal.
88
88
 
89
+ ## xfce4-terminal, terminator
90
+
91
+ These terminals have limited escape sequence support. Modified Enter keys like `Ctrl+Enter` and `Shift+Enter` cannot be distinguished from plain `Enter`, preventing custom keybindings such as `submit: ["ctrl+enter"]` from working.
92
+
93
+ For the best experience, use a terminal that supports the Kitty keyboard protocol:
94
+ - [Kitty](https://sw.kovidgoyal.net/kitty/)
95
+ - [Ghostty](https://ghostty.org/)
96
+ - [WezTerm](https://wezfurlong.org/wezterm/)
97
+ - [iTerm2](https://iterm2.com/)
98
+ - [Alacritty](https://github.com/alacritty/alacritty) (requires compilation with Kitty protocol support)
99
+
89
100
  ## IntelliJ IDEA (Integrated Terminal)
90
101
 
91
102
  The built-in terminal has limited escape sequence support. Shift+Enter cannot be distinguished from Enter in IntelliJ's terminal.
package/docs/tui.md CHANGED
@@ -394,7 +394,7 @@ Components accept theme objects for styling.
394
394
  **In `renderCall`/`renderResult`**, use the `theme` parameter:
395
395
 
396
396
  ```typescript
397
- renderResult(result, options, theme) {
397
+ renderResult(result, options, theme, context) {
398
398
  // Use theme.fg() for foreground colors
399
399
  return new Text(theme.fg("success", "Done!"), 0, 0);
400
400
 
@@ -428,7 +428,7 @@ renderResult(result, options, theme) {
428
428
  import { getMarkdownTheme } from "@draht/coding-agent";
429
429
  import { Markdown } from "@draht/tui";
430
430
 
431
- renderResult(result, options, theme) {
431
+ renderResult(result, options, theme, context) {
432
432
  const mdTheme = getMarkdownTheme();
433
433
  return new Markdown(result.details.markdown, 0, 0, mdTheme);
434
434
  }
@@ -1,6 +1,6 @@
1
1
  # Extension Examples
2
2
 
3
- Example extensions for pi-coding-agent.
3
+ Example extensions for @draht/coding-agent.
4
4
 
5
5
  ## Usage
6
6
 
@@ -119,7 +119,7 @@ cp permission-gate.ts ~/.draht/agent/extensions/
119
119
  | Extension | Description |
120
120
  |-----------|-------------|
121
121
  | `custom-provider-anthropic/` | Custom Anthropic provider with OAuth support and custom streaming implementation |
122
- | `custom-provider-gitlab-duo/` | GitLab Duo provider using pi-ai's built-in Anthropic/OpenAI streaming via proxy |
122
+ | `custom-provider-gitlab-duo/` | GitLab Duo provider using @draht/ai's built-in Anthropic/OpenAI streaming via proxy |
123
123
  | `custom-provider-qwen-cli/` | Qwen CLI provider with OAuth device flow and OpenAI-compatible models |
124
124
 
125
125
  ### External Dependencies
@@ -30,7 +30,7 @@ import { existsSync, readFileSync } from "node:fs";
30
30
  import { mkdir, writeFile } from "node:fs/promises";
31
31
  import { join } from "node:path";
32
32
  import { StringEnum } from "@draht/ai";
33
- import { type ExtensionAPI, getAgentDir } from "@draht/coding-agent";
33
+ import { type ExtensionAPI, getAgentDir, withFileMutationQueue } from "@draht/coding-agent";
34
34
  import { type Static, Type } from "@sinclair/typebox";
35
35
 
36
36
  const PROVIDER = "google-antigravity";
@@ -228,12 +228,14 @@ function imageExtension(mimeType: string): string {
228
228
  }
229
229
 
230
230
  async function saveImage(base64Data: string, mimeType: string, outputDir: string): Promise<string> {
231
- await mkdir(outputDir, { recursive: true });
232
231
  const timestamp = new Date().toISOString().replace(/[:.]/g, "-");
233
232
  const ext = imageExtension(mimeType);
234
233
  const filename = `image-${timestamp}-${randomUUID().slice(0, 8)}.${ext}`;
235
234
  const filePath = join(outputDir, filename);
236
- await writeFile(filePath, Buffer.from(base64Data, "base64"));
235
+ await withFileMutationQueue(filePath, async () => {
236
+ await mkdir(outputDir, { recursive: true });
237
+ await writeFile(filePath, Buffer.from(base64Data, "base64"));
238
+ });
237
239
  return filePath;
238
240
  }
239
241
 
@@ -42,7 +42,7 @@ export default function (pi: ExtensionAPI) {
42
42
  return originalRead.execute(toolCallId, params, signal, onUpdate);
43
43
  },
44
44
 
45
- renderCall(args, theme) {
45
+ renderCall(args, theme, _context) {
46
46
  let text = theme.fg("toolTitle", theme.bold("read "));
47
47
  text += theme.fg("accent", args.path);
48
48
  if (args.offset || args.limit) {
@@ -54,7 +54,7 @@ export default function (pi: ExtensionAPI) {
54
54
  return new Text(text, 0, 0);
55
55
  },
56
56
 
57
- renderResult(result, { expanded, isPartial }, theme) {
57
+ renderResult(result, { expanded, isPartial }, theme, _context) {
58
58
  if (isPartial) return new Text(theme.fg("warning", "Reading..."), 0, 0);
59
59
 
60
60
  const details = result.details as ReadToolDetails | undefined;
@@ -101,7 +101,7 @@ export default function (pi: ExtensionAPI) {
101
101
  return originalBash.execute(toolCallId, params, signal, onUpdate);
102
102
  },
103
103
 
104
- renderCall(args, theme) {
104
+ renderCall(args, theme, _context) {
105
105
  let text = theme.fg("toolTitle", theme.bold("$ "));
106
106
  const cmd = args.command.length > 80 ? `${args.command.slice(0, 77)}...` : args.command;
107
107
  text += theme.fg("accent", cmd);
@@ -111,7 +111,7 @@ export default function (pi: ExtensionAPI) {
111
111
  return new Text(text, 0, 0);
112
112
  },
113
113
 
114
- renderResult(result, { expanded, isPartial }, theme) {
114
+ renderResult(result, { expanded, isPartial }, theme, _context) {
115
115
  if (isPartial) return new Text(theme.fg("warning", "Running..."), 0, 0);
116
116
 
117
117
  const details = result.details as BashToolDetails | undefined;
@@ -160,13 +160,13 @@ export default function (pi: ExtensionAPI) {
160
160
  return originalEdit.execute(toolCallId, params, signal, onUpdate);
161
161
  },
162
162
 
163
- renderCall(args, theme) {
163
+ renderCall(args, theme, _context) {
164
164
  let text = theme.fg("toolTitle", theme.bold("edit "));
165
165
  text += theme.fg("accent", args.path);
166
166
  return new Text(text, 0, 0);
167
167
  },
168
168
 
169
- renderResult(result, { expanded, isPartial }, theme) {
169
+ renderResult(result, { expanded, isPartial }, theme, _context) {
170
170
  if (isPartial) return new Text(theme.fg("warning", "Editing..."), 0, 0);
171
171
 
172
172
  const details = result.details as EditToolDetails | undefined;
@@ -224,7 +224,7 @@ export default function (pi: ExtensionAPI) {
224
224
  return originalWrite.execute(toolCallId, params, signal, onUpdate);
225
225
  },
226
226
 
227
- renderCall(args, theme) {
227
+ renderCall(args, theme, _context) {
228
228
  let text = theme.fg("toolTitle", theme.bold("write "));
229
229
  text += theme.fg("accent", args.path);
230
230
  const lineCount = args.content.split("\n").length;
@@ -232,7 +232,7 @@ export default function (pi: ExtensionAPI) {
232
232
  return new Text(text, 0, 0);
233
233
  },
234
234
 
235
- renderResult(result, { isPartial }, theme) {
235
+ renderResult(result, { isPartial }, theme, _context) {
236
236
  if (isPartial) return new Text(theme.fg("warning", "Writing..."), 0, 0);
237
237
 
238
238
  const content = result.content[0];
@@ -60,10 +60,10 @@ export default function commandsExtension(pi: ExtensionAPI) {
60
60
  if (selected && !selected.startsWith("---")) {
61
61
  const cmdName = selected.split(" - ")[0].slice(1); // Remove leading /
62
62
  const cmd = commands.find((c) => c.name === cmdName);
63
- if (cmd?.path) {
64
- const showPath = await ctx.ui.confirm(cmd.name, `View source path?\n${cmd.path}`);
63
+ if (cmd?.sourceInfo.path) {
64
+ const showPath = await ctx.ui.confirm(cmd.name, `View source path?\n${cmd.sourceInfo.path}`);
65
65
  if (showPath) {
66
- ctx.ui.notify(cmd.path, "info");
66
+ ctx.ui.notify(cmd.sourceInfo.path, "info");
67
67
  }
68
68
  }
69
69
  }
@@ -80,7 +80,7 @@ export default function (pi: ExtensionAPI) {
80
80
  return tools.read.execute(toolCallId, params, signal, onUpdate);
81
81
  },
82
82
 
83
- renderCall(args, theme) {
83
+ renderCall(args, theme, _context) {
84
84
  const path = shortenPath(args.path || "");
85
85
  let pathDisplay = path ? theme.fg("accent", path) : theme.fg("toolOutput", "...");
86
86
 
@@ -94,7 +94,7 @@ export default function (pi: ExtensionAPI) {
94
94
  return new Text(`${theme.fg("toolTitle", theme.bold("read"))} ${pathDisplay}`, 0, 0);
95
95
  },
96
96
 
97
- renderResult(result, { expanded }, theme) {
97
+ renderResult(result, { expanded }, theme, _context) {
98
98
  // Minimal mode: show nothing in collapsed state
99
99
  if (!expanded) {
100
100
  return new Text("", 0, 0);
@@ -127,7 +127,7 @@ export default function (pi: ExtensionAPI) {
127
127
  return tools.bash.execute(toolCallId, params, signal, onUpdate);
128
128
  },
129
129
 
130
- renderCall(args, theme) {
130
+ renderCall(args, theme, _context) {
131
131
  const command = args.command || "...";
132
132
  const timeout = args.timeout as number | undefined;
133
133
  const timeoutSuffix = timeout ? theme.fg("muted", ` (timeout ${timeout}s)`) : "";
@@ -135,7 +135,7 @@ export default function (pi: ExtensionAPI) {
135
135
  return new Text(theme.fg("toolTitle", theme.bold(`$ ${command}`)) + timeoutSuffix, 0, 0);
136
136
  },
137
137
 
138
- renderResult(result, { expanded }, theme) {
138
+ renderResult(result, { expanded }, theme, _context) {
139
139
  // Minimal mode: show nothing in collapsed state
140
140
  if (!expanded) {
141
141
  return new Text("", 0, 0);
@@ -176,7 +176,7 @@ export default function (pi: ExtensionAPI) {
176
176
  return tools.write.execute(toolCallId, params, signal, onUpdate);
177
177
  },
178
178
 
179
- renderCall(args, theme) {
179
+ renderCall(args, theme, _context) {
180
180
  const path = shortenPath(args.path || "");
181
181
  const pathDisplay = path ? theme.fg("accent", path) : theme.fg("toolOutput", "...");
182
182
  const lineCount = args.content ? args.content.split("\n").length : 0;
@@ -185,7 +185,7 @@ export default function (pi: ExtensionAPI) {
185
185
  return new Text(`${theme.fg("toolTitle", theme.bold("write"))} ${pathDisplay}${lineInfo}`, 0, 0);
186
186
  },
187
187
 
188
- renderResult(result, { expanded }, theme) {
188
+ renderResult(result, { expanded }, theme, _context) {
189
189
  // Minimal mode: show nothing (file was written)
190
190
  if (!expanded) {
191
191
  return new Text("", 0, 0);
@@ -218,14 +218,14 @@ export default function (pi: ExtensionAPI) {
218
218
  return tools.edit.execute(toolCallId, params, signal, onUpdate);
219
219
  },
220
220
 
221
- renderCall(args, theme) {
221
+ renderCall(args, theme, _context) {
222
222
  const path = shortenPath(args.path || "");
223
223
  const pathDisplay = path ? theme.fg("accent", path) : theme.fg("toolOutput", "...");
224
224
 
225
225
  return new Text(`${theme.fg("toolTitle", theme.bold("edit"))} ${pathDisplay}`, 0, 0);
226
226
  },
227
227
 
228
- renderResult(result, { expanded }, theme) {
228
+ renderResult(result, { expanded }, theme, _context) {
229
229
  // Minimal mode: show nothing in collapsed state
230
230
  if (!expanded) {
231
231
  return new Text("", 0, 0);
@@ -263,7 +263,7 @@ export default function (pi: ExtensionAPI) {
263
263
  return tools.find.execute(toolCallId, params, signal, onUpdate);
264
264
  },
265
265
 
266
- renderCall(args, theme) {
266
+ renderCall(args, theme, _context) {
267
267
  const pattern = args.pattern || "";
268
268
  const path = shortenPath(args.path || ".");
269
269
  const limit = args.limit;
@@ -277,7 +277,7 @@ export default function (pi: ExtensionAPI) {
277
277
  return new Text(text, 0, 0);
278
278
  },
279
279
 
280
- renderResult(result, { expanded }, theme) {
280
+ renderResult(result, { expanded }, theme, _context) {
281
281
  if (!expanded) {
282
282
  // Minimal: just show count
283
283
  const textContent = result.content.find((c) => c.type === "text");
@@ -321,7 +321,7 @@ export default function (pi: ExtensionAPI) {
321
321
  return tools.grep.execute(toolCallId, params, signal, onUpdate);
322
322
  },
323
323
 
324
- renderCall(args, theme) {
324
+ renderCall(args, theme, _context) {
325
325
  const pattern = args.pattern || "";
326
326
  const path = shortenPath(args.path || ".");
327
327
  const glob = args.glob;
@@ -339,7 +339,7 @@ export default function (pi: ExtensionAPI) {
339
339
  return new Text(text, 0, 0);
340
340
  },
341
341
 
342
- renderResult(result, { expanded }, theme) {
342
+ renderResult(result, { expanded }, theme, _context) {
343
343
  if (!expanded) {
344
344
  // Minimal: just show match count
345
345
  const textContent = result.content.find((c) => c.type === "text");
@@ -383,7 +383,7 @@ export default function (pi: ExtensionAPI) {
383
383
  return tools.ls.execute(toolCallId, params, signal, onUpdate);
384
384
  },
385
385
 
386
- renderCall(args, theme) {
386
+ renderCall(args, theme, _context) {
387
387
  const path = shortenPath(args.path || ".");
388
388
  const limit = args.limit;
389
389
 
@@ -395,7 +395,7 @@ export default function (pi: ExtensionAPI) {
395
395
  return new Text(text, 0, 0);
396
396
  },
397
397
 
398
- renderResult(result, { expanded }, theme) {
398
+ renderResult(result, { expanded }, theme, _context) {
399
399
  if (!expanded) {
400
400
  // Minimal: just show entry count
401
401
  const textContent = result.content.find((c) => c.type === "text");
@@ -227,7 +227,7 @@ export default function question(pi: ExtensionAPI) {
227
227
  };
228
228
  },
229
229
 
230
- renderCall(args, theme) {
230
+ renderCall(args, theme, _context) {
231
231
  let text = theme.fg("toolTitle", theme.bold("question ")) + theme.fg("muted", args.question);
232
232
  const opts = Array.isArray(args.options) ? args.options : [];
233
233
  if (opts.length) {
@@ -238,7 +238,7 @@ export default function question(pi: ExtensionAPI) {
238
238
  return new Text(text, 0, 0);
239
239
  },
240
240
 
241
- renderResult(result, _options, theme) {
241
+ renderResult(result, _options, theme, _context) {
242
242
  const details = result.details as QuestionDetails | undefined;
243
243
  if (!details) {
244
244
  const text = result.content[0];
@@ -393,7 +393,7 @@ export default function questionnaire(pi: ExtensionAPI) {
393
393
  };
394
394
  },
395
395
 
396
- renderCall(args, theme) {
396
+ renderCall(args, theme, _context) {
397
397
  const qs = (args.questions as Question[]) || [];
398
398
  const count = qs.length;
399
399
  const labels = qs.map((q) => q.label || q.id).join(", ");
@@ -405,7 +405,7 @@ export default function questionnaire(pi: ExtensionAPI) {
405
405
  return new Text(text, 0, 0);
406
406
  },
407
407
 
408
- renderResult(result, _options, theme) {
408
+ renderResult(result, _options, theme, _context) {
409
409
  const details = result.details as QuestionnaireResult | undefined;
410
410
  if (!details) {
411
411
  const text = result.content[0];
@@ -19,7 +19,7 @@ import * as path from "node:path";
19
19
  import type { AgentToolResult } from "@draht/agent-core";
20
20
  import type { Message } from "@draht/ai";
21
21
  import { StringEnum } from "@draht/ai";
22
- import { type ExtensionAPI, getMarkdownTheme } from "@draht/coding-agent";
22
+ import { type ExtensionAPI, getMarkdownTheme, withFileMutationQueue } from "@draht/coding-agent";
23
23
  import { Container, Markdown, Spacer, Text } from "@draht/tui";
24
24
  import { Type } from "@sinclair/typebox";
25
25
  import { type AgentConfig, type AgentScope, discoverAgents } from "./agents.js";
@@ -207,14 +207,31 @@ async function mapWithConcurrencyLimit<TIn, TOut>(
207
207
  return results;
208
208
  }
209
209
 
210
- function writePromptToTempFile(agentName: string, prompt: string): { dir: string; filePath: string } {
211
- const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), "pi-subagent-"));
210
+ async function writePromptToTempFile(agentName: string, prompt: string): Promise<{ dir: string; filePath: string }> {
211
+ const tmpDir = await fs.promises.mkdtemp(path.join(os.tmpdir(), "pi-subagent-"));
212
212
  const safeName = agentName.replace(/[^\w.-]+/g, "_");
213
213
  const filePath = path.join(tmpDir, `prompt-${safeName}.md`);
214
- fs.writeFileSync(filePath, prompt, { encoding: "utf-8", mode: 0o600 });
214
+ await withFileMutationQueue(filePath, async () => {
215
+ await fs.promises.writeFile(filePath, prompt, { encoding: "utf-8", mode: 0o600 });
216
+ });
215
217
  return { dir: tmpDir, filePath };
216
218
  }
217
219
 
220
+ function getPiInvocation(args: string[]): { command: string; args: string[] } {
221
+ const currentScript = process.argv[1];
222
+ if (currentScript && fs.existsSync(currentScript)) {
223
+ return { command: process.execPath, args: [currentScript, ...args] };
224
+ }
225
+
226
+ const execName = path.basename(process.execPath).toLowerCase();
227
+ const isGenericRuntime = /^(node|bun)(\.exe)?$/.test(execName);
228
+ if (!isGenericRuntime) {
229
+ return { command: process.execPath, args };
230
+ }
231
+
232
+ return { command: "pi", args };
233
+ }
234
+
218
235
  type OnUpdateCallback = (partial: AgentToolResult<SubagentDetails>) => void;
219
236
 
220
237
  async function runSingleAgent(
@@ -274,7 +291,7 @@ async function runSingleAgent(
274
291
 
275
292
  try {
276
293
  if (agent.systemPrompt.trim()) {
277
- const tmp = writePromptToTempFile(agent.name, agent.systemPrompt);
294
+ const tmp = await writePromptToTempFile(agent.name, agent.systemPrompt);
278
295
  tmpPromptDir = tmp.dir;
279
296
  tmpPromptPath = tmp.filePath;
280
297
  args.push("--append-system-prompt", tmpPromptPath);
@@ -284,7 +301,12 @@ async function runSingleAgent(
284
301
  let wasAborted = false;
285
302
 
286
303
  const exitCode = await new Promise<number>((resolve) => {
287
- const proc = spawn("pi", args, { cwd: cwd ?? defaultCwd, shell: false, stdio: ["ignore", "pipe", "pipe"] });
304
+ const invocation = getPiInvocation(args);
305
+ const proc = spawn(invocation.command, invocation.args, {
306
+ cwd: cwd ?? defaultCwd,
307
+ shell: false,
308
+ stdio: ["ignore", "pipe", "pipe"],
309
+ });
288
310
  let buffer = "";
289
311
 
290
312
  const processLine = (line: string) => {
@@ -646,7 +668,7 @@ export default function (pi: ExtensionAPI) {
646
668
  };
647
669
  },
648
670
 
649
- renderCall(args, theme) {
671
+ renderCall(args, theme, _context) {
650
672
  const scope: AgentScope = args.agentScope ?? "user";
651
673
  if (args.chain && args.chain.length > 0) {
652
674
  let text =
@@ -690,7 +712,7 @@ export default function (pi: ExtensionAPI) {
690
712
  return new Text(text, 0, 0);
691
713
  },
692
714
 
693
- renderResult(result, { expanded }, theme) {
715
+ renderResult(result, { expanded }, theme, _context) {
694
716
  const details = result.details as SubagentDetails | undefined;
695
717
  if (!details || details.results.length === 0) {
696
718
  const text = result.content[0];
@@ -16,7 +16,7 @@ const BRAILLE_FRAMES = ["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧",
16
16
  function getBaseTitle(pi: ExtensionAPI): string {
17
17
  const cwd = path.basename(process.cwd());
18
18
  const session = pi.getSessionName();
19
- return session ? - ${session} - ${cwd}` : - ${cwd}`;
19
+ return session ? `D - ${session} - ${cwd}` : `D - ${cwd}`;
20
20
  }
21
21
 
22
22
  export default function (pi: ExtensionAPI) {
@@ -38,7 +38,7 @@ export default function (pi: ExtensionAPI) {
38
38
  const frame = BRAILLE_FRAMES[frameIndex % BRAILLE_FRAMES.length];
39
39
  const cwd = path.basename(process.cwd());
40
40
  const session = pi.getSessionName();
41
- const title = session ? `${frame} π - ${session} - ${cwd}` : `${frame} π - ${cwd}`;
41
+ const title = session ? `${frame} D - ${session} - ${cwd}` : `${frame} D - ${cwd}`;
42
42
  ctx.ui.setTitle(title);
43
43
  frameIndex++;
44
44
  }, 80);
@@ -220,14 +220,14 @@ export default function (pi: ExtensionAPI) {
220
220
  }
221
221
  },
222
222
 
223
- renderCall(args, theme) {
223
+ renderCall(args, theme, _context) {
224
224
  let text = theme.fg("toolTitle", theme.bold("todo ")) + theme.fg("muted", args.action);
225
225
  if (args.text) text += ` ${theme.fg("dim", `"${args.text}"`)}`;
226
226
  if (args.id !== undefined) text += ` ${theme.fg("accent", `#${args.id}`)}`;
227
227
  return new Text(text, 0, 0);
228
228
  },
229
229
 
230
- renderResult(result, { expanded }, theme) {
230
+ renderResult(result, { expanded }, theme, _context) {
231
231
  const details = result.details as TodoDetails | undefined;
232
232
  if (!details) {
233
233
  const text = result.content[0];
@@ -21,10 +21,10 @@
21
21
  */
22
22
 
23
23
  import type { TextContent } from "@draht/ai";
24
- import { type ExtensionAPI, getAgentDir } from "@draht/coding-agent";
24
+ import { type ExtensionAPI, getAgentDir, withFileMutationQueue } from "@draht/coding-agent";
25
25
  import { Type } from "@sinclair/typebox";
26
- import { appendFileSync, constants, readFileSync } from "fs";
27
- import { access, readFile } from "fs/promises";
26
+ import { constants, readFileSync } from "fs";
27
+ import { access, appendFile, readFile } from "fs/promises";
28
28
  import { join, resolve } from "path";
29
29
 
30
30
  const LOG_FILE = join(getAgentDir(), "read-access.log");
@@ -44,14 +44,16 @@ function isBlockedPath(path: string): boolean {
44
44
  return BLOCKED_PATTERNS.some((pattern) => pattern.test(path));
45
45
  }
46
46
 
47
- function logAccess(path: string, allowed: boolean, reason?: string) {
47
+ async function logAccess(path: string, allowed: boolean, reason?: string) {
48
48
  const timestamp = new Date().toISOString();
49
49
  const status = allowed ? "ALLOWED" : "BLOCKED";
50
50
  const msg = reason ? ` (${reason})` : "";
51
51
  const line = `[${timestamp}] ${status}: ${path}${msg}\n`;
52
52
 
53
53
  try {
54
- appendFileSync(LOG_FILE, line);
54
+ await withFileMutationQueue(LOG_FILE, async () => {
55
+ await appendFile(LOG_FILE, line);
56
+ });
55
57
  } catch {
56
58
  // Ignore logging errors
57
59
  }
@@ -77,7 +79,7 @@ export default function (pi: ExtensionAPI) {
77
79
 
78
80
  // Check if path is blocked
79
81
  if (isBlockedPath(absolutePath)) {
80
- logAccess(absolutePath, false, "matches blocked pattern");
82
+ await logAccess(absolutePath, false, "matches blocked pattern");
81
83
  return {
82
84
  content: [
83
85
  {
@@ -90,7 +92,7 @@ export default function (pi: ExtensionAPI) {
90
92
  }
91
93
 
92
94
  // Log allowed access
93
- logAccess(absolutePath, true);
95
+ await logAccess(absolutePath, true);
94
96
 
95
97
  // Perform the actual read (simplified implementation)
96
98
  try {
@@ -14,6 +14,7 @@
14
14
  * built-in `grep` tool in src/core/tools/grep.ts for a more complete implementation.
15
15
  */
16
16
 
17
+ import { mkdtemp, writeFile } from "node:fs/promises";
17
18
  import type { ExtensionAPI } from "@draht/coding-agent";
18
19
  import {
19
20
  DEFAULT_MAX_BYTES,
@@ -21,11 +22,11 @@ import {
21
22
  formatSize,
22
23
  type TruncationResult,
23
24
  truncateHead,
25
+ withFileMutationQueue,
24
26
  } from "@draht/coding-agent";
25
27
  import { Text } from "@draht/tui";
26
28
  import { Type } from "@sinclair/typebox";
27
29
  import { execSync } from "child_process";
28
- import { mkdtempSync, writeFileSync } from "fs";
29
30
  import { tmpdir } from "os";
30
31
  import { join } from "path";
31
32
 
@@ -108,9 +109,11 @@ export default function (pi: ExtensionAPI) {
108
109
 
109
110
  if (truncation.truncated) {
110
111
  // Save full output to a temp file so LLM can access it if needed
111
- const tempDir = mkdtempSync(join(tmpdir(), "pi-rg-"));
112
+ const tempDir = await mkdtemp(join(tmpdir(), "pi-rg-"));
112
113
  const tempFile = join(tempDir, "output.txt");
113
- writeFileSync(tempFile, output);
114
+ await withFileMutationQueue(tempFile, async () => {
115
+ await writeFile(tempFile, output, "utf8");
116
+ });
114
117
 
115
118
  details.truncation = truncation;
116
119
  details.fullOutputPath = tempFile;
@@ -132,7 +135,7 @@ export default function (pi: ExtensionAPI) {
132
135
  },
133
136
 
134
137
  // Custom rendering of the tool call (shown before/during execution)
135
- renderCall(args, theme) {
138
+ renderCall(args, theme, _context) {
136
139
  let text = theme.fg("toolTitle", theme.bold("rg "));
137
140
  text += theme.fg("accent", `"${args.pattern}"`);
138
141
  if (args.path) {
@@ -145,7 +148,7 @@ export default function (pi: ExtensionAPI) {
145
148
  },
146
149
 
147
150
  // Custom rendering of the tool result
148
- renderResult(result, { expanded, isPartial }, theme) {
151
+ renderResult(result, { expanded, isPartial }, theme, _context) {
149
152
  const details = result.details as RgDetails | undefined;
150
153
 
151
154
  // Handle streaming/partial results