@code-yeongyu/senpi 2026.6.30 → 2026.7.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (209) hide show
  1. package/CHANGELOG.md +42 -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/extensions/index.d.ts +1 -1
  13. package/dist/core/extensions/index.d.ts.map +1 -1
  14. package/dist/core/extensions/index.js.map +1 -1
  15. package/dist/core/extensions/loader.d.ts.map +1 -1
  16. package/dist/core/extensions/loader.js +6 -0
  17. package/dist/core/extensions/loader.js.map +1 -1
  18. package/dist/core/extensions/runner.d.ts +2 -1
  19. package/dist/core/extensions/runner.d.ts.map +1 -1
  20. package/dist/core/extensions/runner.js +9 -0
  21. package/dist/core/extensions/runner.js.map +1 -1
  22. package/dist/core/extensions/types.d.ts +16 -2
  23. package/dist/core/extensions/types.d.ts.map +1 -1
  24. package/dist/core/extensions/types.js.map +1 -1
  25. package/dist/core/http-dispatcher.d.ts.map +1 -1
  26. package/dist/core/http-dispatcher.js +28 -1
  27. package/dist/core/http-dispatcher.js.map +1 -1
  28. package/dist/core/model-resolver.d.ts +10 -0
  29. package/dist/core/model-resolver.d.ts.map +1 -1
  30. package/dist/core/model-resolver.js +16 -22
  31. package/dist/core/model-resolver.js.map +1 -1
  32. package/dist/core/session-manager.d.ts +20 -1
  33. package/dist/core/session-manager.d.ts.map +1 -1
  34. package/dist/core/session-manager.js +87 -68
  35. package/dist/core/session-manager.js.map +1 -1
  36. package/dist/core/settings-manager.d.ts +3 -0
  37. package/dist/core/settings-manager.d.ts.map +1 -1
  38. package/dist/core/settings-manager.js +8 -0
  39. package/dist/core/settings-manager.js.map +1 -1
  40. package/dist/core/tools/bash.d.ts.map +1 -1
  41. package/dist/core/tools/bash.js +20 -5
  42. package/dist/core/tools/bash.js.map +1 -1
  43. package/dist/extension-system-migration.d.ts +2 -0
  44. package/dist/extension-system-migration.d.ts.map +1 -0
  45. package/dist/extension-system-migration.js +51 -0
  46. package/dist/extension-system-migration.js.map +1 -0
  47. package/dist/index.d.ts +3 -2
  48. package/dist/index.d.ts.map +1 -1
  49. package/dist/index.js +2 -1
  50. package/dist/index.js.map +1 -1
  51. package/dist/legacy-senpi-dir-migration.d.ts +2 -0
  52. package/dist/legacy-senpi-dir-migration.d.ts.map +1 -0
  53. package/dist/legacy-senpi-dir-migration.js +76 -0
  54. package/dist/legacy-senpi-dir-migration.js.map +1 -0
  55. package/dist/migrations.d.ts.map +1 -1
  56. package/dist/migrations.js +3 -95
  57. package/dist/migrations.js.map +1 -1
  58. package/dist/modes/interactive/components/assistant-message.d.ts +3 -1
  59. package/dist/modes/interactive/components/assistant-message.d.ts.map +1 -1
  60. package/dist/modes/interactive/components/assistant-message.js +15 -7
  61. package/dist/modes/interactive/components/assistant-message.js.map +1 -1
  62. package/dist/modes/interactive/components/custom-entry.d.ts +19 -0
  63. package/dist/modes/interactive/components/custom-entry.d.ts.map +1 -0
  64. package/dist/modes/interactive/components/custom-entry.js +49 -0
  65. package/dist/modes/interactive/components/custom-entry.js.map +1 -0
  66. package/dist/modes/interactive/components/settings-selector.d.ts +2 -0
  67. package/dist/modes/interactive/components/settings-selector.d.ts.map +1 -1
  68. package/dist/modes/interactive/components/settings-selector.js +13 -1
  69. package/dist/modes/interactive/components/settings-selector.js.map +1 -1
  70. package/dist/modes/interactive/components/status-indicator.d.ts +27 -0
  71. package/dist/modes/interactive/components/status-indicator.d.ts.map +1 -0
  72. package/dist/modes/interactive/components/status-indicator.js +64 -0
  73. package/dist/modes/interactive/components/status-indicator.js.map +1 -0
  74. package/dist/modes/interactive/components/user-message.d.ts +6 -2
  75. package/dist/modes/interactive/components/user-message.d.ts.map +1 -1
  76. package/dist/modes/interactive/components/user-message.js +15 -4
  77. package/dist/modes/interactive/components/user-message.js.map +1 -1
  78. package/dist/modes/interactive/interactive-mode.d.ts +14 -15
  79. package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
  80. package/dist/modes/interactive/interactive-mode.js +176 -137
  81. package/dist/modes/interactive/interactive-mode.js.map +1 -1
  82. package/dist/modes/rpc/rpc-client.d.ts +15 -0
  83. package/dist/modes/rpc/rpc-client.d.ts.map +1 -1
  84. package/dist/modes/rpc/rpc-client.js +14 -0
  85. package/dist/modes/rpc/rpc-client.js.map +1 -1
  86. package/dist/modes/rpc/rpc-mode.d.ts.map +1 -1
  87. package/dist/modes/rpc/rpc-mode.js +16 -0
  88. package/dist/modes/rpc/rpc-mode.js.map +1 -1
  89. package/dist/modes/rpc/rpc-types.d.ts +26 -0
  90. package/dist/modes/rpc/rpc-types.d.ts.map +1 -1
  91. package/dist/modes/rpc/rpc-types.js.map +1 -1
  92. package/docs/extensions.md +55 -8
  93. package/docs/rpc.md +58 -0
  94. package/docs/sdk.md +28 -0
  95. package/docs/session-format.md +18 -8
  96. package/docs/settings.md +1 -0
  97. package/examples/extensions/README.md +1 -0
  98. package/examples/extensions/custom-provider-anthropic/package-lock.json +2 -2
  99. package/examples/extensions/custom-provider-anthropic/package.json +1 -1
  100. package/examples/extensions/custom-provider-gitlab-duo/package.json +1 -1
  101. package/examples/extensions/entry-renderer.ts +41 -0
  102. package/examples/extensions/gondolin/package-lock.json +2 -2
  103. package/examples/extensions/gondolin/package.json +1 -1
  104. package/examples/extensions/sandbox/package-lock.json +2 -2
  105. package/examples/extensions/sandbox/package.json +1 -1
  106. package/examples/extensions/with-deps/package-lock.json +2 -2
  107. package/examples/extensions/with-deps/package.json +1 -1
  108. package/node_modules/@earendil-works/pi-agent-core/dist/agent.d.ts +3 -1
  109. package/node_modules/@earendil-works/pi-agent-core/dist/agent.d.ts.map +1 -1
  110. package/node_modules/@earendil-works/pi-agent-core/dist/agent.js +9 -1
  111. package/node_modules/@earendil-works/pi-agent-core/dist/agent.js.map +1 -1
  112. package/node_modules/@earendil-works/pi-agent-core/dist/harness/compaction/compaction.d.ts.map +1 -1
  113. package/node_modules/@earendil-works/pi-agent-core/dist/harness/compaction/compaction.js +4 -6
  114. package/node_modules/@earendil-works/pi-agent-core/dist/harness/compaction/compaction.js.map +1 -1
  115. package/node_modules/@earendil-works/pi-agent-core/dist/harness/env/nodejs.d.ts.map +1 -1
  116. package/node_modules/@earendil-works/pi-agent-core/dist/harness/env/nodejs.js +20 -2
  117. package/node_modules/@earendil-works/pi-agent-core/dist/harness/env/nodejs.js.map +1 -1
  118. package/node_modules/@earendil-works/pi-agent-core/package.json +2 -2
  119. package/node_modules/@earendil-works/pi-ai/dist/api/azure-openai-responses.d.ts.map +1 -1
  120. package/node_modules/@earendil-works/pi-ai/dist/api/azure-openai-responses.js +2 -14
  121. package/node_modules/@earendil-works/pi-ai/dist/api/azure-openai-responses.js.map +1 -1
  122. package/node_modules/@earendil-works/pi-ai/dist/api/bedrock-converse-stream.d.ts.map +1 -1
  123. package/node_modules/@earendil-works/pi-ai/dist/api/bedrock-converse-stream.js +12 -4
  124. package/node_modules/@earendil-works/pi-ai/dist/api/bedrock-converse-stream.js.map +1 -1
  125. package/node_modules/@earendil-works/pi-ai/dist/api/google-generative-ai.d.ts.map +1 -1
  126. package/node_modules/@earendil-works/pi-ai/dist/api/google-generative-ai.js +2 -1
  127. package/node_modules/@earendil-works/pi-ai/dist/api/google-generative-ai.js.map +1 -1
  128. package/node_modules/@earendil-works/pi-ai/dist/api/google-vertex.d.ts.map +1 -1
  129. package/node_modules/@earendil-works/pi-ai/dist/api/google-vertex.js +2 -1
  130. package/node_modules/@earendil-works/pi-ai/dist/api/google-vertex.js.map +1 -1
  131. package/node_modules/@earendil-works/pi-ai/dist/api/openai-codex-responses.d.ts.map +1 -1
  132. package/node_modules/@earendil-works/pi-ai/dist/api/openai-codex-responses.js +56 -39
  133. package/node_modules/@earendil-works/pi-ai/dist/api/openai-codex-responses.js.map +1 -1
  134. package/node_modules/@earendil-works/pi-ai/dist/api/openai-completions.d.ts.map +1 -1
  135. package/node_modules/@earendil-works/pi-ai/dist/api/openai-completions.js +8 -3
  136. package/node_modules/@earendil-works/pi-ai/dist/api/openai-completions.js.map +1 -1
  137. package/node_modules/@earendil-works/pi-ai/dist/api/openai-responses.d.ts.map +1 -1
  138. package/node_modules/@earendil-works/pi-ai/dist/api/openai-responses.js +2 -14
  139. package/node_modules/@earendil-works/pi-ai/dist/api/openai-responses.js.map +1 -1
  140. package/node_modules/@earendil-works/pi-ai/dist/api/openrouter-images.d.ts.map +1 -1
  141. package/node_modules/@earendil-works/pi-ai/dist/api/openrouter-images.js +2 -1
  142. package/node_modules/@earendil-works/pi-ai/dist/api/openrouter-images.js.map +1 -1
  143. package/node_modules/@earendil-works/pi-ai/dist/image-models.generated.d.ts +15 -45
  144. package/node_modules/@earendil-works/pi-ai/dist/image-models.generated.d.ts.map +1 -1
  145. package/node_modules/@earendil-works/pi-ai/dist/image-models.generated.js +15 -45
  146. package/node_modules/@earendil-works/pi-ai/dist/image-models.generated.js.map +1 -1
  147. package/node_modules/@earendil-works/pi-ai/dist/models.generated.d.ts +375 -54
  148. package/node_modules/@earendil-works/pi-ai/dist/models.generated.d.ts.map +1 -1
  149. package/node_modules/@earendil-works/pi-ai/dist/providers/amazon-bedrock.models.d.ts +123 -0
  150. package/node_modules/@earendil-works/pi-ai/dist/providers/amazon-bedrock.models.d.ts.map +1 -1
  151. package/node_modules/@earendil-works/pi-ai/dist/providers/amazon-bedrock.models.js +124 -4
  152. package/node_modules/@earendil-works/pi-ai/dist/providers/amazon-bedrock.models.js.map +1 -1
  153. package/node_modules/@earendil-works/pi-ai/dist/providers/anthropic.models.d.ts +20 -0
  154. package/node_modules/@earendil-works/pi-ai/dist/providers/anthropic.models.d.ts.map +1 -1
  155. package/node_modules/@earendil-works/pi-ai/dist/providers/anthropic.models.js +18 -0
  156. package/node_modules/@earendil-works/pi-ai/dist/providers/anthropic.models.js.map +1 -1
  157. package/node_modules/@earendil-works/pi-ai/dist/providers/cerebras.models.d.ts +21 -0
  158. package/node_modules/@earendil-works/pi-ai/dist/providers/cerebras.models.d.ts.map +1 -1
  159. package/node_modules/@earendil-works/pi-ai/dist/providers/cerebras.models.js +18 -0
  160. package/node_modules/@earendil-works/pi-ai/dist/providers/cerebras.models.js.map +1 -1
  161. package/node_modules/@earendil-works/pi-ai/dist/providers/cloudflare-ai-gateway.models.d.ts +21 -0
  162. package/node_modules/@earendil-works/pi-ai/dist/providers/cloudflare-ai-gateway.models.d.ts.map +1 -1
  163. package/node_modules/@earendil-works/pi-ai/dist/providers/cloudflare-ai-gateway.models.js +18 -0
  164. package/node_modules/@earendil-works/pi-ai/dist/providers/cloudflare-ai-gateway.models.js.map +1 -1
  165. package/node_modules/@earendil-works/pi-ai/dist/providers/fireworks.models.d.ts +10 -5
  166. package/node_modules/@earendil-works/pi-ai/dist/providers/fireworks.models.d.ts.map +1 -1
  167. package/node_modules/@earendil-works/pi-ai/dist/providers/fireworks.models.js +4 -3
  168. package/node_modules/@earendil-works/pi-ai/dist/providers/fireworks.models.js.map +1 -1
  169. package/node_modules/@earendil-works/pi-ai/dist/providers/github-copilot.models.d.ts +26 -0
  170. package/node_modules/@earendil-works/pi-ai/dist/providers/github-copilot.models.d.ts.map +1 -1
  171. package/node_modules/@earendil-works/pi-ai/dist/providers/github-copilot.models.js +19 -0
  172. package/node_modules/@earendil-works/pi-ai/dist/providers/github-copilot.models.js.map +1 -1
  173. package/node_modules/@earendil-works/pi-ai/dist/providers/nvidia.models.d.ts +0 -28
  174. package/node_modules/@earendil-works/pi-ai/dist/providers/nvidia.models.d.ts.map +1 -1
  175. package/node_modules/@earendil-works/pi-ai/dist/providers/nvidia.models.js +0 -19
  176. package/node_modules/@earendil-works/pi-ai/dist/providers/nvidia.models.js.map +1 -1
  177. package/node_modules/@earendil-works/pi-ai/dist/providers/opencode-go.models.js +5 -5
  178. package/node_modules/@earendil-works/pi-ai/dist/providers/opencode-go.models.js.map +1 -1
  179. package/node_modules/@earendil-works/pi-ai/dist/providers/opencode.models.d.ts +89 -0
  180. package/node_modules/@earendil-works/pi-ai/dist/providers/opencode.models.d.ts.map +1 -1
  181. package/node_modules/@earendil-works/pi-ai/dist/providers/opencode.models.js +75 -2
  182. package/node_modules/@earendil-works/pi-ai/dist/providers/opencode.models.js.map +1 -1
  183. package/node_modules/@earendil-works/pi-ai/dist/providers/openrouter.models.d.ts +21 -21
  184. package/node_modules/@earendil-works/pi-ai/dist/providers/openrouter.models.d.ts.map +1 -1
  185. package/node_modules/@earendil-works/pi-ai/dist/providers/openrouter.models.js +49 -49
  186. package/node_modules/@earendil-works/pi-ai/dist/providers/openrouter.models.js.map +1 -1
  187. package/node_modules/@earendil-works/pi-ai/dist/providers/vercel-ai-gateway.models.d.ts +44 -0
  188. package/node_modules/@earendil-works/pi-ai/dist/providers/vercel-ai-gateway.models.d.ts.map +1 -1
  189. package/node_modules/@earendil-works/pi-ai/dist/providers/vercel-ai-gateway.models.js +37 -0
  190. package/node_modules/@earendil-works/pi-ai/dist/providers/vercel-ai-gateway.models.js.map +1 -1
  191. package/node_modules/@earendil-works/pi-ai/dist/utils/error-body.d.ts +25 -0
  192. package/node_modules/@earendil-works/pi-ai/dist/utils/error-body.d.ts.map +1 -0
  193. package/node_modules/@earendil-works/pi-ai/dist/utils/error-body.js +109 -0
  194. package/node_modules/@earendil-works/pi-ai/dist/utils/error-body.js.map +1 -0
  195. package/node_modules/@earendil-works/pi-ai/dist/utils/oauth/device-code.d.ts +1 -0
  196. package/node_modules/@earendil-works/pi-ai/dist/utils/oauth/device-code.d.ts.map +1 -1
  197. package/node_modules/@earendil-works/pi-ai/dist/utils/oauth/device-code.js +6 -0
  198. package/node_modules/@earendil-works/pi-ai/dist/utils/oauth/device-code.js.map +1 -1
  199. package/node_modules/@earendil-works/pi-ai/dist/utils/oauth/github-copilot.d.ts.map +1 -1
  200. package/node_modules/@earendil-works/pi-ai/dist/utils/oauth/github-copilot.js +1 -0
  201. package/node_modules/@earendil-works/pi-ai/dist/utils/oauth/github-copilot.js.map +1 -1
  202. package/node_modules/@earendil-works/pi-ai/package.json +1 -1
  203. package/node_modules/@earendil-works/pi-tui/dist/tui.d.ts +13 -0
  204. package/node_modules/@earendil-works/pi-tui/dist/tui.d.ts.map +1 -1
  205. package/node_modules/@earendil-works/pi-tui/dist/tui.js +25 -12
  206. package/node_modules/@earendil-works/pi-tui/dist/tui.js.map +1 -1
  207. package/node_modules/@earendil-works/pi-tui/package.json +1 -1
  208. package/npm-shrinkwrap.json +9 -9
  209. 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;
@@ -1522,6 +1503,23 @@ export class InteractiveMode {
1522
1503
  this.hookStatusContainer.clear();
1523
1504
  this.stopToolHookStatusTimer();
1524
1505
  }
1506
+ getWorkingElapsedSeconds() {
1507
+ if (this.workingStartedAt === undefined) {
1508
+ return 0;
1509
+ }
1510
+ return Math.max(0, Math.floor((Date.now() - this.workingStartedAt) / 1000));
1511
+ }
1512
+ getWorkingLoaderMessage() {
1513
+ return this.workingMessage ?? this.defaultWorkingMessage;
1514
+ }
1515
+ refreshWorkingLoaderMessage() {
1516
+ if (this.activeStatusIndicator?.kind === "working") {
1517
+ this.activeStatusIndicator.setMessage(this.getWorkingLoaderMessage());
1518
+ return;
1519
+ }
1520
+ const legacyLoader = this.loadingAnimation;
1521
+ legacyLoader?.setMessage(this.getWorkingLoaderMessage());
1522
+ }
1525
1523
  getWorkingIndicatorOptions() {
1526
1524
  return (this.workingIndicatorOptions ?? {
1527
1525
  frames: theme.getColorMode() === "truecolor" ? ["•"] : [theme.fg("accent", "•"), theme.fg("muted", "◦")],
@@ -1539,36 +1537,53 @@ export class InteractiveMode {
1539
1537
  messageIntervalMs: DEFAULT_WORKING_STATUS_MESSAGE_ANIMATION_INTERVAL_MS,
1540
1538
  });
1541
1539
  }
1542
- createWorkingLoader() {
1543
- return new Loader(this.ui, (spinner) => theme.fg("accent", spinner), (text) => theme.fg("muted", text), this.getWorkingLoaderMessage(), this.getWorkingIndicatorOptions());
1540
+ showStatusIndicator(indicator) {
1541
+ if (indicator.kind === "working") {
1542
+ this.workingStartedAt = Date.now();
1543
+ }
1544
+ this.activeStatusIndicator?.dispose();
1545
+ this.activeStatusIndicator = indicator;
1546
+ this.statusContainer.clear();
1547
+ this.statusContainer.addChild(indicator);
1544
1548
  }
1545
- stopWorkingLoader() {
1546
- this.stopWorkingElapsedTimer();
1547
- this.workingStartedAt = undefined;
1548
- if (this.loadingAnimation) {
1549
- this.loadingAnimation.stop();
1550
- this.loadingAnimation = undefined;
1549
+ updateWorkingIndicatorMessage() {
1550
+ if (this.activeStatusIndicator?.kind === "working") {
1551
+ this.activeStatusIndicator.setMessage(this.workingMessage ?? this.defaultWorkingMessage);
1552
+ }
1553
+ }
1554
+ clearStatusIndicator(kind) {
1555
+ if (kind && this.activeStatusIndicator?.kind !== kind) {
1556
+ return;
1557
+ }
1558
+ const hadActiveStatusIndicator = this.activeStatusIndicator !== undefined;
1559
+ const isClearingWorking = this.activeStatusIndicator?.kind === "working";
1560
+ this.activeStatusIndicator?.dispose();
1561
+ this.activeStatusIndicator = undefined;
1562
+ if (isClearingWorking) {
1563
+ this.workingStartedAt = undefined;
1551
1564
  }
1552
1565
  this.statusContainer.clear();
1566
+ if (hadActiveStatusIndicator && this.ui.getClearOnShrink()) {
1567
+ this.statusContainer.addChild(this.idleStatus);
1568
+ }
1553
1569
  }
1554
1570
  setWorkingVisible(visible) {
1555
1571
  this.workingVisible = visible;
1556
1572
  if (!visible) {
1557
- this.stopWorkingLoader();
1573
+ this.clearStatusIndicator("working");
1558
1574
  this.ui.requestRender();
1559
1575
  return;
1560
1576
  }
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);
1577
+ if (this.session.isStreaming && this.activeStatusIndicator?.kind !== "working") {
1578
+ this.showStatusIndicator(new WorkingStatusIndicator(this.ui, this.workingMessage ?? this.defaultWorkingMessage, this.getWorkingIndicatorOptions()));
1566
1579
  }
1567
1580
  this.ui.requestRender();
1568
1581
  }
1569
1582
  setWorkingIndicator(options) {
1570
1583
  this.workingIndicatorOptions = options;
1571
- this.loadingAnimation?.setIndicator(this.getWorkingIndicatorOptions());
1584
+ if (this.activeStatusIndicator?.kind === "working") {
1585
+ this.activeStatusIndicator.setIndicator(this.getWorkingIndicatorOptions());
1586
+ }
1572
1587
  this.ui.requestRender();
1573
1588
  }
1574
1589
  setHiddenThinkingLabel(label) {
@@ -1660,7 +1675,7 @@ export class InteractiveMode {
1660
1675
  this.workingMessage = undefined;
1661
1676
  this.workingVisible = true;
1662
1677
  this.setWorkingIndicator();
1663
- this.refreshWorkingLoaderMessage();
1678
+ this.updateWorkingIndicatorMessage();
1664
1679
  this.setHiddenThinkingLabel();
1665
1680
  this.clearToolHookStatuses();
1666
1681
  }
@@ -1800,7 +1815,7 @@ export class InteractiveMode {
1800
1815
  setStatus: (key, text) => this.setExtensionStatus(key, text),
1801
1816
  setWorkingMessage: (message) => {
1802
1817
  this.workingMessage = message;
1803
- this.refreshWorkingLoaderMessage();
1818
+ this.updateWorkingIndicatorMessage();
1804
1819
  },
1805
1820
  setWorkingVisible: (visible) => this.setWorkingVisible(visible),
1806
1821
  setWorkingIndicator: (options) => this.setWorkingIndicator(options),
@@ -2443,19 +2458,11 @@ export class InteractiveMode {
2443
2458
  this.defaultEditor.onEscape = this.retryEscapeHandler;
2444
2459
  this.retryEscapeHandler = undefined;
2445
2460
  }
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
2461
  if (this.workingVisible) {
2456
- this.startWorkingElapsedTimer();
2457
- this.loadingAnimation = this.createWorkingLoader();
2458
- this.statusContainer.addChild(this.loadingAnimation);
2462
+ this.showStatusIndicator(new WorkingStatusIndicator(this.ui, this.workingMessage ?? this.defaultWorkingMessage, this.getWorkingIndicatorOptions()));
2463
+ }
2464
+ else {
2465
+ this.clearStatusIndicator();
2459
2466
  }
2460
2467
  this.ui.requestRender();
2461
2468
  break;
@@ -2466,6 +2473,12 @@ export class InteractiveMode {
2466
2473
  case "system_prompt_change":
2467
2474
  this.showStatus(event.systemPromptName ? `System prompt: ${event.systemPromptName}` : "System prompt changed");
2468
2475
  break;
2476
+ case "entry_appended":
2477
+ if (event.entry.type === "custom") {
2478
+ this.addCustomEntryToChat(event.entry);
2479
+ this.ui.requestRender();
2480
+ }
2481
+ break;
2469
2482
  case "session_info_changed":
2470
2483
  this.updateTerminalTitle();
2471
2484
  this.footer.invalidate();
@@ -2486,7 +2499,7 @@ export class InteractiveMode {
2486
2499
  this.ui.requestRender();
2487
2500
  }
2488
2501
  else if (event.message.role === "assistant") {
2489
- this.streamingComponent = new AssistantMessageComponent(undefined, this.hideThinkingBlock, this.getMarkdownThemeWithSettings(), this.hiddenThinkingLabel);
2502
+ this.streamingComponent = new AssistantMessageComponent(undefined, this.hideThinkingBlock, this.getMarkdownThemeWithSettings(), this.hiddenThinkingLabel, this.outputPad);
2490
2503
  this.streamingComponent.setExpanded(this.toolOutputExpanded);
2491
2504
  this.streamingMessage = event.message;
2492
2505
  this.chatContainer.addChild(this.streamingComponent);
@@ -2596,7 +2609,7 @@ export class InteractiveMode {
2596
2609
  if (this.settingsManager.getShowTerminalProgress()) {
2597
2610
  this.ui.terminal.setProgress(false);
2598
2611
  }
2599
- this.stopWorkingLoader();
2612
+ this.clearStatusIndicator("working");
2600
2613
  this.clearActiveToolExecutionStatus();
2601
2614
  this.clearToolHookStatuses();
2602
2615
  if (this.streamingComponent) {
@@ -2617,23 +2630,17 @@ export class InteractiveMode {
2617
2630
  this.defaultEditor.onEscape = () => {
2618
2631
  this.session.abortCompaction();
2619
2632
  };
2633
+ const indicator = new CompactionStatusIndicator(this.ui, event.reason);
2634
+ this.activeStatusIndicator?.dispose();
2635
+ this.activeStatusIndicator = indicator;
2620
2636
  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);
2637
+ this.statusContainer.addChild(indicator);
2631
2638
  this.autoCompactionProgressText = "";
2632
2639
  this.ui.requestRender();
2633
2640
  break;
2634
2641
  }
2635
2642
  case "compaction_progress": {
2636
- if (!this.autoCompactionLoader)
2643
+ if (this.activeStatusIndicator?.kind !== "compaction")
2637
2644
  break;
2638
2645
  const nextText = event.text !== undefined ? event.text : `${this.autoCompactionProgressText}${event.delta ?? ""}`;
2639
2646
  if (!nextText)
@@ -2641,7 +2648,7 @@ export class InteractiveMode {
2641
2648
  this.autoCompactionProgressText = nextText;
2642
2649
  const preview = nextText.length > 4_000 ? `...${nextText.slice(nextText.length - 4_000)}` : nextText;
2643
2650
  this.statusContainer.clear();
2644
- this.statusContainer.addChild(this.autoCompactionLoader);
2651
+ this.statusContainer.addChild(this.activeStatusIndicator);
2645
2652
  this.statusContainer.addChild(new Spacer(1));
2646
2653
  this.statusContainer.addChild(new Text(theme.fg("muted", preview), 1, 0));
2647
2654
  this.ui.requestRender();
@@ -2655,12 +2662,8 @@ export class InteractiveMode {
2655
2662
  this.defaultEditor.onEscape = this.autoCompactionEscapeHandler;
2656
2663
  this.autoCompactionEscapeHandler = undefined;
2657
2664
  }
2658
- if (this.autoCompactionLoader) {
2659
- this.autoCompactionLoader.stop();
2660
- this.autoCompactionLoader = undefined;
2661
- this.autoCompactionProgressText = "";
2662
- this.statusContainer.clear();
2663
- }
2665
+ this.clearStatusIndicator("compaction");
2666
+ this.autoCompactionProgressText = "";
2664
2667
  if (event.aborted) {
2665
2668
  if (event.reason === "manual") {
2666
2669
  this.showError("Compaction cancelled");
@@ -2694,17 +2697,7 @@ export class InteractiveMode {
2694
2697
  // no separate retry-only handler is installed (the prior one only called
2695
2698
  // session.abortRetry() and left queued steering messages stranded).
2696
2699
  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);
2700
+ this.showStatusIndicator(new RetryStatusIndicator(this.ui, event.attempt, event.maxAttempts, event.delayMs));
2708
2701
  this.ui.requestRender();
2709
2702
  break;
2710
2703
  }
@@ -2714,16 +2707,7 @@ export class InteractiveMode {
2714
2707
  this.defaultEditor.onEscape = this.retryEscapeHandler;
2715
2708
  this.retryEscapeHandler = undefined;
2716
2709
  }
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
- }
2710
+ this.clearStatusIndicator("retry");
2727
2711
  // Show error only on final failure (success shows normal response)
2728
2712
  if (!event.success) {
2729
2713
  this.showError(`Retry failed after ${event.attempt} attempts: ${event.finalError || "Unknown error"}`);
@@ -2765,6 +2749,25 @@ export class InteractiveMode {
2765
2749
  this.lastStatusText = text;
2766
2750
  this.ui.requestRender();
2767
2751
  }
2752
+ addCustomEntryToChat(entry) {
2753
+ const renderer = this.session.extensionRunner.getEntryRenderer(entry.customType);
2754
+ if (!renderer) {
2755
+ return;
2756
+ }
2757
+ const component = new CustomEntryComponent(entry, renderer);
2758
+ component.setExpanded(this.toolOutputExpanded);
2759
+ if (!component.hasContent()) {
2760
+ return;
2761
+ }
2762
+ if (this.streamingComponent) {
2763
+ const streamingIndex = this.chatContainer.children.indexOf(this.streamingComponent);
2764
+ if (streamingIndex >= 0) {
2765
+ this.chatContainer.children.splice(streamingIndex, 0, component);
2766
+ return;
2767
+ }
2768
+ }
2769
+ this.chatContainer.addChild(component);
2770
+ }
2768
2771
  addMessageToChat(message, options) {
2769
2772
  switch (message.role) {
2770
2773
  case "bashExecution": {
@@ -2814,12 +2817,12 @@ export class InteractiveMode {
2814
2817
  // Render user message separately if present
2815
2818
  if (skillBlock.userMessage) {
2816
2819
  this.chatContainer.addChild(new Spacer(1));
2817
- const userComponent = new UserMessageComponent(skillBlock.userMessage, this.getMarkdownThemeWithSettings());
2820
+ const userComponent = new UserMessageComponent(skillBlock.userMessage, this.getMarkdownThemeWithSettings(), this.outputPad);
2818
2821
  this.chatContainer.addChild(userComponent);
2819
2822
  }
2820
2823
  }
2821
2824
  else {
2822
- const userComponent = new UserMessageComponent(textContent, this.getMarkdownThemeWithSettings());
2825
+ const userComponent = new UserMessageComponent(textContent, this.getMarkdownThemeWithSettings(), this.outputPad);
2823
2826
  this.chatContainer.addChild(userComponent);
2824
2827
  }
2825
2828
  if (options?.populateHistory) {
@@ -2829,7 +2832,7 @@ export class InteractiveMode {
2829
2832
  break;
2830
2833
  }
2831
2834
  case "assistant": {
2832
- const assistantComponent = new AssistantMessageComponent(message, this.hideThinkingBlock, this.getMarkdownThemeWithSettings(), this.hiddenThinkingLabel);
2835
+ const assistantComponent = new AssistantMessageComponent(message, this.hideThinkingBlock, this.getMarkdownThemeWithSettings(), this.hiddenThinkingLabel, this.outputPad);
2833
2836
  assistantComponent.setExpanded(this.toolOutputExpanded);
2834
2837
  this.chatContainer.addChild(assistantComponent);
2835
2838
  break;
@@ -2844,20 +2847,19 @@ export class InteractiveMode {
2844
2847
  }
2845
2848
  }
2846
2849
  }
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 = {}) {
2850
+ renderSessionItems(items, options = {}) {
2854
2851
  this.clearPendingTools();
2855
2852
  const renderedPendingTools = new Map();
2856
2853
  if (options.updateFooter) {
2857
2854
  this.footer.invalidate();
2858
2855
  this.updateEditorBorderColor();
2859
2856
  }
2860
- for (const message of sessionContext.messages) {
2857
+ for (const item of items) {
2858
+ if (isCustomSessionEntry(item)) {
2859
+ this.addCustomEntryToChat(item);
2860
+ continue;
2861
+ }
2862
+ const message = item;
2861
2863
  // Assistant messages need special handling for tool calls
2862
2864
  if (message.role === "assistant") {
2863
2865
  this.addMessageToChat(message);
@@ -2904,10 +2906,24 @@ export class InteractiveMode {
2904
2906
  }
2905
2907
  this.ui.requestRender();
2906
2908
  }
2909
+ /**
2910
+ * Render session entries to chat. Used for initial load and rebuild after compaction.
2911
+ * @param entries Compaction-aware session entries to render
2912
+ * @param options.updateFooter Update footer state
2913
+ * @param options.populateHistory Add user messages to editor history
2914
+ */
2915
+ renderSessionEntries(entries, options = {}) {
2916
+ const items = entries.flatMap((entry) => {
2917
+ if (entry.type === "custom") {
2918
+ return [entry];
2919
+ }
2920
+ return sessionEntryToContextMessages(entry);
2921
+ });
2922
+ this.renderSessionItems(items, options);
2923
+ }
2907
2924
  renderInitialMessages() {
2908
- // Get aligned messages and entries from session context
2909
- const context = this.sessionManager.buildSessionContext();
2910
- this.renderSessionContext(context, {
2925
+ const entries = this.sessionManager.buildContextEntries();
2926
+ this.renderSessionEntries(entries, {
2911
2927
  updateFooter: true,
2912
2928
  populateHistory: true,
2913
2929
  });
@@ -2942,8 +2958,7 @@ export class InteractiveMode {
2942
2958
  }
2943
2959
  rebuildChatFromMessages() {
2944
2960
  this.chatContainer.clear();
2945
- const context = this.sessionManager.buildSessionContext();
2946
- this.renderSessionContext(context);
2961
+ this.renderSessionEntries(this.sessionManager.buildContextEntries());
2947
2962
  }
2948
2963
  // =========================================================================
2949
2964
  // Key handlers
@@ -3557,6 +3572,7 @@ export class InteractiveMode {
3557
3572
  showHardwareCursor: this.settingsManager.getShowHardwareCursor(),
3558
3573
  defaultProjectTrust: this.settingsManager.getDefaultProjectTrust(),
3559
3574
  editorPaddingX: this.settingsManager.getEditorPaddingX(),
3575
+ outputPad: this.settingsManager.getOutputPad(),
3560
3576
  autocompleteMaxVisible: this.settingsManager.getAutocompleteMaxVisible(),
3561
3577
  quietStartup: this.settingsManager.getQuietStartup(),
3562
3578
  clearOnShrink: this.settingsManager.getClearOnShrink(),
@@ -3658,6 +3674,23 @@ export class InteractiveMode {
3658
3674
  this.editor.setPaddingX(padding);
3659
3675
  }
3660
3676
  },
3677
+ onOutputPadChange: (padding) => {
3678
+ this.settingsManager.setOutputPad(padding);
3679
+ this.outputPad = padding;
3680
+ if (this.streamingComponent || this.session.isStreaming) {
3681
+ for (const child of this.chatContainer.children) {
3682
+ if (child instanceof AssistantMessageComponent || child instanceof UserMessageComponent) {
3683
+ child.setOutputPad(padding);
3684
+ }
3685
+ }
3686
+ if (this.streamingComponent) {
3687
+ this.streamingComponent.setOutputPad(padding);
3688
+ }
3689
+ this.ui.requestRender();
3690
+ return;
3691
+ }
3692
+ this.rebuildChatFromMessages();
3693
+ },
3661
3694
  onAutocompleteMaxVisibleChange: (maxVisible) => {
3662
3695
  this.settingsManager.setAutocompleteMaxVisible(maxVisible);
3663
3696
  this.defaultEditor.setAutocompleteMaxVisible(maxVisible);
@@ -3668,6 +3701,9 @@ export class InteractiveMode {
3668
3701
  onClearOnShrinkChange: (enabled) => {
3669
3702
  this.settingsManager.setClearOnShrink(enabled);
3670
3703
  this.ui.setClearOnShrink(enabled);
3704
+ if (!enabled && !this.activeStatusIndicator) {
3705
+ this.statusContainer.clear();
3706
+ }
3671
3707
  },
3672
3708
  onShowTerminalProgressChange: (enabled) => {
3673
3709
  this.settingsManager.setShowTerminalProgress(enabled);
@@ -3999,16 +4035,16 @@ export class InteractiveMode {
3999
4035
  break;
4000
4036
  }
4001
4037
  }
4002
- // Set up escape handler and loader if summarizing
4003
- let summaryLoader;
4038
+ // Set up escape handler and status indicator if summarizing
4039
+ let showingSummaryIndicator = false;
4004
4040
  const originalOnEscape = this.defaultEditor.onEscape;
4005
4041
  if (wantsSummary) {
4006
4042
  this.defaultEditor.onEscape = () => {
4007
4043
  this.session.abortBranchSummary();
4008
4044
  };
4009
4045
  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);
4046
+ this.showStatusIndicator(new BranchSummaryStatusIndicator(this.ui));
4047
+ showingSummaryIndicator = true;
4012
4048
  this.ui.requestRender();
4013
4049
  }
4014
4050
  try {
@@ -4039,9 +4075,8 @@ export class InteractiveMode {
4039
4075
  this.showError(error instanceof Error ? error.message : String(error));
4040
4076
  }
4041
4077
  finally {
4042
- if (summaryLoader) {
4043
- summaryLoader.stop();
4044
- this.statusContainer.clear();
4078
+ if (showingSummaryIndicator) {
4079
+ this.clearStatusIndicator("branchSummary");
4045
4080
  }
4046
4081
  this.defaultEditor.onEscape = originalOnEscape;
4047
4082
  }
@@ -4082,7 +4117,7 @@ export class InteractiveMode {
4082
4117
  });
4083
4118
  }
4084
4119
  async handleResumeSession(sessionPath, options) {
4085
- this.stopWorkingLoader();
4120
+ this.clearStatusIndicator();
4086
4121
  try {
4087
4122
  const result = await this.runtimeHost.switchSession(sessionPath, {
4088
4123
  withSession: options?.withSession,
@@ -4477,6 +4512,7 @@ export class InteractiveMode {
4477
4512
  return;
4478
4513
  }
4479
4514
  this.hideThinkingBlock = this.settingsManager.getHideThinkingBlock();
4515
+ this.outputPad = this.settingsManager.getOutputPad();
4480
4516
  this.rebuildChatFromMessages();
4481
4517
  chatRestoredBeforeSessionStart = true;
4482
4518
  };
@@ -4500,7 +4536,11 @@ export class InteractiveMode {
4500
4536
  this.editor.setAutocompleteMaxVisible?.(autocompleteMaxVisible);
4501
4537
  }
4502
4538
  this.ui.setShowHardwareCursor(this.settingsManager.getShowHardwareCursor());
4503
- this.ui.setClearOnShrink(this.settingsManager.getClearOnShrink());
4539
+ const clearOnShrink = this.settingsManager.getClearOnShrink();
4540
+ this.ui.setClearOnShrink(clearOnShrink);
4541
+ if (!clearOnShrink && !this.activeStatusIndicator) {
4542
+ this.statusContainer.clear();
4543
+ }
4504
4544
  this.setupAutocompleteProvider();
4505
4545
  const runner = this.session.extensionRunner;
4506
4546
  this.setupExtensionShortcuts(runner);
@@ -4579,7 +4619,7 @@ export class InteractiveMode {
4579
4619
  return;
4580
4620
  }
4581
4621
  try {
4582
- this.stopWorkingLoader();
4622
+ this.clearStatusIndicator();
4583
4623
  const result = await this.runtimeHost.importFromJsonl(inputPath);
4584
4624
  if (result.cancelled) {
4585
4625
  this.showStatus("Import cancelled");
@@ -4882,7 +4922,7 @@ export class InteractiveMode {
4882
4922
  this.ui.requestRender();
4883
4923
  }
4884
4924
  async handleClearCommand() {
4885
- this.stopWorkingLoader();
4925
+ this.clearStatusIndicator();
4886
4926
  try {
4887
4927
  const result = await this.runtimeHost.newSession();
4888
4928
  if (result.cancelled) {
@@ -5015,8 +5055,7 @@ export class InteractiveMode {
5015
5055
  this.showWarning("Nothing to compact (no messages yet)");
5016
5056
  return;
5017
5057
  }
5018
- this.stopWorkingLoader();
5019
- this.statusContainer.clear();
5058
+ this.clearStatusIndicator();
5020
5059
  try {
5021
5060
  await this.session.compact(customInstructions);
5022
5061
  }
@@ -5028,7 +5067,7 @@ export class InteractiveMode {
5028
5067
  if (this.settingsManager.getShowTerminalProgress()) {
5029
5068
  this.ui.terminal.setProgress(false);
5030
5069
  }
5031
- this.stopWorkingLoader();
5070
+ this.clearStatusIndicator();
5032
5071
  this.clearPendingTools();
5033
5072
  this.clearActiveToolExecutionStatus();
5034
5073
  this.clearToolHookStatuses();