@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
@@ -247,11 +247,11 @@ user sends prompt ────────────────────
247
247
  │ ├─► before_provider_request (can inspect or replace payload)
248
248
  │ │ │ │
249
249
  │ │ LLM responds, may call tools: │ │
250
- │ │ ├─► tool_call (can block) │ │
251
250
  │ │ ├─► tool_execution_start │ │
251
+ │ │ ├─► tool_call (can block) │ │
252
252
  │ │ ├─► tool_execution_update │ │
253
- │ │ ├─► tool_execution_end │ │
254
- │ │ └─► tool_result (can modify) │ │
253
+ │ │ ├─► tool_result (can modify) │ │
254
+ │ │ └─► tool_execution_end │ │
255
255
  │ │ │ │
256
256
  │ └─► turn_end │ │
257
257
  │ │
@@ -485,6 +485,11 @@ pi.on("message_end", async (event, ctx) => {
485
485
 
486
486
  Fired for tool execution lifecycle updates.
487
487
 
488
+ In parallel tool mode:
489
+ - `tool_execution_start` is emitted in assistant source order during the preflight phase
490
+ - `tool_execution_update` events may interleave across tools
491
+ - `tool_execution_end` is emitted in assistant source order, matching final tool result message order
492
+
488
493
  ```typescript
489
494
  pi.on("tool_execution_start", async (event, ctx) => {
490
495
  // event.toolCallId, event.toolName, event.args
@@ -553,7 +558,11 @@ Use this to update UI elements (status bars, footers) or perform model-specific
553
558
 
554
559
  #### tool_call
555
560
 
556
- Fired before tool executes. **Can block.** Use `isToolCallEventType` to narrow and get typed inputs.
561
+ Fired after `tool_execution_start`, before the tool executes. **Can block.** Use `isToolCallEventType` to narrow and get typed inputs.
562
+
563
+ Before `tool_call` runs, pi waits for previously emitted Agent events to finish draining through `AgentSession`. This means `ctx.sessionManager` is up to date through the current assistant tool-calling message.
564
+
565
+ In the default parallel tool execution mode, sibling tool calls from the same assistant message are preflighted sequentially, then executed concurrently. `tool_call` is not guaranteed to see sibling tool results from that same assistant message in `ctx.sessionManager`.
557
566
 
558
567
  ```typescript
559
568
  import { isToolCallEventType } from "@draht/coding-agent";
@@ -602,7 +611,7 @@ pi.on("tool_call", (event) => {
602
611
 
603
612
  #### tool_result
604
613
 
605
- Fired after tool executes. **Can modify result.**
614
+ Fired after tool execution finishes and before `tool_execution_end` plus the final tool result message events are emitted. **Can modify result.**
606
615
 
607
616
  `tool_result` handlers chain like middleware:
608
617
  - Handlers run in extension load order
@@ -632,6 +641,8 @@ pi.on("tool_result", async (event, ctx) => {
632
641
  Fired when user executes `!` or `!!` commands. **Can intercept.**
633
642
 
634
643
  ```typescript
644
+ import { createLocalBashOperations } from "@mariozechner/pi-coding-agent";
645
+
635
646
  pi.on("user_bash", (event, ctx) => {
636
647
  // event.command - the bash command
637
648
  // event.excludeFromContext - true if !! prefix
@@ -640,7 +651,17 @@ pi.on("user_bash", (event, ctx) => {
640
651
  // Option 1: Provide custom operations (e.g., SSH)
641
652
  return { operations: remoteBashOps };
642
653
 
643
- // Option 2: Full replacement - return result directly
654
+ // Option 2: Wrap pi's built-in local bash backend
655
+ const local = createLocalBashOperations();
656
+ return {
657
+ operations: {
658
+ exec(command, cwd, options) {
659
+ return local.exec(`source ~/.profile\n${command}`, cwd, options);
660
+ }
661
+ }
662
+ };
663
+
664
+ // Option 3: Full replacement - return result directly
644
665
  return { result: { output: "...", exitCode: 0, cancelled: false, truncated: false } };
645
666
  });
646
667
  ```
@@ -715,6 +736,8 @@ Current working directory.
715
736
 
716
737
  Read-only access to session state. See [session.md](session.md) for the full SessionManager API and entry types.
717
738
 
739
+ For `tool_call`, this state is synchronized through the current assistant message before handlers run. In parallel tool execution mode it is still not guaranteed to include sibling tool results from the same assistant message.
740
+
718
741
  ```typescript
719
742
  ctx.sessionManager.getEntries() // All entries
720
743
  ctx.sessionManager.getBranch() // Current branch
@@ -923,7 +946,7 @@ Register a custom tool callable by the LLM. See [Custom Tools](#custom-tools) fo
923
946
 
924
947
  Use `pi.setActiveTools()` to enable or disable tools (including dynamically added tools) at runtime.
925
948
 
926
- Use `promptSnippet` to customize that tool's one-line entry in `Available tools`, and `promptGuidelines` to append tool-specific bullets to the default `Guidelines` section when the tool is active.
949
+ Use `promptSnippet` to opt a custom tool into a one-line entry in `Available tools`, and `promptGuidelines` to append tool-specific bullets to the default `Guidelines` section when the tool is active.
927
950
 
928
951
  See [dynamic-tools.ts](../examples/extensions/dynamic-tools.ts) for a full example.
929
952
 
@@ -976,7 +999,7 @@ pi.sendMessage({
976
999
 
977
1000
  **Options:**
978
1001
  - `deliverAs` - Delivery mode:
979
- - `"steer"` (default) - Interrupts streaming. Delivered after current tool finishes, remaining tools skipped.
1002
+ - `"steer"` (default) - Queues the message while streaming. Delivered after the current assistant turn finishes executing its tool calls, before the next LLM call.
980
1003
  - `"followUp"` - Waits for agent to finish. Delivered only when agent has no more tool calls.
981
1004
  - `"nextTurn"` - Queued for next user prompt. Does not interrupt or trigger anything.
982
1005
  - `triggerTurn: true` - If agent is idle, trigger an LLM response immediately. Only applies to `"steer"` and `"followUp"` modes (ignored for `"nextTurn"`).
@@ -1002,7 +1025,7 @@ pi.sendUserMessage("And then summarize", { deliverAs: "followUp" });
1002
1025
 
1003
1026
  **Options:**
1004
1027
  - `deliverAs` - Required when agent is streaming:
1005
- - `"steer"` - Interrupts after current tool, remaining tools skipped
1028
+ - `"steer"` - Queues the message for delivery after the current assistant turn finishes executing its tool calls
1006
1029
  - `"followUp"` - Waits for agent to finish all tools
1007
1030
 
1008
1031
  When not streaming, the message is sent immediately and triggers a new turn. When streaming without `deliverAs`, throws an error.
@@ -1066,6 +1089,8 @@ Labels persist in the session and survive restarts. Use them to mark important p
1066
1089
 
1067
1090
  Register a command.
1068
1091
 
1092
+ If multiple extensions register the same command name, pi keeps them all and assigns numeric invocation suffixes in load order, for example `/review:1` and `/review:2`.
1093
+
1069
1094
  ```typescript
1070
1095
  pi.registerCommand("stats", {
1071
1096
  description: "Show session statistics",
@@ -1103,20 +1128,28 @@ The list matches the RPC `get_commands` ordering: extensions first, then templat
1103
1128
  ```typescript
1104
1129
  const commands = pi.getCommands();
1105
1130
  const bySource = commands.filter((command) => command.source === "extension");
1131
+ const userScoped = commands.filter((command) => command.sourceInfo.scope === "user");
1106
1132
  ```
1107
1133
 
1108
1134
  Each entry has this shape:
1109
1135
 
1110
1136
  ```typescript
1111
1137
  {
1112
- name: string; // Command name without the leading slash
1138
+ name: string; // Invokable command name without the leading slash. May be suffixed like "review:1"
1113
1139
  description?: string;
1114
1140
  source: "extension" | "prompt" | "skill";
1115
- location?: "user" | "project" | "path"; // For templates and skills
1116
- path?: string; // Files backing templates, skills, and extensions
1141
+ sourceInfo: {
1142
+ path: string;
1143
+ source: string;
1144
+ scope: "user" | "project" | "temporary";
1145
+ origin: "package" | "top-level";
1146
+ baseDir?: string;
1147
+ };
1117
1148
  }
1118
1149
  ```
1119
1150
 
1151
+ Use `sourceInfo` as the canonical provenance field. Do not infer ownership from command names or from ad hoc path parsing.
1152
+
1120
1153
  Built-in interactive commands (like `/model` and `/settings`) are not included here. They are handled only in interactive
1121
1154
  mode and would not execute if sent via `prompt`.
1122
1155
 
@@ -1168,12 +1201,27 @@ const result = await pi.exec("git", ["status"], { signal, timeout: 5000 });
1168
1201
  Manage active tools. This works for both built-in tools and dynamically registered tools.
1169
1202
 
1170
1203
  ```typescript
1171
- const active = pi.getActiveTools(); // ["read", "bash", "edit", "write"]
1172
- const all = pi.getAllTools(); // [{ name: "read", description: "Read file contents..." }, ...]
1173
- const names = all.map(t => t.name); // Just names if needed
1204
+ const active = pi.getActiveTools();
1205
+ const all = pi.getAllTools();
1206
+ // [{
1207
+ // name: "read",
1208
+ // description: "Read file contents...",
1209
+ // parameters: ...,
1210
+ // sourceInfo: { path: "<builtin:read>", source: "builtin", scope: "temporary", origin: "top-level" }
1211
+ // }, ...]
1212
+ const names = all.map(t => t.name);
1213
+ const builtinTools = all.filter((t) => t.sourceInfo.source === "builtin");
1214
+ const extensionTools = all.filter((t) => t.sourceInfo.source !== "builtin" && t.sourceInfo.source !== "sdk");
1174
1215
  pi.setActiveTools(["read", "bash"]); // Switch to read-only
1175
1216
  ```
1176
1217
 
1218
+ `pi.getAllTools()` returns `name`, `description`, `parameters`, and `sourceInfo`.
1219
+
1220
+ Typical `sourceInfo.source` values:
1221
+ - `builtin` for built-in tools
1222
+ - `sdk` for tools passed via `createAgentSession({ customTools })`
1223
+ - extension source metadata for tools registered by extensions
1224
+
1177
1225
  ### pi.setModel(model)
1178
1226
 
1179
1227
  Set the current model. Returns `false` if no API key is available for the model. See [models.md](models.md) for configuring custom models.
@@ -1325,12 +1373,42 @@ export default function (pi: ExtensionAPI) {
1325
1373
 
1326
1374
  Register tools the LLM can call via `pi.registerTool()`. Tools appear in the system prompt and can have custom rendering.
1327
1375
 
1328
- Use `promptSnippet` for a short one-line entry in the `Available tools` section in the default system prompt. If omitted, pi falls back to `description`.
1376
+ Use `promptSnippet` for a short one-line entry in the `Available tools` section in the default system prompt. If omitted, custom tools are left out of that section.
1329
1377
 
1330
1378
  Use `promptGuidelines` to add tool-specific bullets to the default system prompt `Guidelines` section. These bullets are included only while the tool is active (for example, after `pi.setActiveTools([...])`).
1331
1379
 
1332
1380
  Note: Some models are idiots and include the @ prefix in tool path arguments. Built-in tools strip a leading @ before resolving paths. If your custom tool accepts a path, normalize a leading @ as well.
1333
1381
 
1382
+ If your custom tool mutates files, use `withFileMutationQueue()` so it participates in the same per-file queue as built-in `edit` and `write`. This matters because tool calls run in parallel by default. Without the queue, two tools can read the same old file contents, compute different updates, and then whichever write lands last overwrites the other.
1383
+
1384
+ Example failure case: your custom tool edits `foo.ts` while built-in `edit` also changes `foo.ts` in the same assistant turn. If your tool does not participate in the queue, both can read the original `foo.ts`, apply separate changes, and one of those changes is lost.
1385
+
1386
+ Pass the real target file path to `withFileMutationQueue()`, not the raw user argument. Resolve it to an absolute path first, relative to `ctx.cwd` or your tool's working directory. For existing files, the helper canonicalizes through `realpath()`, so symlink aliases for the same file share one queue. For new files, it falls back to the resolved absolute path because there is nothing to `realpath()` yet.
1387
+
1388
+ Queue the entire mutation window on that target path. That includes read-modify-write logic, not just the final write.
1389
+
1390
+ ```typescript
1391
+ import { withFileMutationQueue } from "@mariozechner/pi-coding-agent";
1392
+ import { mkdir, readFile, writeFile } from "node:fs/promises";
1393
+ import { dirname, resolve } from "node:path";
1394
+
1395
+ async execute(_toolCallId, params, _signal, _onUpdate, ctx) {
1396
+ const absolutePath = resolve(ctx.cwd, params.path);
1397
+
1398
+ return withFileMutationQueue(absolutePath, async () => {
1399
+ await mkdir(dirname(absolutePath), { recursive: true });
1400
+ const current = await readFile(absolutePath, "utf8");
1401
+ const next = current.replace(params.oldText, params.newText);
1402
+ await writeFile(absolutePath, next, "utf8");
1403
+
1404
+ return {
1405
+ content: [{ type: "text", text: `Updated ${params.path}` }],
1406
+ details: {},
1407
+ };
1408
+ });
1409
+ }
1410
+ ```
1411
+
1334
1412
  ### Tool Definition
1335
1413
 
1336
1414
  ```typescript
@@ -1454,6 +1532,8 @@ pi.registerTool({
1454
1532
 
1455
1533
  **Operations interfaces:** `ReadOperations`, `WriteOperations`, `EditOperations`, `BashOperations`, `LsOperations`, `GrepOperations`, `FindOperations`
1456
1534
 
1535
+ For `user_bash`, extensions can reuse pi's local shell backend via `createLocalBashOperations()` instead of reimplementing local process spawning, shell resolution, and process-tree termination.
1536
+
1457
1537
  The bash tool also supports a spawn hook to adjust the command, cwd, or env before execution:
1458
1538
 
1459
1539
  ```typescript
@@ -1592,7 +1672,7 @@ renderResult(result, { expanded, isPartial }, theme) {
1592
1672
 
1593
1673
  #### Keybinding Hints
1594
1674
 
1595
- Use `keyHint()` to display keybinding hints that respect user's keybinding configuration:
1675
+ Use `keyHint()` to display keybinding hints that respect the active keybinding configuration:
1596
1676
 
1597
1677
  ```typescript
1598
1678
  import { keyHint } from "@draht/coding-agent";
@@ -1600,18 +1680,25 @@ import { keyHint } from "@draht/coding-agent";
1600
1680
  renderResult(result, { expanded }, theme) {
1601
1681
  let text = theme.fg("success", "✓ Done");
1602
1682
  if (!expanded) {
1603
- text += ` (${keyHint("expandTools", "to expand")})`;
1683
+ text += ` (${keyHint("app.tools.expand", "to expand")})`;
1604
1684
  }
1605
1685
  return new Text(text, 0, 0);
1606
1686
  }
1607
1687
  ```
1608
1688
 
1609
1689
  Available functions:
1610
- - `keyHint(action, description)` - Editor actions (e.g., `"expandTools"`, `"selectConfirm"`)
1611
- - `appKeyHint(keybindings, action, description)` - App actions (requires `KeybindingsManager`)
1612
- - `editorKey(action)` - Get raw key string for editor action
1690
+ - `keyHint(keybinding, description)` - Formats a configured keybinding id such as `"app.tools.expand"` or `"tui.select.confirm"`
1691
+ - `keyText(keybinding)` - Returns the raw configured key text for a keybinding id
1613
1692
  - `rawKeyHint(key, description)` - Format a raw key string
1614
1693
 
1694
+ Use namespaced keybinding ids:
1695
+ - Coding-agent ids use the `app.*` namespace, for example `app.tools.expand`, `app.editor.external`, `app.session.rename`
1696
+ - Shared TUI ids use the `tui.*` namespace, for example `tui.select.confirm`, `tui.select.cancel`, `tui.input.tab`
1697
+
1698
+ For the exhaustive list of keybinding ids and defaults, see [keybindings.md](keybindings.md). `keybindings.json` uses those same namespaced ids.
1699
+
1700
+ Custom editors and `ctx.ui.custom()` components receive `keybindings: KeybindingsManager` as an injected argument. They should use that injected manager directly instead of calling `getKeybindings()` or `setKeybindings()`.
1701
+
1615
1702
  #### Best Practices
1616
1703
 
1617
1704
  - Use `Text` with padding `(0, 0)` - the Box handles padding
@@ -2,6 +2,12 @@
2
2
 
3
3
  All keyboard shortcuts can be customized via `~/.pi/agent/keybindings.json`. Each action can be bound to one or more keys.
4
4
 
5
+ The config file uses the same namespaced keybinding ids that pi uses internally and that extension authors use in `keyHint()` and injected `keybindings` managers.
6
+
7
+ Older configs using pre-namespaced ids such as `cursorUp` or `expandTools` are migrated automatically to the namespaced ids on startup.
8
+
9
+ After editing `keybindings.json`, run `/reload` in pi to apply the changes without restarting the session.
10
+
5
11
  ## Key Format
6
12
 
7
13
  `modifier+key` where modifiers are `ctrl`, `shift`, `alt` (combinable) and keys are:
@@ -16,127 +22,112 @@ Modifier combinations: `ctrl+shift+x`, `alt+ctrl+x`, `ctrl+shift+alt+x`, `ctrl+1
16
22
 
17
23
  ## All Actions
18
24
 
19
- ### Cursor Movement
25
+ ### TUI Editor Cursor Movement
20
26
 
21
- | Action | Default | Description |
27
+ | Keybinding id | Default | Description |
22
28
  |--------|---------|-------------|
23
- | `cursorUp` | `up` | Move cursor up |
24
- | `cursorDown` | `down` | Move cursor down |
25
- | `cursorLeft` | `left`, `ctrl+b` | Move cursor left |
26
- | `cursorRight` | `right`, `ctrl+f` | Move cursor right |
27
- | `cursorWordLeft` | `alt+left`, `ctrl+left`, `alt+b` | Move cursor word left |
28
- | `cursorWordRight` | `alt+right`, `ctrl+right`, `alt+f` | Move cursor word right |
29
- | `cursorLineStart` | `home`, `ctrl+a` | Move to line start |
30
- | `cursorLineEnd` | `end`, `ctrl+e` | Move to line end |
31
- | `jumpForward` | `ctrl+]` | Jump forward to character |
32
- | `jumpBackward` | `ctrl+alt+]` | Jump backward to character |
33
- | `pageUp` | `pageUp` | Scroll up by page |
34
- | `pageDown` | `pageDown` | Scroll down by page |
35
-
36
- ### Deletion
37
-
38
- | Action | Default | Description |
29
+ | `tui.editor.cursorUp` | `up` | Move cursor up |
30
+ | `tui.editor.cursorDown` | `down` | Move cursor down |
31
+ | `tui.editor.cursorLeft` | `left`, `ctrl+b` | Move cursor left |
32
+ | `tui.editor.cursorRight` | `right`, `ctrl+f` | Move cursor right |
33
+ | `tui.editor.cursorWordLeft` | `alt+left`, `ctrl+left`, `alt+b` | Move cursor word left |
34
+ | `tui.editor.cursorWordRight` | `alt+right`, `ctrl+right`, `alt+f` | Move cursor word right |
35
+ | `tui.editor.cursorLineStart` | `home`, `ctrl+a` | Move to line start |
36
+ | `tui.editor.cursorLineEnd` | `end`, `ctrl+e` | Move to line end |
37
+ | `tui.editor.jumpForward` | `ctrl+]` | Jump forward to character |
38
+ | `tui.editor.jumpBackward` | `ctrl+alt+]` | Jump backward to character |
39
+ | `tui.editor.pageUp` | `pageUp` | Scroll up by page |
40
+ | `tui.editor.pageDown` | `pageDown` | Scroll down by page |
41
+
42
+ ### TUI Editor Deletion
43
+
44
+ | Keybinding id | Default | Description |
39
45
  |--------|---------|-------------|
40
- | `deleteCharBackward` | `backspace` | Delete character backward |
41
- | `deleteCharForward` | `delete`, `ctrl+d` | Delete character forward |
42
- | `deleteWordBackward` | `ctrl+w`, `alt+backspace` | Delete word backward |
43
- | `deleteWordForward` | `alt+d`, `alt+delete` | Delete word forward |
44
- | `deleteToLineStart` | `ctrl+u` | Delete to line start |
45
- | `deleteToLineEnd` | `ctrl+k` | Delete to line end |
46
+ | `tui.editor.deleteCharBackward` | `backspace` | Delete character backward |
47
+ | `tui.editor.deleteCharForward` | `delete`, `ctrl+d` | Delete character forward |
48
+ | `tui.editor.deleteWordBackward` | `ctrl+w`, `alt+backspace` | Delete word backward |
49
+ | `tui.editor.deleteWordForward` | `alt+d`, `alt+delete` | Delete word forward |
50
+ | `tui.editor.deleteToLineStart` | `ctrl+u` | Delete to line start |
51
+ | `tui.editor.deleteToLineEnd` | `ctrl+k` | Delete to line end |
46
52
 
47
- ### Text Input
53
+ ### TUI Input
48
54
 
49
- | Action | Default | Description |
55
+ | Keybinding id | Default | Description |
50
56
  |--------|---------|-------------|
51
- | `newLine` | `shift+enter` | Insert new line |
52
- | `submit` | `enter` | Submit input |
53
- | `tab` | `tab` | Tab / autocomplete |
57
+ | `tui.input.newLine` | `shift+enter` | Insert new line |
58
+ | `tui.input.submit` | `enter` | Submit input |
59
+ | `tui.input.tab` | `tab` | Tab / autocomplete |
54
60
 
55
- ### Kill Ring
61
+ ### TUI Kill Ring
56
62
 
57
- | Action | Default | Description |
63
+ | Keybinding id | Default | Description |
58
64
  |--------|---------|-------------|
59
- | `yank` | `ctrl+y` | Paste most recently deleted text |
60
- | `yankPop` | `alt+y` | Cycle through deleted text after yank |
61
- | `undo` | `ctrl+-` | Undo last edit |
65
+ | `tui.editor.yank` | `ctrl+y` | Paste most recently deleted text |
66
+ | `tui.editor.yankPop` | `alt+y` | Cycle through deleted text after yank |
67
+ | `tui.editor.undo` | `ctrl+-` | Undo last edit |
62
68
 
63
- ### Clipboard
69
+ ### TUI Clipboard and Selection
64
70
 
65
- | Action | Default | Description |
71
+ | Keybinding id | Default | Description |
66
72
  |--------|---------|-------------|
67
- | `copy` | `ctrl+c` | Copy selection |
68
- | `pasteImage` | `ctrl+v` | Paste image from clipboard |
73
+ | `tui.input.copy` | `ctrl+c` | Copy selection |
74
+ | `tui.select.up` | `up` | Move selection up |
75
+ | `tui.select.down` | `down` | Move selection down |
76
+ | `tui.select.pageUp` | `pageUp` | Page up in list |
77
+ | `tui.select.pageDown` | `pageDown` | Page down in list |
78
+ | `tui.select.confirm` | `enter` | Confirm selection |
79
+ | `tui.select.cancel` | `escape`, `ctrl+c` | Cancel selection |
69
80
 
70
81
  ### Application
71
82
 
72
- | Action | Default | Description |
73
- |--------|---------|-------------|
74
- | `interrupt` | `escape` | Cancel / abort |
75
- | `clear` | `ctrl+c` | Clear editor |
76
- | `exit` | `ctrl+d` | Exit (when editor empty) |
77
- | `suspend` | `ctrl+z` | Suspend to background |
78
- | `externalEditor` | `ctrl+g` | Open in external editor (`$VISUAL` or `$EDITOR`) |
79
-
80
- ### Session
81
-
82
- | Action | Default | Description |
83
- |--------|---------|-------------|
84
- | `newSession` | *(none)* | Start a new session (`/new`) |
85
- | `tree` | *(none)* | Open session tree navigator (`/tree`) |
86
- | `fork` | *(none)* | Fork current session (`/fork`) |
87
- | `resume` | *(none)* | Open session resume picker (`/resume`) |
88
-
89
- ### Models & Thinking
90
-
91
- | Action | Default | Description |
83
+ | Keybinding id | Default | Description |
92
84
  |--------|---------|-------------|
93
- | `selectModel` | `ctrl+l` | Open model selector |
94
- | `cycleModelForward` | `ctrl+p` | Cycle to next model |
95
- | `cycleModelBackward` | `shift+ctrl+p` | Cycle to previous model |
96
- | `cycleThinkingLevel` | `shift+tab` | Cycle thinking level |
85
+ | `app.interrupt` | `escape` | Cancel / abort |
86
+ | `app.clear` | `ctrl+c` | Clear editor |
87
+ | `app.exit` | `ctrl+d` | Exit (when editor empty) |
88
+ | `app.suspend` | `ctrl+z` | Suspend to background |
89
+ | `app.editor.external` | `ctrl+g` | Open in external editor (`$VISUAL` or `$EDITOR`) |
90
+ | `app.clipboard.pasteImage` | `ctrl+v` (`alt+v` on Windows) | Paste image from clipboard |
97
91
 
98
- ### Display
92
+ ### Sessions
99
93
 
100
- | Action | Default | Description |
94
+ | Keybinding id | Default | Description |
101
95
  |--------|---------|-------------|
102
- | `expandTools` | `ctrl+o` | Collapse/expand tool output |
103
- | `toggleThinking` | `ctrl+t` | Collapse/expand thinking blocks |
104
-
105
- ### Message Queue
106
-
107
- | Action | Default | Description |
96
+ | `app.session.new` | *(none)* | Start a new session (`/new`) |
97
+ | `app.session.tree` | *(none)* | Open session tree navigator (`/tree`) |
98
+ | `app.session.fork` | *(none)* | Fork current session (`/fork`) |
99
+ | `app.session.resume` | *(none)* | Open session resume picker (`/resume`) |
100
+ | `app.session.togglePath` | `ctrl+p` | Toggle path display |
101
+ | `app.session.toggleSort` | `ctrl+s` | Toggle sort mode |
102
+ | `app.session.toggleNamedFilter` | `ctrl+n` | Toggle named-only filter |
103
+ | `app.session.rename` | `ctrl+r` | Rename session |
104
+ | `app.session.delete` | `ctrl+d` | Delete session |
105
+ | `app.session.deleteNoninvasive` | `ctrl+backspace` | Delete session when query is empty |
106
+
107
+ ### Models and Thinking
108
+
109
+ | Keybinding id | Default | Description |
108
110
  |--------|---------|-------------|
109
- | `followUp` | `alt+enter` | Queue follow-up message |
110
- | `dequeue` | `alt+up` | Restore queued messages to editor |
111
+ | `app.model.select` | `ctrl+l` | Open model selector |
112
+ | `app.model.cycleForward` | `ctrl+p` | Cycle to next model |
113
+ | `app.model.cycleBackward` | `shift+ctrl+p` | Cycle to previous model |
114
+ | `app.thinking.cycle` | `shift+tab` | Cycle thinking level |
115
+ | `app.thinking.toggle` | `ctrl+t` | Collapse or expand thinking blocks |
111
116
 
112
- ### Selection (Lists, Pickers)
117
+ ### Display and Message Queue
113
118
 
114
- | Action | Default | Description |
119
+ | Keybinding id | Default | Description |
115
120
  |--------|---------|-------------|
116
- | `selectUp` | `up` | Move selection up |
117
- | `selectDown` | `down` | Move selection down |
118
- | `selectPageUp` | `pageUp` | Page up in list |
119
- | `selectPageDown` | `pageDown` | Page down in list |
120
- | `selectConfirm` | `enter` | Confirm selection |
121
- | `selectCancel` | `escape`, `ctrl+c` | Cancel selection |
121
+ | `app.tools.expand` | `ctrl+o` | Collapse or expand tool output |
122
+ | `app.message.followUp` | `alt+enter` | Queue follow-up message |
123
+ | `app.message.dequeue` | `alt+up` | Restore queued messages to editor |
122
124
 
123
125
  ### Tree Navigation
124
126
 
125
- | Action | Default | Description |
126
- |--------|---------|-------------|
127
- | `treeFoldOrUp` | `ctrl+left`, `alt+left` | Fold current branch segment, or jump to the previous segment start |
128
- | `treeUnfoldOrDown` | `ctrl+right`, `alt+right` | Unfold current branch segment, or jump to the next segment start or branch end |
129
-
130
- ### Session Picker
131
-
132
- | Action | Default | Description |
127
+ | Keybinding id | Default | Description |
133
128
  |--------|---------|-------------|
134
- | `toggleSessionPath` | `ctrl+p` | Toggle path display |
135
- | `toggleSessionSort` | `ctrl+s` | Toggle sort mode |
136
- | `toggleSessionNamedFilter` | `ctrl+n` | Toggle named-only filter |
137
- | `renameSession` | `ctrl+r` | Rename session |
138
- | `deleteSession` | `ctrl+d` | Delete session |
139
- | `deleteSessionNoninvasive` | `ctrl+backspace` | Delete session (when query empty) |
129
+ | `app.tree.foldOrUp` | `ctrl+left`, `alt+left` | Fold current branch segment, or jump to the previous segment start |
130
+ | `app.tree.unfoldOrDown` | `ctrl+right`, `alt+right` | Unfold current branch segment, or jump to the next segment start or branch end |
140
131
 
141
132
  ## Custom Configuration
142
133
 
@@ -144,9 +135,9 @@ Create `~/.pi/agent/keybindings.json`:
144
135
 
145
136
  ```json
146
137
  {
147
- "cursorUp": ["up", "ctrl+p"],
148
- "cursorDown": ["down", "ctrl+n"],
149
- "deleteWordBackward": ["ctrl+w", "alt+backspace"]
138
+ "tui.editor.cursorUp": ["up", "ctrl+p"],
139
+ "tui.editor.cursorDown": ["down", "ctrl+n"],
140
+ "tui.editor.deleteWordBackward": ["ctrl+w", "alt+backspace"]
150
141
  }
151
142
  ```
152
143
 
@@ -156,15 +147,15 @@ Each action can have a single key or an array of keys. User config overrides def
156
147
 
157
148
  ```json
158
149
  {
159
- "cursorUp": ["up", "ctrl+p"],
160
- "cursorDown": ["down", "ctrl+n"],
161
- "cursorLeft": ["left", "ctrl+b"],
162
- "cursorRight": ["right", "ctrl+f"],
163
- "cursorWordLeft": ["alt+left", "alt+b"],
164
- "cursorWordRight": ["alt+right", "alt+f"],
165
- "deleteCharForward": ["delete", "ctrl+d"],
166
- "deleteCharBackward": ["backspace", "ctrl+h"],
167
- "newLine": ["shift+enter", "ctrl+j"]
150
+ "tui.editor.cursorUp": ["up", "ctrl+p"],
151
+ "tui.editor.cursorDown": ["down", "ctrl+n"],
152
+ "tui.editor.cursorLeft": ["left", "ctrl+b"],
153
+ "tui.editor.cursorRight": ["right", "ctrl+f"],
154
+ "tui.editor.cursorWordLeft": ["alt+left", "alt+b"],
155
+ "tui.editor.cursorWordRight": ["alt+right", "alt+f"],
156
+ "tui.editor.deleteCharForward": ["delete", "ctrl+d"],
157
+ "tui.editor.deleteCharBackward": ["backspace", "ctrl+h"],
158
+ "tui.input.newLine": ["shift+enter", "ctrl+j"]
168
159
  }
169
160
  ```
170
161
 
@@ -172,11 +163,11 @@ Each action can have a single key or an array of keys. User config overrides def
172
163
 
173
164
  ```json
174
165
  {
175
- "cursorUp": ["up", "alt+k"],
176
- "cursorDown": ["down", "alt+j"],
177
- "cursorLeft": ["left", "alt+h"],
178
- "cursorRight": ["right", "alt+l"],
179
- "cursorWordLeft": ["alt+left", "alt+b"],
180
- "cursorWordRight": ["alt+right", "alt+w"]
166
+ "tui.editor.cursorUp": ["up", "alt+k"],
167
+ "tui.editor.cursorDown": ["down", "alt+j"],
168
+ "tui.editor.cursorLeft": ["left", "alt+h"],
169
+ "tui.editor.cursorRight": ["right", "alt+l"],
170
+ "tui.editor.cursorWordLeft": ["alt+left", "alt+b"],
171
+ "tui.editor.cursorWordRight": ["alt+right", "alt+w"]
181
172
  }
182
173
  ```
package/docs/models.md CHANGED
@@ -35,6 +35,32 @@ For local models (Ollama, LM Studio, vLLM), only `id` is required per model:
35
35
 
36
36
  The `apiKey` is required but Ollama ignores it, so any value works.
37
37
 
38
+ Some OpenAI-compatible servers do not understand the `developer` role used for reasoning-capable models. For those providers, set `compat.supportsDeveloperRole` to `false` so pi sends the system prompt as a `system` message instead. If the server also does not support `reasoning_effort`, set `compat.supportsReasoningEffort` to `false` too.
39
+
40
+ You can set `compat` at the provider level to apply to all models, or at the model level to override a specific model. This commonly applies to Ollama, vLLM, SGLang, and similar OpenAI-compatible servers.
41
+
42
+ ```json
43
+ {
44
+ "providers": {
45
+ "ollama": {
46
+ "baseUrl": "http://localhost:11434/v1",
47
+ "api": "openai-completions",
48
+ "apiKey": "ollama",
49
+ "compat": {
50
+ "supportsDeveloperRole": false,
51
+ "supportsReasoningEffort": false
52
+ },
53
+ "models": [
54
+ {
55
+ "id": "gpt-oss:20b",
56
+ "reasoning": true
57
+ }
58
+ ]
59
+ }
60
+ }
61
+ }
62
+ ```
63
+
38
64
  ## Full Example
39
65
 
40
66
  Override defaults when you need specific values:
@@ -136,6 +162,7 @@ The `apiKey` and `headers` fields support three formats:
136
162
  | `contextWindow` | No | `128000` | Context window size in tokens |
137
163
  | `maxTokens` | No | `16384` | Maximum output tokens |
138
164
  | `cost` | No | all zeros | `{"input": 0, "output": 0, "cacheRead": 0, "cacheWrite": 0}` (per million tokens) |
165
+ | `compat` | No | provider `compat` | OpenAI compatibility overrides. Merged with provider-level `compat` when both are set. |
139
166
 
140
167
  Current behavior:
141
168
  - `/model` and `--list-models` list entries by model `id`.
@@ -211,7 +238,10 @@ Behavior notes:
211
238
 
212
239
  ## OpenAI Compatibility
213
240
 
214
- For providers with partial OpenAI compatibility, use the `compat` field:
241
+ For providers with partial OpenAI compatibility, use the `compat` field.
242
+
243
+ - Provider-level `compat` applies defaults to all models under that provider.
244
+ - Model-level `compat` overrides provider-level values for that model.
215
245
 
216
246
  ```json
217
247
  {
@@ -234,11 +264,19 @@ For providers with partial OpenAI compatibility, use the `compat` field:
234
264
  | `supportsStore` | Provider supports `store` field |
235
265
  | `supportsDeveloperRole` | Use `developer` vs `system` role |
236
266
  | `supportsReasoningEffort` | Support for `reasoning_effort` parameter |
267
+ | `reasoningEffortMap` | Map pi thinking levels to provider-specific `reasoning_effort` values |
237
268
  | `supportsUsageInStreaming` | Supports `stream_options: { include_usage: true }` (default: `true`) |
238
269
  | `maxTokensField` | Use `max_completion_tokens` or `max_tokens` |
270
+ | `requiresToolResultName` | Include `name` on tool result messages |
271
+ | `requiresAssistantAfterToolResult` | Insert an assistant message before a user message after tool results |
272
+ | `requiresThinkingAsText` | Convert thinking blocks to plain text |
273
+ | `thinkingFormat` | Use `reasoning_effort`, `zai`, `qwen`, or `qwen-chat-template` thinking parameters |
274
+ | `supportsStrictMode` | Include the `strict` field in tool definitions |
239
275
  | `openRouterRouting` | OpenRouter routing config passed to OpenRouter for model/provider selection |
240
276
  | `vercelGatewayRouting` | Vercel AI Gateway routing config for provider selection (`only`, `order`) |
241
277
 
278
+ `qwen` uses top-level `enable_thinking`. Use `qwen-chat-template` for local Qwen-compatible servers that require `chat_template_kwargs.enable_thinking`.
279
+
242
280
  Example:
243
281
 
244
282
  ```json
package/docs/packages.md CHANGED
@@ -54,6 +54,15 @@ npm:pkg
54
54
  - Versioned specs are pinned and skipped by `pi update`.
55
55
  - Global installs use `npm install -g`.
56
56
  - Project installs go under `.pi/npm/`.
57
+ - Set `npmCommand` in `settings.json` to pin npm package lookup and install operations to a specific wrapper command such as `mise` or `asdf`.
58
+
59
+ Example:
60
+
61
+ ```json
62
+ {
63
+ "npmCommand": ["mise", "exec", "node@20", "--", "npm"]
64
+ }
65
+ ```
57
66
 
58
67
  ### git
59
68
 
package/docs/providers.md CHANGED
@@ -147,6 +147,13 @@ Also supports ECS task roles (`AWS_CONTAINER_CREDENTIALS_*`) and IRSA (`AWS_WEB_
147
147
  pi --provider amazon-bedrock --model us.anthropic.claude-sonnet-4-20250514-v1:0
148
148
  ```
149
149
 
150
+ Prompt caching is enabled automatically for Claude models whose ID contains a recognizable model name (base models and system-defined inference profiles). For application inference profiles (whose ARNs don't contain the model name), set `AWS_BEDROCK_FORCE_CACHE=1` to enable cache points:
151
+
152
+ ```bash
153
+ export AWS_BEDROCK_FORCE_CACHE=1
154
+ pi --provider amazon-bedrock --model arn:aws:bedrock:us-east-1:123456789012:application-inference-profile/abc123
155
+ ```
156
+
150
157
  If you are connecting to a Bedrock API proxy, the following environment variables can be used:
151
158
 
152
159
  ```bash