@code-yeongyu/senpi 2026.8.19 → 2026.8.20

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 (219) hide show
  1. package/CHANGELOG.md +117 -0
  2. package/dist/cli-main.js +4 -0
  3. package/dist/cli-main.js.map +1 -1
  4. package/dist/cli.js +31 -3
  5. package/dist/cli.js.map +1 -1
  6. package/dist/compile-cache.d.ts +2 -0
  7. package/dist/compile-cache.d.ts.map +1 -0
  8. package/dist/compile-cache.js +33 -0
  9. package/dist/compile-cache.js.map +1 -0
  10. package/dist/core/agent-session.d.ts +5 -0
  11. package/dist/core/agent-session.d.ts.map +1 -1
  12. package/dist/core/agent-session.js +94 -12
  13. package/dist/core/agent-session.js.map +1 -1
  14. package/dist/core/compaction/compaction.d.ts +6 -0
  15. package/dist/core/compaction/compaction.d.ts.map +1 -1
  16. package/dist/core/compaction/compaction.js +13 -0
  17. package/dist/core/compaction/compaction.js.map +1 -1
  18. package/dist/core/cursor-exec-bridge-session.d.ts +2 -1
  19. package/dist/core/cursor-exec-bridge-session.d.ts.map +1 -1
  20. package/dist/core/cursor-exec-bridge-session.js +5 -2
  21. package/dist/core/cursor-exec-bridge-session.js.map +1 -1
  22. package/dist/core/cursor-exec-bridge.d.ts +9 -1
  23. package/dist/core/cursor-exec-bridge.d.ts.map +1 -1
  24. package/dist/core/cursor-exec-bridge.js +26 -2
  25. package/dist/core/cursor-exec-bridge.js.map +1 -1
  26. package/dist/core/extensions/builtin/claude-sdk-oauth/custom-tools.d.ts +1 -1
  27. package/dist/core/extensions/builtin/claude-sdk-oauth/custom-tools.d.ts.map +1 -1
  28. package/dist/core/extensions/builtin/claude-sdk-oauth/custom-tools.js +5 -2
  29. package/dist/core/extensions/builtin/claude-sdk-oauth/custom-tools.js.map +1 -1
  30. package/dist/core/extensions/builtin/claude-sdk-oauth/sdk-boundary.d.ts +6 -4
  31. package/dist/core/extensions/builtin/claude-sdk-oauth/sdk-boundary.d.ts.map +1 -1
  32. package/dist/core/extensions/builtin/claude-sdk-oauth/sdk-boundary.js +24 -2
  33. package/dist/core/extensions/builtin/claude-sdk-oauth/sdk-boundary.js.map +1 -1
  34. package/dist/core/extensions/builtin/claude-sdk-oauth/sdk-boundary.lazy.d.ts +39 -0
  35. package/dist/core/extensions/builtin/claude-sdk-oauth/sdk-boundary.lazy.d.ts.map +1 -0
  36. package/dist/core/extensions/builtin/claude-sdk-oauth/sdk-boundary.lazy.js +31 -0
  37. package/dist/core/extensions/builtin/claude-sdk-oauth/sdk-boundary.lazy.js.map +1 -0
  38. package/dist/core/extensions/builtin/claude-sdk-oauth/session-continuity.d.ts +2 -0
  39. package/dist/core/extensions/builtin/claude-sdk-oauth/session-continuity.d.ts.map +1 -1
  40. package/dist/core/extensions/builtin/claude-sdk-oauth/session-continuity.js +35 -0
  41. package/dist/core/extensions/builtin/claude-sdk-oauth/session-continuity.js.map +1 -1
  42. package/dist/core/extensions/builtin/claude-sdk-oauth/session-observability.d.ts +1 -1
  43. package/dist/core/extensions/builtin/claude-sdk-oauth/session-observability.d.ts.map +1 -1
  44. package/dist/core/extensions/builtin/claude-sdk-oauth/session-observability.js +1 -0
  45. package/dist/core/extensions/builtin/claude-sdk-oauth/session-observability.js.map +1 -1
  46. package/dist/core/extensions/builtin/claude-sdk-oauth/session-reattach.d.ts +8 -0
  47. package/dist/core/extensions/builtin/claude-sdk-oauth/session-reattach.d.ts.map +1 -1
  48. package/dist/core/extensions/builtin/claude-sdk-oauth/session-reattach.js +4 -1
  49. package/dist/core/extensions/builtin/claude-sdk-oauth/session-reattach.js.map +1 -1
  50. package/dist/core/extensions/builtin/claude-sdk-oauth/session-turn-attempt.d.ts.map +1 -1
  51. package/dist/core/extensions/builtin/claude-sdk-oauth/session-turn-attempt.js +28 -1
  52. package/dist/core/extensions/builtin/claude-sdk-oauth/session-turn-attempt.js.map +1 -1
  53. package/dist/core/extensions/builtin/claude-sdk-oauth/stream.d.ts.map +1 -1
  54. package/dist/core/extensions/builtin/claude-sdk-oauth/stream.js +5 -2
  55. package/dist/core/extensions/builtin/claude-sdk-oauth/stream.js.map +1 -1
  56. package/dist/core/extensions/builtin/compaction/index.d.ts.map +1 -1
  57. package/dist/core/extensions/builtin/compaction/index.js +5 -0
  58. package/dist/core/extensions/builtin/compaction/index.js.map +1 -1
  59. package/dist/core/extensions/builtin/compaction/log.d.ts +1 -1
  60. package/dist/core/extensions/builtin/compaction/log.d.ts.map +1 -1
  61. package/dist/core/extensions/builtin/compaction/log.js +1 -0
  62. package/dist/core/extensions/builtin/compaction/log.js.map +1 -1
  63. package/dist/core/extensions/builtin/compaction/speculative.d.ts +1 -0
  64. package/dist/core/extensions/builtin/compaction/speculative.d.ts.map +1 -1
  65. package/dist/core/extensions/builtin/compaction/speculative.js +4 -0
  66. package/dist/core/extensions/builtin/compaction/speculative.js.map +1 -1
  67. package/dist/core/extensions/builtin/config-reload/index.d.ts.map +1 -1
  68. package/dist/core/extensions/builtin/config-reload/index.js +13 -10
  69. package/dist/core/extensions/builtin/config-reload/index.js.map +1 -1
  70. package/dist/core/extensions/builtin/config-reload/watch-engine.d.ts +8 -1
  71. package/dist/core/extensions/builtin/config-reload/watch-engine.d.ts.map +1 -1
  72. package/dist/core/extensions/builtin/config-reload/watch-engine.js +98 -18
  73. package/dist/core/extensions/builtin/config-reload/watch-engine.js.map +1 -1
  74. package/dist/core/extensions/builtin/config-reload/watch-event-source.d.ts +1 -1
  75. package/dist/core/extensions/builtin/config-reload/watch-event-source.d.ts.map +1 -1
  76. package/dist/core/extensions/builtin/config-reload/watch-event-source.js +7 -2
  77. package/dist/core/extensions/builtin/config-reload/watch-event-source.js.map +1 -1
  78. package/dist/core/extensions/builtin/goal/elapsed-ticker.d.ts.map +1 -1
  79. package/dist/core/extensions/builtin/goal/elapsed-ticker.js +15 -1
  80. package/dist/core/extensions/builtin/goal/elapsed-ticker.js.map +1 -1
  81. package/dist/core/extensions/builtin/goal/index.d.ts.map +1 -1
  82. package/dist/core/extensions/builtin/goal/index.js +7 -22
  83. package/dist/core/extensions/builtin/goal/index.js.map +1 -1
  84. package/dist/core/extensions/builtin/goal/stale-context.d.ts +9 -0
  85. package/dist/core/extensions/builtin/goal/stale-context.d.ts.map +1 -0
  86. package/dist/core/extensions/builtin/goal/stale-context.js +11 -0
  87. package/dist/core/extensions/builtin/goal/stale-context.js.map +1 -0
  88. package/dist/core/extensions/builtin/goal/wait-ticker.d.ts +8 -0
  89. package/dist/core/extensions/builtin/goal/wait-ticker.d.ts.map +1 -1
  90. package/dist/core/extensions/builtin/goal/wait-ticker.js +37 -3
  91. package/dist/core/extensions/builtin/goal/wait-ticker.js.map +1 -1
  92. package/dist/core/extensions/builtin/mcp/index.d.ts.map +1 -1
  93. package/dist/core/extensions/builtin/mcp/index.js +12 -1
  94. package/dist/core/extensions/builtin/mcp/index.js.map +1 -1
  95. package/dist/core/extensions/builtin/prompt-url-widget.d.ts.map +1 -1
  96. package/dist/core/extensions/builtin/prompt-url-widget.js +7 -15
  97. package/dist/core/extensions/builtin/prompt-url-widget.js.map +1 -1
  98. package/dist/core/extensions/builtin/rules/ui/rules-banner.d.ts.map +1 -1
  99. package/dist/core/extensions/builtin/rules/ui/rules-banner.js +21 -25
  100. package/dist/core/extensions/builtin/rules/ui/rules-banner.js.map +1 -1
  101. package/dist/core/extensions/builtin/todotools/index.d.ts.map +1 -1
  102. package/dist/core/extensions/builtin/todotools/index.js +20 -0
  103. package/dist/core/extensions/builtin/todotools/index.js.map +1 -1
  104. package/dist/core/extensions/builtin/todotools/native-todo-mirror.d.ts +3 -0
  105. package/dist/core/extensions/builtin/todotools/native-todo-mirror.d.ts.map +1 -0
  106. package/dist/core/extensions/builtin/todotools/native-todo-mirror.js +22 -0
  107. package/dist/core/extensions/builtin/todotools/native-todo-mirror.js.map +1 -0
  108. package/dist/core/extensions/builtin/webfetch/webfetch/content.lazy.d.ts +17 -0
  109. package/dist/core/extensions/builtin/webfetch/webfetch/content.lazy.d.ts.map +1 -0
  110. package/dist/core/extensions/builtin/webfetch/webfetch/content.lazy.js +22 -0
  111. package/dist/core/extensions/builtin/webfetch/webfetch/content.lazy.js.map +1 -0
  112. package/dist/core/extensions/builtin/webfetch/webfetch/tool.js +3 -3
  113. package/dist/core/extensions/builtin/webfetch/webfetch/tool.js.map +1 -1
  114. package/dist/core/messages.d.ts +9 -2
  115. package/dist/core/messages.d.ts.map +1 -1
  116. package/dist/core/messages.js +13 -18
  117. package/dist/core/messages.js.map +1 -1
  118. package/dist/core/retry-fallback/hint-policy.d.ts +3 -2
  119. package/dist/core/retry-fallback/hint-policy.d.ts.map +1 -1
  120. package/dist/core/retry-fallback/hint-policy.js +19 -8
  121. package/dist/core/retry-fallback/hint-policy.js.map +1 -1
  122. package/dist/core/retry-fallback/settings.js.map +1 -1
  123. package/dist/core/session-discovery.d.ts +16 -0
  124. package/dist/core/session-discovery.d.ts.map +1 -0
  125. package/dist/core/session-discovery.js +101 -0
  126. package/dist/core/session-discovery.js.map +1 -0
  127. package/dist/core/session-manager.d.ts +2 -1
  128. package/dist/core/session-manager.d.ts.map +1 -1
  129. package/dist/core/session-manager.js +4 -170
  130. package/dist/core/session-manager.js.map +1 -1
  131. package/dist/core/session-record.d.ts +14 -0
  132. package/dist/core/session-record.d.ts.map +1 -0
  133. package/dist/core/session-record.js +47 -0
  134. package/dist/core/session-record.js.map +1 -0
  135. package/dist/core/session-summary-cache.d.ts +40 -0
  136. package/dist/core/session-summary-cache.d.ts.map +1 -0
  137. package/dist/core/session-summary-cache.js +79 -0
  138. package/dist/core/session-summary-cache.js.map +1 -0
  139. package/dist/core/session-summary-lru.d.ts +54 -0
  140. package/dist/core/session-summary-lru.d.ts.map +1 -0
  141. package/dist/core/session-summary-lru.js +72 -0
  142. package/dist/core/session-summary-lru.js.map +1 -0
  143. package/dist/core/session-summary.d.ts +29 -0
  144. package/dist/core/session-summary.d.ts.map +1 -0
  145. package/dist/core/session-summary.js +90 -0
  146. package/dist/core/session-summary.js.map +1 -0
  147. package/dist/index.d.ts +1 -0
  148. package/dist/index.d.ts.map +1 -1
  149. package/dist/index.js +2 -0
  150. package/dist/index.js.map +1 -1
  151. package/dist/inspector-policy.d.ts +6 -0
  152. package/dist/inspector-policy.d.ts.map +1 -1
  153. package/dist/inspector-policy.js +6 -1
  154. package/dist/inspector-policy.js.map +1 -1
  155. package/dist/modes/interactive/components/earendil-announcement.d.ts.map +1 -1
  156. package/dist/modes/interactive/components/earendil-announcement.js +8 -8
  157. package/dist/modes/interactive/components/earendil-announcement.js.map +1 -1
  158. package/dist/modes/interactive/components/progressive-transcript-container.d.ts +72 -0
  159. package/dist/modes/interactive/components/progressive-transcript-container.d.ts.map +1 -0
  160. package/dist/modes/interactive/components/progressive-transcript-container.js +150 -0
  161. package/dist/modes/interactive/components/progressive-transcript-container.js.map +1 -0
  162. package/dist/modes/interactive/components/status-indicator.d.ts +1 -1
  163. package/dist/modes/interactive/components/status-indicator.d.ts.map +1 -1
  164. package/dist/modes/interactive/components/status-indicator.js +8 -2
  165. package/dist/modes/interactive/components/status-indicator.js.map +1 -1
  166. package/dist/modes/interactive/interactive-mode.d.ts +4 -0
  167. package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
  168. package/dist/modes/interactive/interactive-mode.js +127 -65
  169. package/dist/modes/interactive/interactive-mode.js.map +1 -1
  170. package/dist/modes/interactive/split-trailing-assistant-text.d.ts +7 -0
  171. package/dist/modes/interactive/split-trailing-assistant-text.d.ts.map +1 -0
  172. package/dist/modes/interactive/split-trailing-assistant-text.js +19 -0
  173. package/dist/modes/interactive/split-trailing-assistant-text.js.map +1 -0
  174. package/dist/senpi +31 -3
  175. package/node_modules/@code-yeongyu/senpi-codemode/CHANGELOG.md +12 -0
  176. package/node_modules/@code-yeongyu/senpi-codemode/package.json +4 -4
  177. package/node_modules/@earendil-works/pi-agent-core/dist/agent-loop.d.ts.map +1 -1
  178. package/node_modules/@earendil-works/pi-agent-core/dist/agent-loop.js +38 -3
  179. package/node_modules/@earendil-works/pi-agent-core/dist/agent-loop.js.map +1 -1
  180. package/node_modules/@earendil-works/pi-agent-core/dist/assistant-terminal-state.d.ts +8 -1
  181. package/node_modules/@earendil-works/pi-agent-core/dist/assistant-terminal-state.d.ts.map +1 -1
  182. package/node_modules/@earendil-works/pi-agent-core/dist/assistant-terminal-state.js +25 -1
  183. package/node_modules/@earendil-works/pi-agent-core/dist/assistant-terminal-state.js.map +1 -1
  184. package/node_modules/@earendil-works/pi-agent-core/package.json +3 -3
  185. package/node_modules/@earendil-works/pi-ai/dist/api/cursor-agent.d.ts +2 -0
  186. package/node_modules/@earendil-works/pi-ai/dist/api/cursor-agent.d.ts.map +1 -1
  187. package/node_modules/@earendil-works/pi-ai/dist/api/cursor-agent.js +284 -209
  188. package/node_modules/@earendil-works/pi-ai/dist/api/cursor-agent.js.map +1 -1
  189. package/node_modules/@earendil-works/pi-ai/dist/api/cursor-conversation-rotation.d.ts +37 -0
  190. package/node_modules/@earendil-works/pi-ai/dist/api/cursor-conversation-rotation.d.ts.map +1 -0
  191. package/node_modules/@earendil-works/pi-ai/dist/api/cursor-conversation-rotation.js +110 -0
  192. package/node_modules/@earendil-works/pi-ai/dist/api/cursor-conversation-rotation.js.map +1 -0
  193. package/node_modules/@earendil-works/pi-ai/dist/api/cursor-task-args.d.ts +3 -0
  194. package/node_modules/@earendil-works/pi-ai/dist/api/cursor-task-args.d.ts.map +1 -0
  195. package/node_modules/@earendil-works/pi-ai/dist/api/cursor-task-args.js +17 -0
  196. package/node_modules/@earendil-works/pi-ai/dist/api/cursor-task-args.js.map +1 -0
  197. package/node_modules/@earendil-works/pi-ai/dist/cursor/selection-descriptor.d.ts.map +1 -1
  198. package/node_modules/@earendil-works/pi-ai/dist/cursor/selection-descriptor.js +27 -11
  199. package/node_modules/@earendil-works/pi-ai/dist/cursor/selection-descriptor.js.map +1 -1
  200. package/node_modules/@earendil-works/pi-ai/dist/providers/data/.manifest.json +1 -1
  201. package/node_modules/@earendil-works/pi-ai/dist/providers/data/amazon-bedrock.json +1 -1
  202. package/node_modules/@earendil-works/pi-ai/dist/providers/data/baseten.json +1 -1
  203. package/node_modules/@earendil-works/pi-ai/dist/providers/data/huggingface.json +1 -1
  204. package/node_modules/@earendil-works/pi-ai/dist/providers/data/opencode-go.json +1 -1
  205. package/node_modules/@earendil-works/pi-ai/dist/providers/data/opencode.json +1 -1
  206. package/node_modules/@earendil-works/pi-ai/dist/providers/data/openrouter.json +1 -1
  207. package/node_modules/@earendil-works/pi-ai/dist/providers/data/vercel-ai-gateway.json +1 -1
  208. package/node_modules/@earendil-works/pi-ai/dist/tool-call-middleware/index.d.ts.map +1 -1
  209. package/node_modules/@earendil-works/pi-ai/dist/tool-call-middleware/index.js +2 -0
  210. package/node_modules/@earendil-works/pi-ai/dist/tool-call-middleware/index.js.map +1 -1
  211. package/node_modules/@earendil-works/pi-ai/dist/utils/overflow.d.ts +30 -0
  212. package/node_modules/@earendil-works/pi-ai/dist/utils/overflow.d.ts.map +1 -1
  213. package/node_modules/@earendil-works/pi-ai/dist/utils/overflow.js +33 -0
  214. package/node_modules/@earendil-works/pi-ai/dist/utils/overflow.js.map +1 -1
  215. package/node_modules/@earendil-works/pi-ai/package.json +2 -2
  216. package/node_modules/@earendil-works/pi-pty/package.json +1 -1
  217. package/node_modules/@earendil-works/pi-telemetry/package.json +1 -1
  218. package/node_modules/@earendil-works/pi-tui/package.json +1 -1
  219. package/package.json +7 -7
@@ -70,6 +70,7 @@ import { createMermaidMarkdownTransformer } from "./components/mermaid.js";
70
70
  import { getModelFullId, mergeFavoritePatternsForPersist, } from "./components/model-favorites.js";
71
71
  import { ModelSelectorComponent } from "./components/model-selector.js";
72
72
  import { formatAuthSelectorProviderType, OAuthSelectorComponent, } from "./components/oauth-selector.js";
73
+ import { DEFAULT_TAIL_BUDGET, DEFAULT_WARM_CHUNK_SIZE, ProgressiveTranscriptContainer, } from "./components/progressive-transcript-container.js";
73
74
  import { ScopedModelsSelectorComponent } from "./components/scoped-models-selector.js";
74
75
  import { SessionSelectorComponent } from "./components/session-selector.js";
75
76
  import { SettingsSelectorComponent } from "./components/settings-selector.js";
@@ -90,6 +91,7 @@ import { applyKeybindingsFileEdit, seedKeybindingsFile } from "./keybindings-com
90
91
  import { refreshModelCatalogs } from "./model-catalog-refresh.js";
91
92
  import { getModelSearchText } from "./model-search.js";
92
93
  import { isRiskyMainModel, RISKY_MAIN_MODEL_WARNING } from "./risky-main-model-warning.js";
94
+ import { splitTrailingAssistantContent } from "./split-trailing-assistant-text.js";
93
95
  import { DEFAULT_SMOOTH_FPS, StreamingRevealController } from "./streaming-reveal.js";
94
96
  import { getAvailableThemes, getAvailableThemesWithPaths, getEditorTheme, getMarkdownTheme, getThemeByName, onThemeChange, setRegisteredThemes, stopThemeWatcher, Theme, theme, } from "./theme/theme.js";
95
97
  import { InteractiveThemeController } from "./theme/theme-controller.js";
@@ -142,6 +144,8 @@ function isCompactionCostNotice(item) {
142
144
  return "type" in item && item.type === "compaction_cost";
143
145
  }
144
146
  const DEAD_TERMINAL_ERROR_CODES = new Set(["EIO", "EPIPE", "ENOTCONN"]);
147
+ const DEFAULT_RETRY_STATUS_REFRESH_INTERVAL_MS = 80;
148
+ const LARGE_SESSION_RETRY_STATUS_REFRESH_INTERVAL_MS = 60_000;
145
149
  const DEFAULT_WORKING_STATUS_REFRESH_INTERVAL_MS = 600;
146
150
  const DEFAULT_WORKING_STATUS_MESSAGE_ANIMATION_INTERVAL_MS = 32;
147
151
  const LARGE_SESSION_WORKING_STATUS_REFRESH_INTERVAL_MS = 60_000;
@@ -464,6 +468,7 @@ export class InteractiveMode {
464
468
  this.managedToolStatusStarted = false;
465
469
  // Streaming message tracking
466
470
  this.streamingComponent = undefined;
471
+ this.trailingAssistantComponent = undefined;
467
472
  this.streamingMessage = undefined;
468
473
  // Tool execution tracking: toolCallId -> component
469
474
  this.pendingTools = new Map();
@@ -560,7 +565,14 @@ export class InteractiveMode {
560
565
  this.ui.setClearOnShrink(this.settingsManager.getClearOnShrink());
561
566
  this.headerContainer = new Container();
562
567
  this.loadedResourcesContainer = new Container();
563
- this.chatContainer = new Container();
568
+ // Resuming a long session paints a bounded, fully-styled tail first and warms
569
+ // the earlier history in background chunks, so /resume is not blocked on
570
+ // Markdown-rendering every persisted message before the first frame.
571
+ this.chatContainer = new ProgressiveTranscriptContainer({
572
+ tailBudget: DEFAULT_TAIL_BUDGET,
573
+ warmChunkSize: DEFAULT_WARM_CHUNK_SIZE,
574
+ requestRender: () => this.ui.requestRender(),
575
+ });
564
576
  this.documentContainer = new Container();
565
577
  this.documentContainer.addChild(this.headerContainer);
566
578
  this.documentContainer.addChild(this.loadedResourcesContainer);
@@ -796,7 +808,11 @@ export class InteractiveMode {
796
808
  }
797
809
  previousUi.stop({ preserveScreen: true });
798
810
  previousUi.setFocus(null);
799
- previousUi.clear();
811
+ // Detach, not clear: the same live components (spinners, reveals, extension
812
+ // widgets) are remounted on nextUi below. clear() would dispose them,
813
+ // killing every interval they own while they keep rendering static frames
814
+ // forever — the TUI then never self-repaints until an input event forces one.
815
+ previousUi.detachAll();
800
816
  if (TuiLayouts.isViewportTUI(previousUi))
801
817
  previousUi.setLayoutRoot(undefined);
802
818
  const nextUi = createInteractiveTui({
@@ -1563,25 +1579,32 @@ export class InteractiveMode {
1563
1579
  const first = collisionList[0]?.collision;
1564
1580
  if (!first)
1565
1581
  continue;
1566
- lines.push(theme.fg("warning", ` "${name}" collision:`));
1567
- lines.push(theme.fg("dim", ` ${theme.fg("success", "✓")} ${this.formatPathWithSource(first.winnerPath, this.findSourceInfoForPath(first.winnerPath, sourceInfos))}`));
1582
+ lines.push({ text: ` "${name}" collision:`, tone: "warning" });
1583
+ lines.push({
1584
+ text: ` ✓ ${this.formatPathWithSource(first.winnerPath, this.findSourceInfoForPath(first.winnerPath, sourceInfos))}`,
1585
+ tone: "warning",
1586
+ });
1568
1587
  for (const d of collisionList) {
1569
1588
  if (d.collision) {
1570
- lines.push(theme.fg("dim", ` ${theme.fg("warning", "✗")} ${this.formatPathWithSource(d.collision.loserPath, this.findSourceInfoForPath(d.collision.loserPath, sourceInfos))} (skipped)`));
1589
+ lines.push({
1590
+ text: ` ✗ ${this.formatPathWithSource(d.collision.loserPath, this.findSourceInfoForPath(d.collision.loserPath, sourceInfos))} (skipped)`,
1591
+ tone: "warning",
1592
+ });
1571
1593
  }
1572
1594
  }
1573
1595
  }
1574
1596
  for (const d of otherDiagnostics) {
1597
+ const tone = d.type === "error" ? "error" : "warning";
1575
1598
  if (d.path) {
1576
1599
  const formattedPath = this.formatPathWithSource(d.path, this.findSourceInfoForPath(d.path, sourceInfos));
1577
- lines.push(theme.fg(d.type === "error" ? "error" : "warning", ` ${formattedPath}`));
1578
- lines.push(theme.fg(d.type === "error" ? "error" : "warning", ` ${d.message}`));
1600
+ lines.push({ text: ` ${formattedPath}`, tone });
1601
+ lines.push({ text: ` ${d.message}`, tone });
1579
1602
  }
1580
1603
  else {
1581
- lines.push(theme.fg(d.type === "error" ? "error" : "warning", ` ${d.message}`));
1604
+ lines.push({ text: ` ${d.message}`, tone });
1582
1605
  }
1583
1606
  }
1584
- return lines.join("\n");
1607
+ return lines;
1585
1608
  }
1586
1609
  showLoadedResources(options) {
1587
1610
  // Resource rendering is idempotent; chat clears no longer clear this separate container.
@@ -1710,17 +1733,22 @@ export class InteractiveMode {
1710
1733
  }
1711
1734
  }
1712
1735
  if (showDiagnostics) {
1736
+ const addDiagnosticNotice = (title, diagnostics) => {
1737
+ this.loadedResourcesContainer.addChild(buildNoticeBox({
1738
+ title,
1739
+ tone: "warning",
1740
+ why: "Conflicting or invalid loaded resources were found.",
1741
+ extra: this.formatDiagnostics(diagnostics, sourceInfos),
1742
+ }, { expanded: this.toolOutputExpanded }, theme));
1743
+ this.loadedResourcesContainer.addChild(new Spacer(1));
1744
+ };
1713
1745
  const skillDiagnostics = skillsResult.diagnostics;
1714
1746
  if (skillDiagnostics.length > 0) {
1715
- const warningLines = this.formatDiagnostics(skillDiagnostics, sourceInfos);
1716
- this.loadedResourcesContainer.addChild(new Text(`${theme.fg("warning", "[Skill conflicts]")}\n${warningLines}`, 0, 0));
1717
- this.loadedResourcesContainer.addChild(new Spacer(1));
1747
+ addDiagnosticNotice("Skill conflicts", skillDiagnostics);
1718
1748
  }
1719
1749
  const promptDiagnostics = promptsResult.diagnostics;
1720
1750
  if (promptDiagnostics.length > 0) {
1721
- const warningLines = this.formatDiagnostics(promptDiagnostics, sourceInfos);
1722
- this.loadedResourcesContainer.addChild(new Text(`${theme.fg("warning", "[Prompt conflicts]")}\n${warningLines}`, 0, 0));
1723
- this.loadedResourcesContainer.addChild(new Spacer(1));
1751
+ addDiagnosticNotice("Prompt conflicts", promptDiagnostics);
1724
1752
  }
1725
1753
  const extensionDiagnostics = [];
1726
1754
  const extensionErrors = this.session.resourceLoader.getExtensions().errors;
@@ -1739,15 +1767,11 @@ export class InteractiveMode {
1739
1767
  const shortcutDiagnostics = this.session.extensionRunner.getShortcutDiagnostics();
1740
1768
  extensionDiagnostics.push(...shortcutDiagnostics);
1741
1769
  if (extensionDiagnostics.length > 0) {
1742
- const warningLines = this.formatDiagnostics(extensionDiagnostics, sourceInfos);
1743
- this.loadedResourcesContainer.addChild(new Text(`${theme.fg("warning", "[Extension issues]")}\n${warningLines}`, 0, 0));
1744
- this.loadedResourcesContainer.addChild(new Spacer(1));
1770
+ addDiagnosticNotice("Extension issues", extensionDiagnostics);
1745
1771
  }
1746
1772
  const themeDiagnostics = themesResult.diagnostics;
1747
1773
  if (themeDiagnostics.length > 0) {
1748
- const warningLines = this.formatDiagnostics(themeDiagnostics, sourceInfos);
1749
- this.loadedResourcesContainer.addChild(new Text(`${theme.fg("warning", "[Theme conflicts]")}\n${warningLines}`, 0, 0));
1750
- this.loadedResourcesContainer.addChild(new Spacer(1));
1774
+ addDiagnosticNotice("Theme conflicts", themeDiagnostics);
1751
1775
  }
1752
1776
  }
1753
1777
  }
@@ -3376,7 +3400,10 @@ export class InteractiveMode {
3376
3400
  case "message_update":
3377
3401
  if (this.streamingComponent && event.message.role === "assistant") {
3378
3402
  this.streamingMessage = event.message;
3379
- this.streamingReveal.setTarget(this.streamingMessage);
3403
+ {
3404
+ const { head } = splitTrailingAssistantContent(event.message.content);
3405
+ this.streamingReveal.setTarget({ ...event.message, content: head });
3406
+ }
3380
3407
  for (const content of this.streamingMessage.content) {
3381
3408
  if (content.type === "toolCall") {
3382
3409
  let component = this.pendingTools.get(content.id);
@@ -3396,6 +3423,7 @@ export class InteractiveMode {
3396
3423
  }
3397
3424
  }
3398
3425
  }
3426
+ this.syncTrailingAssistantText(event.message);
3399
3427
  this.ui.requestRender();
3400
3428
  }
3401
3429
  break;
@@ -3419,7 +3447,8 @@ export class InteractiveMode {
3419
3447
  errorMessage = abortedErrorLabel(undefined, this.session.retryAttempt);
3420
3448
  this.streamingMessage.errorMessage = errorMessage;
3421
3449
  }
3422
- this.streamingComponent.updateContent(this.streamingMessage, false);
3450
+ this.syncTrailingAssistantText(this.streamingMessage);
3451
+ this.trailingAssistantComponent = undefined;
3423
3452
  this.addContinuityNotice(this.streamingMessage);
3424
3453
  if (this.streamingMessage.stopReason === "aborted" || this.streamingMessage.stopReason === "error") {
3425
3454
  if (!errorMessage) {
@@ -3492,13 +3521,17 @@ export class InteractiveMode {
3492
3521
  case "tool_execution_end": {
3493
3522
  this.handleToolExecutionEnd(event);
3494
3523
  this.toolArgsReveal.finish(event.toolCallId);
3495
- const component = this.pendingTools.get(event.toolCallId);
3496
- if (component) {
3497
- this.toolResultReveal.finish(event.toolCallId);
3498
- component.updateResult({ ...event.result, isError: event.isError });
3499
- this.pendingTools.delete(event.toolCallId);
3500
- this.ui.requestRender();
3524
+ let component = this.pendingTools.get(event.toolCallId);
3525
+ if (!component) {
3526
+ component = this.createToolExecutionComponent(event.toolName, event.toolCallId, {});
3527
+ component.setExpanded(this.toolOutputExpanded);
3528
+ this.chatContainer.addChild(component);
3529
+ this.pendingTools.set(event.toolCallId, component);
3501
3530
  }
3531
+ this.toolResultReveal.finish(event.toolCallId);
3532
+ component.updateResult({ ...event.result, isError: event.isError });
3533
+ this.pendingTools.delete(event.toolCallId);
3534
+ this.ui.requestRender();
3502
3535
  break;
3503
3536
  }
3504
3537
  case "agent_end":
@@ -3769,8 +3802,7 @@ export class InteractiveMode {
3769
3802
  }
3770
3803
  case "summarization_retry_scheduled": {
3771
3804
  this.showError(event.errorMessage);
3772
- this.showStatusIndicator(new RetryStatusIndicator(this.ui, event.attempt, event.maxAttempts, event.delayMs));
3773
- this.ui.requestRender();
3805
+ this.showSummarizationRetryStatusIndicator(event);
3774
3806
  break;
3775
3807
  }
3776
3808
  case "summarization_retry_attempt_start": {
@@ -3811,7 +3843,15 @@ export class InteractiveMode {
3811
3843
  }
3812
3844
  }
3813
3845
  showRetryStatusIndicator(event) {
3814
- this.showStatusIndicator(new RetryStatusIndicator(this.ui, event.attempt, event.maxAttempts, event.delayMs));
3846
+ this.showRetryStatusIndicatorWithCadence(event);
3847
+ }
3848
+ showSummarizationRetryStatusIndicator(event) {
3849
+ this.showRetryStatusIndicatorWithCadence(event);
3850
+ }
3851
+ showRetryStatusIndicatorWithCadence(event) {
3852
+ const refreshIntervalMs = largeSessionWorkingStatusInterval(this.sessionManager.getEntries().length, DEFAULT_RETRY_STATUS_REFRESH_INTERVAL_MS, LARGE_SESSION_RETRY_STATUS_REFRESH_INTERVAL_MS);
3853
+ const indicator = refreshIntervalMs === DEFAULT_RETRY_STATUS_REFRESH_INTERVAL_MS ? undefined : { intervalMs: refreshIntervalMs };
3854
+ this.showStatusIndicator(new RetryStatusIndicator(this.ui, event.attempt, event.maxAttempts, event.delayMs, indicator));
3815
3855
  this.ui.requestRender();
3816
3856
  }
3817
3857
  /** Extract text content from a user message */
@@ -3957,6 +3997,29 @@ export class InteractiveMode {
3957
3997
  }
3958
3998
  }
3959
3999
  }
4000
+ syncTrailingAssistantText(message) {
4001
+ if (!this.streamingComponent)
4002
+ return;
4003
+ const { head, tail } = splitTrailingAssistantContent(message.content);
4004
+ this.streamingComponent.updateContent({ ...message, content: head }, true);
4005
+ if (tail.length === 0) {
4006
+ if (this.trailingAssistantComponent) {
4007
+ this.chatContainer.detachChild(this.trailingAssistantComponent);
4008
+ this.trailingAssistantComponent = undefined;
4009
+ }
4010
+ return;
4011
+ }
4012
+ const tailMessage = { ...message, content: tail };
4013
+ if (!this.trailingAssistantComponent) {
4014
+ this.trailingAssistantComponent = new AssistantMessageComponent(tailMessage, this.hideThinkingBlock, this.getMarkdownThemeWithSettings(), this.hiddenThinkingLabel, this.outputPad, this.getMarkdownTransformers());
4015
+ this.trailingAssistantComponent.setExpanded(this.toolOutputExpanded);
4016
+ this.chatContainer.addChild(this.trailingAssistantComponent);
4017
+ return;
4018
+ }
4019
+ this.trailingAssistantComponent.updateContent(tailMessage, true);
4020
+ this.chatContainer.detachChild(this.trailingAssistantComponent);
4021
+ this.chatContainer.addChild(this.trailingAssistantComponent);
4022
+ }
3960
4023
  createToolExecutionComponent(toolName, toolCallId, args) {
3961
4024
  if (this.chrome) {
3962
4025
  return new ToolExecutionComponent(toolName, toolCallId, args, {
@@ -4555,48 +4618,43 @@ export class InteractiveMode {
4555
4618
  this.ui.requestRender();
4556
4619
  }
4557
4620
  showNewVersionNotification(newVersion) {
4558
- const action = theme.fg("accent", BRAND?.update?.command ?? `${APP_NAME} update`);
4559
- const updateInstruction = theme.fg("muted", `New version ${newVersion} is available. Run `) + action;
4621
+ const action = BRAND?.update?.command ?? `${APP_NAME} update`;
4560
4622
  const changelogUrl = getReleaseChangelogUrl(newVersion);
4561
- const changelogLink = getCapabilities().hyperlinks
4562
- ? hyperlink(theme.fg("accent", changelogUrl), changelogUrl)
4563
- : theme.fg("accent", changelogUrl);
4564
- const changelogLine = theme.fg("muted", "Changelog: ") + changelogLink;
4565
- this.chatContainer.addChild(new Spacer(1));
4566
- this.chatContainer.addChild(new DynamicBorder((text) => theme.fg("warning", text)));
4567
- this.chatContainer.addChild(new Text(`${theme.bold(theme.fg("warning", "Update Available"))}\n${updateInstruction}\n${changelogLine}`, 1, 0));
4568
- this.chatContainer.addChild(new DynamicBorder((text) => theme.fg("warning", text)));
4569
- this.ui.requestRender();
4623
+ const changelogLink = getCapabilities().hyperlinks ? hyperlink(changelogUrl, changelogUrl) : changelogUrl;
4624
+ this.showNoticeBox({
4625
+ title: "Update Available",
4626
+ tone: "warning",
4627
+ why: `New version ${newVersion} is available. Run ${action}`,
4628
+ extra: [{ text: `Changelog: ${changelogLink}`, tone: "accent" }],
4629
+ });
4570
4630
  }
4571
4631
  showRiskyMainModelWarning(model) {
4572
4632
  if (!model || !isRiskyMainModel(model))
4573
4633
  return;
4574
- this.chatContainer.addChild(new Spacer(1));
4575
- this.chatContainer.addChild(new DynamicBorder((text) => theme.fg("error", text)));
4576
- this.chatContainer.addChild(new Text(`${theme.bold(theme.fg("error", "Risky model warning"))}\n${theme.fg("error", RISKY_MAIN_MODEL_WARNING)}`, 1, 0));
4577
- this.chatContainer.addChild(new DynamicBorder((text) => theme.fg("error", text)));
4578
- this.ui.requestRender();
4634
+ this.showNoticeBox({ title: "Risky model warning", tone: "error", why: RISKY_MAIN_MODEL_WARNING });
4579
4635
  }
4580
4636
  showSettingsSourceSelected(event) {
4581
4637
  this.showStatus(`Settings: ${path.basename(event.path)} (${event.format.toUpperCase()})`);
4582
4638
  }
4583
4639
  showHighReasoningWarning(event) {
4584
4640
  const { title, body } = buildHighReasoningWarning({ id: event.modelId, provider: event.provider }, event.thinkingLevel);
4585
- this.chatContainer.addChild(new Spacer(1));
4586
- this.chatContainer.addChild(new DynamicBorder((text) => theme.fg("error", text)));
4587
- this.chatContainer.addChild(new Text(`${theme.bold(theme.fg("error", title))}\n${body.map((line) => theme.fg("error", line)).join("\n")}`, 1, 0));
4588
- this.chatContainer.addChild(new DynamicBorder((text) => theme.fg("error", text)));
4589
- this.ui.requestRender();
4641
+ this.showNoticeBox({
4642
+ title,
4643
+ tone: "error",
4644
+ why: body[0] ?? "High reasoning may affect reliability or cost.",
4645
+ extra: body.slice(1).map((text) => ({ text, tone: "error" })),
4646
+ });
4590
4647
  }
4591
4648
  showPackageUpdateNotification(packages) {
4592
- const action = theme.fg("accent", `${APP_NAME} update --extensions`);
4593
- const updateInstruction = theme.fg("muted", "Package updates are available. Run ") + action;
4594
- const packageLines = packages.map((pkg) => `- ${pkg}`).join("\n");
4595
- this.chatContainer.addChild(new Spacer(1));
4596
- this.chatContainer.addChild(new DynamicBorder((text) => theme.fg("warning", text)));
4597
- this.chatContainer.addChild(new Text(`${theme.bold(theme.fg("warning", "Package Updates Available"))}\n${updateInstruction}\n${theme.fg("muted", "Packages:")}\n${packageLines}`, 1, 0));
4598
- this.chatContainer.addChild(new DynamicBorder((text) => theme.fg("warning", text)));
4599
- this.ui.requestRender();
4649
+ this.showNoticeBox({
4650
+ title: "Package Updates Available",
4651
+ tone: "warning",
4652
+ why: `Package updates are available. Run ${APP_NAME} update --extensions`,
4653
+ extra: [
4654
+ { text: "Packages:", tone: "dim" },
4655
+ ...packages.map((pkg) => ({ text: `- ${pkg}`, tone: "dim" })),
4656
+ ],
4657
+ });
4600
4658
  }
4601
4659
  /**
4602
4660
  * Get all queued messages (read-only).
@@ -6189,7 +6247,6 @@ export class InteractiveMode {
6189
6247
  this.showWarning(veto.reason ?? "Reload blocked by an extension.");
6190
6248
  return;
6191
6249
  }
6192
- this.resetExtensionUI();
6193
6250
  const reloadBox = new Container();
6194
6251
  const borderColor = (s) => theme.fg("border", s);
6195
6252
  reloadBox.addChild(new DynamicBorder(borderColor));
@@ -6215,6 +6272,13 @@ export class InteractiveMode {
6215
6272
  if (chatRestoredBeforeSessionStart) {
6216
6273
  return;
6217
6274
  }
6275
+ // Reset extension UI only once the reload is actually proceeding (this
6276
+ // callback runs after reload()'s internal veto re-check, right before the
6277
+ // new runner's session_start re-registers extension UI). Resetting before
6278
+ // reload() destroyed live extension footers/widgets/tickers on a vetoed
6279
+ // or failed reload with nothing left to restore them, so the TUI stopped
6280
+ // self-repainting until an input event forced a frame.
6281
+ this.resetExtensionUI();
6218
6282
  this.hideThinkingBlock = this.settingsManager.getHideThinkingBlock();
6219
6283
  this.outputPad = this.settingsManager.getOutputPad();
6220
6284
  this.rebuildChatFromMessages();
@@ -6718,9 +6782,7 @@ export class InteractiveMode {
6718
6782
  ].join("\n");
6719
6783
  fs.mkdirSync(path.dirname(debugLogPath), { recursive: true });
6720
6784
  fs.writeFileSync(debugLogPath, debugData);
6721
- this.chatContainer.addChild(new Spacer(1));
6722
- this.chatContainer.addChild(new Text(`${theme.fg("accent", "✓ Debug log written")}\n${theme.fg("muted", debugLogPath)}`, 1, 1));
6723
- this.ui.requestRender();
6785
+ this.showNoticeBox({ title: "✓ Debug log written", tone: "success", why: debugLogPath });
6724
6786
  }
6725
6787
  handleArminSaysHi() {
6726
6788
  this.chatContainer.addChild(new Spacer(1));