apex-code 0.5.0 → 0.5.2

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 (512) hide show
  1. package/CHANGELOG.md +263 -5
  2. package/README.md +624 -0
  3. package/dist/bun/cli.d.ts +3 -1
  4. package/dist/bun/cli.d.ts.map +1 -1
  5. package/dist/bun/cli.js +3 -9
  6. package/dist/bun/cli.js.map +1 -1
  7. package/dist/bun/runtime-setup.d.ts +2 -0
  8. package/dist/bun/runtime-setup.d.ts.map +1 -0
  9. package/dist/bun/runtime-setup.js +9 -0
  10. package/dist/bun/runtime-setup.js.map +1 -0
  11. package/dist/bun/sandbox-env-setup.d.ts +2 -0
  12. package/dist/bun/sandbox-env-setup.d.ts.map +1 -0
  13. package/dist/bun/sandbox-env-setup.js +4 -0
  14. package/dist/bun/sandbox-env-setup.js.map +1 -0
  15. package/dist/cli/args.d.ts.map +1 -1
  16. package/dist/cli/args.js +16 -5
  17. package/dist/cli/args.js.map +1 -1
  18. package/dist/cli/config-selector.d.ts.map +1 -1
  19. package/dist/cli/config-selector.js +2 -1
  20. package/dist/cli/config-selector.js.map +1 -1
  21. package/dist/cli/file-processor.d.ts +1 -1
  22. package/dist/cli/file-processor.d.ts.map +1 -1
  23. package/dist/cli/file-processor.js.map +1 -1
  24. package/dist/cli/session-picker.d.ts +1 -1
  25. package/dist/cli/session-picker.d.ts.map +1 -1
  26. package/dist/cli/session-picker.js.map +1 -1
  27. package/dist/cli/setup.d.ts +2 -0
  28. package/dist/cli/setup.d.ts.map +1 -0
  29. package/dist/cli/setup.js +13 -0
  30. package/dist/cli/setup.js.map +1 -0
  31. package/dist/config.d.ts +1 -0
  32. package/dist/config.d.ts.map +1 -1
  33. package/dist/config.js +1 -0
  34. package/dist/config.js.map +1 -1
  35. package/dist/core/agent-session-runtime.d.ts.map +1 -1
  36. package/dist/core/agent-session-runtime.js +16 -8
  37. package/dist/core/agent-session-runtime.js.map +1 -1
  38. package/dist/core/agent-session.d.ts +85 -15
  39. package/dist/core/agent-session.d.ts.map +1 -1
  40. package/dist/core/agent-session.js +685 -266
  41. package/dist/core/agent-session.js.map +1 -1
  42. package/dist/core/auth-storage.d.ts +4 -0
  43. package/dist/core/auth-storage.d.ts.map +1 -1
  44. package/dist/core/auth-storage.js +55 -0
  45. package/dist/core/auth-storage.js.map +1 -1
  46. package/dist/core/bug-report.d.ts +176 -0
  47. package/dist/core/bug-report.d.ts.map +1 -0
  48. package/dist/core/bug-report.js +291 -0
  49. package/dist/core/bug-report.js.map +1 -0
  50. package/dist/core/cache-stats.d.ts.map +1 -1
  51. package/dist/core/cache-stats.js +12 -1
  52. package/dist/core/cache-stats.js.map +1 -1
  53. package/dist/core/cache-warmer.d.ts +103 -0
  54. package/dist/core/cache-warmer.d.ts.map +1 -0
  55. package/dist/core/cache-warmer.js +356 -0
  56. package/dist/core/cache-warmer.js.map +1 -0
  57. package/dist/core/compaction/branch-summarization.d.ts +1 -1
  58. package/dist/core/compaction/branch-summarization.d.ts.map +1 -1
  59. package/dist/core/compaction/branch-summarization.js +4 -3
  60. package/dist/core/compaction/branch-summarization.js.map +1 -1
  61. package/dist/core/compaction/compaction.d.ts +5 -3
  62. package/dist/core/compaction/compaction.d.ts.map +1 -1
  63. package/dist/core/compaction/compaction.js +163 -66
  64. package/dist/core/compaction/compaction.js.map +1 -1
  65. package/dist/core/context/pipeline.d.ts +8 -8
  66. package/dist/core/context/pipeline.d.ts.map +1 -1
  67. package/dist/core/context/pipeline.js +21 -12
  68. package/dist/core/context/pipeline.js.map +1 -1
  69. package/dist/core/crash-log.d.ts +27 -0
  70. package/dist/core/crash-log.d.ts.map +1 -0
  71. package/dist/core/crash-log.js +139 -0
  72. package/dist/core/crash-log.js.map +1 -0
  73. package/dist/core/export-html/template.js +6 -1
  74. package/dist/core/extensions/index.d.ts +1 -1
  75. package/dist/core/extensions/index.d.ts.map +1 -1
  76. package/dist/core/extensions/index.js.map +1 -1
  77. package/dist/core/extensions/jiti-loader.d.ts +2 -0
  78. package/dist/core/extensions/jiti-loader.d.ts.map +1 -0
  79. package/dist/core/extensions/jiti-loader.js +4 -0
  80. package/dist/core/extensions/jiti-loader.js.map +1 -0
  81. package/dist/core/extensions/jiti-static-loader.d.ts +2 -0
  82. package/dist/core/extensions/jiti-static-loader.d.ts.map +1 -0
  83. package/dist/core/extensions/jiti-static-loader.js +4 -0
  84. package/dist/core/extensions/jiti-static-loader.js.map +1 -0
  85. package/dist/core/extensions/loader.d.ts.map +1 -1
  86. package/dist/core/extensions/loader.js +40 -59
  87. package/dist/core/extensions/loader.js.map +1 -1
  88. package/dist/core/extensions/runner.d.ts +24 -8
  89. package/dist/core/extensions/runner.d.ts.map +1 -1
  90. package/dist/core/extensions/runner.js +195 -72
  91. package/dist/core/extensions/runner.js.map +1 -1
  92. package/dist/core/extensions/types.d.ts +142 -55
  93. package/dist/core/extensions/types.d.ts.map +1 -1
  94. package/dist/core/extensions/types.js.map +1 -1
  95. package/dist/core/extensions/virtual-modules.d.ts +3 -0
  96. package/dist/core/extensions/virtual-modules.d.ts.map +1 -0
  97. package/dist/core/extensions/virtual-modules.js +41 -0
  98. package/dist/core/extensions/virtual-modules.js.map +1 -0
  99. package/dist/core/extensions/wrapper.d.ts.map +1 -1
  100. package/dist/core/extensions/wrapper.js +1 -20
  101. package/dist/core/extensions/wrapper.js.map +1 -1
  102. package/dist/core/http-dispatcher.d.ts.map +1 -1
  103. package/dist/core/http-dispatcher.js +2 -0
  104. package/dist/core/http-dispatcher.js.map +1 -1
  105. package/dist/core/index.d.ts +2 -1
  106. package/dist/core/index.d.ts.map +1 -1
  107. package/dist/core/index.js.map +1 -1
  108. package/dist/core/keybindings.d.ts +6 -1
  109. package/dist/core/keybindings.d.ts.map +1 -1
  110. package/dist/core/keybindings.js +5 -1
  111. package/dist/core/keybindings.js.map +1 -1
  112. package/dist/core/messages.d.ts +1 -1
  113. package/dist/core/messages.d.ts.map +1 -1
  114. package/dist/core/messages.js +1 -0
  115. package/dist/core/messages.js.map +1 -1
  116. package/dist/core/model-config.d.ts +168 -20
  117. package/dist/core/model-config.d.ts.map +1 -1
  118. package/dist/core/model-config.js +44 -18
  119. package/dist/core/model-config.js.map +1 -1
  120. package/dist/core/model-registry.d.ts +5 -1
  121. package/dist/core/model-registry.d.ts.map +1 -1
  122. package/dist/core/model-registry.js +8 -0
  123. package/dist/core/model-registry.js.map +1 -1
  124. package/dist/core/model-resolver.d.ts.map +1 -1
  125. package/dist/core/model-resolver.js +4 -3
  126. package/dist/core/model-resolver.js.map +1 -1
  127. package/dist/core/model-runtime.d.ts +1 -0
  128. package/dist/core/model-runtime.d.ts.map +1 -1
  129. package/dist/core/model-runtime.js +12 -6
  130. package/dist/core/model-runtime.js.map +1 -1
  131. package/dist/core/permissions/store.d.ts.map +1 -1
  132. package/dist/core/permissions/store.js +11 -8
  133. package/dist/core/permissions/store.js.map +1 -1
  134. package/dist/core/prompt-templates.d.ts +6 -1
  135. package/dist/core/prompt-templates.d.ts.map +1 -1
  136. package/dist/core/prompt-templates.js +61 -35
  137. package/dist/core/prompt-templates.js.map +1 -1
  138. package/dist/core/provider-composer.d.ts +4 -2
  139. package/dist/core/provider-composer.d.ts.map +1 -1
  140. package/dist/core/provider-composer.js +29 -2
  141. package/dist/core/provider-composer.js.map +1 -1
  142. package/dist/core/radius.d.ts +3 -0
  143. package/dist/core/radius.d.ts.map +1 -1
  144. package/dist/core/radius.js +6 -0
  145. package/dist/core/radius.js.map +1 -1
  146. package/dist/core/resource-loader.d.ts.map +1 -1
  147. package/dist/core/resource-loader.js +6 -2
  148. package/dist/core/resource-loader.js.map +1 -1
  149. package/dist/core/sdk.d.ts.map +1 -1
  150. package/dist/core/sdk.js +63 -42
  151. package/dist/core/sdk.js.map +1 -1
  152. package/dist/core/session-export.d.ts +6 -2
  153. package/dist/core/session-export.d.ts.map +1 -1
  154. package/dist/core/session-export.js +14 -13
  155. package/dist/core/session-export.js.map +1 -1
  156. package/dist/core/session-manager.d.ts +66 -15
  157. package/dist/core/session-manager.d.ts.map +1 -1
  158. package/dist/core/session-manager.js +295 -115
  159. package/dist/core/session-manager.js.map +1 -1
  160. package/dist/core/settings-manager.d.ts +27 -4
  161. package/dist/core/settings-manager.d.ts.map +1 -1
  162. package/dist/core/settings-manager.js +55 -7
  163. package/dist/core/settings-manager.js.map +1 -1
  164. package/dist/core/skills.d.ts +1 -1
  165. package/dist/core/skills.d.ts.map +1 -1
  166. package/dist/core/skills.js +5 -4
  167. package/dist/core/skills.js.map +1 -1
  168. package/dist/core/slash-commands.d.ts.map +1 -1
  169. package/dist/core/slash-commands.js +1 -0
  170. package/dist/core/slash-commands.js.map +1 -1
  171. package/dist/core/system-prompt.d.ts +49 -6
  172. package/dist/core/system-prompt.d.ts.map +1 -1
  173. package/dist/core/system-prompt.js +140 -79
  174. package/dist/core/system-prompt.js.map +1 -1
  175. package/dist/core/tools/bash.d.ts +7 -8
  176. package/dist/core/tools/bash.d.ts.map +1 -1
  177. package/dist/core/tools/bash.js +20 -115
  178. package/dist/core/tools/bash.js.map +1 -1
  179. package/dist/core/tools/edit.d.ts +1 -12
  180. package/dist/core/tools/edit.d.ts.map +1 -1
  181. package/dist/core/tools/edit.js +6 -204
  182. package/dist/core/tools/edit.js.map +1 -1
  183. package/dist/core/tools/find.d.ts.map +1 -1
  184. package/dist/core/tools/find.js +4 -55
  185. package/dist/core/tools/find.js.map +1 -1
  186. package/dist/core/tools/grep.d.ts.map +1 -1
  187. package/dist/core/tools/grep.js +4 -60
  188. package/dist/core/tools/grep.js.map +1 -1
  189. package/dist/core/tools/ls.d.ts.map +1 -1
  190. package/dist/core/tools/ls.js +4 -49
  191. package/dist/core/tools/ls.js.map +1 -1
  192. package/dist/core/tools/read.d.ts +4 -1
  193. package/dist/core/tools/read.d.ts.map +1 -1
  194. package/dist/core/tools/read.js +10 -125
  195. package/dist/core/tools/read.js.map +1 -1
  196. package/dist/core/tools/renderers/bash.d.ts +18 -0
  197. package/dist/core/tools/renderers/bash.d.ts.map +1 -0
  198. package/dist/core/tools/renderers/bash.js +126 -0
  199. package/dist/core/tools/renderers/bash.js.map +1 -0
  200. package/dist/core/tools/renderers/edit.d.ts +23 -0
  201. package/dist/core/tools/renderers/edit.d.ts.map +1 -0
  202. package/dist/core/tools/renderers/edit.js +207 -0
  203. package/dist/core/tools/renderers/edit.js.map +1 -0
  204. package/dist/core/tools/renderers/find.d.ts +10 -0
  205. package/dist/core/tools/renderers/find.d.ts.map +1 -0
  206. package/dist/core/tools/renderers/find.js +64 -0
  207. package/dist/core/tools/renderers/find.js.map +1 -0
  208. package/dist/core/tools/renderers/grep.d.ts +10 -0
  209. package/dist/core/tools/renderers/grep.d.ts.map +1 -0
  210. package/dist/core/tools/renderers/grep.js +69 -0
  211. package/dist/core/tools/renderers/grep.js.map +1 -0
  212. package/dist/core/tools/renderers/index.d.ts +34 -0
  213. package/dist/core/tools/renderers/index.d.ts.map +1 -0
  214. package/dist/core/tools/renderers/index.js +47 -0
  215. package/dist/core/tools/renderers/index.js.map +1 -0
  216. package/dist/core/tools/renderers/ls.d.ts +10 -0
  217. package/dist/core/tools/renderers/ls.d.ts.map +1 -0
  218. package/dist/core/tools/renderers/ls.js +58 -0
  219. package/dist/core/tools/renderers/ls.js.map +1 -0
  220. package/dist/core/tools/renderers/read.d.ts +11 -0
  221. package/dist/core/tools/renderers/read.d.ts.map +1 -0
  222. package/dist/core/tools/renderers/read.js +132 -0
  223. package/dist/core/tools/renderers/read.js.map +1 -0
  224. package/dist/core/tools/renderers/write.d.ts +10 -0
  225. package/dist/core/tools/renderers/write.d.ts.map +1 -0
  226. package/dist/core/tools/renderers/write.js +152 -0
  227. package/dist/core/tools/renderers/write.js.map +1 -0
  228. package/dist/core/tools/write.d.ts.map +1 -1
  229. package/dist/core/tools/write.js +6 -146
  230. package/dist/core/tools/write.js.map +1 -1
  231. package/dist/core/usage-totals.d.ts +1 -1
  232. package/dist/core/usage-totals.d.ts.map +1 -1
  233. package/dist/core/usage-totals.js +5 -1
  234. package/dist/core/usage-totals.js.map +1 -1
  235. package/dist/extensions/llama/client.d.ts +2 -0
  236. package/dist/extensions/llama/client.d.ts.map +1 -1
  237. package/dist/extensions/llama/client.js +7 -3
  238. package/dist/extensions/llama/client.js.map +1 -1
  239. package/dist/extensions/llama/provider.d.ts.map +1 -1
  240. package/dist/extensions/llama/provider.js +20 -5
  241. package/dist/extensions/llama/provider.js.map +1 -1
  242. package/dist/index.d.ts +5 -4
  243. package/dist/index.d.ts.map +1 -1
  244. package/dist/index.js +1 -1
  245. package/dist/index.js.map +1 -1
  246. package/dist/main.d.ts.map +1 -1
  247. package/dist/main.js +21 -13
  248. package/dist/main.js.map +1 -1
  249. package/dist/modes/interactive/bug-report.d.ts +16 -0
  250. package/dist/modes/interactive/bug-report.d.ts.map +1 -0
  251. package/dist/modes/interactive/bug-report.js +167 -0
  252. package/dist/modes/interactive/bug-report.js.map +1 -0
  253. package/dist/modes/interactive/chat-viewport.d.ts +20 -0
  254. package/dist/modes/interactive/chat-viewport.d.ts.map +1 -0
  255. package/dist/modes/interactive/chat-viewport.js +28 -0
  256. package/dist/modes/interactive/chat-viewport.js.map +1 -0
  257. package/dist/modes/interactive/components/assistant-message.d.ts +7 -0
  258. package/dist/modes/interactive/components/assistant-message.d.ts.map +1 -1
  259. package/dist/modes/interactive/components/assistant-message.js +63 -17
  260. package/dist/modes/interactive/components/assistant-message.js.map +1 -1
  261. package/dist/modes/interactive/components/branch-summary-message.d.ts.map +1 -1
  262. package/dist/modes/interactive/components/branch-summary-message.js +12 -5
  263. package/dist/modes/interactive/components/branch-summary-message.js.map +1 -1
  264. package/dist/modes/interactive/components/compaction-summary-message.d.ts.map +1 -1
  265. package/dist/modes/interactive/components/compaction-summary-message.js +12 -5
  266. package/dist/modes/interactive/components/compaction-summary-message.js.map +1 -1
  267. package/dist/modes/interactive/components/custom-editor.d.ts +12 -0
  268. package/dist/modes/interactive/components/custom-editor.d.ts.map +1 -1
  269. package/dist/modes/interactive/components/custom-editor.js +38 -0
  270. package/dist/modes/interactive/components/custom-editor.js.map +1 -1
  271. package/dist/modes/interactive/components/error-summary.d.ts +11 -0
  272. package/dist/modes/interactive/components/error-summary.d.ts.map +1 -0
  273. package/dist/modes/interactive/components/error-summary.js +31 -0
  274. package/dist/modes/interactive/components/error-summary.js.map +1 -0
  275. package/dist/modes/interactive/components/extension-editor.d.ts +4 -1
  276. package/dist/modes/interactive/components/extension-editor.d.ts.map +1 -1
  277. package/dist/modes/interactive/components/extension-editor.js +6 -1
  278. package/dist/modes/interactive/components/extension-editor.js.map +1 -1
  279. package/dist/modes/interactive/components/extension-input.d.ts +2 -0
  280. package/dist/modes/interactive/components/extension-input.d.ts.map +1 -1
  281. package/dist/modes/interactive/components/extension-input.js +6 -0
  282. package/dist/modes/interactive/components/extension-input.js.map +1 -1
  283. package/dist/modes/interactive/components/extension-selector.d.ts +2 -0
  284. package/dist/modes/interactive/components/extension-selector.d.ts.map +1 -1
  285. package/dist/modes/interactive/components/extension-selector.js +4 -0
  286. package/dist/modes/interactive/components/extension-selector.js.map +1 -1
  287. package/dist/modes/interactive/components/footer.d.ts.map +1 -1
  288. package/dist/modes/interactive/components/footer.js +4 -1
  289. package/dist/modes/interactive/components/footer.js.map +1 -1
  290. package/dist/modes/interactive/components/index.d.ts +1 -1
  291. package/dist/modes/interactive/components/index.d.ts.map +1 -1
  292. package/dist/modes/interactive/components/index.js.map +1 -1
  293. package/dist/modes/interactive/components/model-selector.d.ts.map +1 -1
  294. package/dist/modes/interactive/components/model-selector.js +3 -3
  295. package/dist/modes/interactive/components/model-selector.js.map +1 -1
  296. package/dist/modes/interactive/components/scoped-models-selector.d.ts.map +1 -1
  297. package/dist/modes/interactive/components/scoped-models-selector.js +13 -15
  298. package/dist/modes/interactive/components/scoped-models-selector.js.map +1 -1
  299. package/dist/modes/interactive/components/session-selector.d.ts +5 -5
  300. package/dist/modes/interactive/components/session-selector.d.ts.map +1 -1
  301. package/dist/modes/interactive/components/session-selector.js +74 -48
  302. package/dist/modes/interactive/components/session-selector.js.map +1 -1
  303. package/dist/modes/interactive/components/settings-selector.d.ts +5 -1
  304. package/dist/modes/interactive/components/settings-selector.d.ts.map +1 -1
  305. package/dist/modes/interactive/components/settings-selector.js +35 -10
  306. package/dist/modes/interactive/components/settings-selector.js.map +1 -1
  307. package/dist/modes/interactive/components/skill-invocation-message.d.ts.map +1 -1
  308. package/dist/modes/interactive/components/skill-invocation-message.js +11 -4
  309. package/dist/modes/interactive/components/skill-invocation-message.js.map +1 -1
  310. package/dist/modes/interactive/components/status-indicator.d.ts +3 -1
  311. package/dist/modes/interactive/components/status-indicator.d.ts.map +1 -1
  312. package/dist/modes/interactive/components/status-indicator.js +10 -3
  313. package/dist/modes/interactive/components/status-indicator.js.map +1 -1
  314. package/dist/modes/interactive/components/thinking-selector.d.ts.map +1 -1
  315. package/dist/modes/interactive/components/thinking-selector.js +6 -6
  316. package/dist/modes/interactive/components/thinking-selector.js.map +1 -1
  317. package/dist/modes/interactive/components/tool-execution.d.ts +27 -5
  318. package/dist/modes/interactive/components/tool-execution.d.ts.map +1 -1
  319. package/dist/modes/interactive/components/tool-execution.js +64 -42
  320. package/dist/modes/interactive/components/tool-execution.js.map +1 -1
  321. package/dist/modes/interactive/components/tree-selector.d.ts.map +1 -1
  322. package/dist/modes/interactive/components/tree-selector.js +9 -0
  323. package/dist/modes/interactive/components/tree-selector.js.map +1 -1
  324. package/dist/modes/interactive/components/trust-selector.d.ts.map +1 -1
  325. package/dist/modes/interactive/components/trust-selector.js +2 -2
  326. package/dist/modes/interactive/components/trust-selector.js.map +1 -1
  327. package/dist/modes/interactive/interactive-mode.d.ts +43 -29
  328. package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
  329. package/dist/modes/interactive/interactive-mode.js +431 -166
  330. package/dist/modes/interactive/interactive-mode.js.map +1 -1
  331. package/dist/modes/interactive/theme/dark.json +2 -1
  332. package/dist/modes/interactive/theme/light.json +2 -1
  333. package/dist/modes/interactive/theme/theme-controller.d.ts +1 -0
  334. package/dist/modes/interactive/theme/theme-controller.d.ts.map +1 -1
  335. package/dist/modes/interactive/theme/theme-controller.js +5 -0
  336. package/dist/modes/interactive/theme/theme-controller.js.map +1 -1
  337. package/dist/modes/interactive/theme/theme-json.d.ts +84 -0
  338. package/dist/modes/interactive/theme/theme-json.d.ts.map +1 -0
  339. package/dist/modes/interactive/theme/theme-json.js +130 -0
  340. package/dist/modes/interactive/theme/theme-json.js.map +1 -0
  341. package/dist/modes/interactive/theme/theme-schema.json +6 -2
  342. package/dist/modes/interactive/theme/theme.d.ts +14 -5
  343. package/dist/modes/interactive/theme/theme.d.ts.map +1 -1
  344. package/dist/modes/interactive/theme/theme.js +18 -119
  345. package/dist/modes/interactive/theme/theme.js.map +1 -1
  346. package/dist/modes/interactive/tui-renderer.d.ts +21 -0
  347. package/dist/modes/interactive/tui-renderer.d.ts.map +1 -0
  348. package/dist/modes/interactive/tui-renderer.js +66 -0
  349. package/dist/modes/interactive/tui-renderer.js.map +1 -0
  350. package/dist/modes/rpc/rpc-mode.d.ts.map +1 -1
  351. package/dist/modes/rpc/rpc-mode.js +2 -2
  352. package/dist/modes/rpc/rpc-mode.js.map +1 -1
  353. package/dist/package-manager-cli.d.ts.map +1 -1
  354. package/dist/package-manager-cli.js +31 -72
  355. package/dist/package-manager-cli.js.map +1 -1
  356. package/dist/testing/replay/recorded-provider.d.ts +2 -2
  357. package/dist/testing/replay/recorded-provider.d.ts.map +1 -1
  358. package/dist/testing/replay/recorded-provider.js +2 -5
  359. package/dist/testing/replay/recorded-provider.js.map +1 -1
  360. package/dist/testing/replay/runner.d.ts.map +1 -1
  361. package/dist/testing/replay/runner.js +0 -1
  362. package/dist/testing/replay/runner.js.map +1 -1
  363. package/dist/utils/clipboard-command.d.ts +7 -0
  364. package/dist/utils/clipboard-command.d.ts.map +1 -0
  365. package/dist/utils/clipboard-command.js +45 -0
  366. package/dist/utils/clipboard-command.js.map +1 -0
  367. package/dist/utils/clipboard-image.d.ts.map +1 -1
  368. package/dist/utils/clipboard-image.js +53 -81
  369. package/dist/utils/clipboard-image.js.map +1 -1
  370. package/dist/utils/clipboard.d.ts.map +1 -1
  371. package/dist/utils/clipboard.js +111 -121
  372. package/dist/utils/clipboard.js.map +1 -1
  373. package/dist/utils/exif-orientation.d.ts.map +1 -1
  374. package/dist/utils/exif-orientation.js +2 -3
  375. package/dist/utils/exif-orientation.js.map +1 -1
  376. package/dist/utils/mime.d.ts.map +1 -1
  377. package/dist/utils/mime.js +1 -1
  378. package/dist/utils/mime.js.map +1 -1
  379. package/dist/utils/syntax-highlight.d.ts.map +1 -1
  380. package/dist/utils/syntax-highlight.js +21 -21
  381. package/dist/utils/syntax-highlight.js.map +1 -1
  382. package/dist/utils/tool-result-images.d.ts +3 -1
  383. package/dist/utils/tool-result-images.d.ts.map +1 -1
  384. package/dist/utils/tool-result-images.js +4 -1
  385. package/dist/utils/tool-result-images.js.map +1 -1
  386. package/dist/utils/tools-manager.d.ts.map +1 -1
  387. package/dist/utils/tools-manager.js +11 -2
  388. package/dist/utils/tools-manager.js.map +1 -1
  389. package/dist/utils/wsl.d.ts +3 -0
  390. package/dist/utils/wsl.d.ts.map +1 -0
  391. package/dist/utils/wsl.js +15 -0
  392. package/dist/utils/wsl.js.map +1 -0
  393. package/dist/utils/zip.d.ts +7 -0
  394. package/dist/utils/zip.d.ts.map +1 -0
  395. package/dist/utils/zip.js +60 -0
  396. package/dist/utils/zip.js.map +1 -0
  397. package/docs/cli-integration.md +107 -0
  398. package/docs/cli.md +269 -0
  399. package/docs/compaction.md +73 -24
  400. package/docs/configuration.md +45 -0
  401. package/docs/containerization.md +22 -21
  402. package/docs/custom-provider.md +21 -12
  403. package/docs/development.md +19 -0
  404. package/docs/docs.json +139 -95
  405. package/docs/extensions.md +243 -371
  406. package/docs/how-pi-works.md +49 -0
  407. package/docs/images/interactive-mode.png +0 -0
  408. package/docs/index.md +3 -3
  409. package/docs/json.md +7 -3
  410. package/docs/keybindings.md +57 -54
  411. package/docs/llama-cpp.md +2 -2
  412. package/docs/message-types.md +261 -0
  413. package/docs/models.md +103 -49
  414. package/docs/packages.md +49 -47
  415. package/docs/prompt-templates.md +29 -56
  416. package/docs/providers.md +91 -139
  417. package/docs/quickstart.md +42 -14
  418. package/docs/rpc-commands.md +854 -0
  419. package/docs/rpc-extension-ui.md +200 -0
  420. package/docs/rpc.md +121 -223
  421. package/docs/sdk.md +99 -124
  422. package/docs/security.md +7 -7
  423. package/docs/session-format.md +85 -82
  424. package/docs/sessions.md +41 -57
  425. package/docs/settings.md +94 -87
  426. package/docs/shell-aliases.md +68 -3
  427. package/docs/skills.md +62 -49
  428. package/docs/slash-commands.md +60 -0
  429. package/docs/terminal-setup.md +86 -55
  430. package/docs/termux.md +65 -75
  431. package/docs/themes.md +64 -86
  432. package/docs/tmux.md +29 -7
  433. package/docs/tui.md +67 -122
  434. package/docs/usage.md +31 -42
  435. package/docs/windows.md +41 -13
  436. package/examples/README.md +16 -2
  437. package/examples/extensions/README.md +0 -1
  438. package/examples/extensions/custom-provider-anthropic/index.ts +18 -12
  439. package/examples/extensions/custom-provider-anthropic/package-lock.json +2 -2
  440. package/examples/extensions/custom-provider-anthropic/package.json +1 -1
  441. package/examples/extensions/custom-provider-gitlab-duo/index.ts +2 -2
  442. package/examples/extensions/custom-provider-gitlab-duo/package.json +1 -1
  443. package/examples/extensions/dynamic-resources/dynamic.json +2 -0
  444. package/examples/extensions/gondolin/package-lock.json +2 -2
  445. package/examples/extensions/gondolin/package.json +1 -1
  446. package/examples/extensions/prompt-customizer.ts +19 -67
  447. package/examples/extensions/sandbox/package-lock.json +2 -2
  448. package/examples/extensions/sandbox/package.json +1 -1
  449. package/examples/extensions/with-deps/package-lock.json +2 -2
  450. package/examples/extensions/with-deps/package.json +1 -1
  451. package/examples/plugins/pi-example-plugin/README.md +38 -0
  452. package/examples/plugins/pi-example-plugin/package.json +10 -0
  453. package/examples/plugins/pi-example-plugin/src/contract.ts +13 -0
  454. package/examples/plugins/pi-example-plugin/src/session.ts +24 -0
  455. package/examples/plugins/pi-example-plugin/src/tui.ts +31 -0
  456. package/examples/rpc-client.ts +35 -0
  457. package/examples/rpc-extension-ui.ts +25 -5
  458. package/examples/sdk/README.md +1 -1
  459. package/npm-shrinkwrap.json +861 -513
  460. package/package.json +27 -18
  461. package/dist/bun/register-bedrock.d.ts +0 -2
  462. package/dist/bun/register-bedrock.d.ts.map +0 -1
  463. package/dist/bun/register-bedrock.js +0 -4
  464. package/dist/bun/register-bedrock.js.map +0 -1
  465. package/dist/cli/experimental/auth.d.ts +0 -16
  466. package/dist/cli/experimental/auth.d.ts.map +0 -1
  467. package/dist/cli/experimental/auth.js +0 -13
  468. package/dist/cli/experimental/auth.js.map +0 -1
  469. package/dist/cli/experimental/cli.d.ts +0 -6
  470. package/dist/cli/experimental/cli.d.ts.map +0 -1
  471. package/dist/cli/experimental/cli.js +0 -5
  472. package/dist/cli/experimental/cli.js.map +0 -1
  473. package/dist/cli/experimental/command-options.d.ts +0 -17
  474. package/dist/cli/experimental/command-options.d.ts.map +0 -1
  475. package/dist/cli/experimental/command-options.js +0 -35
  476. package/dist/cli/experimental/command-options.js.map +0 -1
  477. package/dist/cli/experimental/command.d.ts +0 -63
  478. package/dist/cli/experimental/command.d.ts.map +0 -1
  479. package/dist/cli/experimental/command.js +0 -130
  480. package/dist/cli/experimental/command.js.map +0 -1
  481. package/dist/cli/experimental/commands/client.d.ts +0 -13
  482. package/dist/cli/experimental/commands/client.d.ts.map +0 -1
  483. package/dist/cli/experimental/commands/client.js +0 -25
  484. package/dist/cli/experimental/commands/client.js.map +0 -1
  485. package/dist/cli/experimental/commands/pi.d.ts +0 -15
  486. package/dist/cli/experimental/commands/pi.d.ts.map +0 -1
  487. package/dist/cli/experimental/commands/pi.js +0 -28
  488. package/dist/cli/experimental/commands/pi.js.map +0 -1
  489. package/dist/cli/experimental/commands/server.d.ts +0 -13
  490. package/dist/cli/experimental/commands/server.d.ts.map +0 -1
  491. package/dist/cli/experimental/commands/server.js +0 -25
  492. package/dist/cli/experimental/commands/server.js.map +0 -1
  493. package/dist/cli/experimental/transport-address.d.ts +0 -10
  494. package/dist/cli/experimental/transport-address.d.ts.map +0 -1
  495. package/dist/cli/experimental/transport-address.js +0 -38
  496. package/dist/cli/experimental/transport-address.js.map +0 -1
  497. package/dist/client/index.d.ts +0 -3
  498. package/dist/client/index.d.ts.map +0 -1
  499. package/dist/client/index.js +0 -3
  500. package/dist/client/index.js.map +0 -1
  501. package/dist/client/remote-session.d.ts +0 -53
  502. package/dist/client/remote-session.d.ts.map +0 -1
  503. package/dist/client/remote-session.js +0 -340
  504. package/dist/client/remote-session.js.map +0 -1
  505. package/dist/client/transcript.d.ts +0 -12
  506. package/dist/client/transcript.d.ts.map +0 -1
  507. package/dist/client/transcript.js +0 -98
  508. package/dist/client/transcript.js.map +0 -1
  509. package/dist/utils/clipboard-native.d.ts +0 -11
  510. package/dist/utils/clipboard-native.d.ts.map +0 -1
  511. package/dist/utils/clipboard-native.js +0 -20
  512. package/dist/utils/clipboard-native.js.map +0 -1
@@ -6,29 +6,15 @@ Extensions are TypeScript modules that extend Apex Code's behavior. They can sub
6
6
 
7
7
  > **Placement for /reload:** Put extensions in `~/.apex-code/agent/extensions/` (global) or `.apex-code/extensions/` (project-local) for auto-discovery. Use `apex-code -e ./path.ts` only for quick tests. Extensions in auto-discovered locations can be hot-reloaded with `/reload`.
8
8
 
9
- **Key capabilities:**
10
- - **Custom tools** - Register tools the LLM can call via `pi.registerTool()`
11
- - **Event interception** - Block or modify tool calls, inject context, customize compaction
12
- - **User interaction** - Prompt users via `ctx.ui` (select, confirm, input, notify)
13
- - **Custom UI components** - Full TUI components with keyboard input via `ctx.ui.custom()` for complex interactions
14
- - **Custom commands** - Register commands like `/mycommand` via `pi.registerCommand()`
15
- - **Session persistence** - Store state that survives restarts via `pi.appendEntry()`
16
- - **Custom rendering** - Control how tool calls/results and messages appear in TUI
17
-
18
- **Example use cases:**
19
- - Permission gates (confirm before `rm -rf`, `sudo`, etc.)
20
- - Git checkpointing (stash at each turn, restore on branch)
21
- - Path protection (block writes to `.env`, `node_modules/`)
22
- - Custom compaction (summarize conversation your way)
23
- - Conversation summaries (see `summarize.ts` example)
24
- - Interactive tools (questions, wizards, custom dialogs)
25
- - Stateful tools (todo lists, connection pools)
26
- - External integrations (file watchers, webhooks, CI triggers)
27
- - Games while you wait (see `snake.ts` example)
28
-
29
- See [examples/extensions/](../examples/extensions/) for working implementations.
30
-
31
- ## Table of Contents
9
+ Typical extensions add an agent tool, protect paths, confirm dangerous commands, react to session events, modify context, expose a command, or display persistent status.
10
+
11
+ <a id="quick-start"></a>
12
+ <a id="writing-an-extension"></a>
13
+ <a id="create-an-extension"></a>
14
+
15
+ ## Create and load an extension
16
+
17
+ An extension exports a default factory that receives `ExtensionAPI`. The factory registers capabilities for the current extension runtime.
32
18
 
33
19
  - [Quick Start](#quick-start)
34
20
  - [Extension Locations](#extension-locations)
@@ -62,53 +48,26 @@ import type { ExtensionAPI } from "apex-code";
62
48
  import { Type } from "typebox";
63
49
 
64
50
  export default function (pi: ExtensionAPI) {
65
- // React to events
66
- pi.on("session_start", async (_event, ctx) => {
67
- ctx.ui.notify("Extension loaded!", "info");
68
- });
69
-
70
- pi.on("tool_call", async (event, ctx) => {
71
- if (event.toolName === "bash" && event.input.command?.includes("rm -rf")) {
72
- const ok = await ctx.ui.confirm("Dangerous!", "Allow rm -rf?");
73
- if (!ok) return { block: true, reason: "Blocked by user" };
74
- }
75
- });
76
-
77
- // Register a custom tool
78
- pi.registerTool({
79
- name: "greet",
80
- label: "Greet",
81
- description: "Greet someone by name",
82
- parameters: Type.Object({
83
- name: Type.String({ description: "Name to greet" }),
84
- }),
85
- async execute(toolCallId, params, signal, onUpdate, ctx) {
86
- return {
87
- content: [{ type: "text", text: `Hello, ${params.name}!` }],
88
- details: {},
89
- };
90
- },
91
- });
92
-
93
- // Register a command
94
51
  pi.registerCommand("hello", {
95
- description: "Say hello",
96
- handler: async (args, ctx) => {
97
- ctx.ui.notify(`Hello ${args || "world"}!`, "info");
52
+ description: "Show a greeting",
53
+ handler: async (name, ctx) => {
54
+ ctx.ui.notify(`Hello, ${name || "world"}!`, "info");
98
55
  },
99
56
  });
100
57
  }
101
58
  ```
102
59
 
103
- Test with `--extension` (or `-e`) flag:
60
+ Start Pi and run `/hello`. During development, load a file directly:
104
61
 
105
62
  ```bash
106
63
  apex-code -e ./my-extension.ts
107
64
  ```
108
65
 
109
- ## Extension Locations
66
+ Pi uses `jiti`, so local TypeScript extensions do not need a separate compilation step. Use [Pi packages](packages.md) for distributed extensions and dependencies.
110
67
 
111
- > **Security:** Extensions run with your full system permissions and can execute arbitrary code. Only install from sources you trust.
68
+ <a id="extension-locations"></a>
69
+ <a id="available-imports"></a>
70
+ <a id="choose-where-it-loads"></a>
112
71
 
113
72
  Extensions are auto-discovered from trusted locations. Project-local `.apex-code/extensions` entries load only after the project is trusted.
114
73
 
@@ -119,7 +78,7 @@ Extensions are auto-discovered from trusted locations. Project-local `.apex-code
119
78
  | `.apex-code/extensions/*.ts` | Project-local |
120
79
  | `.apex-code/extensions/*/index.ts` | Project-local (subdirectory) |
121
80
 
122
- Additional paths via `settings.json`:
81
+ Use a single file for a small extension and a directory for a multi-file implementation. Put npm dependencies in a nearby `package.json`. See [Configuration](configuration.md) for conventional locations and [Settings](settings.md#resources) for additional paths.
123
82
 
124
83
  ```json
125
84
  {
@@ -294,7 +253,8 @@ user sends prompt ────────────────────
294
253
  │ ┌─── turn (repeats while LLM calls tools) ───┐ │
295
254
  │ │ │ │
296
255
  │ ├─► turn_start │ │
297
- │ ├─► context (can modify messages) │ │
256
+ │ ├─► context (can modify conversation messages) │
257
+ │ ├─► context_with_system (can modify the full transcript)
298
258
  │ ├─► before_provider_headers (can mutate headers) |
299
259
  │ ├─► before_provider_request (can inspect or replace payload)
300
260
  │ ├─► after_provider_response (status + headers, before stream consume)
@@ -307,9 +267,13 @@ user sends prompt ────────────────────
307
267
  │ │ └─► tool_execution_end │ │
308
268
  │ │ │ │
309
269
  │ └─► turn_end │ │
270
+ │ └─► threshold compaction before a naturally required next turn
310
271
  │ │
311
272
  ├─► agent_end │
312
- └─► agent_settled (no retry/compaction/follow-up left) │
273
+ ├─► retry backoff or final-attempt recovery (when selected)
274
+ │ └─► fresh agent_start on successful recovery │
275
+ ├─► agent_before_settle (can append entries and continue)│
276
+ └─► agent_settled (final, notification only) │
313
277
  │
314
278
  user sends another prompt ◄────────────────────────────────┘
315
279
 
@@ -538,10 +502,13 @@ pi.on("before_agent_start", async (event, ctx) => {
538
502
  // event.systemPrompt - current chained system prompt for this handler
539
503
  // (includes changes from earlier before_agent_start handlers)
540
504
  // event.systemPromptOptions - structured options used to build the system prompt
541
- // .customPrompt - any custom system prompt (from --system-prompt, SYSTEM.md, or custom templates)
505
+ // .customPrompt - exact prompt prefix from --system-prompt, SYSTEM.md, or custom templates
506
+ // .forceSystemPrompt - optional exact replacement for the complete prompt
542
507
  // .selectedTools - tools currently active in the prompt
543
508
  // .toolSnippets - one-line descriptions for each tool
544
- // .promptGuidelines - custom guideline bullets
509
+ // .toolGuidelines - guideline bullets keyed by tool name
510
+ // .promptGuidelines - additional custom guideline bullets
511
+ // .sections - custom XML-wrapped sections keyed by tag name
545
512
  // .appendSystemPrompt - text from --append-system-prompt flags
546
513
  // .cwd - working directory
547
514
  // .contextFiles - AGENTS.md files and other loaded context files
@@ -564,9 +531,9 @@ The `systemPromptOptions` field gives extensions access to the same structured d
564
531
 
565
532
  Inside `before_agent_start`, `event.systemPrompt` and `ctx.getSystemPrompt()` both reflect the chained system prompt as of the current handler. Later `before_agent_start` handlers can still modify it again.
566
533
 
567
- #### agent_start / agent_end / agent_settled
534
+ #### agent_start / agent_end / agent_before_settle / agent_settled
568
535
 
569
- `agent_start` fires when a low-level agent run begins. `agent_end` fires when that run ends, but Apex Code may still auto-retry, auto-compact and retry, or continue with queued follow-up messages. Use `agent_settled` for status integrations that need to know Apex Code will not continue running automatically.
536
+ `agent_start` fires when a low-level agent run begins. `agent_end` fires when that run ends, but Apex Code may still retry, compact and retry, or continue with queued follow-up messages. `agent_before_settle` is the final actionable boundary. It can append session entries and request one continuation. `agent_settled` is final and notification-only. Use it when a status integration needs to know that Apex Code will not continue running automatically.
570
537
 
571
538
  ```typescript
572
539
  pi.on("agent_start", async (_event, ctx) => {});
@@ -575,11 +542,28 @@ pi.on("agent_end", async (event, ctx) => {
575
542
  // event.messages - messages from this low-level run
576
543
  });
577
544
 
545
+ let addedReviewReminder = false;
546
+ pi.on("agent_before_settle", async (event, ctx) => {
547
+ if (addedReviewReminder) return;
548
+ addedReviewReminder = true;
549
+ return {
550
+ entries: [...event.entries, {
551
+ type: "custom_message",
552
+ customType: "review-reminder",
553
+ content: "Review the final diff before replying.",
554
+ display: false,
555
+ }],
556
+ continue: true,
557
+ };
558
+ });
559
+
578
560
  pi.on("agent_settled", async (_event, ctx) => {
579
- // ctx.isIdle() is true here unless another extension started a new run.
561
+ // ctx.isIdle() is true; runs requested here start after all settled handlers finish.
580
562
  });
581
563
  ```
582
564
 
565
+ If the run is aborted while `agent_before_settle` handlers are running, valid returned entries are still committed, but requested continuation is suppressed. Work requested from `agent_settled` is deferred until every settled handler completes, so notification dispatch is non-reentrant.
566
+
583
567
  #### ui_prompt_start / ui_prompt_end
584
568
 
585
569
  Notification-only lifecycle events for blocking user-facing extension UI prompts. They fire around `ctx.ui.select()`, `ctx.ui.confirm()`, `ctx.ui.input()`, `ctx.ui.editor()`, and `ctx.ui.custom()` so host/status integrations can report "waiting for user" instead of just "running".
@@ -607,11 +591,34 @@ pi.on("turn_start", async (event, ctx) => {
607
591
  // event.turnIndex, event.timestamp
608
592
  });
609
593
 
594
+ let replacedResponse = false;
610
595
  pi.on("turn_end", async (event, ctx) => {
611
596
  // event.turnIndex, event.message, event.toolResults
597
+ // event.entries contains the structural entries proposed so far.
598
+ if (replacedResponse || event.outcome !== "completed" || event.toolResults.length > 0) return;
599
+ replacedResponse = true;
600
+ return {
601
+ entries: [
602
+ ...event.entries,
603
+ { type: "context_edit", targetId: event.messageEntryId, replacement: null },
604
+ {
605
+ type: "custom_message",
606
+ customType: "replacement-instruction",
607
+ content: "Answer again using the persisted user request.",
608
+ display: false,
609
+ },
610
+ ],
611
+ continue: true,
612
+ };
612
613
  });
613
614
  ```
614
615
 
616
+ `turn_end` runs after the assistant and tool-result messages have been persisted and before the low-level `turn_end` event. Retry backoff and final-attempt recovery still happen after `agent_end`, preserving their existing lifecycle and queue ordering; `agent_before_settle` sees the repaired projection after that work completes. Boundary handlers run in extension load and registration order. Each handler sees prior proposals in `event.entries` and sees `event.context` rebuilt from them. Returning `entries` or `continue` replaces only that field; omitted fields preserve the current proposal. Allowed draft entry types are `custom`, `custom_message`, `context_edit`, and `compaction`. The complete proposal is validated before it is appended in list order after all handlers finish; a handler error is reported and later handlers still run. Validation prevents partially applied semantic errors, but persistence is not transactional.
617
+
618
+ `continue: true` ensures one next provider request for that boundary invocation. If tool results, steering, or a follow-up already cause that request, they satisfy the decision and no additional request is made; otherwise Pi makes one context-only request. Error and aborted responses remain hard exits. `continue: false` never suppresses natural work. Guard continuation conditions: an unconditional `continue: true` is evaluated again after the next response and can create an endless loop. A `custom_message` draft contributes a user-role model message but is extension-authored: it does not run human input hooks, slash commands, skills, or prompt templates.
619
+
620
+ Host integrations that construct `TurnEndEvent` values must now provide `messageEntryId`, `toolResultEntryIds`, `outcome`, `entries`, `continue`, and `context`. `ExtensionEvent` exhaustive switches must also handle `agent_before_settle`. `ExtensionRunner.emit()` excludes actionable turn boundaries; dispatch `turn_end` and `agent_before_settle` through `emitBoundary(baseEvent, buildContext)` so handlers receive chained previews. Other dedicated runner methods still return results for events such as `session_before_*`.
621
+
615
622
  #### message_start / message_update / message_end
616
623
 
617
624
  Fired for message lifecycle updates.
@@ -678,12 +685,31 @@ Fired before each LLM call. Modify messages non-destructively. See [Session Form
678
685
 
679
686
  ```typescript
680
687
  pi.on("context", async (event, ctx) => {
681
- // event.messages - deep copy, safe to modify
688
+ // event.messages - deep copy without system messages, safe to modify
682
689
  const filtered = event.messages.filter(m => !shouldPrune(m));
683
690
  return { messages: filtered };
684
691
  });
685
692
  ```
686
693
 
694
+ `event.messages` holds the conversation without system messages. The prompt and tool declarations belong to Pi and are not part of this hook: when the handler returns a changed list, Pi replays the current prompt sections and tool declarations into one leading system message ahead of the returned messages. Filtering, windowing, or slicing from a compaction summary therefore cannot drop the prompt or the tools. An unchanged list keeps mid-conversation system messages in place, so models that accept them retain their cached prefix. System messages a handler adds are kept after Pi's head. To change the prompt or the tool set durably, use [`before_agent_start`](#before_agent_start) or `pi.setActiveTools()`; to edit system messages for one request, use [`context_with_system`](#context_with_system).
695
+
696
+ #### context_with_system
697
+
698
+ Fired before each LLM call, after every `context` handler has run and Pi has restored the prompt and tool state. `event.messages` is the full transcript, including the leading system message and any mid-conversation prompt or tool patches (see [Session Format](session-format.md#sessionmessageentry)). The returned messages are sent as they are: this hook owns the prompt and tool declarations for the request.
699
+
700
+ ```typescript
701
+ import { getCurrentSystemMessage } from "@earendil-works/pi-ai";
702
+
703
+ pi.on("context_with_system", async (event, ctx) => {
704
+ const cut = findCutIndex(event.messages);
705
+ // Fold the dropped prefix so its prompt and tool state survives as the new head.
706
+ const head = getCurrentSystemMessage(event.messages.slice(0, cut));
707
+ return { messages: head ? [head, ...event.messages.slice(cut)] : event.messages.slice(cut) };
708
+ });
709
+ ```
710
+
711
+ Rules: keep a system message at index 0 (providers read the prompt and initial tool declarations there; Pi reports an error if a handler drops it). Removing a system message removes the tool declarations and section patches it carries. Check your output with `getCurrentSystemPrompt()` and `getCurrentTools()` from `@earendil-works/pi-ai`. Handlers run in extension load order; a `systemPrompt` forced from `before_agent_start` is still projected onto the request afterwards.
712
+
687
713
  #### before_provider_headers
688
714
 
689
715
  Fired after the outgoing HTTP headers are assembled. Use it to add, override, or remove request headers.
@@ -735,6 +761,25 @@ pi.on("after_provider_response", (event, ctx) => {
735
761
 
736
762
  Header availability depends on provider and transport. Providers that abstract HTTP responses may not expose headers.
737
763
 
764
+ #### cache_warming_decision
765
+
766
+ Fired before each prompt-cache refresh with pi's decision filled in. The event carries only pi's cost estimates; use `ctx.model`, `ctx.isIdle()`, and `ctx.getContextUsage()` for everything else.
767
+
768
+ ```typescript
769
+ pi.on("cache_warming_decision", (event, ctx) => {
770
+ // event.warmCost: price of this refresh
771
+ // event.missCost: extra price of the next request if the entry is lost
772
+ // event.continuationProbability: pi's estimate that a request arrives in time
773
+ // event.action: "warm" | "stop", pi's decision
774
+
775
+ if (ctx.model?.provider === "my-provider") {
776
+ return { action: "stop" };
777
+ }
778
+ });
779
+ ```
780
+
781
+ Return `{ action: "warm" }` or `{ action: "stop" }` to override; the last handler that returns an action wins. `"stop"` ends warming until the next real request.
782
+
738
783
  ### Model Events
739
784
 
740
785
  #### model_select
@@ -906,6 +951,8 @@ pi.on("user_bash", (event, ctx) => {
906
951
  });
907
952
  ```
908
953
 
954
+ Returning `undefined` continues to the next handler, then local execution if none handles the event. A valid result stops propagation: `operations` executes the command through the supplied backend, while `result` records the completed command without executing it.
955
+
909
956
  ### Input Events
910
957
 
911
958
  #### input
@@ -1016,6 +1063,12 @@ Access to models, providers, and resolved authentication. `ctx.modelRegistry.get
1016
1063
 
1017
1064
  `ctx.scopedModels` is the read-only list of models scoped to the current session — the same set the `/scoped-models` command shows. It is resolved at session start from the `--models` CLI flag and the `enabledModels` setting (matched against the available catalogue with minimatch on `provider/modelId` or a bare `modelId`). It is empty when no scoping is configured, meaning every available model is usable. Each entry is `{ model, thinkingLevel? }`, where `thinkingLevel` is set only when a pattern pinned it (e.g. `anthropic/*:high`). Use it to populate a model picker that mirrors the built-in one instead of enumerating the whole catalogue via `ctx.modelRegistry.getAvailable()`.
1018
1065
 
1066
+ #### Streaming model calls
1067
+
1068
+ Use `ctx.modelRegistry.streamSimple(model, context, options)` for provider-neutral options such as `reasoning`, or `stream()` for API-specific options. Both use configured providers and resolve authentication, including for providers registered with `pi.registerProvider()`. Use these instead of `pi-ai/compat` streaming functions, which cannot see extension provider registrations.
1069
+
1070
+ Both return an `AssistantMessageEventStream`. Iterate it for response events and await `.result()` for the final message. Setup failures produce error events and error results.
1071
+
1019
1072
  ### ctx.signal
1020
1073
 
1021
1074
  The current agent abort signal, or `undefined` when no agent turn is active.
@@ -1119,7 +1172,7 @@ const options = ctx.getSystemPromptOptions();
1119
1172
  const contextPaths = options.contextFiles?.map((file) => file.path) ?? [];
1120
1173
  ```
1121
1174
 
1122
- This has the same shape and mutability as `before_agent_start` `event.systemPromptOptions`: custom prompt, active tools, tool snippets, prompt guidelines, appended system prompt text, cwd, loaded context files, and loaded skills. It may include full context file contents, so treat it as sensitive extension-local data and avoid exposing it through command lists, logs, or autocomplete metadata.
1175
+ This has the same shape and mutability as `before_agent_start` `event.systemPromptOptions`: custom or forced prompt, active tools, tool snippets, per-tool and custom rules, custom sections, appended prompt text, cwd, loaded context files, and loaded skills. It may include full context file contents, so treat it as sensitive extension-local data and avoid exposing it through command lists, logs, or autocomplete metadata.
1123
1176
 
1124
1177
  This reports the current base prompt inputs. It does not include per-turn `before_agent_start` chained system-prompt changes, later `context` event message mutations, or `before_provider_request` payload rewrites.
1125
1178
 
@@ -1197,7 +1250,7 @@ Options:
1197
1250
 
1198
1251
  ### ctx.navigateTree(targetId, options?)
1199
1252
 
1200
- Navigate to a different point in the session tree:
1253
+ Navigate to a different point in the session tree. Rejects while an agent response, manual or automatic compaction, or another tree navigation is active, even with `summarize: false`. These conflicts leave the active branch unchanged and reject the promise rather than returning `{ cancelled: true }`. Wait for the active operation to finish (for example, with `await ctx.waitForIdle()` in a command handler) and retry:
1201
1254
 
1202
1255
  ```typescript
1203
1256
  const result = await ctx.navigateTree("entry-id-456", {
@@ -1360,7 +1413,16 @@ export default function (pi: ExtensionAPI) {
1360
1413
 
1361
1414
  ### pi.on(event, handler)
1362
1415
 
1363
- Subscribe to events. See [Events](#events) for event types and return values.
1416
+ Subscribe to events. Returns an unsubscribe function that removes only that registration. See [Events](#events) for event types and return values.
1417
+
1418
+ ```typescript
1419
+ const unsubscribe = pi.on("agent_end", async (event) => {
1420
+ unsubscribe();
1421
+ await updateIntegration(event.messages);
1422
+ });
1423
+ ```
1424
+
1425
+ Handlers run in extension load order, then registration order within each extension. Adding or removing a handler does not affect a dispatch already in progress.
1364
1426
 
1365
1427
  ### pi.registerTool(definition)
1366
1428
 
@@ -1615,22 +1677,15 @@ pi.registerMarkdownTransformer((markdown, { messageType, isStreaming }) => {
1615
1677
 
1616
1678
  If a transformer throws, Apex Code keeps the Markdown produced so far and continues with the next transformer. The hook is display-only: the original message remains unchanged in the session and model context. It runs for new user messages, assistant streaming updates, restored session messages, and terminal width changes, so transformers should remain synchronous and inexpensive.
1617
1679
 
1618
- ### pi.registerEntryRenderer(customType, renderer)
1680
+ <a id="understand-the-lifecycle"></a>
1619
1681
 
1620
- Register a custom TUI renderer for custom entries with your `customType`. Custom entries are created with `pi.appendEntry()` and do not participate in LLM context.
1682
+ ## Respect the runtime lifecycle
1621
1683
 
1622
- ```typescript
1623
- import { Box, Text } from "@earendil-works/pi-tui";
1684
+ The factory can be synchronous or asynchronous. Pi waits for an asynchronous factory before startup continues, allowing it to fetch configuration or register providers needed during startup.
1624
1685
 
1625
- pi.registerEntryRenderer("status-card", (entry, { expanded }, theme) => {
1626
- const data = entry.data as { title: string; count: number };
1627
- const box = new Box(1, 1, (text) => theme.bg("customMessageBg", text));
1628
- box.addChild(new Text(`${theme.bold(data.title)}: ${data.count}`));
1629
- if (expanded) {
1630
- box.addChild(new Text(theme.fg("dim", JSON.stringify(data, null, 2))));
1631
- }
1632
- return box;
1633
- });
1686
+ Do not start processes, sockets, watchers, or timers in the factory because some invocations load extensions without starting a session.
1687
+ Start long-lived resources from `session_start` or from the command or tool that needs them.
1688
+ Close session-scoped resources from an idempotent `session_shutdown` handler.
1634
1689
 
1635
1690
  pi.appendEntry("status-card", { title: "Indexed files", count: 17 });
1636
1691
  ```
@@ -1703,7 +1758,7 @@ Typical `sourceInfo.source` values:
1703
1758
 
1704
1759
  ### pi.setModel(model)
1705
1760
 
1706
- Set the current model. Returns `false` if no API key is available for the model. See [models.md](models.md) for configuring custom models.
1761
+ Set the model for the current session. The change is recorded in session history and restored when that session is resumed, but it does not change the configured `defaultProvider` or `defaultModel` used by new sessions. Returns `false` if authentication is not configured for the model's provider. See [models.md](models.md) for configuring custom models.
1707
1762
 
1708
1763
  ```typescript
1709
1764
  const model = ctx.modelRegistry.find("anthropic", "claude-sonnet-4-5");
@@ -1717,7 +1772,9 @@ if (model) {
1717
1772
 
1718
1773
  ### pi.getThinkingLevel() / pi.setThinkingLevel(level)
1719
1774
 
1720
- Get or set the thinking level. Level is clamped to model capabilities (non-reasoning models always use "off"). Changes emit `thinking_level_select`.
1775
+ Get the current thinking level. Level is clamped to model capabilities (non-reasoning models always use "off"). Changes emit `thinking_level_select`.
1776
+
1777
+ `pi.setThinkingLevel()` changes the thinking level for the current session. The change is recorded in session history and restored when that session is resumed, but it does not change the configured default used by new sessions.
1721
1778
 
1722
1779
  ```typescript
1723
1780
  const current = pi.getThinkingLevel(); // "off" | "minimal" | "low" | "medium" | "high" | "xhigh" | "max"
@@ -2184,26 +2241,26 @@ import {
2184
2241
  DEFAULT_MAX_LINES, // 2000
2185
2242
  } from "apex-code";
2186
2243
 
2187
- async execute(toolCallId, params, signal, onUpdate, ctx) {
2188
- const output = await runCommand();
2244
+ <a id="extensionapi-methods"></a>
2189
2245
 
2190
- // Apply truncation
2191
- const truncation = truncateHead(output, {
2192
- maxLines: DEFAULT_MAX_LINES,
2193
- maxBytes: DEFAULT_MAX_BYTES,
2194
- });
2246
+ ## Choose an integration point
2195
2247
 
2196
- let result = truncation.content;
2248
+ | Capability | Main API |
2249
+ |---|---|
2250
+ | Observe or modify lifecycle behavior | `pi.on()` |
2251
+ | Add a model-callable operation | `pi.registerTool()` |
2252
+ | Add a `/` command | `pi.registerCommand()` |
2253
+ | Add a shortcut or CLI flag | `pi.registerShortcut()` or `pi.registerFlag()` |
2254
+ | Send user or custom messages | `pi.sendUserMessage()` or `pi.sendMessage()` |
2255
+ | Persist non-context session data | `pi.appendEntry()` |
2256
+ | Change active tools, model, or thinking level | Session control methods on `pi` |
2257
+ | Add a model provider | `pi.registerProvider()` |
2258
+ | Add terminal rendering | Renderer registration and `ctx.ui` |
2259
+ | Communicate with another extension | `pi.events` |
2197
2260
 
2198
- if (truncation.truncated) {
2199
- // Write full output to temp file
2200
- const tempFile = writeTempFile(output);
2261
+ Use the exported declarations in [`extensions/types.ts`](https://github.com/earendil-works/pi/blob/main/packages/coding-agent/src/core/extensions/types.ts) for exact event, context, tool, and result types.
2201
2262
 
2202
- // Inform the LLM where to find complete output
2203
- result += `\n\n[Output truncated: ${truncation.outputLines} of ${truncation.totalLines} lines`;
2204
- result += ` (${formatSize(truncation.outputBytes)} of ${formatSize(truncation.totalBytes)}).`;
2205
- result += ` Full output saved to: ${tempFile}]`;
2206
- }
2263
+ ## Follow the extension contracts
2207
2264
 
2208
2265
  return { content: [{ type: "text", text: result }] };
2209
2266
  }
@@ -2362,9 +2419,7 @@ If a slot renderer is not defined or throws:
2362
2419
 
2363
2420
  ### Dynamic Tool Loading
2364
2421
 
2365
- Extensions can register many tools while keeping only a small initial set active. A tool can then add more tools with `pi.setActiveTools()` during execution. Pi detects purely additive changes, records the newly available tool names on that tool result, and applies the updated active set before the next model request.
2366
-
2367
- This works with every model. Models with native deferred-loading support preserve the stable prompt prefix and load the new definitions at the tool-result position. Other models use the fallback described below.
2422
+ Extensions can register many tools while keeping only a small initial set active. A tool can then change the active set with `pi.setActiveTools()` during execution. Pi stores the initial prompt and tool loadout in the transcript's first system message, then appends tool and prompt deltas before the next model request. Providers that cannot represent a transition receive a complete transcript checkpoint, which may invalidate the cached prefix.
2368
2423
 
2369
2424
  The lifecycle is:
2370
2425
 
@@ -2495,7 +2550,7 @@ export default function (pi: ExtensionAPI) {
2495
2550
  }
2496
2551
  ```
2497
2552
 
2498
- When `search_tools` adds a match, the model receives that definition on the immediately following request. On a native-capable model the definition is anchored after the search result without changing the initial tool-schema prefix. On other models it appears in the normal tool list on that same following request.
2553
+ When `search_tools` adds a match, the model receives the complete updated tool list on the immediately following request.
2499
2554
 
2500
2555
  ## Custom UI
2501
2556
 
@@ -2517,14 +2572,13 @@ Extensions can interact with users via `ctx.ui` methods and customize how messag
2517
2572
  // Select from options
2518
2573
  const choice = await ctx.ui.select("Pick one:", ["A", "B", "C"]);
2519
2574
 
2520
- // Confirm dialog
2521
- const ok = await ctx.ui.confirm("Delete?", "This cannot be undone");
2575
+ ### Events and concurrency
2522
2576
 
2523
- // Text input
2524
- const name = await ctx.ui.input("Name:", "placeholder");
2577
+ Handlers run in extension load and registration order. `pi.on()` returns a function that unsubscribes that registration; changes do not affect a dispatch already in progress.
2578
+ Some events notify; others transform data, replace results, or cancel an operation.
2579
+ Use each event’s declared result type rather than assuming every return value has an effect.
2525
2580
 
2526
- // Multi-line editor
2527
- const text = await ctx.ui.editor("Edit:", "prefilled text");
2581
+ Events cover resource discovery, sessions, agent and message lifecycle, providers, tools, and raw input.
2528
2582
 
2529
2583
  // Notification (non-blocking)
2530
2584
  ctx.ui.notify("Done!", "info"); // "info" | "warning" | "error"
@@ -2683,93 +2737,23 @@ Custom working-indicator frames are rendered verbatim. If you want colors, add t
2683
2737
 
2684
2738
  ### Autocomplete Providers
2685
2739
 
2686
- Use `ctx.ui.addAutocompleteProvider()` to stack custom autocomplete logic on top of the built-in slash-command and path provider. Set `triggerCharacters` for custom natural triggers such as `$`.
2740
+ `message_end` can replace a finalized message while preserving its role. `tool_call` can mutate input or block execution. `tool_result` handlers compose, with each handler seeing prior changes.
2687
2741
 
2688
- Typical pattern:
2742
+ <a id="context_with_system"></a>
2689
2743
 
2690
- - inspect the text before the cursor
2691
- - return your own suggestions when your extension-specific syntax matches
2692
- - otherwise delegate to `current.getSuggestions(...)`
2693
- - delegate `applyCompletion(...)` unless you need custom insertion behavior
2744
+ `context` transforms conversation messages without prompt and tool system messages; Pi restores that state afterward. Use `context_with_system` only when a request-local transformation must own the complete transcript, and keep a system message at index zero.
2694
2745
 
2695
- ```typescript
2696
- pi.on("session_start", (_event, ctx) => {
2697
- ctx.ui.addAutocompleteProvider((current) => ({
2698
- triggerCharacters: ["#"],
2699
- async getSuggestions(lines, cursorLine, cursorCol, options) {
2700
- const line = lines[cursorLine] ?? "";
2701
- const beforeCursor = line.slice(0, cursorCol);
2702
- const match = beforeCursor.match(/(?:^|[ \t])#([^\s#]*)$/);
2703
- if (!match) {
2704
- return current.getSuggestions(lines, cursorLine, cursorCol, options);
2705
- }
2746
+ `turn_end` and `agent_before_settle` are actionable boundaries. Their handlers can chain proposed `custom`, `custom_message`, `context_edit`, or `compaction` entries and return `continue: true` for one next model request. Guard continuation conditions because an unconditional continuation can loop. Use the exported event declarations for the complete validation and ordering contract.
2706
2747
 
2707
- return {
2708
- prefix: `#${match[1] ?? ""}`,
2709
- items: [
2710
- { value: "#2983", label: "#2983", description: "Extension API for registering custom @ autocomplete providers" },
2711
- { value: "#2753", label: "#2753", description: "Reload stale resource settings" },
2712
- ],
2713
- };
2714
- },
2715
-
2716
- applyCompletion(lines, cursorLine, cursorCol, item, prefix) {
2717
- return current.applyCompletion(lines, cursorLine, cursorCol, item, prefix);
2718
- },
2719
-
2720
- shouldTriggerFileCompletion(lines, cursorLine, cursorCol) {
2721
- return current.shouldTriggerFileCompletion?.(lines, cursorLine, cursorCol) ?? true;
2722
- },
2723
- }));
2724
- });
2725
- ```
2748
+ <a id="cache_warming_decision"></a>
2726
2749
 
2727
- See [github-issue-autocomplete.ts](../examples/extensions/github-issue-autocomplete.ts) for a complete example that preloads the latest open GitHub issues with `gh issue list` and filters them locally for fast `#...` completion. It requires GitHub CLI (`gh`) and a GitHub repository checkout.
2750
+ `cache_warming_decision` can override an idle prompt-cache refresh with `{ action: "warm" }` or `{ action: "stop" }`. The last handler that returns an action wins.
2728
2751
 
2729
- ### Custom Components
2752
+ Tool calls from one assistant message can run in parallel.
2753
+ Do not assume a sibling call or result exists when another tool event runs.
2754
+ Use `ctx.signal` for nested work owned by an active turn; commands and idle session events often have no operation signal.
2730
2755
 
2731
- For complex UI, use `ctx.ui.custom()`. This temporarily replaces the editor with your component until `done()` is called:
2732
-
2733
- ```typescript
2734
- import { Text, Component } from "@earendil-works/pi-tui";
2735
-
2736
- const result = await ctx.ui.custom<boolean>((tui, theme, keybindings, done) => {
2737
- const text = new Text("Press Enter to confirm, Escape to cancel", 1, 1);
2738
-
2739
- text.onKey = (key) => {
2740
- if (key === "return") done(true);
2741
- if (key === "escape") done(false);
2742
- return true;
2743
- };
2744
-
2745
- return text;
2746
- });
2747
-
2748
- if (result) {
2749
- // User pressed Enter
2750
- }
2751
- ```
2752
-
2753
- The callback receives:
2754
- - `tui` - TUI instance (for screen dimensions, focus management)
2755
- - `theme` - Current theme for styling
2756
- - `keybindings` - App keybinding manager (for checking shortcuts)
2757
- - `done(value)` - Call to close component and return value
2758
-
2759
- See [tui.md](tui.md) for the full component API.
2760
-
2761
- #### Overlay Mode (Experimental)
2762
-
2763
- Pass `{ overlay: true }` to render the component as a floating modal on top of existing content, without clearing the screen:
2764
-
2765
- ```typescript
2766
- const result = await ctx.ui.custom<string | null>(
2767
- (tui, theme, keybindings, done) => new MyOverlayComponent({ onClose: done }),
2768
- { overlay: true }
2769
- );
2770
- ```
2771
-
2772
- For advanced positioning (anchors, margins, percentages, responsive visibility), pass `overlayOptions`. Use `onHandle` to control focus or visibility programmatically:
2756
+ A `user_bash` handler that returns `undefined` passes the command to the next handler and then to local execution if no handler handles it. Returning `operations` or `result` stops propagation. A handler failure blocks the command rather than falling through to local execution.
2773
2757
 
2774
2758
  ```typescript
2775
2759
  const result = await ctx.ui.custom<string | null>(
@@ -2815,206 +2799,94 @@ class VimEditor extends CustomEditor {
2815
2799
  }
2816
2800
  }
2817
2801
 
2818
- export default function (pi: ExtensionAPI) {
2819
- pi.on("session_start", (_event, ctx) => {
2820
- ctx.ui.setEditorComponent((tui, theme, keybindings) =>
2821
- new VimEditor(tui, theme, keybindings)
2822
- );
2823
- });
2824
- }
2825
- ```
2826
-
2827
- **Key points:**
2828
- - Extend `CustomEditor` (not base `Editor`) to get app keybindings (escape to abort, ctrl+d, model switching)
2829
- - Call `super.handleInput(data)` for keys you don't handle
2830
- - Factory receives `tui`, `theme`, and `keybindings` from the app
2831
- - Use `ctx.ui.getEditorComponent()` before `setEditorComponent()` to wrap the previously configured custom editor
2832
- - Pass `undefined` to restore default: `ctx.ui.setEditorComponent(undefined)`
2802
+ ### Tools
2833
2803
 
2834
- To compose with another extension that already replaced the editor, capture the previous factory before setting yours:
2804
+ A custom tool defines a name, model-facing description, TypeBox parameter schema, and `execute()` function.
2805
+ Its result requires model-facing `content` and a `details` field for rendering or state reconstruction.
2806
+ Use `details: undefined` when there are no structured details. If the tool makes nested model calls, include their `usage` in the result so session totals remain accurate.
2835
2807
 
2836
- ```typescript
2837
- const previous = ctx.ui.getEditorComponent();
2838
- ctx.ui.setEditorComponent((tui, theme, keybindings) =>
2839
- new MyEditor(tui, theme, keybindings, { base: previous?.(tui, theme, keybindings) })
2840
- );
2841
- ```
2808
+ Throw from `execute()` to produce a failed tool result.
2809
+ Returning an object does not mark it as an error.
2810
+ Return `terminate: true` only when the agent should skip its automatic follow-up after every completed tool in that batch agrees to terminate.
2842
2811
 
2843
- See [tui.md](tui.md) Pattern 7 for a complete example with mode indicator.
2812
+ Use sequential execution when tools share mutable in-memory state.
2813
+ File-mutating tools should wrap the complete read-modify-write operation with `withFileMutationQueue()`.
2814
+ Truncate large model-facing results and tell the model where to read the complete output.
2844
2815
 
2845
- ### Message and Entry Rendering
2816
+ See [`hello.ts`](../examples/extensions/hello.ts), [`todo.ts`](../examples/extensions/todo.ts), [`dynamic-tools.ts`](../examples/extensions/dynamic-tools.ts), and [`truncated-tool.ts`](../examples/extensions/truncated-tool.ts).
2846
2817
 
2847
- Register a custom renderer for messages with your `customType`. Use message renderers for content that should participate in LLM context:
2818
+ ### Activate tools dynamically
2848
2819
 
2849
- ```typescript
2850
- import { Text } from "@earendil-works/pi-tui";
2820
+ Register every tool first, keep optional tools inactive, and use `pi.setActiveTools()` from a loader tool to select the desired active tools. Names must already be registered; unknown names are ignored.
2851
2821
 
2852
- pi.registerMessageRenderer("my-extension", (message, options, theme) => {
2853
- const { expanded, outputPad } = options;
2854
- let text = theme.fg("accent", `[${message.customType}] `);
2855
- text += message.content;
2822
+ Pi records the initial prompt and tool set in the transcript's first system message, then appends tool and prompt changes before the next model request. Providers that cannot represent the transition receive a complete transcript checkpoint, which can invalidate the cached prefix.
2856
2823
 
2857
- if (expanded && message.details) {
2858
- text += "\n" + theme.fg("dim", JSON.stringify(message.details, null, 2));
2859
- }
2824
+ <a id="extensioncontext"></a>
2825
+ <a id="extensioncommandcontext"></a>
2826
+ <a id="use-extension-context"></a>
2860
2827
 
2861
- return new Text(text, outputPad, 0);
2862
- });
2863
- ```
2828
+ ### Context and session changes
2864
2829
 
2865
- Messages are sent via `pi.sendMessage()`:
2830
+ `ExtensionContext` provides the working directory, mode, UI, session manager, model runtime, abort signal, context usage, and controls for compaction and shutdown.
2831
+ Use `ctx.modelRegistry.streamSimple()` for provider-neutral nested model calls.
2866
2832
 
2867
- ```typescript
2868
- pi.sendMessage({
2869
- customType: "my-extension", // Matches registerMessageRenderer
2870
- content: "Status update",
2871
- display: true, // Show in TUI
2872
- details: { ... }, // Available in renderer
2873
- });
2874
- ```
2833
+ Command handlers receive `ExtensionCommandContext`, which adds operations for waiting until idle, reloading, tree navigation, and session replacement.
2834
+ These operations are command-only because calling them from lifecycle handlers can deadlock the runtime.
2875
2835
 
2876
- For TUI-only content that should not be sent to the LLM, render custom entries instead:
2836
+ Session replacement invalidates the old context. Capture only plain data before switching, then use the fresh context supplied to `withSession` for session-bound work.
2877
2837
 
2878
- ```typescript
2879
- pi.registerEntryRenderer("my-card", (entry, options, theme) => {
2880
- return new Text(theme.fg("accent", JSON.stringify(entry.data)));
2881
- });
2838
+ <a id="state-management"></a>
2839
+ <a id="persist-state"></a>
2882
2840
 
2883
- pi.appendEntry("my-card", { status: "done" });
2884
- ```
2841
+ ### State
2885
2842
 
2886
- ### Theme Colors
2843
+ Choose storage based on how state participates in the conversation:
2887
2844
 
2888
- All render functions receive a `theme` object. See [themes.md](themes.md) for creating custom themes and the full color palette.
2845
+ | State | Storage |
2846
+ |---|---|
2847
+ | Tool state that follows the active branch | Tool-result `details` |
2848
+ | Durable data excluded from model context | `pi.appendEntry()` |
2849
+ | Custom content stored and sent to the model | `pi.sendMessage()` |
2850
+ | Data outside one session | External storage |
2889
2851
 
2890
- ```typescript
2891
- // Foreground colors
2892
- theme.fg("toolTitle", text) // Tool names
2893
- theme.fg("accent", text) // Highlights
2894
- theme.fg("success", text) // Success (green)
2895
- theme.fg("error", text) // Errors (red)
2896
- theme.fg("warning", text) // Warnings (yellow)
2897
- theme.fg("muted", text) // Secondary text
2898
- theme.fg("dim", text) // Tertiary text
2852
+ Reconstruct branch-sensitive state from `ctx.sessionManager.getBranch()` during `session_start`.
2853
+ Do not rebuild it from every file entry because abandoned branches represent alternative histories.
2854
+ Register an entry or message renderer when custom stored content should appear in the transcript.
2899
2855
 
2900
- // Text styles
2901
- theme.bold(text)
2902
- theme.italic(text)
2903
- theme.strikethrough(text)
2904
- ```
2856
+ <a id="custom-ui"></a>
2857
+ <a id="mode-behavior"></a>
2858
+ <a id="interact-with-the-user"></a>
2859
+ <a id="account-for-each-mode"></a>
2905
2860
 
2906
- For syntax highlighting in custom tool renderers:
2861
+ ### UI and modes
2907
2862
 
2908
2863
  ```typescript
2909
2864
  import { highlightCode, getLanguageFromPath } from "apex-code";
2910
2865
 
2911
- // Highlight code with explicit language
2912
- const highlighted = highlightCode("const x = 1;", "typescript", theme);
2913
-
2914
- // Auto-detect language from file path
2915
- const lang = getLanguageFromPath("/path/to/file.rs"); // "rust"
2916
- const highlighted = highlightCode(code, lang, theme);
2917
- ```
2918
-
2919
- ## Error Handling
2920
-
2921
- - Extension errors are logged, agent continues
2922
- - `tool_call` errors block the tool (fail-safe)
2923
- - Tool `execute` errors must be signaled by throwing; the thrown error is caught, reported to the LLM with `isError: true`, and execution continues
2924
-
2925
- ## Mode Behavior
2926
-
2927
- | Mode | `ctx.mode` | `ctx.hasUI` | Notes |
2928
- |------|------------|-------------|-------|
2929
- | Interactive | `"tui"` | `true` | Full TUI with terminal rendering |
2930
- | RPC (`--mode rpc`) | `"rpc"` | `true` | Dialogs and notifications via JSON protocol; `custom()` returns `undefined`. See [rpc.md](rpc.md) |
2931
- | JSON (`--mode json`) | `"json"` | `false` | Event stream to stdout; UI methods are no-ops |
2932
- | Print (`-p`) | `"print"` | `false` | Extensions run but can't prompt |
2933
-
2934
- Use `ctx.mode === "tui"` before TUI-specific features (`custom()`, component factories, terminal input). Use `ctx.hasUI` before dialog and notification methods that work in both TUI and RPC modes.
2935
-
2936
- ## Examples Reference
2937
-
2938
- All examples in [examples/extensions/](../examples/extensions/).
2939
-
2940
- | Example | Description | Key APIs |
2941
- |---------|-------------|----------|
2942
- | **Tools** |||
2943
- | `hello.ts` | Minimal tool registration | `registerTool` |
2944
- | `question.ts` | Tool with user interaction | `registerTool`, `ui.select` |
2945
- | `questionnaire.ts` | Multi-step wizard tool | `registerTool`, `ui.custom` |
2946
- | `todo.ts` | Stateful tool with persistence | `registerTool`, `appendEntry`, `renderResult`, session events |
2947
- | `dynamic-tools.ts` | Register tools after startup and during commands | `registerTool`, `session_start`, `registerCommand` |
2948
- | `structured-output.ts` | Final structured-output tool with `terminate: true` | `registerTool`, terminating tool results |
2949
- | `truncated-tool.ts` | Output truncation example | `registerTool`, `truncateHead` |
2950
- | `tool-override.ts` | Override built-in read tool | `registerTool` (same name as built-in) |
2951
- | **Commands** |||
2952
- | `pirate.ts` | Modify system prompt per-turn | `registerCommand`, `before_agent_start` |
2953
- | `summarize.ts` | Conversation summary command | `registerCommand`, `ui.custom` |
2954
- | `handoff.ts` | Cross-provider model handoff | `registerCommand`, `ui.editor`, `ui.custom` |
2955
- | `qna.ts` | Q&A with custom UI | `registerCommand`, `ui.custom`, `setEditorText` |
2956
- | `send-user-message.ts` | Inject user messages | `registerCommand`, `sendUserMessage` |
2957
- | `reload-runtime.ts` | Reload command and LLM tool handoff | `registerCommand`, `ctx.reload()`, `sendUserMessage` |
2958
- | `shutdown-command.ts` | Graceful shutdown command | `registerCommand`, `shutdown()` |
2959
- | **Events & Gates** |||
2960
- | `permission-gate.ts` | Block dangerous commands | `on("tool_call")`, `ui.confirm` |
2961
- | `project-trust.ts` | Decide or defer project trust from a user/global or CLI extension | `on("project_trust")`, trust UI, required trust result |
2962
- | `protected-paths.ts` | Block writes to specific paths | `on("tool_call")` |
2963
- | `confirm-destructive.ts` | Confirm session changes | `on("session_before_switch")`, `on("session_before_fork")` |
2964
- | `dirty-repo-guard.ts` | Warn on dirty git repo | `on("session_before_*")`, `exec` |
2965
- | `input-transform.ts` | Transform user input | `on("input")` |
2966
- | `input-transform-streaming.ts` | Streaming-aware input transform | `on("input")`, `streamingBehavior` |
2967
- | `model-status.ts` | React to model changes | `on("model_select")`, `setStatus` |
2968
- | `provider-payload.ts` | Inspect payloads and provider response headers | `on("before_provider_request")`, `on("after_provider_response")` |
2969
- | `system-prompt-header.ts` | Display system prompt info | `on("agent_start")`, `getSystemPrompt` |
2970
- | `claude-rules.ts` | Load rules from files | `on("session_start")`, `on("before_agent_start")` |
2971
- | `prompt-customizer.ts` | Add context-aware tool guidance using `systemPromptOptions` | `on("before_agent_start")`, `BuildSystemPromptOptions` |
2972
- | `file-trigger.ts` | File watcher triggers messages | `sendMessage` |
2973
- | **Compaction & Sessions** |||
2974
- | `custom-compaction.ts` | Custom compaction summary | `on("session_before_compact")` |
2975
- | `trigger-compact.ts` | Trigger compaction manually | `compact()` |
2976
- | `git-checkpoint.ts` | Git stash on turns | `on("turn_start")`, `on("session_before_fork")`, `exec` |
2977
- | `git-merge-and-resolve.ts` | Fetch, merge, and resolve conflicts | `on("agent_end")`, `exec`, `sendUserMessage` |
2978
- | `auto-commit-on-exit.ts` | Commit on shutdown | `on("session_shutdown")`, `exec` |
2979
- | **UI Components** |||
2980
- | `status-line.ts` | Footer status indicator | `setStatus`, session events |
2981
- | `working-indicator.ts` | Customize the streaming working indicator | `setWorkingIndicator`, `registerCommand` |
2982
- | `github-issue-autocomplete.ts` | Add `#1234` issue completions on top of built-in autocomplete by preloading recent open issues from `gh issue list` | `addAutocompleteProvider`, `on("session_start")`, `exec` |
2983
- | `custom-footer.ts` | Replace footer entirely | `registerCommand`, `setFooter` |
2984
- | `custom-header.ts` | Replace startup header | `on("session_start")`, `setHeader` |
2985
- | `modal-editor.ts` | Vim-style modal editor | `setEditorComponent`, `CustomEditor` |
2986
- | `rainbow-editor.ts` | Custom editor styling | `setEditorComponent` |
2987
- | `widget-placement.ts` | Widget above/below editor | `setWidget` |
2988
- | `overlay-test.ts` | Overlay components | `ui.custom` with overlay options |
2989
- | `overlay-qa-tests.ts` | Comprehensive overlay tests | `ui.custom`, all overlay options |
2990
- | `notify.ts` | Simple notifications | `ui.notify` |
2991
- | `timed-confirm.ts` | Dialogs with timeout | `ui.confirm` with timeout/signal |
2992
- | `mac-system-theme.ts` | Auto-switch theme | `setTheme`, `exec` |
2993
- | **Complex Extensions** |||
2994
- | `plan-mode/` | Full plan mode implementation | All event types, `registerCommand`, `registerShortcut`, `registerFlag`, `setStatus`, `setWidget`, `sendMessage`, `setActiveTools` |
2995
- | `preset.ts` | Saveable presets (model, tools, thinking) | `registerCommand`, `registerShortcut`, `registerFlag`, `setModel`, `setActiveTools`, `setThinkingLevel`, `appendEntry` |
2996
- | `tools.ts` | Toggle tools on/off UI | `registerCommand`, `setActiveTools`, `SettingsList`, session events |
2997
- | **Remote & Sandbox** |||
2998
- | `ssh.ts` | SSH remote execution | `registerFlag`, `on("user_bash")`, `on("before_agent_start")`, tool operations |
2999
- | `interactive-shell.ts` | Persistent shell session | `on("user_bash")` |
3000
- | `sandbox/` | Sandboxed tool execution | Tool operations |
3001
- | `gondolin/` | Route built-in tools and `!` commands into a Gondolin micro-VM | Tool operations, built-in tool overrides, `on("user_bash")` |
3002
- | `subagent/` | Spawn sub-agents | `registerTool`, `exec` |
3003
- | **Games** |||
3004
- | `snake.ts` | Snake game | `registerCommand`, `ui.custom`, keyboard handling |
3005
- | `space-invaders.ts` | Space Invaders game | `registerCommand`, `ui.custom` |
3006
- | `doom-overlay/` | Doom in overlay | `ui.custom` with overlay |
3007
- | **Providers** |||
3008
- | `custom-provider-anthropic/` | Custom Anthropic proxy | `registerProvider` |
3009
- | `custom-provider-gitlab-duo/` | GitLab Duo integration | `registerProvider` with OAuth |
3010
- | **Messages & Communication** |||
3011
- | `message-renderer.ts` | Custom message rendering | `registerMessageRenderer`, `sendMessage` |
3012
- | `entry-renderer.ts` | TUI-only custom entry rendering | `registerEntryRenderer`, `appendEntry` |
3013
- | `event-bus.ts` | Inter-extension events | `pi.events` |
3014
- | **Session Metadata** |||
3015
- | `session-name.ts` | Name sessions for selector | `setSessionName`, `getSessionName` |
3016
- | `bookmark.ts` | Bookmark entries for /tree | `setLabel` |
3017
- | **Misc** |||
3018
- | `inline-bash.ts` | Inline bash in tool calls | `on("tool_call")` |
3019
- | `bash-spawn-hook.ts` | Adjust bash command, cwd, and env before execution | `createBashTool`, `spawnHook` |
3020
- | `with-deps/` | Extension with npm dependencies | Package structure with `package.json` |
2866
+ Extensions load in interactive, RPC, JSON, and print modes.
2867
+ Interactive mode provides the complete terminal UI.
2868
+ RPC can forward supported dialogs and notifications through the [RPC Extension UI protocol](rpc-extension-ui.md), but not custom terminal components; JSON and print modes have no UI.
2869
+ Guard terminal-only behavior with `ctx.mode === "tui"` and use `ctx.hasUI` for interactions supported by interactive and RPC clients.
2870
+
2871
+ Keep tool and event behavior independent from rendering so non-interactive modes remain functional.
2872
+
2873
+ <a id="error-handling"></a>
2874
+ <a id="handle-errors-and-shutdown"></a>
2875
+
2876
+ ### Errors and cleanup
2877
+
2878
+ Pi reports handler errors and continues where possible. A `tool_call` handler failure blocks the tool as a fail-safe; a tool execution failure becomes an error result for the model.
2879
+
2880
+ Release resources in `session_shutdown` even when normal operation attempted cleanup.
2881
+ Keep cleanup idempotent because cancellation, reload, session replacement, and process exit can converge on the same path.
2882
+ Use `ctx.shutdown()` to request an orderly process shutdown.
2883
+
2884
+ <a id="examples-reference"></a>
2885
+ <a id="use-examples-as-the-implementation-reference"></a>
2886
+
2887
+ ## Examples and reference
2888
+
2889
+ The checked [extension examples](../examples/extensions/) cover tools, lifecycle events, commands, flags, shortcuts, state, rendering, providers, OAuth, remote execution, and terminal components.
2890
+ Start with the smallest example matching your integration point.
2891
+
2892
+ Use [Custom Providers](custom-provider.md) for model-service integrations, [Terminal UI](tui.md) for custom components, and [Pi Packages](packages.md) to install or distribute extensions with other resources.