@code-yeongyu/senpi 2026.8.18-3 → 2026.8.19

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 (353) hide show
  1. package/CHANGELOG.md +52 -0
  2. package/dist/cli/args.d.ts +1 -0
  3. package/dist/cli/args.d.ts.map +1 -1
  4. package/dist/cli/args.js +4 -0
  5. package/dist/cli/args.js.map +1 -1
  6. package/dist/core/agent-session.d.ts +33 -4
  7. package/dist/core/agent-session.d.ts.map +1 -1
  8. package/dist/core/agent-session.js +293 -55
  9. package/dist/core/agent-session.js.map +1 -1
  10. package/dist/core/compaction/branch-summarization.d.ts.map +1 -1
  11. package/dist/core/compaction/branch-summarization.js +3 -0
  12. package/dist/core/compaction/branch-summarization.js.map +1 -1
  13. package/dist/core/compaction/compaction.d.ts +20 -3
  14. package/dist/core/compaction/compaction.d.ts.map +1 -1
  15. package/dist/core/compaction/compaction.js +182 -52
  16. package/dist/core/compaction/compaction.js.map +1 -1
  17. package/dist/core/extensions/builtin/claude-sdk-oauth/index.d.ts +3 -0
  18. package/dist/core/extensions/builtin/claude-sdk-oauth/index.d.ts.map +1 -1
  19. package/dist/core/extensions/builtin/claude-sdk-oauth/index.js +18 -1
  20. package/dist/core/extensions/builtin/claude-sdk-oauth/index.js.map +1 -1
  21. package/dist/core/extensions/builtin/claude-sdk-oauth/oauth-login.d.ts +1 -0
  22. package/dist/core/extensions/builtin/claude-sdk-oauth/oauth-login.d.ts.map +1 -1
  23. package/dist/core/extensions/builtin/claude-sdk-oauth/oauth-login.js +7 -1
  24. package/dist/core/extensions/builtin/claude-sdk-oauth/oauth-login.js.map +1 -1
  25. package/dist/core/extensions/builtin/claude-sdk-oauth/settings.d.ts +8 -0
  26. package/dist/core/extensions/builtin/claude-sdk-oauth/settings.d.ts.map +1 -1
  27. package/dist/core/extensions/builtin/claude-sdk-oauth/settings.js +18 -0
  28. package/dist/core/extensions/builtin/claude-sdk-oauth/settings.js.map +1 -1
  29. package/dist/core/extensions/builtin/cursor-cli-oauth/guardrails.d.ts +7 -0
  30. package/dist/core/extensions/builtin/cursor-cli-oauth/guardrails.d.ts.map +1 -1
  31. package/dist/core/extensions/builtin/cursor-cli-oauth/guardrails.js +10 -1
  32. package/dist/core/extensions/builtin/cursor-cli-oauth/guardrails.js.map +1 -1
  33. package/dist/core/extensions/builtin/cursor-cli-oauth/index.d.ts.map +1 -1
  34. package/dist/core/extensions/builtin/cursor-cli-oauth/index.js +12 -1
  35. package/dist/core/extensions/builtin/cursor-cli-oauth/index.js.map +1 -1
  36. package/dist/core/extensions/builtin/cursor-cli-oauth/oauth-login.d.ts +12 -1
  37. package/dist/core/extensions/builtin/cursor-cli-oauth/oauth-login.d.ts.map +1 -1
  38. package/dist/core/extensions/builtin/cursor-cli-oauth/oauth-login.js +20 -1
  39. package/dist/core/extensions/builtin/cursor-cli-oauth/oauth-login.js.map +1 -1
  40. package/dist/core/extensions/builtin/cursor-cli-oauth/settings.d.ts +11 -0
  41. package/dist/core/extensions/builtin/cursor-cli-oauth/settings.d.ts.map +1 -1
  42. package/dist/core/extensions/builtin/cursor-cli-oauth/settings.js +7 -2
  43. package/dist/core/extensions/builtin/cursor-cli-oauth/settings.js.map +1 -1
  44. package/dist/core/extensions/builtin/cursor-cli-oauth/stream.d.ts.map +1 -1
  45. package/dist/core/extensions/builtin/cursor-cli-oauth/stream.js +7 -2
  46. package/dist/core/extensions/builtin/cursor-cli-oauth/stream.js.map +1 -1
  47. package/dist/core/extensions/builtin/index.d.ts.map +1 -1
  48. package/dist/core/extensions/builtin/index.js +2 -0
  49. package/dist/core/extensions/builtin/index.js.map +1 -1
  50. package/dist/core/extensions/builtin/loop/command-registration.d.ts +7 -0
  51. package/dist/core/extensions/builtin/loop/command-registration.d.ts.map +1 -0
  52. package/dist/core/extensions/builtin/loop/command-registration.js +13 -0
  53. package/dist/core/extensions/builtin/loop/command-registration.js.map +1 -0
  54. package/dist/core/extensions/builtin/loop/command.d.ts +41 -0
  55. package/dist/core/extensions/builtin/loop/command.d.ts.map +1 -0
  56. package/dist/core/extensions/builtin/loop/command.js +281 -0
  57. package/dist/core/extensions/builtin/loop/command.js.map +1 -0
  58. package/dist/core/extensions/builtin/loop/cron-planner.d.ts +23 -0
  59. package/dist/core/extensions/builtin/loop/cron-planner.d.ts.map +1 -0
  60. package/dist/core/extensions/builtin/loop/cron-planner.js +88 -0
  61. package/dist/core/extensions/builtin/loop/cron-planner.js.map +1 -0
  62. package/dist/core/extensions/builtin/loop/index.d.ts +134 -0
  63. package/dist/core/extensions/builtin/loop/index.d.ts.map +1 -0
  64. package/dist/core/extensions/builtin/loop/index.js +742 -0
  65. package/dist/core/extensions/builtin/loop/index.js.map +1 -0
  66. package/dist/core/extensions/builtin/loop/loopfile.d.ts +40 -0
  67. package/dist/core/extensions/builtin/loop/loopfile.d.ts.map +1 -0
  68. package/dist/core/extensions/builtin/loop/loopfile.js +102 -0
  69. package/dist/core/extensions/builtin/loop/loopfile.js.map +1 -0
  70. package/dist/core/extensions/builtin/loop/parse.d.ts +45 -0
  71. package/dist/core/extensions/builtin/loop/parse.d.ts.map +1 -0
  72. package/dist/core/extensions/builtin/loop/parse.js +92 -0
  73. package/dist/core/extensions/builtin/loop/parse.js.map +1 -0
  74. package/dist/core/extensions/builtin/loop/scheduler.d.ts +225 -0
  75. package/dist/core/extensions/builtin/loop/scheduler.d.ts.map +1 -0
  76. package/dist/core/extensions/builtin/loop/scheduler.js +708 -0
  77. package/dist/core/extensions/builtin/loop/scheduler.js.map +1 -0
  78. package/dist/core/extensions/builtin/loop/status.d.ts +24 -0
  79. package/dist/core/extensions/builtin/loop/status.d.ts.map +1 -0
  80. package/dist/core/extensions/builtin/loop/status.js +113 -0
  81. package/dist/core/extensions/builtin/loop/status.js.map +1 -0
  82. package/dist/core/extensions/builtin/loop/store.d.ts +49 -0
  83. package/dist/core/extensions/builtin/loop/store.d.ts.map +1 -0
  84. package/dist/core/extensions/builtin/loop/store.js +424 -0
  85. package/dist/core/extensions/builtin/loop/store.js.map +1 -0
  86. package/dist/core/extensions/builtin/loop/tick-prompt.d.ts +64 -0
  87. package/dist/core/extensions/builtin/loop/tick-prompt.d.ts.map +1 -0
  88. package/dist/core/extensions/builtin/loop/tick-prompt.js +183 -0
  89. package/dist/core/extensions/builtin/loop/tick-prompt.js.map +1 -0
  90. package/dist/core/extensions/builtin/loop/tools.d.ts +122 -0
  91. package/dist/core/extensions/builtin/loop/tools.d.ts.map +1 -0
  92. package/dist/core/extensions/builtin/loop/tools.js +155 -0
  93. package/dist/core/extensions/builtin/loop/tools.js.map +1 -0
  94. package/dist/core/extensions/builtin/loop/types.d.ts +206 -0
  95. package/dist/core/extensions/builtin/loop/types.d.ts.map +1 -0
  96. package/dist/core/extensions/builtin/loop/types.js +37 -0
  97. package/dist/core/extensions/builtin/loop/types.js.map +1 -0
  98. package/dist/core/extensions/index.d.ts +1 -1
  99. package/dist/core/extensions/index.d.ts.map +1 -1
  100. package/dist/core/extensions/index.js.map +1 -1
  101. package/dist/core/extensions/loader.d.ts.map +1 -1
  102. package/dist/core/extensions/loader.js +10 -5
  103. package/dist/core/extensions/loader.js.map +1 -1
  104. package/dist/core/extensions/types.d.ts +30 -3
  105. package/dist/core/extensions/types.d.ts.map +1 -1
  106. package/dist/core/extensions/types.js.map +1 -1
  107. package/dist/core/footer-data-provider.d.ts.map +1 -1
  108. package/dist/core/footer-data-provider.js +5 -10
  109. package/dist/core/footer-data-provider.js.map +1 -1
  110. package/dist/core/model-registry.d.ts +2 -0
  111. package/dist/core/model-registry.d.ts.map +1 -1
  112. package/dist/core/model-registry.js +4 -0
  113. package/dist/core/model-registry.js.map +1 -1
  114. package/dist/core/model-runtime.d.ts +7 -0
  115. package/dist/core/model-runtime.d.ts.map +1 -1
  116. package/dist/core/model-runtime.js +17 -0
  117. package/dist/core/model-runtime.js.map +1 -1
  118. package/dist/core/package-manager.d.ts.map +1 -1
  119. package/dist/core/package-manager.js +8 -4
  120. package/dist/core/package-manager.js.map +1 -1
  121. package/dist/core/provider-composer.d.ts +8 -0
  122. package/dist/core/provider-composer.d.ts.map +1 -1
  123. package/dist/core/provider-composer.js.map +1 -1
  124. package/dist/core/remote-catalog-provider.d.ts.map +1 -1
  125. package/dist/core/remote-catalog-provider.js +2 -1
  126. package/dist/core/remote-catalog-provider.js.map +1 -1
  127. package/dist/core/retry-fallback/chains.d.ts +1 -0
  128. package/dist/core/retry-fallback/chains.d.ts.map +1 -1
  129. package/dist/core/retry-fallback/chains.js +3 -0
  130. package/dist/core/retry-fallback/chains.js.map +1 -1
  131. package/dist/core/retry-fallback/controller.d.ts +2 -0
  132. package/dist/core/retry-fallback/controller.d.ts.map +1 -1
  133. package/dist/core/retry-fallback/controller.js.map +1 -1
  134. package/dist/core/retry-fallback/expansion.d.ts +8 -0
  135. package/dist/core/retry-fallback/expansion.d.ts.map +1 -1
  136. package/dist/core/retry-fallback/expansion.js +2 -0
  137. package/dist/core/retry-fallback/expansion.js.map +1 -1
  138. package/dist/core/settings-manager.d.ts +1 -1
  139. package/dist/core/settings-manager.d.ts.map +1 -1
  140. package/dist/core/settings-manager.js +14 -3
  141. package/dist/core/settings-manager.js.map +1 -1
  142. package/dist/core/skills.d.ts.map +1 -1
  143. package/dist/core/skills.js +51 -33
  144. package/dist/core/skills.js.map +1 -1
  145. package/dist/core/tools/edit.d.ts.map +1 -1
  146. package/dist/core/tools/edit.js +17 -2
  147. package/dist/core/tools/edit.js.map +1 -1
  148. package/dist/extensions/llama/index.d.ts.map +1 -1
  149. package/dist/extensions/llama/index.js +2 -0
  150. package/dist/extensions/llama/index.js.map +1 -1
  151. package/dist/extensions/llama/provider.d.ts.map +1 -1
  152. package/dist/extensions/llama/provider.js +6 -6
  153. package/dist/extensions/llama/provider.js.map +1 -1
  154. package/dist/index.d.ts +1 -1
  155. package/dist/index.d.ts.map +1 -1
  156. package/dist/index.js.map +1 -1
  157. package/dist/main.d.ts +4 -0
  158. package/dist/main.d.ts.map +1 -1
  159. package/dist/main.js +4 -4
  160. package/dist/main.js.map +1 -1
  161. package/dist/modes/interactive/components/settings-selector.js +1 -1
  162. package/dist/modes/interactive/components/settings-selector.js.map +1 -1
  163. package/dist/modes/interactive/editor-paste-transfer.d.ts +10 -1
  164. package/dist/modes/interactive/editor-paste-transfer.d.ts.map +1 -1
  165. package/dist/modes/interactive/editor-paste-transfer.js +27 -2
  166. package/dist/modes/interactive/editor-paste-transfer.js.map +1 -1
  167. package/dist/modes/interactive/interactive-mode.d.ts +69 -1
  168. package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
  169. package/dist/modes/interactive/interactive-mode.js +434 -63
  170. package/dist/modes/interactive/interactive-mode.js.map +1 -1
  171. package/dist/modes/interactive/tips/catalog/dag-tips.d.ts +7 -0
  172. package/dist/modes/interactive/tips/catalog/dag-tips.d.ts.map +1 -0
  173. package/dist/modes/interactive/tips/catalog/dag-tips.js +57 -0
  174. package/dist/modes/interactive/tips/catalog/dag-tips.js.map +1 -0
  175. package/dist/modes/interactive/tips/catalog/memory-tips.d.ts +7 -0
  176. package/dist/modes/interactive/tips/catalog/memory-tips.d.ts.map +1 -0
  177. package/dist/modes/interactive/tips/catalog/memory-tips.js +100 -0
  178. package/dist/modes/interactive/tips/catalog/memory-tips.js.map +1 -0
  179. package/dist/modes/interactive/tips/registry.d.ts.map +1 -1
  180. package/dist/modes/interactive/tips/registry.js +4 -0
  181. package/dist/modes/interactive/tips/registry.js.map +1 -1
  182. package/dist/utils/management-http.d.ts +5 -3
  183. package/dist/utils/management-http.d.ts.map +1 -1
  184. package/dist/utils/management-http.js +14 -10
  185. package/dist/utils/management-http.js.map +1 -1
  186. package/docs/compaction.md +17 -0
  187. package/docs/custom-provider.md +5 -2
  188. package/docs/extensions.md +11 -2
  189. package/docs/llama-cpp.md +2 -0
  190. package/docs/models.md +8 -2
  191. package/docs/providers.md +5 -3
  192. package/docs/settings.md +22 -2
  193. package/docs/skills.md +4 -3
  194. package/examples/extensions/border-status-editor.ts +1 -1
  195. package/examples/extensions/git-checkpoint.ts +2 -2
  196. package/examples/extensions/notify.ts +3 -1
  197. package/examples/extensions/subagent/index.ts +6 -1
  198. package/examples/extensions/titlebar-spinner.ts +1 -1
  199. package/examples/rpc-extension-ui.ts +1 -1
  200. package/examples/sdk/06-extensions.ts +1 -1
  201. package/examples/sdk/README.md +1 -1
  202. package/node_modules/@code-yeongyu/senpi-codemode/CHANGELOG.md +12 -0
  203. package/node_modules/@code-yeongyu/senpi-codemode/package.json +4 -4
  204. package/node_modules/@earendil-works/pi-agent-core/dist/agent-loop.d.ts +3 -1
  205. package/node_modules/@earendil-works/pi-agent-core/dist/agent-loop.d.ts.map +1 -1
  206. package/node_modules/@earendil-works/pi-agent-core/dist/agent-loop.js +51 -17
  207. package/node_modules/@earendil-works/pi-agent-core/dist/agent-loop.js.map +1 -1
  208. package/node_modules/@earendil-works/pi-agent-core/dist/agent.d.ts +4 -2
  209. package/node_modules/@earendil-works/pi-agent-core/dist/agent.d.ts.map +1 -1
  210. package/node_modules/@earendil-works/pi-agent-core/dist/agent.js +5 -1
  211. package/node_modules/@earendil-works/pi-agent-core/dist/agent.js.map +1 -1
  212. package/node_modules/@earendil-works/pi-agent-core/dist/harness/skills.d.ts +3 -2
  213. package/node_modules/@earendil-works/pi-agent-core/dist/harness/skills.d.ts.map +1 -1
  214. package/node_modules/@earendil-works/pi-agent-core/dist/harness/skills.js +13 -3
  215. package/node_modules/@earendil-works/pi-agent-core/dist/harness/skills.js.map +1 -1
  216. package/node_modules/@earendil-works/pi-agent-core/dist/harness/tools/edit.d.ts.map +1 -1
  217. package/node_modules/@earendil-works/pi-agent-core/dist/harness/tools/edit.js +14 -1
  218. package/node_modules/@earendil-works/pi-agent-core/dist/harness/tools/edit.js.map +1 -1
  219. package/node_modules/@earendil-works/pi-agent-core/package.json +3 -3
  220. package/node_modules/@earendil-works/pi-ai/README.md +5 -3
  221. package/node_modules/@earendil-works/pi-ai/dist/api/anthropic-messages.d.ts +7 -1
  222. package/node_modules/@earendil-works/pi-ai/dist/api/anthropic-messages.d.ts.map +1 -1
  223. package/node_modules/@earendil-works/pi-ai/dist/api/anthropic-messages.js +21 -4
  224. package/node_modules/@earendil-works/pi-ai/dist/api/anthropic-messages.js.map +1 -1
  225. package/node_modules/@earendil-works/pi-ai/dist/api/azure-openai-responses.d.ts +2 -0
  226. package/node_modules/@earendil-works/pi-ai/dist/api/azure-openai-responses.d.ts.map +1 -1
  227. package/node_modules/@earendil-works/pi-ai/dist/api/azure-openai-responses.js +7 -1
  228. package/node_modules/@earendil-works/pi-ai/dist/api/azure-openai-responses.js.map +1 -1
  229. package/node_modules/@earendil-works/pi-ai/dist/api/bedrock-converse-stream.d.ts.map +1 -1
  230. package/node_modules/@earendil-works/pi-ai/dist/api/bedrock-converse-stream.js +39 -2
  231. package/node_modules/@earendil-works/pi-ai/dist/api/bedrock-converse-stream.js.map +1 -1
  232. package/node_modules/@earendil-works/pi-ai/dist/api/cursor-agent/gen/agent_pb.d.ts +22 -1
  233. package/node_modules/@earendil-works/pi-ai/dist/api/cursor-agent/gen/agent_pb.d.ts.map +1 -1
  234. package/node_modules/@earendil-works/pi-ai/dist/api/cursor-agent/gen/agent_pb.js +1 -1
  235. package/node_modules/@earendil-works/pi-ai/dist/api/cursor-agent/gen/agent_pb.js.map +1 -1
  236. package/node_modules/@earendil-works/pi-ai/dist/api/cursor-agent.d.ts +1 -0
  237. package/node_modules/@earendil-works/pi-ai/dist/api/cursor-agent.d.ts.map +1 -1
  238. package/node_modules/@earendil-works/pi-ai/dist/api/cursor-agent.js +51 -2
  239. package/node_modules/@earendil-works/pi-ai/dist/api/cursor-agent.js.map +1 -1
  240. package/node_modules/@earendil-works/pi-ai/dist/api/google-generative-ai.d.ts +2 -2
  241. package/node_modules/@earendil-works/pi-ai/dist/api/google-generative-ai.d.ts.map +1 -1
  242. package/node_modules/@earendil-works/pi-ai/dist/api/google-generative-ai.js +14 -11
  243. package/node_modules/@earendil-works/pi-ai/dist/api/google-generative-ai.js.map +1 -1
  244. package/node_modules/@earendil-works/pi-ai/dist/api/google-shared.d.ts +5 -2
  245. package/node_modules/@earendil-works/pi-ai/dist/api/google-shared.d.ts.map +1 -1
  246. package/node_modules/@earendil-works/pi-ai/dist/api/google-shared.js +16 -0
  247. package/node_modules/@earendil-works/pi-ai/dist/api/google-shared.js.map +1 -1
  248. package/node_modules/@earendil-works/pi-ai/dist/api/google-vertex.d.ts +2 -2
  249. package/node_modules/@earendil-works/pi-ai/dist/api/google-vertex.d.ts.map +1 -1
  250. package/node_modules/@earendil-works/pi-ai/dist/api/google-vertex.js +13 -10
  251. package/node_modules/@earendil-works/pi-ai/dist/api/google-vertex.js.map +1 -1
  252. package/node_modules/@earendil-works/pi-ai/dist/api/mistral-conversations.d.ts.map +1 -1
  253. package/node_modules/@earendil-works/pi-ai/dist/api/mistral-conversations.js +4 -1
  254. package/node_modules/@earendil-works/pi-ai/dist/api/mistral-conversations.js.map +1 -1
  255. package/node_modules/@earendil-works/pi-ai/dist/api/openai-codex-responses.d.ts.map +1 -1
  256. package/node_modules/@earendil-works/pi-ai/dist/api/openai-codex-responses.js +4 -1
  257. package/node_modules/@earendil-works/pi-ai/dist/api/openai-codex-responses.js.map +1 -1
  258. package/node_modules/@earendil-works/pi-ai/dist/api/openai-completions.d.ts +1 -1
  259. package/node_modules/@earendil-works/pi-ai/dist/api/openai-completions.d.ts.map +1 -1
  260. package/node_modules/@earendil-works/pi-ai/dist/api/openai-completions.js +45 -29
  261. package/node_modules/@earendil-works/pi-ai/dist/api/openai-completions.js.map +1 -1
  262. package/node_modules/@earendil-works/pi-ai/dist/api/openai-responses.d.ts.map +1 -1
  263. package/node_modules/@earendil-works/pi-ai/dist/api/openai-responses.js +8 -1
  264. package/node_modules/@earendil-works/pi-ai/dist/api/openai-responses.js.map +1 -1
  265. package/node_modules/@earendil-works/pi-ai/dist/api/pi-messages.js +1 -1
  266. package/node_modules/@earendil-works/pi-ai/dist/api/pi-messages.js.map +1 -1
  267. package/node_modules/@earendil-works/pi-ai/dist/api/simple-options.d.ts +4 -0
  268. package/node_modules/@earendil-works/pi-ai/dist/api/simple-options.d.ts.map +1 -1
  269. package/node_modules/@earendil-works/pi-ai/dist/api/simple-options.js +18 -9
  270. package/node_modules/@earendil-works/pi-ai/dist/api/simple-options.js.map +1 -1
  271. package/node_modules/@earendil-works/pi-ai/dist/auth/oauth/device-code.d.ts +1 -0
  272. package/node_modules/@earendil-works/pi-ai/dist/auth/oauth/device-code.d.ts.map +1 -1
  273. package/node_modules/@earendil-works/pi-ai/dist/auth/oauth/device-code.js +1 -1
  274. package/node_modules/@earendil-works/pi-ai/dist/auth/oauth/device-code.js.map +1 -1
  275. package/node_modules/@earendil-works/pi-ai/dist/auth/oauth/github-copilot.d.ts.map +1 -1
  276. package/node_modules/@earendil-works/pi-ai/dist/auth/oauth/github-copilot.js +22 -9
  277. package/node_modules/@earendil-works/pi-ai/dist/auth/oauth/github-copilot.js.map +1 -1
  278. package/node_modules/@earendil-works/pi-ai/dist/index.d.ts +2 -1
  279. package/node_modules/@earendil-works/pi-ai/dist/index.d.ts.map +1 -1
  280. package/node_modules/@earendil-works/pi-ai/dist/index.js +1 -0
  281. package/node_modules/@earendil-works/pi-ai/dist/index.js.map +1 -1
  282. package/node_modules/@earendil-works/pi-ai/dist/providers/data/.manifest.json +1 -1
  283. package/node_modules/@earendil-works/pi-ai/dist/providers/data/anthropic.json +1 -1
  284. package/node_modules/@earendil-works/pi-ai/dist/providers/data/baseten.json +1 -1
  285. package/node_modules/@earendil-works/pi-ai/dist/providers/data/github-copilot.json +1 -1
  286. package/node_modules/@earendil-works/pi-ai/dist/providers/data/opencode-go.json +1 -1
  287. package/node_modules/@earendil-works/pi-ai/dist/providers/data/opencode.json +1 -1
  288. package/node_modules/@earendil-works/pi-ai/dist/providers/data/openrouter.json +1 -1
  289. package/node_modules/@earendil-works/pi-ai/dist/providers/data/qwen-token-plan-individual.json +1 -1
  290. package/node_modules/@earendil-works/pi-ai/dist/providers/data/vercel-ai-gateway.json +1 -1
  291. package/node_modules/@earendil-works/pi-ai/dist/providers/data/xai.json +1 -1
  292. package/node_modules/@earendil-works/pi-ai/dist/providers/data/xiaomi-token-plan-ams.json +1 -1
  293. package/node_modules/@earendil-works/pi-ai/dist/providers/data/xiaomi-token-plan-cn.json +1 -1
  294. package/node_modules/@earendil-works/pi-ai/dist/providers/data/xiaomi-token-plan-sgp.json +1 -1
  295. package/node_modules/@earendil-works/pi-ai/dist/providers/data/xiaomi.json +1 -1
  296. package/node_modules/@earendil-works/pi-ai/dist/providers/data/zai-coding-cn.json +1 -1
  297. package/node_modules/@earendil-works/pi-ai/dist/providers/data/zai.json +1 -1
  298. package/node_modules/@earendil-works/pi-ai/dist/providers/xai.d.ts +1 -1
  299. package/node_modules/@earendil-works/pi-ai/dist/providers/xai.d.ts.map +1 -1
  300. package/node_modules/@earendil-works/pi-ai/dist/providers/xai.js +1 -5
  301. package/node_modules/@earendil-works/pi-ai/dist/providers/xai.js.map +1 -1
  302. package/node_modules/@earendil-works/pi-ai/dist/types.d.ts +28 -4
  303. package/node_modules/@earendil-works/pi-ai/dist/types.d.ts.map +1 -1
  304. package/node_modules/@earendil-works/pi-ai/dist/types.js.map +1 -1
  305. package/node_modules/@earendil-works/pi-ai/dist/utils/overflow.d.ts.map +1 -1
  306. package/node_modules/@earendil-works/pi-ai/dist/utils/overflow.js +15 -0
  307. package/node_modules/@earendil-works/pi-ai/dist/utils/overflow.js.map +1 -1
  308. package/node_modules/@earendil-works/pi-ai/dist/utils/pi-user-agent.d.ts +2 -0
  309. package/node_modules/@earendil-works/pi-ai/dist/utils/pi-user-agent.d.ts.map +1 -1
  310. package/node_modules/@earendil-works/pi-ai/dist/utils/pi-user-agent.js +7 -0
  311. package/node_modules/@earendil-works/pi-ai/dist/utils/pi-user-agent.js.map +1 -1
  312. package/node_modules/@earendil-works/pi-ai/dist/utils/prompt-cache-ttl.d.ts +2 -1
  313. package/node_modules/@earendil-works/pi-ai/dist/utils/prompt-cache-ttl.d.ts.map +1 -1
  314. package/node_modules/@earendil-works/pi-ai/dist/utils/prompt-cache-ttl.js +2 -0
  315. package/node_modules/@earendil-works/pi-ai/dist/utils/prompt-cache-ttl.js.map +1 -1
  316. package/node_modules/@earendil-works/pi-ai/package.json +2 -3
  317. package/node_modules/@earendil-works/pi-pty/dist/loader.d.ts +6 -0
  318. package/node_modules/@earendil-works/pi-pty/dist/loader.d.ts.map +1 -1
  319. package/node_modules/@earendil-works/pi-pty/dist/loader.js +11 -0
  320. package/node_modules/@earendil-works/pi-pty/dist/loader.js.map +1 -1
  321. package/node_modules/@earendil-works/pi-pty/dist/quarantine.d.ts +15 -0
  322. package/node_modules/@earendil-works/pi-pty/dist/quarantine.d.ts.map +1 -0
  323. package/node_modules/@earendil-works/pi-pty/dist/quarantine.js +27 -0
  324. package/node_modules/@earendil-works/pi-pty/dist/quarantine.js.map +1 -0
  325. package/node_modules/@earendil-works/pi-pty/package.json +1 -1
  326. package/node_modules/@earendil-works/pi-telemetry/package.json +1 -1
  327. package/node_modules/@earendil-works/pi-tui/dist/components/editor.d.ts +73 -1
  328. package/node_modules/@earendil-works/pi-tui/dist/components/editor.d.ts.map +1 -1
  329. package/node_modules/@earendil-works/pi-tui/dist/components/editor.js +173 -17
  330. package/node_modules/@earendil-works/pi-tui/dist/components/editor.js.map +1 -1
  331. package/node_modules/@earendil-works/pi-tui/dist/components/text.d.ts.map +1 -1
  332. package/node_modules/@earendil-works/pi-tui/dist/components/text.js +5 -4
  333. package/node_modules/@earendil-works/pi-tui/dist/components/text.js.map +1 -1
  334. package/node_modules/@earendil-works/pi-tui/dist/editor-component.d.ts +69 -0
  335. package/node_modules/@earendil-works/pi-tui/dist/editor-component.d.ts.map +1 -1
  336. package/node_modules/@earendil-works/pi-tui/dist/editor-component.js.map +1 -1
  337. package/node_modules/@earendil-works/pi-tui/dist/image-markers.d.ts +46 -0
  338. package/node_modules/@earendil-works/pi-tui/dist/image-markers.d.ts.map +1 -0
  339. package/node_modules/@earendil-works/pi-tui/dist/image-markers.js +130 -0
  340. package/node_modules/@earendil-works/pi-tui/dist/image-markers.js.map +1 -0
  341. package/node_modules/@earendil-works/pi-tui/dist/index.d.ts +1 -0
  342. package/node_modules/@earendil-works/pi-tui/dist/index.d.ts.map +1 -1
  343. package/node_modules/@earendil-works/pi-tui/dist/index.js +2 -0
  344. package/node_modules/@earendil-works/pi-tui/dist/index.js.map +1 -1
  345. package/node_modules/@earendil-works/pi-tui/dist/paste-markers.d.ts +3 -0
  346. package/node_modules/@earendil-works/pi-tui/dist/paste-markers.d.ts.map +1 -1
  347. package/node_modules/@earendil-works/pi-tui/dist/paste-markers.js +23 -7
  348. package/node_modules/@earendil-works/pi-tui/dist/paste-markers.js.map +1 -1
  349. package/node_modules/@earendil-works/pi-tui/dist/tui-alt-screen.d.ts.map +1 -1
  350. package/node_modules/@earendil-works/pi-tui/dist/tui-alt-screen.js +5 -1
  351. package/node_modules/@earendil-works/pi-tui/dist/tui-alt-screen.js.map +1 -1
  352. package/node_modules/@earendil-works/pi-tui/package.json +1 -1
  353. package/package.json +7 -7
@@ -2,7 +2,6 @@
2
2
  * Interactive mode for the coding agent.
3
3
  * Handles TUI rendering and user interaction, delegating business logic to AgentSession.
4
4
  */
5
- import * as crypto from "node:crypto";
6
5
  import * as fs from "node:fs";
7
6
  import * as os from "node:os";
8
7
  import * as path from "node:path";
@@ -36,8 +35,9 @@ import { getUsageCostBreakdown } from "../../core/usage-totals.js";
36
35
  import { consumeEarlyInspectorVmImportRecoveries, INSPECTOR_VM_IMPORT_WARNING, isRecoverableInspectorVmImportError, } from "../../inspector-policy.js";
37
36
  import { getChangelogPath, getNewEntries, normalizeChangelogLinks, parseChangelog } from "../../utils/changelog.js";
38
37
  import { copyToClipboard, readClipboardText } from "../../utils/clipboard.js";
39
- import { extensionForImageMimeType, readClipboardImage } from "../../utils/clipboard-image.js";
38
+ import { readClipboardImage } from "../../utils/clipboard-image.js";
40
39
  import { parseGitUrl } from "../../utils/git.js";
40
+ import { processImage } from "../../utils/image-process.js";
41
41
  import { openBrowser } from "../../utils/open-browser.js";
42
42
  import { getCwdRelativePath } from "../../utils/paths.js";
43
43
  import { getPiUserAgent } from "../../utils/pi-user-agent.js";
@@ -104,6 +104,11 @@ import { ToolArgsRevealController } from "./tool-args-reveal.js";
104
104
  import { readToolProgress } from "./tool-progress.js";
105
105
  import { ToolResultRevealController } from "./tool-result-reveal.js";
106
106
  import { blendWorkingStatusShimmerRgbColor, formatActiveToolWorkingLabel, formatToolHookStatusMessageFrame, formatWorkingStatusMessageFrame, largeSessionWorkingStatusInterval, sanitizeWorkingStatusPlainText, } from "./working-status.js";
107
+ function llamaCppPostLoginGuidance(actionLabel, loadedModelCount) {
108
+ return loadedModelCount === 0
109
+ ? `${actionLabel}. No llama.cpp models are loaded. Use /llama to load a model, then /model to select it.`
110
+ : `${actionLabel}. Use /model to select a loaded llama.cpp model, or /llama to manage models.`;
111
+ }
107
112
  function isExpandable(obj) {
108
113
  return typeof obj === "object" && obj !== null && "setExpanded" in obj && typeof obj.setExpanded === "function";
109
114
  }
@@ -133,6 +138,9 @@ function formatToolHookTerminalTitle(event) {
133
138
  function isCustomSessionEntry(item) {
134
139
  return "type" in item && item.type === "custom";
135
140
  }
141
+ function isCompactionCostNotice(item) {
142
+ return "type" in item && item.type === "compaction_cost";
143
+ }
136
144
  const DEAD_TERMINAL_ERROR_CODES = new Set(["EIO", "EPIPE", "ENOTCONN"]);
137
145
  const DEFAULT_WORKING_STATUS_REFRESH_INTERVAL_MS = 600;
138
146
  const DEFAULT_WORKING_STATUS_MESSAGE_ANIMATION_INTERVAL_MS = 32;
@@ -140,10 +148,26 @@ const LARGE_SESSION_WORKING_STATUS_REFRESH_INTERVAL_MS = 60_000;
140
148
  const LARGE_SESSION_WORKING_STATUS_MESSAGE_INTERVAL_MS = 1_000;
141
149
  const FALLBACK_STATUS_KEY = "fallback";
142
150
  const RGB_FOREGROUND_PATTERN = /\x1b\[38;2;(\d+);(\d+);(\d+)m/;
143
- const DARK_DEFAULT_WORKING_TEXT_RGB = { r: 229, g: 229, b: 231 };
144
- const LIGHT_DEFAULT_WORKING_TEXT_RGB = { r: 17, g: 17, b: 17 };
145
- const DARK_DEFAULT_WORKING_BASE_RGB = { r: 102, g: 102, b: 102 };
146
- const LIGHT_DEFAULT_WORKING_BASE_RGB = { r: 118, g: 118, b: 118 };
151
+ const DARK_DEFAULT_WORKING_TEXT_RGB = {
152
+ r: 229,
153
+ g: 229,
154
+ b: 231,
155
+ };
156
+ const LIGHT_DEFAULT_WORKING_TEXT_RGB = {
157
+ r: 17,
158
+ g: 17,
159
+ b: 17,
160
+ };
161
+ const DARK_DEFAULT_WORKING_BASE_RGB = {
162
+ r: 102,
163
+ g: 102,
164
+ b: 102,
165
+ };
166
+ const LIGHT_DEFAULT_WORKING_BASE_RGB = {
167
+ r: 118,
168
+ g: 118,
169
+ b: 118,
170
+ };
147
171
  function parseAnsiRgbForeground(ansi) {
148
172
  const match = RGB_FOREGROUND_PATTERN.exec(ansi);
149
173
  const red = match?.[1];
@@ -258,6 +282,63 @@ function formatLoginProviderCompletionDescription(provider) {
258
282
  const authTypes = provider.authTypes.map(formatAuthSelectorProviderType).join("/");
259
283
  return provider.name === provider.id ? authTypes : `${provider.name} · ${authTypes}`;
260
284
  }
285
+ /** Local copy of pi-tui's image-marker pattern so submission scanning never mutates a shared /g regex. */
286
+ const IMAGE_MARKER_PATTERN = /\[Image #([1-9]\d*)\]/g;
287
+ /**
288
+ * Attach a clipboard bitmap as an in-memory image behind an atomic
289
+ * `[Image #N]` marker. Returns false when nothing was attached, so the caller
290
+ * falls through to the plain-text clipboard path.
291
+ *
292
+ * The bytes deliberately never touch the filesystem: writing a temp file and
293
+ * inserting its path as literal text (the previous behavior) shipped an
294
+ * unreadable `/var/folders/.../pi-clipboard-<uuid>.png` string to the model and
295
+ * attached no image at all.
296
+ */
297
+ async function attachClipboardImage(deps, image) {
298
+ if (deps.settings.getBlockImages()) {
299
+ // Pinned behavior: nothing is attached and nothing is inserted, but the
300
+ // paste is never a silent no-op - the user must learn why their screenshot
301
+ // vanished, and which setting to flip.
302
+ deps.showStatus("Image paste blocked by the images.blockImages setting");
303
+ return false;
304
+ }
305
+ if (!deps.editor.insertImageMarker) {
306
+ // A marker-unaware editor cannot keep the marker atomic, and a dead literal
307
+ // `[Image #N]` with a live payload behind it would misnumber every other
308
+ // attachment at submit.
309
+ deps.showStatus("Image paste is not supported by the active editor");
310
+ return false;
311
+ }
312
+ if (deps.isCompacting()) {
313
+ // The compaction queue carries text only; an attachment queued behind
314
+ // compaction would be silently lost at delivery, so drop it here with a
315
+ // visible status. Consume the paste (true) - the clipboard holds a
316
+ // bitmap, so the plain-text fallback has nothing useful to insert.
317
+ deps.showStatus("Image paste dropped: messages sent during compaction cannot carry images - paste again after compaction finishes");
318
+ return true;
319
+ }
320
+ const processed = await processImage(image.bytes, image.mimeType, {
321
+ autoResizeImages: deps.settings.getImageAutoResize(),
322
+ });
323
+ if (!processed.ok) {
324
+ deps.showStatus(processed.message);
325
+ return false;
326
+ }
327
+ // insertImageMarker fires onImageMarkersChanged (with the pre-renumber
328
+ // ids) synchronously and only then returns the marker's FINAL canonical id,
329
+ // whose slot the reconcile pass just vacated - so this write can neither
330
+ // land in an orphaned map (reconcile mutates pendingImages in place) nor
331
+ // overwrite a surviving payload (the new marker had no payload when the
332
+ // reconcile ran, so nothing was keyed onto its slot).
333
+ const id = deps.editor.insertImageMarker();
334
+ deps.pendingImages.set(id, {
335
+ type: "image",
336
+ data: processed.data,
337
+ mimeType: processed.mimeType,
338
+ });
339
+ deps.requestRender();
340
+ return true;
341
+ }
261
342
  /** Composition root for selecting the interactive terminal renderer. */
262
343
  export function createInteractiveTui(options) {
263
344
  const terminal = options.terminal ?? new ProcessTerminal({ onExternalStdoutWrite: appendHiddenTuiStdout });
@@ -348,6 +429,14 @@ export class InteractiveMode {
348
429
  this.autocompleteProviderWrappers = [];
349
430
  this.isInitialized = false;
350
431
  this.pendingUserInputs = [];
432
+ /**
433
+ * Clipboard images pasted into the composer, keyed by their visible
434
+ * `[Image #N]` marker number. The editor stores marker ids only, so these
435
+ * bytes must live here (and survive an editor swap, which discards the
436
+ * editor instance). Kept aligned with the displayed numbers by
437
+ * {@link reconcilePendingImages}.
438
+ */
439
+ this.pendingImages = new Map();
351
440
  this.activeStatusIndicator = undefined;
352
441
  this.idleStatus = new IdleStatus();
353
442
  this.workingMessage = undefined;
@@ -777,7 +866,13 @@ export class InteractiveMode {
777
866
  { component: this.footerContainer, shrink: 1, minSize: 1 },
778
867
  ]);
779
868
  this.fullscreenLayoutRoot = new TuiLayouts.VStack([
780
- { component: this.transcriptScrollView, basis: 0, grow: 1, shrink: 1, minSize: 1 },
869
+ {
870
+ component: this.transcriptScrollView,
871
+ basis: 0,
872
+ grow: 1,
873
+ shrink: 1,
874
+ minSize: 1,
875
+ },
781
876
  { component: dock, basis: "auto", grow: 0, shrink: 1, minSize: 1 },
782
877
  ]);
783
878
  const rootComponents = [
@@ -1016,7 +1111,10 @@ export class InteractiveMode {
1016
1111
  while (true) {
1017
1112
  const userInput = await this.getUserInput();
1018
1113
  try {
1019
- await this.session.prompt(userInput, { streamingBehavior: "steer" });
1114
+ await this.session.prompt(userInput.text, {
1115
+ streamingBehavior: "steer",
1116
+ ...(userInput.images ? { images: userInput.images } : {}),
1117
+ });
1020
1118
  }
1021
1119
  catch (error) {
1022
1120
  const errorMessage = error instanceof Error ? error.message : "Unknown error occurred";
@@ -1173,7 +1271,10 @@ export class InteractiveMode {
1173
1271
  return;
1174
1272
  }
1175
1273
  const seeded = seedKeybindingsFile(configPath, this.keybindings);
1176
- const edit = await editFileInExternalEditor({ command: editorCommand, path: configPath });
1274
+ const edit = await editFileInExternalEditor({
1275
+ command: editorCommand,
1276
+ path: configPath,
1277
+ });
1177
1278
  if (edit.status === "launch-failed") {
1178
1279
  // The editor never ran, so a file we just seeded carries no user content.
1179
1280
  if (seeded)
@@ -1356,7 +1457,11 @@ export class InteractiveMode {
1356
1457
  return { label: "path", color: "muted" };
1357
1458
  }
1358
1459
  if (source === "cli") {
1359
- return { label: "path", scopeLabel: scope === "temporary" ? "temp" : undefined, color: "muted" };
1460
+ return {
1461
+ label: "path",
1462
+ scopeLabel: scope === "temporary" ? "temp" : undefined,
1463
+ color: "muted",
1464
+ };
1360
1465
  }
1361
1466
  const scopeLabel = scope === "user" ? "user" : scope === "project" ? "project" : scope === "temporary" ? "temp" : undefined;
1362
1467
  return { label: source, scopeLabel, color: "accent" };
@@ -1548,7 +1653,10 @@ export class InteractiveMode {
1548
1653
  }
1549
1654
  const skills = skillsResult.skills;
1550
1655
  if (skills.length > 0) {
1551
- const groups = this.buildScopeGroups(skills.map((skill) => ({ path: skill.filePath, sourceInfo: skill.sourceInfo })));
1656
+ const groups = this.buildScopeGroups(skills.map((skill) => ({
1657
+ path: skill.filePath,
1658
+ sourceInfo: skill.sourceInfo,
1659
+ })));
1552
1660
  const skillList = this.formatScopeGroups(groups, {
1553
1661
  formatPath: (item) => this.formatDisplayPath(item.path),
1554
1662
  formatPackagePath: (item) => this.getShortPath(item.path, item.sourceInfo),
@@ -1558,7 +1666,10 @@ export class InteractiveMode {
1558
1666
  }
1559
1667
  const templates = this.session.promptTemplates;
1560
1668
  if (templates.length > 0) {
1561
- const groups = this.buildScopeGroups(templates.map((template) => ({ path: template.filePath, sourceInfo: template.sourceInfo })));
1669
+ const groups = this.buildScopeGroups(templates.map((template) => ({
1670
+ path: template.filePath,
1671
+ sourceInfo: template.sourceInfo,
1672
+ })));
1562
1673
  const templateByPath = new Map(templates.map((t) => [t.filePath, t]));
1563
1674
  const templateList = this.formatScopeGroups(groups, {
1564
1675
  formatPath: (item) => {
@@ -1615,7 +1726,11 @@ export class InteractiveMode {
1615
1726
  const extensionErrors = this.session.resourceLoader.getExtensions().errors;
1616
1727
  if (extensionErrors.length > 0) {
1617
1728
  for (const error of extensionErrors) {
1618
- extensionDiagnostics.push({ type: "error", message: error.error, path: error.path });
1729
+ extensionDiagnostics.push({
1730
+ type: "error",
1731
+ message: error.error,
1732
+ path: error.path,
1733
+ });
1619
1734
  }
1620
1735
  }
1621
1736
  const commandDiagnostics = this.session.extensionRunner.getCommandDiagnostics();
@@ -2312,7 +2427,10 @@ export class InteractiveMode {
2312
2427
  this.ui.requestRender();
2313
2428
  }
2314
2429
  addExtensionTerminalInputListener(handler) {
2315
- const subscription = { handler, unsubscribe: this.ui.addInputListener(handler) };
2430
+ const subscription = {
2431
+ handler,
2432
+ unsubscribe: this.ui.addInputListener(handler),
2433
+ };
2316
2434
  this.extensionTerminalInputSubscriptions.add(subscription);
2317
2435
  return () => {
2318
2436
  subscription.unsubscribe();
@@ -2423,7 +2541,11 @@ export class InteractiveMode {
2423
2541
  opts?.signal?.removeEventListener("abort", onAbort);
2424
2542
  this.hideExtensionSelector();
2425
2543
  resolve(undefined);
2426
- }, { tui: this.ui, timeout: opts?.timeout, onToggleToolsExpanded: () => this.toggleToolOutputExpansion() });
2544
+ }, {
2545
+ tui: this.ui,
2546
+ timeout: opts?.timeout,
2547
+ onToggleToolsExpanded: () => this.toggleToolOutputExpansion(),
2548
+ });
2427
2549
  this.disposeActiveSelector();
2428
2550
  this.editorContainer.clear();
2429
2551
  this.editorContainer.addChild(this.extensionSelector);
@@ -2556,8 +2678,13 @@ export class InteractiveMode {
2556
2678
  this.defaultEditor.onSubmit?.(expandSubmittedText(newEditor, text));
2557
2679
  };
2558
2680
  newEditor.onChange = this.defaultEditor.onChange;
2559
- // Copy text (and any collapsed paste markers) from previous editor
2560
- transferEditorContent(this.editor, newEditor);
2681
+ // Copy text (and any collapsed paste/image markers) from previous editor.
2682
+ // Image payloads live on this instance, so they must be dropped when the
2683
+ // destination cannot own their markers.
2684
+ if (!transferEditorContent(this.editor, newEditor).imageMarkersTransferred) {
2685
+ this.pendingImages.clear();
2686
+ }
2687
+ this.subscribeImageMarkers(newEditor);
2561
2688
  // Copy appearance settings if supported
2562
2689
  if (newEditor.borderColor !== undefined) {
2563
2690
  newEditor.borderColor = this.defaultEditor.borderColor;
@@ -2600,7 +2727,10 @@ export class InteractiveMode {
2600
2727
  // hand-off, and a setText round-trip would be pure churn on the
2601
2728
  // user's draft.
2602
2729
  if (this.editor !== this.defaultEditor) {
2603
- transferEditorContent(this.editor, this.defaultEditor);
2730
+ if (!transferEditorContent(this.editor, this.defaultEditor).imageMarkersTransferred) {
2731
+ this.pendingImages.clear();
2732
+ }
2733
+ this.subscribeImageMarkers(this.defaultEditor);
2604
2734
  }
2605
2735
  this.editor = this.defaultEditor;
2606
2736
  }
@@ -2787,8 +2917,11 @@ export class InteractiveMode {
2787
2917
  }
2788
2918
  this.updateShortcutOverlay(text);
2789
2919
  };
2790
- // Handle clipboard paste (triggered on Ctrl+V). Images are attached by path;
2791
- // otherwise, paste plain text from the system clipboard.
2920
+ // Keep pendingImages aligned with the markers the editor displays.
2921
+ this.subscribeImageMarkers(this.defaultEditor);
2922
+ // Handle clipboard paste (triggered on Ctrl+V). Images are attached in
2923
+ // memory behind an atomic `[Image #N]` marker; otherwise, paste plain text
2924
+ // from the system clipboard.
2792
2925
  this.defaultEditor.onPasteImage = () => {
2793
2926
  this.lastInputWasPaste = true;
2794
2927
  void this.handleClipboardPaste();
@@ -2818,16 +2951,17 @@ export class InteractiveMode {
2818
2951
  async handleClipboardPaste() {
2819
2952
  try {
2820
2953
  const image = await readClipboardImage();
2821
- if (image) {
2822
- const tmpDir = os.tmpdir();
2823
- const ext = extensionForImageMimeType(image.mimeType) ?? "png";
2824
- const fileName = `pi-clipboard-${crypto.randomUUID()}.${ext}`;
2825
- const filePath = path.join(tmpDir, fileName);
2826
- fs.writeFileSync(filePath, Buffer.from(image.bytes));
2827
- this.editor.insertTextAtCursor?.(filePath);
2828
- this.ui.requestRender();
2954
+ const attached = image &&
2955
+ (await attachClipboardImage({
2956
+ editor: this.editor,
2957
+ pendingImages: this.pendingImages,
2958
+ settings: this.settingsManager,
2959
+ isCompacting: () => this.session?.isCompacting === true,
2960
+ showStatus: (message) => this.showStatus(message),
2961
+ requestRender: () => this.ui.requestRender(),
2962
+ }, image));
2963
+ if (attached)
2829
2964
  return;
2830
- }
2831
2965
  const text = await readClipboardText();
2832
2966
  if (text) {
2833
2967
  this.editor.insertTextAtCursor?.(text);
@@ -2836,10 +2970,89 @@ export class InteractiveMode {
2836
2970
  }
2837
2971
  catch (error) {
2838
2972
  const message = error instanceof Error ? error.message : String(error);
2839
- this.getSessionLogger().warn("clipboard_error", { op: "paste", error: message });
2973
+ this.getSessionLogger().warn("clipboard_error", {
2974
+ op: "paste",
2975
+ error: message,
2976
+ });
2840
2977
  this.showStatus(`Clipboard paste failed: ${sanitizeTuiErrorMessage(message)}`);
2841
2978
  }
2842
2979
  }
2980
+ /** Route an editor's image-marker changes into {@link reconcilePendingImages}. */
2981
+ subscribeImageMarkers(editor) {
2982
+ if (!editor.insertImageMarker)
2983
+ return;
2984
+ editor.onImageMarkersChanged = (order) => this.reconcilePendingImages(order);
2985
+ // The editor's undo stack restores marker TEXT and registry ids, but the
2986
+ // payloads live HERE; mirror them into every undo snapshot so undo restores
2987
+ // both halves of the pairing. Without this, deleting a marker re-keys the
2988
+ // survivors onto its number and a later undo re-displays the deleted marker
2989
+ // with no (or the wrong) payload behind it.
2990
+ editor.snapshotAttachmentState = () => new Map(this.pendingImages);
2991
+ editor.restoreAttachmentState = (state) => {
2992
+ if (!(state instanceof Map))
2993
+ return;
2994
+ this.pendingImages.clear();
2995
+ for (const [key, image] of state) {
2996
+ this.pendingImages.set(key, image);
2997
+ }
2998
+ };
2999
+ }
3000
+ /**
3001
+ * Re-key {@link pendingImages} onto the marker numbers the editor now
3002
+ * displays. `order` lists the SURVIVING marker ids (pre-renumber, i.e. the
3003
+ * keys the payloads currently sit under) in text reading order, and the
3004
+ * editor keeps the visible numbers canonical 1..k in reading order, so a
3005
+ * payload's new key is its position in that list. Ids absent from `order`
3006
+ * are dropped; reported ids with no payload (a hand-typed marker) are skipped
3007
+ * without consuming anyone else's slot.
3008
+ *
3009
+ * The map is mutated IN PLACE, never replaced: handleClipboardPaste hands
3010
+ * this.pendingImages by reference into attachClipboardImage's deps, and a
3011
+ * fresh identity would orphan that reference - the second paste in a turn
3012
+ * then wrote into the dead map and silently destroyed its image.
3013
+ */
3014
+ reconcilePendingImages(order) {
3015
+ if (this.pendingImages.size === 0)
3016
+ return;
3017
+ const reconciled = new Map();
3018
+ order.forEach((id, index) => {
3019
+ const image = this.pendingImages.get(id);
3020
+ if (image)
3021
+ reconciled.set(index + 1, image);
3022
+ });
3023
+ this.pendingImages.clear();
3024
+ for (const [key, image] of reconciled) {
3025
+ this.pendingImages.set(key, image);
3026
+ }
3027
+ }
3028
+ /**
3029
+ * Resolve the `[Image #N]` markers in `submittedText` (in READING order)
3030
+ * into the image array submitted alongside it, then clear
3031
+ * {@link pendingImages}.
3032
+ *
3033
+ * Reads ONLY the submitted text plus pendingImages - never live editor
3034
+ * state: pi-tui's `Editor.submitValue()` resets the editor and clears its
3035
+ * registries BEFORE `onSubmit` fires. A marker with no pending payload (a
3036
+ * hand-typed `[Image #N]`, or the second half of a kill/yank duplicate) is
3037
+ * passed through untouched and consumes no slot; a payload-bearing
3038
+ * marker's FIRST occurrence wins. Slots are assigned 1..k in reading
3039
+ * order, so the Nth marker in the final text is `images[N-1]`.
3040
+ */
3041
+ takeSubmissionImages(submittedText) {
3042
+ const images = [];
3043
+ const consumed = new Set();
3044
+ for (const match of submittedText.matchAll(IMAGE_MARKER_PATTERN)) {
3045
+ const id = Number.parseInt(match[1] ?? "0", 10);
3046
+ if (consumed.has(id))
3047
+ continue;
3048
+ consumed.add(id);
3049
+ const image = this.pendingImages.get(id);
3050
+ if (image)
3051
+ images.push(image);
3052
+ }
3053
+ this.pendingImages.clear();
3054
+ return images;
3055
+ }
2843
3056
  getSessionLogger() {
2844
3057
  this.sessionLogger ??= createSessionLogger(this.runtimeHost.services.agentDir);
2845
3058
  return this.sessionLogger;
@@ -2850,6 +3063,13 @@ export class InteractiveMode {
2850
3063
  }
2851
3064
  setupEditorSubmitHandler() {
2852
3065
  this.defaultEditor.onSubmit = async (text) => {
3066
+ // Capture-then-clear BEFORE any branch: handleFollowUp's non-streaming
3067
+ // path pre-resolves images and hands off here, but slash / extension /
3068
+ // bash submissions return before the consuming branches below. Clearing
3069
+ // only after use would leak a stale array into a later ordinary
3070
+ // submission whose text never references it.
3071
+ const preResolvedImages = this.preResolvedSubmissionImages;
3072
+ this.preResolvedSubmissionImages = undefined;
2853
3073
  this.hideShortcutOverlay();
2854
3074
  this.lastEditorText = "";
2855
3075
  text = text.trim();
@@ -3033,7 +3253,7 @@ export class InteractiveMode {
3033
3253
  await this.session.prompt(text);
3034
3254
  }
3035
3255
  else {
3036
- this.queueCompactionMessage(text, "steer");
3256
+ this.queueCompactionSubmission(text, "steer");
3037
3257
  }
3038
3258
  return;
3039
3259
  }
@@ -3043,9 +3263,15 @@ export class InteractiveMode {
3043
3263
  // behavior here applies only to ordinary text, prompt template expansion,
3044
3264
  // and queueing.
3045
3265
  if (this.session.isStreaming) {
3266
+ // Resolve BEFORE setText(""): the editor's prune chain fires
3267
+ // onImageMarkersChanged([]) and destroys pendingImages.
3268
+ const images = preResolvedImages ?? this.takeSubmissionImages(text);
3046
3269
  this.editor.addToHistory?.(text);
3047
3270
  this.editor.setText("");
3048
- await this.session.prompt(text, { streamingBehavior: "steer" });
3271
+ await this.session.prompt(text, {
3272
+ streamingBehavior: "steer",
3273
+ ...(images.length > 0 ? { images } : {}),
3274
+ });
3049
3275
  this.updatePendingMessagesDisplay();
3050
3276
  this.ui.requestRender();
3051
3277
  return;
@@ -3053,11 +3279,13 @@ export class InteractiveMode {
3053
3279
  // Normal message submission
3054
3280
  // First, move any pending bash components to chat
3055
3281
  this.flushPendingBashComponents();
3282
+ const images = preResolvedImages ?? this.takeSubmissionImages(text);
3283
+ const submission = images.length > 0 ? { text, images } : { text };
3056
3284
  if (this.onInputCallback) {
3057
- this.onInputCallback(text);
3285
+ this.onInputCallback(submission);
3058
3286
  }
3059
3287
  else {
3060
- this.pendingUserInputs.push(text);
3288
+ this.pendingUserInputs.push(submission);
3061
3289
  }
3062
3290
  this.editor.addToHistory?.(text);
3063
3291
  };
@@ -3248,7 +3476,9 @@ export class InteractiveMode {
3248
3476
  }
3249
3477
  const activity = readToolProgress(event.partialResult.details)?.activity;
3250
3478
  if (activity) {
3251
- const label = formatActiveToolWorkingLabel(event.toolName, { command: activity });
3479
+ const label = formatActiveToolWorkingLabel(event.toolName, {
3480
+ command: activity,
3481
+ });
3252
3482
  this.activeToolExecutions.set(event.toolCallId, label);
3253
3483
  this.workingMessage = label;
3254
3484
  this.activeToolExecutionTerminalTitle = `${APP_TITLE} - ${label}`;
@@ -3351,9 +3581,35 @@ export class InteractiveMode {
3351
3581
  }
3352
3582
  }
3353
3583
  else if (event.result) {
3584
+ // Compaction event consumers in the fork are session-backed and do not
3585
+ // necessarily expose InteractiveMode's SessionManager convenience getter.
3586
+ // Keep the structural fallback for focused handler consumers while using
3587
+ // the session-owned manager on the real TUI path.
3588
+ const sessionManager = this.session.sessionManager ?? this.sessionManager;
3589
+ const entries = sessionManager.buildContextEntries();
3590
+ if (entries[0]?.type !== "compaction") {
3591
+ throw new Error("Completed compaction is missing from the session context");
3592
+ }
3354
3593
  this.chatContainer.clear();
3355
- this.rebuildChatFromMessages();
3356
- this.addMessageToChat(createCompactionSummaryMessage(sanitizeTerminalLabel(event.result.summary), event.result.tokensBefore, new Date().toISOString(), event.result.details));
3594
+ const summaryMessage = createCompactionSummaryMessage(sanitizeTerminalLabel(event.result.summary), event.result.tokensBefore, new Date().toISOString(), event.result.details);
3595
+ if (typeof this.renderSessionEntries === "function") {
3596
+ // The latest compaction is prepended for model context; append it below at its chronological position.
3597
+ this.renderSessionEntries(entries.slice(1));
3598
+ this.addMessageToChat(summaryMessage);
3599
+ if (event.result.usage) {
3600
+ this.addCompactionCostNotice({
3601
+ type: "compaction_cost",
3602
+ kind: "compaction",
3603
+ usage: event.result.usage,
3604
+ });
3605
+ }
3606
+ }
3607
+ else {
3608
+ // Fork-owned compaction consumers expose the established rebuild helper,
3609
+ // not InteractiveMode's private entry renderer.
3610
+ this.rebuildChatFromMessages();
3611
+ this.addMessageToChat(summaryMessage);
3612
+ }
3357
3613
  this.footer.invalidate();
3358
3614
  }
3359
3615
  else if (event.errorMessage) {
@@ -3389,7 +3645,10 @@ export class InteractiveMode {
3389
3645
  // over-threshold context would just repeat the failure.
3390
3646
  const compactionSucceeded = event.accepted === true || event.result !== undefined;
3391
3647
  if (compactionSucceeded) {
3392
- void this.flushCompactionQueue({ willRetry: event.willRetry, deferAdmission: false });
3648
+ void this.flushCompactionQueue({
3649
+ willRetry: event.willRetry,
3650
+ deferAdmission: false,
3651
+ });
3393
3652
  }
3394
3653
  else if (event.willRetry === true) {
3395
3654
  const heldCount = this.compactionQueuedMessages.length;
@@ -3401,7 +3660,10 @@ export class InteractiveMode {
3401
3660
  });
3402
3661
  this.showStatus(`${heldCount} queued message${heldCount === 1 ? "" : "s"} will send with the next turn (compaction will retry)`);
3403
3662
  }
3404
- void this.flushCompactionQueue({ willRetry: true, deferAdmission: true });
3663
+ void this.flushCompactionQueue({
3664
+ willRetry: true,
3665
+ deferAdmission: true,
3666
+ });
3405
3667
  }
3406
3668
  else {
3407
3669
  const restoredCount = this.restoreQueuedMessagesToEditor();
@@ -3731,6 +3993,10 @@ export class InteractiveMode {
3731
3993
  this.addCustomEntryToChat(item);
3732
3994
  continue;
3733
3995
  }
3996
+ if (isCompactionCostNotice(item)) {
3997
+ this.addCompactionCostNotice(item);
3998
+ continue;
3999
+ }
3734
4000
  const message = item;
3735
4001
  // Assistant messages need special handling for tool calls
3736
4002
  if (message.role === "assistant") {
@@ -3749,7 +4015,10 @@ export class InteractiveMode {
3749
4015
  else {
3750
4016
  errorMessage = message.errorMessage || "Error";
3751
4017
  }
3752
- component.updateResult({ content: [{ type: "text", text: errorMessage }], isError: true });
4018
+ component.updateResult({
4019
+ content: [{ type: "text", text: errorMessage }],
4020
+ isError: true,
4021
+ });
3753
4022
  }
3754
4023
  else {
3755
4024
  renderedPendingTools.set(content.id, component);
@@ -3795,10 +4064,28 @@ export class InteractiveMode {
3795
4064
  if (entry.type === "custom") {
3796
4065
  return [entry];
3797
4066
  }
3798
- return sessionEntryToContextMessages(entry);
4067
+ const messages = sessionEntryToContextMessages(entry);
4068
+ if ((entry.type === "compaction" || entry.type === "branch_summary") && entry.usage && messages.length > 0) {
4069
+ return [...messages, { type: "compaction_cost", kind: entry.type, usage: entry.usage }];
4070
+ }
4071
+ return messages;
3799
4072
  });
3800
4073
  this.renderSessionItems(items, options);
3801
4074
  }
4075
+ /**
4076
+ * Render billing usage for a compaction or branch summary. The notice is derived
4077
+ * from persisted summary usage and is not stored as a separate session entry.
4078
+ */
4079
+ addCompactionCostNotice(notice) {
4080
+ if (!this.settingsManager.getShowCacheMissNotices())
4081
+ return;
4082
+ const { usage } = notice;
4083
+ const tokens = usage.input + usage.output + usage.cacheRead + usage.cacheWrite;
4084
+ const cost = usage.cost.total >= 0.01 ? ` (~$${usage.cost.total.toFixed(2)})` : "";
4085
+ const label = notice.kind === "compaction" ? "Compaction" : "Branch summary";
4086
+ this.chatContainer.addChild(new Spacer(1));
4087
+ this.chatContainer.addChild(new Text(theme.fg("warning", `${label}: ${formatTokens(tokens)} tokens billed${cost}`), 1, 0));
4088
+ }
3802
4089
  /**
3803
4090
  * Show a transcript notice when a completed assistant message paid for a
3804
4091
  * significant cache miss. Only states observable facts: the miss itself,
@@ -3865,9 +4152,9 @@ export class InteractiveMode {
3865
4152
  return queuedInput;
3866
4153
  }
3867
4154
  return new Promise((resolve) => {
3868
- this.onInputCallback = (text) => {
4155
+ this.onInputCallback = (input) => {
3869
4156
  this.onInputCallback = undefined;
3870
- resolve(text);
4157
+ resolve(input);
3871
4158
  };
3872
4159
  });
3873
4160
  }
@@ -4070,7 +4357,10 @@ export class InteractiveMode {
4070
4357
  // does NOT pass through onSubmit, so the isExtensionCommand check below is the
4071
4358
  // live dispatch point here: commands go straight to AgentSession.prompt(),
4072
4359
  // which runs them immediately even while compaction is active, while ordinary
4073
- // text is queued for delivery after compaction settles.
4360
+ // text is queued for delivery after compaction settles. Image attachments
4361
+ // are consumed and dropped VISIBLY inside queueCompactionSubmission - the
4362
+ // queue is text-only, so resolving them here and queueing the text with a
4363
+ // dead marker would lose the images without telling the user.
4074
4364
  if (this.session.isCompacting) {
4075
4365
  if (this.isExtensionCommand(text)) {
4076
4366
  this.editor.addToHistory?.(text);
@@ -4078,10 +4368,15 @@ export class InteractiveMode {
4078
4368
  await this.session.prompt(text);
4079
4369
  }
4080
4370
  else {
4081
- this.queueCompactionMessage(text, "followUp");
4371
+ this.queueCompactionSubmission(text, "followUp");
4082
4372
  }
4083
4373
  return;
4084
4374
  }
4375
+ // Resolve attachments BEFORE any setText("") below: the editor's prune
4376
+ // chain fires onImageMarkersChanged([]) and the reconciler destroys
4377
+ // pendingImages, so resolving after the clear would ship a literal
4378
+ // `[Image #N]` with no attachment behind it.
4379
+ const images = this.takeSubmissionImages(text);
4085
4380
  // Alt+Enter queues a follow-up message (waits until agent finishes).
4086
4381
  // Extension commands never reach this branch: the compaction branch above
4087
4382
  // dispatches them while compacting, and otherwise prompt() runs them
@@ -4090,12 +4385,20 @@ export class InteractiveMode {
4090
4385
  if (this.session.isStreaming) {
4091
4386
  this.editor.addToHistory?.(text);
4092
4387
  this.editor.setText("");
4093
- await this.session.prompt(text, { streamingBehavior: "followUp" });
4388
+ await this.session.prompt(text, {
4389
+ streamingBehavior: "followUp",
4390
+ ...(images.length > 0 ? { images } : {}),
4391
+ });
4094
4392
  this.updatePendingMessagesDisplay();
4095
4393
  this.ui.requestRender();
4096
4394
  }
4097
4395
  // If not streaming, Alt+Enter acts like regular Enter (trigger onSubmit)
4098
4396
  else if (this.editor.onSubmit) {
4397
+ // The public `onSubmit(text: string)` API cannot be widened, so hand
4398
+ // the pre-resolved images over out-of-band; the submit handler
4399
+ // captures-and-clears the field at entry and delivers them through the
4400
+ // widened main-loop channel.
4401
+ this.preResolvedSubmissionImages = images.length > 0 ? images : undefined;
4099
4402
  this.editor.setText("");
4100
4403
  this.editor.onSubmit(text);
4101
4404
  }
@@ -4234,7 +4537,12 @@ export class InteractiveMode {
4234
4537
  why: sanitizeTuiErrorMessage(spec.why),
4235
4538
  ...(spec.extra === undefined
4236
4539
  ? {}
4237
- : { extra: spec.extra.map((line) => ({ ...line, text: sanitizeTuiErrorMessage(line.text) })) }),
4540
+ : {
4541
+ extra: spec.extra.map((line) => ({
4542
+ ...line,
4543
+ text: sanitizeTuiErrorMessage(line.text),
4544
+ })),
4545
+ }),
4238
4546
  ...(spec.expandedLine === undefined ? {} : { expandedLine: sanitizeTuiErrorMessage(spec.expandedLine) }),
4239
4547
  };
4240
4548
  this.chatContainer.addChild(new Spacer(1));
@@ -4316,7 +4624,11 @@ export class InteractiveMode {
4316
4624
  const clearedNative = this.session.clearQueue(options);
4317
4625
  const { steering, followUp } = clearedNative;
4318
4626
  const nativeMessages = clearedNative.ordered ?? [
4319
- ...steering.map((text, enqueueOrder) => ({ text, mode: "steer", enqueueOrder })),
4627
+ ...steering.map((text, enqueueOrder) => ({
4628
+ text,
4629
+ mode: "steer",
4630
+ enqueueOrder,
4631
+ })),
4320
4632
  ...followUp.map((text, index) => ({
4321
4633
  text,
4322
4634
  mode: "followUp",
@@ -4343,7 +4655,10 @@ export class InteractiveMode {
4343
4655
  steering: [...steering, ...compactionSteering],
4344
4656
  followUp: [...followUp, ...compactionFollowUp],
4345
4657
  };
4346
- Object.defineProperty(cleared, "ordered", { value: ordered, enumerable: false });
4658
+ Object.defineProperty(cleared, "ordered", {
4659
+ value: ordered,
4660
+ enumerable: false,
4661
+ });
4347
4662
  return cleared;
4348
4663
  }
4349
4664
  updatePendingMessagesDisplay() {
@@ -4365,7 +4680,9 @@ export class InteractiveMode {
4365
4680
  }
4366
4681
  }
4367
4682
  restoreQueuedMessagesToEditor(options) {
4368
- const { steering, followUp, ordered } = this.clearAllQueues({ abortWillFollow: options?.abort === true });
4683
+ const { steering, followUp, ordered } = this.clearAllQueues({
4684
+ abortWillFollow: options?.abort === true,
4685
+ });
4369
4686
  const allQueued = ordered?.map((message) => message.text) ?? [...steering, ...followUp];
4370
4687
  if (allQueued.length === 0) {
4371
4688
  this.updatePendingMessagesDisplay();
@@ -4394,7 +4711,9 @@ export class InteractiveMode {
4394
4711
  * - The helper never auto-prompts restored queue text; the user decides whether to send it.
4395
4712
  */
4396
4713
  async abortAndFireQueuedMessages() {
4397
- const { steering, followUp, ordered } = this.clearAllQueues({ abortWillFollow: true });
4714
+ const { steering, followUp, ordered } = this.clearAllQueues({
4715
+ abortWillFollow: true,
4716
+ });
4398
4717
  const allQueued = ordered?.map((message) => message.text) ?? [...steering, ...followUp];
4399
4718
  this.updatePendingMessagesDisplay();
4400
4719
  await this.session.abort();
@@ -4407,13 +4726,43 @@ export class InteractiveMode {
4407
4726
  this.editor.setText(combinedText);
4408
4727
  return allQueued.length;
4409
4728
  }
4410
- queueCompactionMessage(text, mode) {
4411
- this.compactionQueuedMessages.push({ text, mode, enqueueOrder: this.session.reserveQueuedInputOrder() });
4412
- this.getSessionLogger().debug("compaction_queue_enqueue", { mode, count: this.compactionQueuedMessages.length });
4729
+ queueCompactionMessage(text, mode, droppedImageCount = 0) {
4730
+ this.compactionQueuedMessages.push({
4731
+ text,
4732
+ mode,
4733
+ enqueueOrder: this.session.reserveQueuedInputOrder(),
4734
+ });
4735
+ this.getSessionLogger().debug("compaction_queue_enqueue", {
4736
+ mode,
4737
+ count: this.compactionQueuedMessages.length,
4738
+ });
4413
4739
  this.editor.addToHistory?.(text);
4414
4740
  this.editor.setText("");
4415
4741
  this.updatePendingMessagesDisplay();
4416
- this.showStatus("Queued message for after compaction");
4742
+ this.showStatus(droppedImageCount > 0
4743
+ ? `Queued message for after compaction; dropped ${droppedImageCount} image${droppedImageCount > 1 ? "s" : ""}: messages sent during compaction cannot carry images - paste again after compaction finishes`
4744
+ : "Queued message for after compaction");
4745
+ }
4746
+ /**
4747
+ * Queue a user submission for delivery after compaction. The compaction
4748
+ * queue carries text only, so pasted attachments are dropped here -
4749
+ * VISIBLY, never silently, mirroring attachClipboardImage's paste-time
4750
+ * contract - and their `[Image #N]` markers are stripped, because a queued
4751
+ * literal marker has no payload behind it and would ship an unreadable
4752
+ * `[Image #N]` string to the model once the queue drains.
4753
+ */
4754
+ queueCompactionSubmission(text, mode) {
4755
+ const images = this.takeSubmissionImages(text);
4756
+ if (images.length === 0) {
4757
+ this.queueCompactionMessage(text, mode);
4758
+ return;
4759
+ }
4760
+ const queued = text.replace(IMAGE_MARKER_PATTERN, "").trim();
4761
+ if (queued) {
4762
+ this.queueCompactionMessage(queued, mode, images.length);
4763
+ return;
4764
+ }
4765
+ this.showStatus(`Dropped ${images.length} image${images.length > 1 ? "s" : ""}: messages sent during compaction cannot carry images - paste again after compaction finishes`);
4417
4766
  }
4418
4767
  hasRegisteredCommand(command) {
4419
4768
  return !!this.session.extensionRunner.getCommand(command);
@@ -4480,8 +4829,12 @@ export class InteractiveMode {
4480
4829
  return message.mode === "followUp" ? session.followUp(message.text) : session.steer(message.text);
4481
4830
  }
4482
4831
  return message.mode === "followUp"
4483
- ? session.followUp(message.text, undefined, { enqueueOrder: message.enqueueOrder })
4484
- : session.steer(message.text, undefined, { enqueueOrder: message.enqueueOrder });
4832
+ ? session.followUp(message.text, undefined, {
4833
+ enqueueOrder: message.enqueueOrder,
4834
+ })
4835
+ : session.steer(message.text, undefined, {
4836
+ enqueueOrder: message.enqueueOrder,
4837
+ });
4485
4838
  },
4486
4839
  reportFailure: (error, undeliveredCount) => {
4487
4840
  this.showError(`Failed to send queued message${undeliveredCount === 1 ? "" : "s"}: ${error instanceof Error ? error.message : String(error)}`);
@@ -4991,7 +5344,11 @@ export class InteractiveMode {
4991
5344
  await this.applyFavoriteSelection(favoriteIds, allModels, true, await favoritePatternSnapshot);
4992
5345
  },
4993
5346
  });
4994
- return { component: selector, focus: selector, dispose: () => selector.dispose() };
5347
+ return {
5348
+ component: selector,
5349
+ focus: selector,
5350
+ dispose: () => selector.dispose(),
5351
+ };
4995
5352
  });
4996
5353
  }
4997
5354
  async showFavoriteModelsSelector() {
@@ -5399,7 +5756,9 @@ export class InteractiveMode {
5399
5756
  return options.sort((a, b) => a.name.localeCompare(b.name));
5400
5757
  }
5401
5758
  async getLogoutProviderOptions() {
5402
- return (await this.session.modelRuntime.listCredentials({ signal: AbortSignal.timeout(15_000) }))
5759
+ return (await this.session.modelRuntime.listCredentials({
5760
+ signal: AbortSignal.timeout(15_000),
5761
+ }))
5403
5762
  .map(({ providerId, type }) => ({
5404
5763
  id: providerId,
5405
5764
  name: this.session.modelRuntime.getProvider(providerId)?.name ?? providerId,
@@ -5580,7 +5939,11 @@ export class InteractiveMode {
5580
5939
  if (isUnknownModel(previousModel)) {
5581
5940
  const availableModels = this.session.modelRuntime.getAvailableSnapshot();
5582
5941
  const providerModels = availableModels.filter((model) => model.provider === providerId);
5583
- if (!hasDefaultModelProvider(providerId)) {
5942
+ // Matches LLAMA_PROVIDER_ID from extensions/llama/provider.ts; kept inline to avoid coupling interactive mode to the built-in extension.
5943
+ if (providerId === "llama.cpp") {
5944
+ selectionError = llamaCppPostLoginGuidance(actionLabel, providerModels.length);
5945
+ }
5946
+ else if (!hasDefaultModelProvider(providerId)) {
5584
5947
  selectionError = `${actionLabel}, but no default model is configured for provider "${providerId}". Use /model to select a model.`;
5585
5948
  }
5586
5949
  else if (providerModels.length === 0) {
@@ -5627,7 +5990,11 @@ export class InteractiveMode {
5627
5990
  const timeout = setTimeout(() => controller.abort(), 15_000);
5628
5991
  const refreshProviders = providerId === "cursor" ? [providerId, "cursor-cli-oauth"] : [providerId];
5629
5992
  void this.session.modelRuntime
5630
- .refresh({ allowNetwork: true, providers: refreshProviders, signal: controller.signal })
5993
+ .refresh({
5994
+ allowNetwork: true,
5995
+ providers: refreshProviders,
5996
+ signal: controller.signal,
5997
+ })
5631
5998
  .then((result) => {
5632
5999
  if (result.aborted) {
5633
6000
  this.showWarning(`${actionLabel}, but its model catalog refresh timed out; using cached models.`);
@@ -5855,7 +6222,9 @@ export class InteractiveMode {
5855
6222
  chatRestoredBeforeSessionStart = true;
5856
6223
  };
5857
6224
  try {
5858
- const reloadResult = await this.session.reload({ beforeSessionStart: restoreChatBeforeSessionStart });
6225
+ const reloadResult = await this.session.reload({
6226
+ beforeSessionStart: restoreChatBeforeSessionStart,
6227
+ });
5859
6228
  if (reloadResult.cancelled) {
5860
6229
  dismissReloadBox(previousEditor);
5861
6230
  reloadBoxDismissed = true;
@@ -5986,7 +6355,9 @@ export class InteractiveMode {
5986
6355
  async handleShareCommand() {
5987
6356
  // Check if gh is available and logged in
5988
6357
  try {
5989
- const authResult = spawnSync("gh", ["auth", "status"], { encoding: "utf-8" });
6358
+ const authResult = spawnSync("gh", ["auth", "status"], {
6359
+ encoding: "utf-8",
6360
+ });
5990
6361
  if (authResult.status !== 0) {
5991
6362
  this.showError("GitHub CLI is not logged in. Run 'gh auth login' first.");
5992
6363
  return;