@code-yeongyu/senpi 2026.6.30 → 2026.7.3

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 (272) hide show
  1. package/CHANGELOG.md +70 -0
  2. package/dist/core/agent-session.d.ts +6 -1
  3. package/dist/core/agent-session.d.ts.map +1 -1
  4. package/dist/core/agent-session.js +42 -25
  5. package/dist/core/agent-session.js.map +1 -1
  6. package/dist/core/auth-storage.d.ts.map +1 -1
  7. package/dist/core/auth-storage.js +13 -5
  8. package/dist/core/auth-storage.js.map +1 -1
  9. package/dist/core/compaction/compaction.d.ts.map +1 -1
  10. package/dist/core/compaction/compaction.js +5 -8
  11. package/dist/core/compaction/compaction.js.map +1 -1
  12. package/dist/core/dynamic-prompt/build.d.ts +13 -0
  13. package/dist/core/dynamic-prompt/build.d.ts.map +1 -1
  14. package/dist/core/dynamic-prompt/build.js +24 -21
  15. package/dist/core/dynamic-prompt/build.js.map +1 -1
  16. package/dist/core/dynamic-prompt/exploration.d.ts.map +1 -1
  17. package/dist/core/dynamic-prompt/exploration.js +1 -3
  18. package/dist/core/dynamic-prompt/exploration.js.map +1 -1
  19. package/dist/core/dynamic-prompt/index.d.ts +1 -1
  20. package/dist/core/dynamic-prompt/index.d.ts.map +1 -1
  21. package/dist/core/dynamic-prompt/index.js.map +1 -1
  22. package/dist/core/dynamic-prompt/intent-gate.d.ts.map +1 -1
  23. package/dist/core/dynamic-prompt/intent-gate.js +20 -35
  24. package/dist/core/dynamic-prompt/intent-gate.js.map +1 -1
  25. package/dist/core/dynamic-prompt/parallel-tools.d.ts.map +1 -1
  26. package/dist/core/dynamic-prompt/parallel-tools.js +2 -4
  27. package/dist/core/dynamic-prompt/parallel-tools.js.map +1 -1
  28. package/dist/core/dynamic-prompt/style.d.ts.map +1 -1
  29. package/dist/core/dynamic-prompt/style.js +7 -26
  30. package/dist/core/dynamic-prompt/style.js.map +1 -1
  31. package/dist/core/extensions/builtin/prompt-preset/claude-fable-5.d.ts +3 -0
  32. package/dist/core/extensions/builtin/prompt-preset/claude-fable-5.d.ts.map +1 -0
  33. package/dist/core/extensions/builtin/prompt-preset/claude-fable-5.js +16 -0
  34. package/dist/core/extensions/builtin/prompt-preset/claude-fable-5.js.map +1 -0
  35. package/dist/core/extensions/builtin/prompt-preset/claude-opus-4-5.d.ts.map +1 -1
  36. package/dist/core/extensions/builtin/prompt-preset/claude-opus-4-5.js +3 -1
  37. package/dist/core/extensions/builtin/prompt-preset/claude-opus-4-5.js.map +1 -1
  38. package/dist/core/extensions/builtin/prompt-preset/claude-opus-4-6.d.ts.map +1 -1
  39. package/dist/core/extensions/builtin/prompt-preset/claude-opus-4-6.js +3 -1
  40. package/dist/core/extensions/builtin/prompt-preset/claude-opus-4-6.js.map +1 -1
  41. package/dist/core/extensions/builtin/prompt-preset/claude-opus-4-7.d.ts.map +1 -1
  42. package/dist/core/extensions/builtin/prompt-preset/claude-opus-4-7.js +6 -2
  43. package/dist/core/extensions/builtin/prompt-preset/claude-opus-4-7.js.map +1 -1
  44. package/dist/core/extensions/builtin/prompt-preset/claude-opus-4-8.d.ts.map +1 -1
  45. package/dist/core/extensions/builtin/prompt-preset/claude-opus-4-8.js +5 -3
  46. package/dist/core/extensions/builtin/prompt-preset/claude-opus-4-8.js.map +1 -1
  47. package/dist/core/extensions/builtin/prompt-preset/gpt-5.5.d.ts.map +1 -1
  48. package/dist/core/extensions/builtin/prompt-preset/gpt-5.5.js +68 -8
  49. package/dist/core/extensions/builtin/prompt-preset/gpt-5.5.js.map +1 -1
  50. package/dist/core/extensions/builtin/prompt-preset/presets.d.ts.map +1 -1
  51. package/dist/core/extensions/builtin/prompt-preset/presets.js +9 -0
  52. package/dist/core/extensions/builtin/prompt-preset/presets.js.map +1 -1
  53. package/dist/core/extensions/builtin/prompt-preset/settings.d.ts +1 -1
  54. package/dist/core/extensions/builtin/prompt-preset/settings.d.ts.map +1 -1
  55. package/dist/core/extensions/builtin/prompt-preset/settings.js +1 -0
  56. package/dist/core/extensions/builtin/prompt-preset/settings.js.map +1 -1
  57. package/dist/core/extensions/index.d.ts +1 -1
  58. package/dist/core/extensions/index.d.ts.map +1 -1
  59. package/dist/core/extensions/index.js.map +1 -1
  60. package/dist/core/extensions/loader.d.ts.map +1 -1
  61. package/dist/core/extensions/loader.js +6 -0
  62. package/dist/core/extensions/loader.js.map +1 -1
  63. package/dist/core/extensions/runner.d.ts +2 -1
  64. package/dist/core/extensions/runner.d.ts.map +1 -1
  65. package/dist/core/extensions/runner.js +33 -1
  66. package/dist/core/extensions/runner.js.map +1 -1
  67. package/dist/core/extensions/types.d.ts +16 -2
  68. package/dist/core/extensions/types.d.ts.map +1 -1
  69. package/dist/core/extensions/types.js.map +1 -1
  70. package/dist/core/http-dispatcher.d.ts.map +1 -1
  71. package/dist/core/http-dispatcher.js +28 -1
  72. package/dist/core/http-dispatcher.js.map +1 -1
  73. package/dist/core/model-resolver.d.ts +10 -0
  74. package/dist/core/model-resolver.d.ts.map +1 -1
  75. package/dist/core/model-resolver.js +21 -27
  76. package/dist/core/model-resolver.js.map +1 -1
  77. package/dist/core/session-manager.d.ts +20 -1
  78. package/dist/core/session-manager.d.ts.map +1 -1
  79. package/dist/core/session-manager.js +87 -68
  80. package/dist/core/session-manager.js.map +1 -1
  81. package/dist/core/settings-manager.d.ts +3 -0
  82. package/dist/core/settings-manager.d.ts.map +1 -1
  83. package/dist/core/settings-manager.js +8 -0
  84. package/dist/core/settings-manager.js.map +1 -1
  85. package/dist/core/tools/bash.d.ts.map +1 -1
  86. package/dist/core/tools/bash.js +20 -5
  87. package/dist/core/tools/bash.js.map +1 -1
  88. package/dist/extension-system-migration.d.ts +2 -0
  89. package/dist/extension-system-migration.d.ts.map +1 -0
  90. package/dist/extension-system-migration.js +51 -0
  91. package/dist/extension-system-migration.js.map +1 -0
  92. package/dist/index.d.ts +3 -2
  93. package/dist/index.d.ts.map +1 -1
  94. package/dist/index.js +2 -1
  95. package/dist/index.js.map +1 -1
  96. package/dist/legacy-senpi-dir-migration.d.ts +2 -0
  97. package/dist/legacy-senpi-dir-migration.d.ts.map +1 -0
  98. package/dist/legacy-senpi-dir-migration.js +76 -0
  99. package/dist/legacy-senpi-dir-migration.js.map +1 -0
  100. package/dist/migrations.d.ts.map +1 -1
  101. package/dist/migrations.js +3 -95
  102. package/dist/migrations.js.map +1 -1
  103. package/dist/modes/interactive/components/assistant-message.d.ts +3 -1
  104. package/dist/modes/interactive/components/assistant-message.d.ts.map +1 -1
  105. package/dist/modes/interactive/components/assistant-message.js +15 -7
  106. package/dist/modes/interactive/components/assistant-message.js.map +1 -1
  107. package/dist/modes/interactive/components/custom-entry.d.ts +19 -0
  108. package/dist/modes/interactive/components/custom-entry.d.ts.map +1 -0
  109. package/dist/modes/interactive/components/custom-entry.js +49 -0
  110. package/dist/modes/interactive/components/custom-entry.js.map +1 -0
  111. package/dist/modes/interactive/components/settings-selector.d.ts +2 -0
  112. package/dist/modes/interactive/components/settings-selector.d.ts.map +1 -1
  113. package/dist/modes/interactive/components/settings-selector.js +13 -1
  114. package/dist/modes/interactive/components/settings-selector.js.map +1 -1
  115. package/dist/modes/interactive/components/status-indicator.d.ts +27 -0
  116. package/dist/modes/interactive/components/status-indicator.d.ts.map +1 -0
  117. package/dist/modes/interactive/components/status-indicator.js +64 -0
  118. package/dist/modes/interactive/components/status-indicator.js.map +1 -0
  119. package/dist/modes/interactive/components/user-message.d.ts +6 -2
  120. package/dist/modes/interactive/components/user-message.d.ts.map +1 -1
  121. package/dist/modes/interactive/components/user-message.js +15 -4
  122. package/dist/modes/interactive/components/user-message.js.map +1 -1
  123. package/dist/modes/interactive/interactive-mode.d.ts +14 -15
  124. package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
  125. package/dist/modes/interactive/interactive-mode.js +177 -137
  126. package/dist/modes/interactive/interactive-mode.js.map +1 -1
  127. package/dist/modes/rpc/rpc-client.d.ts +15 -0
  128. package/dist/modes/rpc/rpc-client.d.ts.map +1 -1
  129. package/dist/modes/rpc/rpc-client.js +14 -0
  130. package/dist/modes/rpc/rpc-client.js.map +1 -1
  131. package/dist/modes/rpc/rpc-mode.d.ts.map +1 -1
  132. package/dist/modes/rpc/rpc-mode.js +16 -0
  133. package/dist/modes/rpc/rpc-mode.js.map +1 -1
  134. package/dist/modes/rpc/rpc-types.d.ts +26 -0
  135. package/dist/modes/rpc/rpc-types.d.ts.map +1 -1
  136. package/dist/modes/rpc/rpc-types.js.map +1 -1
  137. package/docs/extensions.md +55 -8
  138. package/docs/rpc.md +58 -0
  139. package/docs/sdk.md +28 -0
  140. package/docs/session-format.md +18 -8
  141. package/docs/settings.md +2 -1
  142. package/examples/extensions/README.md +1 -0
  143. package/examples/extensions/custom-provider-anthropic/package-lock.json +2 -2
  144. package/examples/extensions/custom-provider-anthropic/package.json +1 -1
  145. package/examples/extensions/custom-provider-gitlab-duo/package.json +1 -1
  146. package/examples/extensions/entry-renderer.ts +41 -0
  147. package/examples/extensions/gondolin/package-lock.json +2 -2
  148. package/examples/extensions/gondolin/package.json +1 -1
  149. package/examples/extensions/question.ts +1 -0
  150. package/examples/extensions/sandbox/package-lock.json +2 -2
  151. package/examples/extensions/sandbox/package.json +1 -1
  152. package/examples/extensions/with-deps/package-lock.json +2 -2
  153. package/examples/extensions/with-deps/package.json +1 -1
  154. package/node_modules/@earendil-works/pi-agent-core/dist/agent.d.ts +3 -1
  155. package/node_modules/@earendil-works/pi-agent-core/dist/agent.d.ts.map +1 -1
  156. package/node_modules/@earendil-works/pi-agent-core/dist/agent.js +9 -1
  157. package/node_modules/@earendil-works/pi-agent-core/dist/agent.js.map +1 -1
  158. package/node_modules/@earendil-works/pi-agent-core/dist/harness/compaction/compaction.d.ts.map +1 -1
  159. package/node_modules/@earendil-works/pi-agent-core/dist/harness/compaction/compaction.js +4 -6
  160. package/node_modules/@earendil-works/pi-agent-core/dist/harness/compaction/compaction.js.map +1 -1
  161. package/node_modules/@earendil-works/pi-agent-core/dist/harness/env/nodejs.d.ts.map +1 -1
  162. package/node_modules/@earendil-works/pi-agent-core/dist/harness/env/nodejs.js +20 -2
  163. package/node_modules/@earendil-works/pi-agent-core/dist/harness/env/nodejs.js.map +1 -1
  164. package/node_modules/@earendil-works/pi-agent-core/package.json +2 -2
  165. package/node_modules/@earendil-works/pi-ai/dist/api/anthropic-messages.d.ts.map +1 -1
  166. package/node_modules/@earendil-works/pi-ai/dist/api/anthropic-messages.js +6 -0
  167. package/node_modules/@earendil-works/pi-ai/dist/api/anthropic-messages.js.map +1 -1
  168. package/node_modules/@earendil-works/pi-ai/dist/api/azure-openai-responses.d.ts.map +1 -1
  169. package/node_modules/@earendil-works/pi-ai/dist/api/azure-openai-responses.js +2 -14
  170. package/node_modules/@earendil-works/pi-ai/dist/api/azure-openai-responses.js.map +1 -1
  171. package/node_modules/@earendil-works/pi-ai/dist/api/bedrock-converse-stream.d.ts.map +1 -1
  172. package/node_modules/@earendil-works/pi-ai/dist/api/bedrock-converse-stream.js +16 -5
  173. package/node_modules/@earendil-works/pi-ai/dist/api/bedrock-converse-stream.js.map +1 -1
  174. package/node_modules/@earendil-works/pi-ai/dist/api/google-generative-ai.d.ts.map +1 -1
  175. package/node_modules/@earendil-works/pi-ai/dist/api/google-generative-ai.js +2 -1
  176. package/node_modules/@earendil-works/pi-ai/dist/api/google-generative-ai.js.map +1 -1
  177. package/node_modules/@earendil-works/pi-ai/dist/api/google-vertex.d.ts.map +1 -1
  178. package/node_modules/@earendil-works/pi-ai/dist/api/google-vertex.js +2 -1
  179. package/node_modules/@earendil-works/pi-ai/dist/api/google-vertex.js.map +1 -1
  180. package/node_modules/@earendil-works/pi-ai/dist/api/openai-codex-responses.d.ts.map +1 -1
  181. package/node_modules/@earendil-works/pi-ai/dist/api/openai-codex-responses.js +56 -39
  182. package/node_modules/@earendil-works/pi-ai/dist/api/openai-codex-responses.js.map +1 -1
  183. package/node_modules/@earendil-works/pi-ai/dist/api/openai-completions.d.ts.map +1 -1
  184. package/node_modules/@earendil-works/pi-ai/dist/api/openai-completions.js +8 -3
  185. package/node_modules/@earendil-works/pi-ai/dist/api/openai-completions.js.map +1 -1
  186. package/node_modules/@earendil-works/pi-ai/dist/api/openai-responses.d.ts.map +1 -1
  187. package/node_modules/@earendil-works/pi-ai/dist/api/openai-responses.js +2 -14
  188. package/node_modules/@earendil-works/pi-ai/dist/api/openai-responses.js.map +1 -1
  189. package/node_modules/@earendil-works/pi-ai/dist/api/openrouter-images.d.ts.map +1 -1
  190. package/node_modules/@earendil-works/pi-ai/dist/api/openrouter-images.js +2 -1
  191. package/node_modules/@earendil-works/pi-ai/dist/api/openrouter-images.js.map +1 -1
  192. package/node_modules/@earendil-works/pi-ai/dist/image-models.generated.d.ts +15 -45
  193. package/node_modules/@earendil-works/pi-ai/dist/image-models.generated.d.ts.map +1 -1
  194. package/node_modules/@earendil-works/pi-ai/dist/image-models.generated.js +15 -45
  195. package/node_modules/@earendil-works/pi-ai/dist/image-models.generated.js.map +1 -1
  196. package/node_modules/@earendil-works/pi-ai/dist/models.generated.d.ts +456 -37
  197. package/node_modules/@earendil-works/pi-ai/dist/models.generated.d.ts.map +1 -1
  198. package/node_modules/@earendil-works/pi-ai/dist/providers/amazon-bedrock.models.d.ts +123 -0
  199. package/node_modules/@earendil-works/pi-ai/dist/providers/amazon-bedrock.models.d.ts.map +1 -1
  200. package/node_modules/@earendil-works/pi-ai/dist/providers/amazon-bedrock.models.js +124 -4
  201. package/node_modules/@earendil-works/pi-ai/dist/providers/amazon-bedrock.models.js.map +1 -1
  202. package/node_modules/@earendil-works/pi-ai/dist/providers/anthropic.models.d.ts +20 -0
  203. package/node_modules/@earendil-works/pi-ai/dist/providers/anthropic.models.d.ts.map +1 -1
  204. package/node_modules/@earendil-works/pi-ai/dist/providers/anthropic.models.js +18 -0
  205. package/node_modules/@earendil-works/pi-ai/dist/providers/anthropic.models.js.map +1 -1
  206. package/node_modules/@earendil-works/pi-ai/dist/providers/cerebras.models.d.ts +21 -0
  207. package/node_modules/@earendil-works/pi-ai/dist/providers/cerebras.models.d.ts.map +1 -1
  208. package/node_modules/@earendil-works/pi-ai/dist/providers/cerebras.models.js +18 -0
  209. package/node_modules/@earendil-works/pi-ai/dist/providers/cerebras.models.js.map +1 -1
  210. package/node_modules/@earendil-works/pi-ai/dist/providers/cloudflare-ai-gateway.models.d.ts +21 -0
  211. package/node_modules/@earendil-works/pi-ai/dist/providers/cloudflare-ai-gateway.models.d.ts.map +1 -1
  212. package/node_modules/@earendil-works/pi-ai/dist/providers/cloudflare-ai-gateway.models.js +18 -0
  213. package/node_modules/@earendil-works/pi-ai/dist/providers/cloudflare-ai-gateway.models.js.map +1 -1
  214. package/node_modules/@earendil-works/pi-ai/dist/providers/fireworks.models.d.ts +10 -5
  215. package/node_modules/@earendil-works/pi-ai/dist/providers/fireworks.models.d.ts.map +1 -1
  216. package/node_modules/@earendil-works/pi-ai/dist/providers/fireworks.models.js +4 -3
  217. package/node_modules/@earendil-works/pi-ai/dist/providers/fireworks.models.js.map +1 -1
  218. package/node_modules/@earendil-works/pi-ai/dist/providers/github-copilot.models.d.ts +82 -0
  219. package/node_modules/@earendil-works/pi-ai/dist/providers/github-copilot.models.d.ts.map +1 -1
  220. package/node_modules/@earendil-works/pi-ai/dist/providers/github-copilot.models.js +57 -0
  221. package/node_modules/@earendil-works/pi-ai/dist/providers/github-copilot.models.js.map +1 -1
  222. package/node_modules/@earendil-works/pi-ai/dist/providers/nvidia.models.d.ts +0 -28
  223. package/node_modules/@earendil-works/pi-ai/dist/providers/nvidia.models.d.ts.map +1 -1
  224. package/node_modules/@earendil-works/pi-ai/dist/providers/nvidia.models.js +0 -19
  225. package/node_modules/@earendil-works/pi-ai/dist/providers/nvidia.models.js.map +1 -1
  226. package/node_modules/@earendil-works/pi-ai/dist/providers/opencode-go.models.js +5 -5
  227. package/node_modules/@earendil-works/pi-ai/dist/providers/opencode-go.models.js.map +1 -1
  228. package/node_modules/@earendil-works/pi-ai/dist/providers/opencode.models.d.ts +89 -0
  229. package/node_modules/@earendil-works/pi-ai/dist/providers/opencode.models.d.ts.map +1 -1
  230. package/node_modules/@earendil-works/pi-ai/dist/providers/opencode.models.js +75 -2
  231. package/node_modules/@earendil-works/pi-ai/dist/providers/opencode.models.js.map +1 -1
  232. package/node_modules/@earendil-works/pi-ai/dist/providers/openrouter.models.d.ts +46 -4
  233. package/node_modules/@earendil-works/pi-ai/dist/providers/openrouter.models.d.ts.map +1 -1
  234. package/node_modules/@earendil-works/pi-ai/dist/providers/openrouter.models.js +91 -55
  235. package/node_modules/@earendil-works/pi-ai/dist/providers/openrouter.models.js.map +1 -1
  236. package/node_modules/@earendil-works/pi-ai/dist/providers/together.models.js +2 -2
  237. package/node_modules/@earendil-works/pi-ai/dist/providers/together.models.js.map +1 -1
  238. package/node_modules/@earendil-works/pi-ai/dist/providers/vercel-ai-gateway.models.d.ts +44 -0
  239. package/node_modules/@earendil-works/pi-ai/dist/providers/vercel-ai-gateway.models.d.ts.map +1 -1
  240. package/node_modules/@earendil-works/pi-ai/dist/providers/vercel-ai-gateway.models.js +41 -4
  241. package/node_modules/@earendil-works/pi-ai/dist/providers/vercel-ai-gateway.models.js.map +1 -1
  242. package/node_modules/@earendil-works/pi-ai/dist/utils/error-body.d.ts +25 -0
  243. package/node_modules/@earendil-works/pi-ai/dist/utils/error-body.d.ts.map +1 -0
  244. package/node_modules/@earendil-works/pi-ai/dist/utils/error-body.js +109 -0
  245. package/node_modules/@earendil-works/pi-ai/dist/utils/error-body.js.map +1 -0
  246. package/node_modules/@earendil-works/pi-ai/dist/utils/oauth/device-code.d.ts +1 -0
  247. package/node_modules/@earendil-works/pi-ai/dist/utils/oauth/device-code.d.ts.map +1 -1
  248. package/node_modules/@earendil-works/pi-ai/dist/utils/oauth/device-code.js +6 -0
  249. package/node_modules/@earendil-works/pi-ai/dist/utils/oauth/device-code.js.map +1 -1
  250. package/node_modules/@earendil-works/pi-ai/dist/utils/oauth/github-copilot.d.ts.map +1 -1
  251. package/node_modules/@earendil-works/pi-ai/dist/utils/oauth/github-copilot.js +1 -0
  252. package/node_modules/@earendil-works/pi-ai/dist/utils/oauth/github-copilot.js.map +1 -1
  253. package/node_modules/@earendil-works/pi-ai/dist/utils/overflow.d.ts +1 -0
  254. package/node_modules/@earendil-works/pi-ai/dist/utils/overflow.d.ts.map +1 -1
  255. package/node_modules/@earendil-works/pi-ai/dist/utils/overflow.js +3 -0
  256. package/node_modules/@earendil-works/pi-ai/dist/utils/overflow.js.map +1 -1
  257. package/node_modules/@earendil-works/pi-ai/package.json +1 -1
  258. package/node_modules/@earendil-works/pi-tui/dist/mux.d.ts +4 -0
  259. package/node_modules/@earendil-works/pi-tui/dist/mux.d.ts.map +1 -0
  260. package/node_modules/@earendil-works/pi-tui/dist/mux.js +10 -0
  261. package/node_modules/@earendil-works/pi-tui/dist/mux.js.map +1 -0
  262. package/node_modules/@earendil-works/pi-tui/dist/tui.d.ts +42 -1
  263. package/node_modules/@earendil-works/pi-tui/dist/tui.d.ts.map +1 -1
  264. package/node_modules/@earendil-works/pi-tui/dist/tui.js +422 -101
  265. package/node_modules/@earendil-works/pi-tui/dist/tui.js.map +1 -1
  266. package/node_modules/@earendil-works/pi-tui/dist/utils.d.ts +10 -0
  267. package/node_modules/@earendil-works/pi-tui/dist/utils.d.ts.map +1 -1
  268. package/node_modules/@earendil-works/pi-tui/dist/utils.js +53 -13
  269. package/node_modules/@earendil-works/pi-tui/dist/utils.js.map +1 -1
  270. package/node_modules/@earendil-works/pi-tui/package.json +2 -1
  271. package/npm-shrinkwrap.json +9 -9
  272. package/package.json +4 -4
@@ -7,7 +7,7 @@ import * as fs from "node:fs";
7
7
  import * as os from "node:os";
8
8
  import * as path from "node:path";
9
9
  import { getProviders, } from "@earendil-works/pi-ai/compat";
10
- import { CombinedAutocompleteProvider, Container, fuzzyFilter, getCapabilities, hyperlink, Loader, Markdown, matchesKey, ProcessTerminal, Spacer, setKeybindings, Text, TruncatedText, TUI, visibleWidth, } from "@earendil-works/pi-tui";
10
+ import { CombinedAutocompleteProvider, Container, fuzzyFilter, getCapabilities, hyperlink, Markdown, matchesKey, ProcessTerminal, Spacer, setKeybindings, Text, TruncatedText, TUI, visibleWidth, } from "@earendil-works/pi-tui";
11
11
  import chalk from "chalk";
12
12
  import { spawn, spawnSync } from "child_process";
13
13
  import { APP_NAME, APP_TITLE, CONFIG_DIR_NAME, expandTildePath, getAuthPath, getDebugLogPath, getDocsPath, getShareViewerUrl, VERSION, } from "../../config.js";
@@ -20,7 +20,7 @@ import { createCompactionSummaryMessage } from "../../core/messages.js";
20
20
  import { defaultModelPerProvider, findExactModelReferenceMatch, resolveModelScope, } from "../../core/model-resolver.js";
21
21
  import { BUILT_IN_PROVIDER_DISPLAY_NAMES } from "../../core/provider-display-names.js";
22
22
  import { formatMissingSessionCwdPrompt, MissingSessionCwdError } from "../../core/session-cwd.js";
23
- import { SessionManager } from "../../core/session-manager.js";
23
+ import { SessionManager, sessionEntryToContextMessages } from "../../core/session-manager.js";
24
24
  import { BUILTIN_SLASH_COMMANDS } from "../../core/slash-commands.js";
25
25
  import { isInstallTelemetryEnabled } from "../../core/telemetry.js";
26
26
  import { hasTrustRequiringProjectResources, ProjectTrustStore } from "../../core/trust-manager.js";
@@ -39,8 +39,8 @@ import { BashExecutionComponent } from "./components/bash-execution.js";
39
39
  import { BorderedLoader } from "./components/bordered-loader.js";
40
40
  import { BranchSummaryMessageComponent } from "./components/branch-summary-message.js";
41
41
  import { CompactionSummaryMessageComponent } from "./components/compaction-summary-message.js";
42
- import { CountdownTimer } from "./components/countdown-timer.js";
43
42
  import { CustomEditor } from "./components/custom-editor.js";
43
+ import { CustomEntryComponent } from "./components/custom-entry.js";
44
44
  import { CustomMessageComponent } from "./components/custom-message.js";
45
45
  import { DaxnutsComponent } from "./components/daxnuts.js";
46
46
  import { DynamicBorder } from "./components/dynamic-border.js";
@@ -58,6 +58,7 @@ import { OAuthSelectorComponent } from "./components/oauth-selector.js";
58
58
  import { SessionSelectorComponent } from "./components/session-selector.js";
59
59
  import { SettingsSelectorComponent } from "./components/settings-selector.js";
60
60
  import { SkillInvocationMessageComponent } from "./components/skill-invocation-message.js";
61
+ import { BranchSummaryStatusIndicator, CompactionStatusIndicator, IdleStatus, RetryStatusIndicator, WorkingStatusIndicator, } from "./components/status-indicator.js";
61
62
  import { ToolExecutionComponent } from "./components/tool-execution.js";
62
63
  import { TreeSelectorComponent } from "./components/tree-selector.js";
63
64
  import { TrustSelectorComponent } from "./components/trust-selector.js";
@@ -87,6 +88,9 @@ function formatToolHookTerminalTitle(event) {
87
88
  const statusMessage = sanitizeWorkingStatusPlainText(event.statusMessage);
88
89
  return `${APP_TITLE} - ${hookName}: ${statusMessage}`;
89
90
  }
91
+ function isCustomSessionEntry(item) {
92
+ return "type" in item && item.type === "custom";
93
+ }
90
94
  const DEAD_TERMINAL_ERROR_CODES = new Set(["EIO", "EPIPE", "ENOTCONN"]);
91
95
  const DEFAULT_WORKING_STATUS_REFRESH_INTERVAL_MS = 600;
92
96
  const DEFAULT_WORKING_STATUS_MESSAGE_ANIMATION_INTERVAL_MS = 32;
@@ -197,12 +201,12 @@ export class InteractiveMode {
197
201
  this.autocompleteProviderWrappers = [];
198
202
  this.isInitialized = false;
199
203
  this.pendingUserInputs = [];
200
- this.loadingAnimation = undefined;
204
+ this.activeStatusIndicator = undefined;
205
+ this.idleStatus = new IdleStatus();
201
206
  this.workingMessage = undefined;
202
207
  this.workingVisible = true;
203
208
  this.workingIndicatorOptions = undefined;
204
209
  this.workingStartedAt = undefined;
205
- this.workingElapsedIntervalId = undefined;
206
210
  this.defaultWorkingMessage = "Working";
207
211
  this.defaultHiddenThinkingLabel = "Thinking...";
208
212
  this.hiddenThinkingLabel = this.defaultHiddenThinkingLabel;
@@ -230,6 +234,7 @@ export class InteractiveMode {
230
234
  this.toolOutputExpanded = false;
231
235
  // Thinking block visibility state
232
236
  this.hideThinkingBlock = false;
237
+ this.outputPad = 1;
233
238
  // Skill commands: command name -> skill file path
234
239
  this.skillCommands = new Map();
235
240
  this.signalCleanupHandlers = [];
@@ -239,12 +244,7 @@ export class InteractiveMode {
239
244
  this.bashComponent = undefined;
240
245
  // Track pending bash components (shown in pending area, moved to chat on submit)
241
246
  this.pendingBashComponents = [];
242
- // Auto-compaction state
243
- this.autoCompactionLoader = undefined;
244
247
  this.autoCompactionProgressText = "";
245
- // Auto-retry state
246
- this.retryLoader = undefined;
247
- this.retryCountdown = undefined;
248
248
  // Messages queued while compaction is running
249
249
  this.compactionQueuedMessages = [];
250
250
  // Shutdown state
@@ -305,6 +305,7 @@ export class InteractiveMode {
305
305
  this.footer.setAutoCompactEnabled(this.session.autoCompactionEnabled);
306
306
  // Load hide thinking block setting
307
307
  this.hideThinkingBlock = this.settingsManager.getHideThinkingBlock();
308
+ this.outputPad = this.settingsManager.getOutputPad();
308
309
  // Register themes from resource loader and initialize
309
310
  setRegisteredThemes(this.session.resourceLoader.getThemes().themes);
310
311
  this.themeController = new InteractiveThemeController(this.ui, this.settingsManager, (message) => this.showError(message), () => this.updateEditorBorderColor());
@@ -1200,7 +1201,7 @@ export class InteractiveMode {
1200
1201
  commandContextActions: {
1201
1202
  waitForIdle: () => this.session.agent.waitForIdle(),
1202
1203
  newSession: async (options) => {
1203
- this.stopWorkingLoader();
1204
+ this.clearStatusIndicator();
1204
1205
  try {
1205
1206
  return await this.runtimeHost.newSession(options);
1206
1207
  }
@@ -1270,8 +1271,13 @@ export class InteractiveMode {
1270
1271
  this.footer.setAutoCompactEnabled(this.session.autoCompactionEnabled);
1271
1272
  this.footerDataProvider.setCwd(this.sessionManager.getCwd());
1272
1273
  this.hideThinkingBlock = this.settingsManager.getHideThinkingBlock();
1274
+ this.outputPad = this.settingsManager.getOutputPad();
1273
1275
  this.ui.setShowHardwareCursor(this.settingsManager.getShowHardwareCursor());
1274
- this.ui.setClearOnShrink(this.settingsManager.getClearOnShrink());
1276
+ const clearOnShrink = this.settingsManager.getClearOnShrink();
1277
+ this.ui.setClearOnShrink(clearOnShrink);
1278
+ if (!clearOnShrink && !this.activeStatusIndicator) {
1279
+ this.statusContainer.clear();
1280
+ }
1275
1281
  const editorPaddingX = this.settingsManager.getEditorPaddingX();
1276
1282
  const autocompleteMaxVisible = this.settingsManager.getAutocompleteMaxVisible();
1277
1283
  this.defaultEditor.setPaddingX(editorPaddingX);
@@ -1387,31 +1393,6 @@ export class InteractiveMode {
1387
1393
  this.footerDataProvider.setExtensionStatus(key, text);
1388
1394
  this.ui.requestRender();
1389
1395
  }
1390
- getWorkingLoaderMessage() {
1391
- return this.workingMessage ?? this.defaultWorkingMessage;
1392
- }
1393
- getWorkingElapsedSeconds() {
1394
- if (this.workingStartedAt === undefined) {
1395
- return 0;
1396
- }
1397
- return Math.max(0, Math.floor((Date.now() - this.workingStartedAt) / 1000));
1398
- }
1399
- refreshWorkingLoaderMessage() {
1400
- this.loadingAnimation?.setMessage(this.getWorkingLoaderMessage());
1401
- }
1402
- startWorkingElapsedTimer() {
1403
- this.stopWorkingElapsedTimer();
1404
- this.workingStartedAt = Date.now();
1405
- this.workingElapsedIntervalId = setInterval(() => {
1406
- this.refreshWorkingLoaderMessage();
1407
- }, DEFAULT_WORKING_STATUS_REFRESH_INTERVAL_MS);
1408
- }
1409
- stopWorkingElapsedTimer() {
1410
- if (this.workingElapsedIntervalId) {
1411
- clearInterval(this.workingElapsedIntervalId);
1412
- this.workingElapsedIntervalId = undefined;
1413
- }
1414
- }
1415
1396
  startToolHookStatusTimer() {
1416
1397
  if (this.hookStatusIntervalId) {
1417
1398
  return;
@@ -1419,6 +1400,7 @@ export class InteractiveMode {
1419
1400
  this.hookStatusIntervalId = setInterval(() => {
1420
1401
  this.refreshToolHookStatuses();
1421
1402
  }, DEFAULT_WORKING_STATUS_MESSAGE_ANIMATION_INTERVAL_MS);
1403
+ this.hookStatusIntervalId.unref();
1422
1404
  }
1423
1405
  stopToolHookStatusTimer() {
1424
1406
  if (this.hookStatusIntervalId) {
@@ -1522,6 +1504,23 @@ export class InteractiveMode {
1522
1504
  this.hookStatusContainer.clear();
1523
1505
  this.stopToolHookStatusTimer();
1524
1506
  }
1507
+ getWorkingElapsedSeconds() {
1508
+ if (this.workingStartedAt === undefined) {
1509
+ return 0;
1510
+ }
1511
+ return Math.max(0, Math.floor((Date.now() - this.workingStartedAt) / 1000));
1512
+ }
1513
+ getWorkingLoaderMessage() {
1514
+ return this.workingMessage ?? this.defaultWorkingMessage;
1515
+ }
1516
+ refreshWorkingLoaderMessage() {
1517
+ if (this.activeStatusIndicator?.kind === "working") {
1518
+ this.activeStatusIndicator.setMessage(this.getWorkingLoaderMessage());
1519
+ return;
1520
+ }
1521
+ const legacyLoader = this.loadingAnimation;
1522
+ legacyLoader?.setMessage(this.getWorkingLoaderMessage());
1523
+ }
1525
1524
  getWorkingIndicatorOptions() {
1526
1525
  return (this.workingIndicatorOptions ?? {
1527
1526
  frames: theme.getColorMode() === "truecolor" ? ["•"] : [theme.fg("accent", "•"), theme.fg("muted", "◦")],
@@ -1539,36 +1538,53 @@ export class InteractiveMode {
1539
1538
  messageIntervalMs: DEFAULT_WORKING_STATUS_MESSAGE_ANIMATION_INTERVAL_MS,
1540
1539
  });
1541
1540
  }
1542
- createWorkingLoader() {
1543
- return new Loader(this.ui, (spinner) => theme.fg("accent", spinner), (text) => theme.fg("muted", text), this.getWorkingLoaderMessage(), this.getWorkingIndicatorOptions());
1541
+ showStatusIndicator(indicator) {
1542
+ if (indicator.kind === "working") {
1543
+ this.workingStartedAt = Date.now();
1544
+ }
1545
+ this.activeStatusIndicator?.dispose();
1546
+ this.activeStatusIndicator = indicator;
1547
+ this.statusContainer.clear();
1548
+ this.statusContainer.addChild(indicator);
1544
1549
  }
1545
- stopWorkingLoader() {
1546
- this.stopWorkingElapsedTimer();
1547
- this.workingStartedAt = undefined;
1548
- if (this.loadingAnimation) {
1549
- this.loadingAnimation.stop();
1550
- this.loadingAnimation = undefined;
1550
+ updateWorkingIndicatorMessage() {
1551
+ if (this.activeStatusIndicator?.kind === "working") {
1552
+ this.activeStatusIndicator.setMessage(this.workingMessage ?? this.defaultWorkingMessage);
1553
+ }
1554
+ }
1555
+ clearStatusIndicator(kind) {
1556
+ if (kind && this.activeStatusIndicator?.kind !== kind) {
1557
+ return;
1558
+ }
1559
+ const hadActiveStatusIndicator = this.activeStatusIndicator !== undefined;
1560
+ const isClearingWorking = this.activeStatusIndicator?.kind === "working";
1561
+ this.activeStatusIndicator?.dispose();
1562
+ this.activeStatusIndicator = undefined;
1563
+ if (isClearingWorking) {
1564
+ this.workingStartedAt = undefined;
1551
1565
  }
1552
1566
  this.statusContainer.clear();
1567
+ if (hadActiveStatusIndicator && this.ui.getClearOnShrink()) {
1568
+ this.statusContainer.addChild(this.idleStatus);
1569
+ }
1553
1570
  }
1554
1571
  setWorkingVisible(visible) {
1555
1572
  this.workingVisible = visible;
1556
1573
  if (!visible) {
1557
- this.stopWorkingLoader();
1574
+ this.clearStatusIndicator("working");
1558
1575
  this.ui.requestRender();
1559
1576
  return;
1560
1577
  }
1561
- if (this.session.isStreaming && !this.loadingAnimation) {
1562
- this.statusContainer.clear();
1563
- this.startWorkingElapsedTimer();
1564
- this.loadingAnimation = this.createWorkingLoader();
1565
- this.statusContainer.addChild(this.loadingAnimation);
1578
+ if (this.session.isStreaming && this.activeStatusIndicator?.kind !== "working") {
1579
+ this.showStatusIndicator(new WorkingStatusIndicator(this.ui, this.workingMessage ?? this.defaultWorkingMessage, this.getWorkingIndicatorOptions()));
1566
1580
  }
1567
1581
  this.ui.requestRender();
1568
1582
  }
1569
1583
  setWorkingIndicator(options) {
1570
1584
  this.workingIndicatorOptions = options;
1571
- this.loadingAnimation?.setIndicator(this.getWorkingIndicatorOptions());
1585
+ if (this.activeStatusIndicator?.kind === "working") {
1586
+ this.activeStatusIndicator.setIndicator(this.getWorkingIndicatorOptions());
1587
+ }
1572
1588
  this.ui.requestRender();
1573
1589
  }
1574
1590
  setHiddenThinkingLabel(label) {
@@ -1660,7 +1676,7 @@ export class InteractiveMode {
1660
1676
  this.workingMessage = undefined;
1661
1677
  this.workingVisible = true;
1662
1678
  this.setWorkingIndicator();
1663
- this.refreshWorkingLoaderMessage();
1679
+ this.updateWorkingIndicatorMessage();
1664
1680
  this.setHiddenThinkingLabel();
1665
1681
  this.clearToolHookStatuses();
1666
1682
  }
@@ -1800,7 +1816,7 @@ export class InteractiveMode {
1800
1816
  setStatus: (key, text) => this.setExtensionStatus(key, text),
1801
1817
  setWorkingMessage: (message) => {
1802
1818
  this.workingMessage = message;
1803
- this.refreshWorkingLoaderMessage();
1819
+ this.updateWorkingIndicatorMessage();
1804
1820
  },
1805
1821
  setWorkingVisible: (visible) => this.setWorkingVisible(visible),
1806
1822
  setWorkingIndicator: (options) => this.setWorkingIndicator(options),
@@ -2443,19 +2459,11 @@ export class InteractiveMode {
2443
2459
  this.defaultEditor.onEscape = this.retryEscapeHandler;
2444
2460
  this.retryEscapeHandler = undefined;
2445
2461
  }
2446
- if (this.retryCountdown) {
2447
- this.retryCountdown.dispose();
2448
- this.retryCountdown = undefined;
2449
- }
2450
- if (this.retryLoader) {
2451
- this.retryLoader.stop();
2452
- this.retryLoader = undefined;
2453
- }
2454
- this.stopWorkingLoader();
2455
2462
  if (this.workingVisible) {
2456
- this.startWorkingElapsedTimer();
2457
- this.loadingAnimation = this.createWorkingLoader();
2458
- this.statusContainer.addChild(this.loadingAnimation);
2463
+ this.showStatusIndicator(new WorkingStatusIndicator(this.ui, this.workingMessage ?? this.defaultWorkingMessage, this.getWorkingIndicatorOptions()));
2464
+ }
2465
+ else {
2466
+ this.clearStatusIndicator();
2459
2467
  }
2460
2468
  this.ui.requestRender();
2461
2469
  break;
@@ -2466,6 +2474,12 @@ export class InteractiveMode {
2466
2474
  case "system_prompt_change":
2467
2475
  this.showStatus(event.systemPromptName ? `System prompt: ${event.systemPromptName}` : "System prompt changed");
2468
2476
  break;
2477
+ case "entry_appended":
2478
+ if (event.entry.type === "custom") {
2479
+ this.addCustomEntryToChat(event.entry);
2480
+ this.ui.requestRender();
2481
+ }
2482
+ break;
2469
2483
  case "session_info_changed":
2470
2484
  this.updateTerminalTitle();
2471
2485
  this.footer.invalidate();
@@ -2486,7 +2500,7 @@ export class InteractiveMode {
2486
2500
  this.ui.requestRender();
2487
2501
  }
2488
2502
  else if (event.message.role === "assistant") {
2489
- this.streamingComponent = new AssistantMessageComponent(undefined, this.hideThinkingBlock, this.getMarkdownThemeWithSettings(), this.hiddenThinkingLabel);
2503
+ this.streamingComponent = new AssistantMessageComponent(undefined, this.hideThinkingBlock, this.getMarkdownThemeWithSettings(), this.hiddenThinkingLabel, this.outputPad);
2490
2504
  this.streamingComponent.setExpanded(this.toolOutputExpanded);
2491
2505
  this.streamingMessage = event.message;
2492
2506
  this.chatContainer.addChild(this.streamingComponent);
@@ -2596,7 +2610,7 @@ export class InteractiveMode {
2596
2610
  if (this.settingsManager.getShowTerminalProgress()) {
2597
2611
  this.ui.terminal.setProgress(false);
2598
2612
  }
2599
- this.stopWorkingLoader();
2613
+ this.clearStatusIndicator("working");
2600
2614
  this.clearActiveToolExecutionStatus();
2601
2615
  this.clearToolHookStatuses();
2602
2616
  if (this.streamingComponent) {
@@ -2617,23 +2631,17 @@ export class InteractiveMode {
2617
2631
  this.defaultEditor.onEscape = () => {
2618
2632
  this.session.abortCompaction();
2619
2633
  };
2634
+ const indicator = new CompactionStatusIndicator(this.ui, event.reason);
2635
+ this.activeStatusIndicator?.dispose();
2636
+ this.activeStatusIndicator = indicator;
2620
2637
  this.statusContainer.clear();
2621
- const cancelHint = `(${keyText("app.interrupt")} to cancel)`;
2622
- const label = event.reason === "threshold"
2623
- ? `Auto-compacting... ${cancelHint}`
2624
- : event.reason === "overflow"
2625
- ? `Context overflow detected, compacting... ${cancelHint}`
2626
- : event.reason === "pre_prompt"
2627
- ? `Compacting before next prompt... ${cancelHint}`
2628
- : `Compacting context... ${cancelHint}`;
2629
- this.autoCompactionLoader = new Loader(this.ui, (spinner) => theme.fg("accent", spinner), (text) => theme.fg("muted", text), label);
2630
- this.statusContainer.addChild(this.autoCompactionLoader);
2638
+ this.statusContainer.addChild(indicator);
2631
2639
  this.autoCompactionProgressText = "";
2632
2640
  this.ui.requestRender();
2633
2641
  break;
2634
2642
  }
2635
2643
  case "compaction_progress": {
2636
- if (!this.autoCompactionLoader)
2644
+ if (this.activeStatusIndicator?.kind !== "compaction")
2637
2645
  break;
2638
2646
  const nextText = event.text !== undefined ? event.text : `${this.autoCompactionProgressText}${event.delta ?? ""}`;
2639
2647
  if (!nextText)
@@ -2641,7 +2649,7 @@ export class InteractiveMode {
2641
2649
  this.autoCompactionProgressText = nextText;
2642
2650
  const preview = nextText.length > 4_000 ? `...${nextText.slice(nextText.length - 4_000)}` : nextText;
2643
2651
  this.statusContainer.clear();
2644
- this.statusContainer.addChild(this.autoCompactionLoader);
2652
+ this.statusContainer.addChild(this.activeStatusIndicator);
2645
2653
  this.statusContainer.addChild(new Spacer(1));
2646
2654
  this.statusContainer.addChild(new Text(theme.fg("muted", preview), 1, 0));
2647
2655
  this.ui.requestRender();
@@ -2655,12 +2663,8 @@ export class InteractiveMode {
2655
2663
  this.defaultEditor.onEscape = this.autoCompactionEscapeHandler;
2656
2664
  this.autoCompactionEscapeHandler = undefined;
2657
2665
  }
2658
- if (this.autoCompactionLoader) {
2659
- this.autoCompactionLoader.stop();
2660
- this.autoCompactionLoader = undefined;
2661
- this.autoCompactionProgressText = "";
2662
- this.statusContainer.clear();
2663
- }
2666
+ this.clearStatusIndicator("compaction");
2667
+ this.autoCompactionProgressText = "";
2664
2668
  if (event.aborted) {
2665
2669
  if (event.reason === "manual") {
2666
2670
  this.showError("Compaction cancelled");
@@ -2694,17 +2698,7 @@ export class InteractiveMode {
2694
2698
  // no separate retry-only handler is installed (the prior one only called
2695
2699
  // session.abortRetry() and left queued steering messages stranded).
2696
2700
  this.retryEscapeHandler = undefined;
2697
- // Show retry indicator
2698
- this.statusContainer.clear();
2699
- this.retryCountdown?.dispose();
2700
- const retryMessage = (seconds) => `Retrying (${event.attempt}/${event.maxAttempts}) in ${seconds}s... (${keyText("app.interrupt")} to cancel)`;
2701
- this.retryLoader = new Loader(this.ui, (spinner) => theme.fg("warning", spinner), (text) => theme.fg("muted", text), retryMessage(Math.ceil(event.delayMs / 1000)));
2702
- this.retryCountdown = new CountdownTimer(event.delayMs, this.ui, (seconds) => {
2703
- this.retryLoader?.setMessage(retryMessage(seconds));
2704
- }, () => {
2705
- this.retryCountdown = undefined;
2706
- });
2707
- this.statusContainer.addChild(this.retryLoader);
2701
+ this.showStatusIndicator(new RetryStatusIndicator(this.ui, event.attempt, event.maxAttempts, event.delayMs));
2708
2702
  this.ui.requestRender();
2709
2703
  break;
2710
2704
  }
@@ -2714,16 +2708,7 @@ export class InteractiveMode {
2714
2708
  this.defaultEditor.onEscape = this.retryEscapeHandler;
2715
2709
  this.retryEscapeHandler = undefined;
2716
2710
  }
2717
- if (this.retryCountdown) {
2718
- this.retryCountdown.dispose();
2719
- this.retryCountdown = undefined;
2720
- }
2721
- // Stop loader
2722
- if (this.retryLoader) {
2723
- this.retryLoader.stop();
2724
- this.retryLoader = undefined;
2725
- this.statusContainer.clear();
2726
- }
2711
+ this.clearStatusIndicator("retry");
2727
2712
  // Show error only on final failure (success shows normal response)
2728
2713
  if (!event.success) {
2729
2714
  this.showError(`Retry failed after ${event.attempt} attempts: ${event.finalError || "Unknown error"}`);
@@ -2765,6 +2750,25 @@ export class InteractiveMode {
2765
2750
  this.lastStatusText = text;
2766
2751
  this.ui.requestRender();
2767
2752
  }
2753
+ addCustomEntryToChat(entry) {
2754
+ const renderer = this.session.extensionRunner.getEntryRenderer(entry.customType);
2755
+ if (!renderer) {
2756
+ return;
2757
+ }
2758
+ const component = new CustomEntryComponent(entry, renderer);
2759
+ component.setExpanded(this.toolOutputExpanded);
2760
+ if (!component.hasContent()) {
2761
+ return;
2762
+ }
2763
+ if (this.streamingComponent) {
2764
+ const streamingIndex = this.chatContainer.children.indexOf(this.streamingComponent);
2765
+ if (streamingIndex >= 0) {
2766
+ this.chatContainer.children.splice(streamingIndex, 0, component);
2767
+ return;
2768
+ }
2769
+ }
2770
+ this.chatContainer.addChild(component);
2771
+ }
2768
2772
  addMessageToChat(message, options) {
2769
2773
  switch (message.role) {
2770
2774
  case "bashExecution": {
@@ -2814,12 +2818,12 @@ export class InteractiveMode {
2814
2818
  // Render user message separately if present
2815
2819
  if (skillBlock.userMessage) {
2816
2820
  this.chatContainer.addChild(new Spacer(1));
2817
- const userComponent = new UserMessageComponent(skillBlock.userMessage, this.getMarkdownThemeWithSettings());
2821
+ const userComponent = new UserMessageComponent(skillBlock.userMessage, this.getMarkdownThemeWithSettings(), this.outputPad);
2818
2822
  this.chatContainer.addChild(userComponent);
2819
2823
  }
2820
2824
  }
2821
2825
  else {
2822
- const userComponent = new UserMessageComponent(textContent, this.getMarkdownThemeWithSettings());
2826
+ const userComponent = new UserMessageComponent(textContent, this.getMarkdownThemeWithSettings(), this.outputPad);
2823
2827
  this.chatContainer.addChild(userComponent);
2824
2828
  }
2825
2829
  if (options?.populateHistory) {
@@ -2829,7 +2833,7 @@ export class InteractiveMode {
2829
2833
  break;
2830
2834
  }
2831
2835
  case "assistant": {
2832
- const assistantComponent = new AssistantMessageComponent(message, this.hideThinkingBlock, this.getMarkdownThemeWithSettings(), this.hiddenThinkingLabel);
2836
+ const assistantComponent = new AssistantMessageComponent(message, this.hideThinkingBlock, this.getMarkdownThemeWithSettings(), this.hiddenThinkingLabel, this.outputPad);
2833
2837
  assistantComponent.setExpanded(this.toolOutputExpanded);
2834
2838
  this.chatContainer.addChild(assistantComponent);
2835
2839
  break;
@@ -2844,20 +2848,19 @@ export class InteractiveMode {
2844
2848
  }
2845
2849
  }
2846
2850
  }
2847
- /**
2848
- * Render session context to chat. Used for initial load and rebuild after compaction.
2849
- * @param sessionContext Session context to render
2850
- * @param options.updateFooter Update footer state
2851
- * @param options.populateHistory Add user messages to editor history
2852
- */
2853
- renderSessionContext(sessionContext, options = {}) {
2851
+ renderSessionItems(items, options = {}) {
2854
2852
  this.clearPendingTools();
2855
2853
  const renderedPendingTools = new Map();
2856
2854
  if (options.updateFooter) {
2857
2855
  this.footer.invalidate();
2858
2856
  this.updateEditorBorderColor();
2859
2857
  }
2860
- for (const message of sessionContext.messages) {
2858
+ for (const item of items) {
2859
+ if (isCustomSessionEntry(item)) {
2860
+ this.addCustomEntryToChat(item);
2861
+ continue;
2862
+ }
2863
+ const message = item;
2861
2864
  // Assistant messages need special handling for tool calls
2862
2865
  if (message.role === "assistant") {
2863
2866
  this.addMessageToChat(message);
@@ -2904,10 +2907,24 @@ export class InteractiveMode {
2904
2907
  }
2905
2908
  this.ui.requestRender();
2906
2909
  }
2910
+ /**
2911
+ * Render session entries to chat. Used for initial load and rebuild after compaction.
2912
+ * @param entries Compaction-aware session entries to render
2913
+ * @param options.updateFooter Update footer state
2914
+ * @param options.populateHistory Add user messages to editor history
2915
+ */
2916
+ renderSessionEntries(entries, options = {}) {
2917
+ const items = entries.flatMap((entry) => {
2918
+ if (entry.type === "custom") {
2919
+ return [entry];
2920
+ }
2921
+ return sessionEntryToContextMessages(entry);
2922
+ });
2923
+ this.renderSessionItems(items, options);
2924
+ }
2907
2925
  renderInitialMessages() {
2908
- // Get aligned messages and entries from session context
2909
- const context = this.sessionManager.buildSessionContext();
2910
- this.renderSessionContext(context, {
2926
+ const entries = this.sessionManager.buildContextEntries();
2927
+ this.renderSessionEntries(entries, {
2911
2928
  updateFooter: true,
2912
2929
  populateHistory: true,
2913
2930
  });
@@ -2942,8 +2959,7 @@ export class InteractiveMode {
2942
2959
  }
2943
2960
  rebuildChatFromMessages() {
2944
2961
  this.chatContainer.clear();
2945
- const context = this.sessionManager.buildSessionContext();
2946
- this.renderSessionContext(context);
2962
+ this.renderSessionEntries(this.sessionManager.buildContextEntries());
2947
2963
  }
2948
2964
  // =========================================================================
2949
2965
  // Key handlers
@@ -3557,6 +3573,7 @@ export class InteractiveMode {
3557
3573
  showHardwareCursor: this.settingsManager.getShowHardwareCursor(),
3558
3574
  defaultProjectTrust: this.settingsManager.getDefaultProjectTrust(),
3559
3575
  editorPaddingX: this.settingsManager.getEditorPaddingX(),
3576
+ outputPad: this.settingsManager.getOutputPad(),
3560
3577
  autocompleteMaxVisible: this.settingsManager.getAutocompleteMaxVisible(),
3561
3578
  quietStartup: this.settingsManager.getQuietStartup(),
3562
3579
  clearOnShrink: this.settingsManager.getClearOnShrink(),
@@ -3658,6 +3675,23 @@ export class InteractiveMode {
3658
3675
  this.editor.setPaddingX(padding);
3659
3676
  }
3660
3677
  },
3678
+ onOutputPadChange: (padding) => {
3679
+ this.settingsManager.setOutputPad(padding);
3680
+ this.outputPad = padding;
3681
+ if (this.streamingComponent || this.session.isStreaming) {
3682
+ for (const child of this.chatContainer.children) {
3683
+ if (child instanceof AssistantMessageComponent || child instanceof UserMessageComponent) {
3684
+ child.setOutputPad(padding);
3685
+ }
3686
+ }
3687
+ if (this.streamingComponent) {
3688
+ this.streamingComponent.setOutputPad(padding);
3689
+ }
3690
+ this.ui.requestRender();
3691
+ return;
3692
+ }
3693
+ this.rebuildChatFromMessages();
3694
+ },
3661
3695
  onAutocompleteMaxVisibleChange: (maxVisible) => {
3662
3696
  this.settingsManager.setAutocompleteMaxVisible(maxVisible);
3663
3697
  this.defaultEditor.setAutocompleteMaxVisible(maxVisible);
@@ -3668,6 +3702,9 @@ export class InteractiveMode {
3668
3702
  onClearOnShrinkChange: (enabled) => {
3669
3703
  this.settingsManager.setClearOnShrink(enabled);
3670
3704
  this.ui.setClearOnShrink(enabled);
3705
+ if (!enabled && !this.activeStatusIndicator) {
3706
+ this.statusContainer.clear();
3707
+ }
3671
3708
  },
3672
3709
  onShowTerminalProgressChange: (enabled) => {
3673
3710
  this.settingsManager.setShowTerminalProgress(enabled);
@@ -3999,16 +4036,16 @@ export class InteractiveMode {
3999
4036
  break;
4000
4037
  }
4001
4038
  }
4002
- // Set up escape handler and loader if summarizing
4003
- let summaryLoader;
4039
+ // Set up escape handler and status indicator if summarizing
4040
+ let showingSummaryIndicator = false;
4004
4041
  const originalOnEscape = this.defaultEditor.onEscape;
4005
4042
  if (wantsSummary) {
4006
4043
  this.defaultEditor.onEscape = () => {
4007
4044
  this.session.abortBranchSummary();
4008
4045
  };
4009
4046
  this.chatContainer.addChild(new Spacer(1));
4010
- summaryLoader = new Loader(this.ui, (spinner) => theme.fg("accent", spinner), (text) => theme.fg("muted", text), `Summarizing branch... (${keyText("app.interrupt")} to cancel)`);
4011
- this.statusContainer.addChild(summaryLoader);
4047
+ this.showStatusIndicator(new BranchSummaryStatusIndicator(this.ui));
4048
+ showingSummaryIndicator = true;
4012
4049
  this.ui.requestRender();
4013
4050
  }
4014
4051
  try {
@@ -4039,9 +4076,8 @@ export class InteractiveMode {
4039
4076
  this.showError(error instanceof Error ? error.message : String(error));
4040
4077
  }
4041
4078
  finally {
4042
- if (summaryLoader) {
4043
- summaryLoader.stop();
4044
- this.statusContainer.clear();
4079
+ if (showingSummaryIndicator) {
4080
+ this.clearStatusIndicator("branchSummary");
4045
4081
  }
4046
4082
  this.defaultEditor.onEscape = originalOnEscape;
4047
4083
  }
@@ -4082,7 +4118,7 @@ export class InteractiveMode {
4082
4118
  });
4083
4119
  }
4084
4120
  async handleResumeSession(sessionPath, options) {
4085
- this.stopWorkingLoader();
4121
+ this.clearStatusIndicator();
4086
4122
  try {
4087
4123
  const result = await this.runtimeHost.switchSession(sessionPath, {
4088
4124
  withSession: options?.withSession,
@@ -4477,6 +4513,7 @@ export class InteractiveMode {
4477
4513
  return;
4478
4514
  }
4479
4515
  this.hideThinkingBlock = this.settingsManager.getHideThinkingBlock();
4516
+ this.outputPad = this.settingsManager.getOutputPad();
4480
4517
  this.rebuildChatFromMessages();
4481
4518
  chatRestoredBeforeSessionStart = true;
4482
4519
  };
@@ -4500,7 +4537,11 @@ export class InteractiveMode {
4500
4537
  this.editor.setAutocompleteMaxVisible?.(autocompleteMaxVisible);
4501
4538
  }
4502
4539
  this.ui.setShowHardwareCursor(this.settingsManager.getShowHardwareCursor());
4503
- this.ui.setClearOnShrink(this.settingsManager.getClearOnShrink());
4540
+ const clearOnShrink = this.settingsManager.getClearOnShrink();
4541
+ this.ui.setClearOnShrink(clearOnShrink);
4542
+ if (!clearOnShrink && !this.activeStatusIndicator) {
4543
+ this.statusContainer.clear();
4544
+ }
4504
4545
  this.setupAutocompleteProvider();
4505
4546
  const runner = this.session.extensionRunner;
4506
4547
  this.setupExtensionShortcuts(runner);
@@ -4579,7 +4620,7 @@ export class InteractiveMode {
4579
4620
  return;
4580
4621
  }
4581
4622
  try {
4582
- this.stopWorkingLoader();
4623
+ this.clearStatusIndicator();
4583
4624
  const result = await this.runtimeHost.importFromJsonl(inputPath);
4584
4625
  if (result.cancelled) {
4585
4626
  this.showStatus("Import cancelled");
@@ -4882,7 +4923,7 @@ export class InteractiveMode {
4882
4923
  this.ui.requestRender();
4883
4924
  }
4884
4925
  async handleClearCommand() {
4885
- this.stopWorkingLoader();
4926
+ this.clearStatusIndicator();
4886
4927
  try {
4887
4928
  const result = await this.runtimeHost.newSession();
4888
4929
  if (result.cancelled) {
@@ -5015,8 +5056,7 @@ export class InteractiveMode {
5015
5056
  this.showWarning("Nothing to compact (no messages yet)");
5016
5057
  return;
5017
5058
  }
5018
- this.stopWorkingLoader();
5019
- this.statusContainer.clear();
5059
+ this.clearStatusIndicator();
5020
5060
  try {
5021
5061
  await this.session.compact(customInstructions);
5022
5062
  }
@@ -5028,7 +5068,7 @@ export class InteractiveMode {
5028
5068
  if (this.settingsManager.getShowTerminalProgress()) {
5029
5069
  this.ui.terminal.setProgress(false);
5030
5070
  }
5031
- this.stopWorkingLoader();
5071
+ this.clearStatusIndicator();
5032
5072
  this.clearPendingTools();
5033
5073
  this.clearActiveToolExecutionStatus();
5034
5074
  this.clearToolHookStatuses();