@gajae-code/coding-agent 0.16.3 → 0.16.6

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 (277) hide show
  1. package/CHANGELOG.md +113 -0
  2. package/dist/types/capability/types.d.ts +4 -0
  3. package/dist/types/cli/args.d.ts +2 -2
  4. package/dist/types/cli/customize-doctor.d.ts +22 -0
  5. package/dist/types/cli/root-flags.d.ts +4 -2
  6. package/dist/types/cli.d.ts +1 -1
  7. package/dist/types/commands/launch.d.ts +1 -1
  8. package/dist/types/config/autorouting-tier-map.d.ts +3 -0
  9. package/dist/types/config/file-lock.d.ts +31 -3
  10. package/dist/types/config/model-registry.d.ts +4 -0
  11. package/dist/types/config/model-resolver.d.ts +9 -0
  12. package/dist/types/config/settings-schema.d.ts +29 -1
  13. package/dist/types/coordinator-mcp/question-state.d.ts +7 -0
  14. package/dist/types/coordinator-mcp/server.d.ts +7 -0
  15. package/dist/types/discovery/claude-plugins.d.ts +3 -1
  16. package/dist/types/discovery/helpers.d.ts +35 -0
  17. package/dist/types/exec/bash-executor.d.ts +17 -1
  18. package/dist/types/exec/bash-shell-guardian.d.ts +15 -0
  19. package/dist/types/exec/bash-shell-runtime-entry.d.ts +2 -0
  20. package/dist/types/exec/bash-shell-signals.d.ts +2 -0
  21. package/dist/types/exec/bash-shell-supervisor-entry.d.ts +1 -0
  22. package/dist/types/exec/bash-shell-supervisor.d.ts +10 -0
  23. package/dist/types/exec/bash-shell-worker-entry.d.ts +2 -0
  24. package/dist/types/exec/bash-shell-worker-protocol.d.ts +63 -0
  25. package/dist/types/exec/bash-shell-worker.d.ts +1 -0
  26. package/dist/types/exec/isolated-shell.d.ts +19 -0
  27. package/dist/types/extensibility/extensions/function-hooks-internal.d.ts +9 -0
  28. package/dist/types/extensibility/extensions/function-hooks.d.ts +169 -0
  29. package/dist/types/extensibility/extensions/index.d.ts +1 -0
  30. package/dist/types/extensibility/extensions/runner.d.ts +46 -6
  31. package/dist/types/extensibility/extensions/types.d.ts +16 -0
  32. package/dist/types/extensibility/extensions/wrapper.d.ts +3 -1
  33. package/dist/types/extensibility/gjc-plugins/constrained-hooks.d.ts +35 -2
  34. package/dist/types/extensibility/gjc-plugins/mcp-policy.d.ts +16 -0
  35. package/dist/types/extensibility/gjc-plugins/paths.d.ts +2 -0
  36. package/dist/types/extensibility/gjc-plugins/types.d.ts +25 -0
  37. package/dist/types/extensibility/hooks/types.d.ts +4 -0
  38. package/dist/types/extensibility/runtime-skill-discovery.d.ts +5 -2
  39. package/dist/types/extensibility/shared-events.d.ts +1 -1
  40. package/dist/types/extensibility/skill-management.d.ts +9 -4
  41. package/dist/types/extensibility/skills.d.ts +2 -0
  42. package/dist/types/gjc-runtime/empty-delete-gc.d.ts +4 -1
  43. package/dist/types/gjc-runtime/linux-proc.d.ts +2 -0
  44. package/dist/types/gjc-runtime/session-state-lock.d.ts +14 -0
  45. package/dist/types/gjc-runtime/ultragoal-runtime.d.ts +41 -3
  46. package/dist/types/gjc-runtime/ultragoal-succession.d.ts +251 -0
  47. package/dist/types/modes/components/assistant-message.d.ts +1 -2
  48. package/dist/types/modes/components/bash-execution.d.ts +1 -0
  49. package/dist/types/modes/components/execution-shared.d.ts +1 -0
  50. package/dist/types/modes/components/settings-selector.d.ts +6 -1
  51. package/dist/types/modes/components/status-line/command.d.ts +35 -0
  52. package/dist/types/modes/components/status-line/index.d.ts +1 -0
  53. package/dist/types/modes/components/status-line/types.d.ts +5 -0
  54. package/dist/types/modes/components/tool-status-header.d.ts +14 -0
  55. package/dist/types/modes/controllers/event-controller.d.ts +6 -0
  56. package/dist/types/modes/interactive-mode.d.ts +3 -0
  57. package/dist/types/modes/types.d.ts +7 -0
  58. package/dist/types/runtime-mcp/client.d.ts +9 -1
  59. package/dist/types/runtime-mcp/manager.d.ts +7 -1
  60. package/dist/types/runtime-mcp/pool.d.ts +22 -3
  61. package/dist/types/runtime-mcp/transports/stdio.d.ts +0 -4
  62. package/dist/types/runtime-mcp/types.d.ts +22 -0
  63. package/dist/types/sdk/broker/ensure.d.ts +32 -0
  64. package/dist/types/sdk/broker/lifecycle-ledger.d.ts +0 -1
  65. package/dist/types/sdk/broker/lifecycle.d.ts +6 -0
  66. package/dist/types/sdk/broker/startup-failure.d.ts +4 -1
  67. package/dist/types/sdk/bus/chat-daemon-control.d.ts +2 -0
  68. package/dist/types/sdk/bus/chat-daemon-runtime.d.ts +12 -0
  69. package/dist/types/sdk/bus/chat-effect-journal.d.ts +17 -2
  70. package/dist/types/sdk/bus/conversation-store.d.ts +11 -0
  71. package/dist/types/sdk/bus/lifecycle-commands.d.ts +8 -8
  72. package/dist/types/sdk/bus/slack-daemon.d.ts +1 -1
  73. package/dist/types/sdk/bus/slack-live-provider.d.ts +6 -0
  74. package/dist/types/sdk/bus/slack-provider.d.ts +12 -0
  75. package/dist/types/sdk/bus/topic-registry.d.ts +8 -0
  76. package/dist/types/sdk/client/client.d.ts +6 -0
  77. package/dist/types/sdk/host/host.d.ts +3 -1
  78. package/dist/types/sdk/host/session-runtime.d.ts +26 -1
  79. package/dist/types/sdk/lifecycle/service.d.ts +1 -0
  80. package/dist/types/sdk/router/session-router.d.ts +10 -2
  81. package/dist/types/sdk/session-api-key.d.ts +26 -0
  82. package/dist/types/sdk/session.d.ts +5 -1
  83. package/dist/types/sdk/transport/serve-cli.d.ts +1 -1
  84. package/dist/types/session/agent-session.d.ts +11 -0
  85. package/dist/types/session/fallback-chain-controller.d.ts +15 -0
  86. package/dist/types/session/internal/managed-session-storage.d.ts +7 -0
  87. package/dist/types/session/messages.d.ts +1 -0
  88. package/dist/types/session/session-manager.d.ts +3 -1
  89. package/dist/types/setup/paseo/announce.d.ts +112 -0
  90. package/dist/types/setup/paseo/setup-deps.d.ts +2 -1
  91. package/dist/types/skill-state/canonical-skills.d.ts +4 -0
  92. package/dist/types/system-prompt.d.ts +10 -0
  93. package/dist/types/task/executor.d.ts +5 -1
  94. package/dist/types/tools/bash.d.ts +1 -0
  95. package/dist/types/tools/fetch.d.ts +7 -0
  96. package/dist/types/tools/github-cache.d.ts +2 -1
  97. package/dist/types/tools/index.d.ts +10 -0
  98. package/dist/types/tools/path-utils.d.ts +2 -0
  99. package/dist/types/tools/search.d.ts +6 -0
  100. package/package.json +9 -7
  101. package/scripts/capture-status-line-custom-editor-showcase.ts +151 -0
  102. package/scripts/generate-sdk-operation-inventory.ts +11 -0
  103. package/scripts/verify-status-line-custom-editor-showcase.ts +309 -0
  104. package/src/capability/index.ts +14 -2
  105. package/src/capability/types.ts +4 -0
  106. package/src/cli/args.ts +20 -9
  107. package/src/cli/customize-doctor.ts +155 -12
  108. package/src/cli/root-flags.ts +5 -3
  109. package/src/cli.ts +22 -0
  110. package/src/commands/sdk.ts +74 -31
  111. package/src/config/autorouting-tier-map.ts +1 -0
  112. package/src/config/file-lock-gc.ts +40 -2
  113. package/src/config/file-lock.ts +379 -31
  114. package/src/config/model-preset-registry.ts +120 -19
  115. package/src/config/model-profiles.ts +40 -0
  116. package/src/config/model-registry.ts +73 -23
  117. package/src/config/model-resolver.ts +48 -0
  118. package/src/config/settings-schema.ts +33 -1
  119. package/src/config/settings.ts +1 -0
  120. package/src/coordinator-mcp/question-state.ts +33 -2
  121. package/src/coordinator-mcp/server.ts +477 -101
  122. package/src/crash/sanitize.ts +1 -1
  123. package/src/defaults/gjc/extensions/grok-cli-vendor/src/models/catalog.ts +11 -11
  124. package/src/defaults/gjc/extensions/grok-cli-vendor/src/provider/register.ts +13 -2
  125. package/src/defaults/gjc/skills/ultragoal/SKILL.md +22 -0
  126. package/src/discovery/builtin.ts +52 -55
  127. package/src/discovery/claude-plugins.ts +12 -4
  128. package/src/discovery/helpers.ts +307 -34
  129. package/src/exec/bash-executor.ts +330 -50
  130. package/src/exec/bash-shell-guardian.ts +388 -0
  131. package/src/exec/bash-shell-runtime-entry.ts +5 -0
  132. package/src/exec/bash-shell-signals.ts +29 -0
  133. package/src/exec/bash-shell-supervisor-entry.ts +3 -0
  134. package/src/exec/bash-shell-supervisor.ts +359 -0
  135. package/src/exec/bash-shell-worker-entry.ts +5 -0
  136. package/src/exec/bash-shell-worker-protocol.ts +36 -0
  137. package/src/exec/bash-shell-worker.ts +116 -0
  138. package/src/exec/isolated-shell.ts +393 -0
  139. package/src/extensibility/extensions/function-hooks-internal.ts +73 -0
  140. package/src/extensibility/extensions/function-hooks.ts +959 -0
  141. package/src/extensibility/extensions/index.ts +22 -0
  142. package/src/extensibility/extensions/loader.ts +53 -2
  143. package/src/extensibility/extensions/runner.ts +895 -264
  144. package/src/extensibility/extensions/types.ts +42 -0
  145. package/src/extensibility/extensions/wrapper.ts +56 -15
  146. package/src/extensibility/gjc-plugins/compiler.ts +228 -60
  147. package/src/extensibility/gjc-plugins/constrained-hooks.ts +321 -23
  148. package/src/extensibility/gjc-plugins/installer.ts +13 -12
  149. package/src/extensibility/gjc-plugins/mcp-policy.ts +81 -52
  150. package/src/extensibility/gjc-plugins/paths.ts +13 -0
  151. package/src/extensibility/gjc-plugins/registry.ts +113 -1
  152. package/src/extensibility/gjc-plugins/runtime-adapters.ts +758 -16
  153. package/src/extensibility/gjc-plugins/runtime-quarantine.ts +20 -2
  154. package/src/extensibility/gjc-plugins/schema.ts +121 -4
  155. package/src/extensibility/gjc-plugins/types.ts +25 -0
  156. package/src/extensibility/hooks/loader.ts +133 -16
  157. package/src/extensibility/hooks/types.ts +28 -0
  158. package/src/extensibility/runtime-skill-discovery.ts +149 -190
  159. package/src/extensibility/shared-events.ts +1 -1
  160. package/src/extensibility/skill-management.ts +200 -52
  161. package/src/extensibility/skills.ts +73 -25
  162. package/src/gjc-runtime/empty-delete-gc.ts +94 -4
  163. package/src/gjc-runtime/gc-runtime.ts +29 -0
  164. package/src/gjc-runtime/linux-proc.ts +14 -0
  165. package/src/gjc-runtime/repository-binding.ts +1 -1
  166. package/src/gjc-runtime/session-state-lock.ts +143 -10
  167. package/src/gjc-runtime/session-state-sidecar.ts +27 -11
  168. package/src/gjc-runtime/state-writer.ts +30 -29
  169. package/src/gjc-runtime/ultragoal-runtime.ts +189 -47
  170. package/src/gjc-runtime/ultragoal-succession.ts +1667 -0
  171. package/src/gjc-runtime/workflow-manifest.generated.json +52 -1
  172. package/src/gjc-runtime/workflow-manifest.ts +16 -0
  173. package/src/harness-control-plane/session-lease.ts +2 -2
  174. package/src/internal-urls/docs-index.generated.ts +12 -12
  175. package/src/lsp/config.ts +10 -3
  176. package/src/lsp/index.ts +1 -1
  177. package/src/lsp/path-trust.ts +69 -14
  178. package/src/main.ts +78 -35
  179. package/src/modes/DESIGN.md +54 -0
  180. package/src/modes/acp/acp-agent.ts +16 -3
  181. package/src/modes/components/assistant-message.ts +22 -9
  182. package/src/modes/components/bash-execution.ts +4 -1
  183. package/src/modes/components/execution-shared.ts +4 -1
  184. package/src/modes/components/model-selector.ts +166 -33
  185. package/src/modes/components/session-observer-overlay.ts +1 -1
  186. package/src/modes/components/settings-selector.ts +1075 -235
  187. package/src/modes/components/status-line/command.ts +190 -0
  188. package/src/modes/components/status-line/index.ts +1 -0
  189. package/src/modes/components/status-line/segments.ts +32 -5
  190. package/src/modes/components/status-line/types.ts +5 -0
  191. package/src/modes/components/tool-status-header.ts +396 -22
  192. package/src/modes/controllers/command-controller.ts +3 -0
  193. package/src/modes/controllers/event-controller.ts +151 -7
  194. package/src/modes/controllers/extension-ui-controller.ts +1 -0
  195. package/src/modes/controllers/input-controller.ts +6 -3
  196. package/src/modes/controllers/selector-controller.ts +21 -0
  197. package/src/modes/interactive-mode.ts +53 -6
  198. package/src/modes/print-mode.ts +5 -1
  199. package/src/modes/prompt-action-autocomplete.ts +54 -27
  200. package/src/modes/runtime-init.ts +2 -0
  201. package/src/modes/types.ts +17 -3
  202. package/src/modes/utils/ui-helpers.ts +2 -0
  203. package/src/prompts/system/system-prompt.md +8 -0
  204. package/src/prompts/tools/search.md +2 -0
  205. package/src/prompts/tools/subagent.md +1 -0
  206. package/src/runtime-mcp/client.ts +60 -17
  207. package/src/runtime-mcp/manager.ts +189 -14
  208. package/src/runtime-mcp/pool.test.ts +218 -0
  209. package/src/runtime-mcp/pool.ts +224 -23
  210. package/src/runtime-mcp/transports/stdio.ts +158 -24
  211. package/src/runtime-mcp/types.ts +24 -0
  212. package/src/sdk/broker/broker.ts +124 -32
  213. package/src/sdk/broker/ensure.ts +248 -41
  214. package/src/sdk/broker/lifecycle-ledger.ts +7 -17
  215. package/src/sdk/broker/lifecycle.ts +56 -6
  216. package/src/sdk/broker/startup-failure.ts +15 -6
  217. package/src/sdk/bus/chat-daemon-control.ts +3 -1
  218. package/src/sdk/bus/chat-daemon-runtime.ts +72 -6
  219. package/src/sdk/bus/chat-effect-journal.ts +132 -7
  220. package/src/sdk/bus/conversation-store.ts +29 -0
  221. package/src/sdk/bus/lifecycle-commands.ts +17 -11
  222. package/src/sdk/bus/slack-conversation.ts +1 -0
  223. package/src/sdk/bus/slack-daemon.ts +478 -49
  224. package/src/sdk/bus/slack-live-provider.ts +12 -0
  225. package/src/sdk/bus/slack-provider.ts +5 -0
  226. package/src/sdk/bus/telegram-daemon.ts +16 -2
  227. package/src/sdk/bus/topic-registry.ts +19 -0
  228. package/src/sdk/cli/master-cli.ts +1 -1
  229. package/src/sdk/cli/session-cli.ts +11 -8
  230. package/src/sdk/client/client.ts +66 -17
  231. package/src/sdk/host/host.ts +29 -9
  232. package/src/sdk/host/query/revision-store.ts +19 -13
  233. package/src/sdk/host/reverse-leases.ts +7 -4
  234. package/src/sdk/host/session-runtime.test.ts +52 -10
  235. package/src/sdk/host/session-runtime.ts +383 -44
  236. package/src/sdk/lifecycle/service.ts +4 -0
  237. package/src/sdk/protocol/operation-inventory.generated.json +77 -0
  238. package/src/sdk/router/session-router.ts +89 -38
  239. package/src/sdk/session-api-key.ts +94 -0
  240. package/src/sdk/session.ts +228 -55
  241. package/src/sdk/transport/relay.ts +35 -13
  242. package/src/sdk/transport/serve-cli.ts +3 -3
  243. package/src/session/agent-session.ts +240 -90
  244. package/src/session/contribution-prep.ts +214 -21
  245. package/src/session/fallback-chain-controller.ts +38 -0
  246. package/src/session/internal/managed-session-storage.ts +19 -5
  247. package/src/session/messages.ts +5 -0
  248. package/src/session/session-manager.ts +29 -5
  249. package/src/session-import/redact.ts +5 -1
  250. package/src/setup/paseo/announce.ts +456 -0
  251. package/src/setup/paseo/setup-deps.ts +15 -3
  252. package/src/skill-state/canonical-skills.ts +12 -0
  253. package/src/skills/skills.test.ts +41 -21
  254. package/src/slash-commands/builtin-registry.ts +1 -0
  255. package/src/system-prompt.ts +29 -3
  256. package/src/task/executor.ts +7 -1
  257. package/src/task/index.ts +21 -4
  258. package/src/tools/bash.ts +63 -19
  259. package/src/tools/cron.ts +1 -1
  260. package/src/tools/fetch.ts +29 -1
  261. package/src/tools/gh.ts +26 -3
  262. package/src/tools/github-cache.ts +68 -16
  263. package/src/tools/index.ts +10 -0
  264. package/src/tools/job.ts +21 -4
  265. package/src/tools/monitor.ts +8 -2
  266. package/src/tools/path-utils.ts +7 -0
  267. package/src/tools/read.ts +14 -2
  268. package/src/tools/render-utils.ts +1 -3
  269. package/src/tools/resource-gc.ts +28 -8
  270. package/src/tools/search.ts +116 -58
  271. package/src/tools/skill-discovery.ts +5 -0
  272. package/src/tools/skill.ts +37 -2
  273. package/src/tools/subagent.ts +6 -3
  274. package/src/tools/tool-catalog.generated.ts +10 -3
  275. package/src/tools/ultragoal-ask-guard.ts +2 -2
  276. package/src/web/insane/bridge.ts +113 -23
  277. package/src/web/search/providers/perplexity.ts +102 -41
@@ -1,7 +1,9 @@
1
1
  import * as fs from "node:fs";
2
2
 
3
+ import { resolveOAuthStorageProvider } from "@gajae-code/ai/core";
3
4
  import { type Component, truncateToWidth, visibleWidth } from "@gajae-code/tui";
4
- import { formatCount, getProjectDir } from "@gajae-code/utils";
5
+ import { formatCount, getProjectDir, logger } from "@gajae-code/utils";
6
+ import { getShellConfig } from "@gajae-code/utils/shell-config";
5
7
  import {
6
8
  type AppKeybinding,
7
9
  KEYBINDINGS,
@@ -19,6 +21,11 @@ import type { ActionRegistry, FocusDomain } from "../action-registry";
19
21
  import { EMPTY_JOBS_SNAPSHOT, type JobsSnapshot } from "../jobs-observer";
20
22
  import { sanitizeStatusText } from "../shared";
21
23
  import { renderSkillHudBar } from "./skill-hud/render";
24
+ import {
25
+ normalizeStatusLineCommandOptions,
26
+ runStatusLineCommand,
27
+ type StatusLineCommandOptions,
28
+ } from "./status-line/command";
22
29
  import { lookupCurrentPrCached } from "./status-line/gh";
23
30
  import {
24
31
  canReuseCachedPr,
@@ -40,6 +47,7 @@ export interface StatusLineSegmentOptions {
40
47
  git?: { showBranch?: boolean; showStaged?: boolean; showUnstaged?: boolean; showUntracked?: boolean };
41
48
  time?: { format?: "12h" | "24h"; showSeconds?: boolean };
42
49
  usage?: { mode?: "used" | "remaining" };
50
+ command?: StatusLineCommandOptions;
43
51
  }
44
52
 
45
53
  export interface StatusLineSettings {
@@ -49,6 +57,7 @@ export interface StatusLineSettings {
49
57
  separator?: StatusLineSeparatorStyle;
50
58
  segmentOptions?: StatusLineSegmentOptions;
51
59
  previewHighlightSegment?: StatusLineSegmentId;
60
+ previewHighlightStyle?: "focus" | "selected";
52
61
  showHookStatus?: boolean;
53
62
  showSkillHud?: boolean;
54
63
  showActionHints?: boolean;
@@ -62,6 +71,7 @@ export interface StatusLineComponentOptions {
62
71
  getKeybindings?: () => KeybindingsManager;
63
72
  focusDomain?: FocusDomain;
64
73
  keyDisplayContext?: KeyDisplayContext;
74
+ onUpdate?: () => void;
65
75
  }
66
76
 
67
77
  export interface StatusLineActionHint {
@@ -69,6 +79,14 @@ export interface StatusLineActionHint {
69
79
  content: string;
70
80
  }
71
81
 
82
+ export interface StatusLinePreviewParts {
83
+ left: string[];
84
+ leftIds: StatusLineSegmentId[];
85
+ right: string[];
86
+ rightIds: (StatusLineSegmentId | null)[];
87
+ separator: SeparatorDef;
88
+ }
89
+
72
90
  const ACTION_HINT_PRIORITY: readonly AppKeybinding[] = [
73
91
  "app.message.sendNow",
74
92
  "app.message.followUp",
@@ -136,6 +154,7 @@ interface CollectedStatusSegments {
136
154
  /** Parallel to `right`; null for action hints, job counts and the version tag. */
137
155
  rightSegIds: (StatusLineSegmentId | null)[];
138
156
  previewHighlightSegment: StatusLineSegmentId | undefined;
157
+ previewHighlightStyle: "focus" | "selected";
139
158
  sessionAccent: boolean | undefined;
140
159
  leftSepWidth: number;
141
160
  rightSepWidth: number;
@@ -155,6 +174,8 @@ export class StatusLineComponent implements Component {
155
174
  #branchInFlight = false;
156
175
  #gitWatcher: fs.FSWatcher | null = null;
157
176
  #onBranchChange: (() => void) | null = null;
177
+ #onUpdate: (() => void) | null = null;
178
+ #disposed = false;
158
179
  #autoCompactEnabled: boolean = true;
159
180
  #hookStatuses: Map<string, string> = new Map();
160
181
  #subagentCount: number = 0;
@@ -195,9 +216,22 @@ export class StatusLineComponent implements Component {
195
216
 
196
217
  // Provider usage caching (5-min TTL, OAuth/sub only)
197
218
  #cachedUsage: SegmentContext["usage"] = null;
219
+ #cachedUsageReports: unknown = null;
220
+ #cachedUsageProvider: string | undefined;
198
221
  #usageFetchedAt = 0;
199
222
  #usageInFlight = false;
200
223
 
224
+ // Configured command segment cache. Command execution is always backgrounded;
225
+ // rendering only reads these bounded, last-known values.
226
+ #commandConfigKey: string | undefined;
227
+ #commandOutput: string | null = null;
228
+ #commandFailed = false;
229
+ #commandFetchedAt = 0;
230
+ #commandInFlightKey: string | undefined;
231
+ #commandAbortController: AbortController | undefined;
232
+ #commandRefreshTimer: ReturnType<typeof setTimeout> | undefined;
233
+ #commandDiagnosticKey: string | undefined;
234
+
201
235
  constructor(
202
236
  private readonly session: AgentSession,
203
237
  options: StatusLineComponentOptions = {},
@@ -218,6 +252,7 @@ export class StatusLineComponent implements Component {
218
252
  this.#getKeybindings = options.getKeybindings;
219
253
  this.#focusDomain = options.focusDomain ?? "composer";
220
254
  this.#keyDisplayContext = options.keyDisplayContext;
255
+ this.#onUpdate = options.onUpdate ?? null;
221
256
  }
222
257
 
223
258
  updateSettings(settings: StatusLineSettings): void {
@@ -300,12 +335,175 @@ export class StatusLineComponent implements Component {
300
335
  }
301
336
 
302
337
  dispose(): void {
338
+ this.#disposed = true;
339
+ this.#onBranchChange = null;
340
+ this.#onUpdate = null;
341
+ this.#cancelCommandExecution();
342
+ this.#clearCommandRefreshTimer();
303
343
  if (this.#gitWatcher) {
304
344
  this.#gitWatcher.close();
305
345
  this.#gitWatcher = null;
306
346
  }
307
347
  }
308
348
 
349
+ #notifyUpdate(): void {
350
+ if (this.#onUpdate) this.#onUpdate();
351
+ else this.#onBranchChange?.();
352
+ }
353
+
354
+ #logCommandDiagnostic(key: string, message: string, details: Record<string, unknown> = {}): void {
355
+ if (this.#commandDiagnosticKey === key) return;
356
+ this.#commandDiagnosticKey = key;
357
+ logger.warn(message, details);
358
+ }
359
+
360
+ #clearCommandRefreshTimer(): void {
361
+ if (this.#commandRefreshTimer) {
362
+ clearTimeout(this.#commandRefreshTimer);
363
+ this.#commandRefreshTimer = undefined;
364
+ }
365
+ }
366
+
367
+ #cancelCommandExecution(): void {
368
+ this.#commandAbortController?.abort();
369
+ this.#commandAbortController = undefined;
370
+ this.#commandInFlightKey = undefined;
371
+ }
372
+
373
+ #disableCommandSegment(): void {
374
+ this.#clearCommandRefreshTimer();
375
+ this.#cancelCommandExecution();
376
+ this.#commandConfigKey = undefined;
377
+ this.#commandOutput = null;
378
+ this.#commandFailed = false;
379
+ this.#commandFetchedAt = 0;
380
+ this.#commandDiagnosticKey = undefined;
381
+ }
382
+
383
+ #armCommandRefreshTimer(key: string, refreshMs: number): void {
384
+ this.#clearCommandRefreshTimer();
385
+ if (this.#disposed || this.#commandConfigKey !== key) return;
386
+ this.#commandRefreshTimer = setTimeout(
387
+ () => {
388
+ this.#commandRefreshTimer = undefined;
389
+ if (this.#disposed || this.#commandConfigKey !== key) return;
390
+ this.#notifyUpdate();
391
+ },
392
+ Math.max(0, refreshMs),
393
+ );
394
+ this.#commandRefreshTimer.unref?.();
395
+ }
396
+
397
+ #trustedCommandOptions(): StatusLineCommandOptions | undefined {
398
+ const globalLeft = this.session.settings.getGlobal("statusLine.leftSegments");
399
+ const globalRight = this.session.settings.getGlobal("statusLine.rightSegments");
400
+ const includesCommand = (value: unknown): boolean => Array.isArray(value) && value.includes("command");
401
+ if (!includesCommand(globalLeft) && !includesCommand(globalRight)) return undefined;
402
+
403
+ const globalSegmentOptions = this.session.settings.getGlobal("statusLine.segmentOptions");
404
+ if (!globalSegmentOptions || typeof globalSegmentOptions !== "object" || Array.isArray(globalSegmentOptions)) {
405
+ return undefined;
406
+ }
407
+ const commandOptions = (globalSegmentOptions as Record<string, unknown>).command;
408
+ const resolved = normalizeStatusLineCommandOptions(commandOptions);
409
+ return resolved.command ? resolved : undefined;
410
+ }
411
+
412
+ #refreshCommandInBackground(options: StatusLineCommandOptions | undefined): string {
413
+ const resolved = normalizeStatusLineCommandOptions(options);
414
+ const key = JSON.stringify(resolved);
415
+ if (this.#commandConfigKey !== key) {
416
+ this.#clearCommandRefreshTimer();
417
+ this.#cancelCommandExecution();
418
+ this.#commandConfigKey = key;
419
+ this.#commandOutput = null;
420
+ this.#commandFailed = false;
421
+ this.#commandFetchedAt = 0;
422
+ this.#commandDiagnosticKey = undefined;
423
+ this.#renderedRowsCache = undefined;
424
+ }
425
+
426
+ if (!resolved.command) {
427
+ this.#commandFailed = true;
428
+ this.#logCommandDiagnostic(key, "Status line command segment is configured without a command");
429
+ return key;
430
+ }
431
+
432
+ const now = Date.now();
433
+ if (this.#commandInFlightKey === key) return key;
434
+ const elapsed = now - this.#commandFetchedAt;
435
+ if (this.#commandFetchedAt > 0 && elapsed < resolved.refreshMs) {
436
+ this.#armCommandRefreshTimer(key, resolved.refreshMs - elapsed);
437
+ return key;
438
+ }
439
+ this.#clearCommandRefreshTimer();
440
+
441
+ this.#commandInFlightKey = key;
442
+ const abortController = new AbortController();
443
+ this.#commandAbortController = abortController;
444
+ let shellConfig: { shell: string; args: string[]; env: Record<string, string> };
445
+ try {
446
+ shellConfig = getShellConfig(this.session.settings.getGlobal("shellPath"));
447
+ } catch (error) {
448
+ if (this.#commandInFlightKey === key && this.#commandAbortController === abortController) {
449
+ this.#commandInFlightKey = undefined;
450
+ this.#commandAbortController = undefined;
451
+ }
452
+ this.#commandFailed = true;
453
+ this.#commandFetchedAt = Date.now();
454
+ this.#logCommandDiagnostic(key, "Status line command shell configuration failed", {
455
+ error: sanitizeStatusText(error instanceof Error ? error.message : String(error)).slice(0, 256),
456
+ });
457
+ this.#armCommandRefreshTimer(key, resolved.refreshMs);
458
+ return key;
459
+ }
460
+
461
+ void runStatusLineCommand(resolved.command, {
462
+ cwd: getProjectDir(),
463
+ shell: shellConfig.shell,
464
+ shellArgs: shellConfig.args,
465
+ env: shellConfig.env,
466
+ timeoutMs: resolved.timeoutMs,
467
+ signal: abortController.signal,
468
+ })
469
+ .then(result => {
470
+ if (this.#disposed || this.#commandConfigKey !== key) return;
471
+ this.#commandFetchedAt = Date.now();
472
+ if (result.timedOut || result.exitCode !== 0) {
473
+ this.#commandFailed = true;
474
+ this.#logCommandDiagnostic(key, "Status line command failed", {
475
+ exitCode: result.exitCode,
476
+ timedOut: result.timedOut,
477
+ stderr: result.stderr,
478
+ outputTruncated: result.outputTruncated,
479
+ });
480
+ } else {
481
+ this.#commandOutput = result.stdout;
482
+ this.#commandFailed = false;
483
+ this.#commandDiagnosticKey = undefined;
484
+ }
485
+ this.#renderedRowsCache = undefined;
486
+ this.#notifyUpdate();
487
+ })
488
+ .catch(error => {
489
+ if (this.#disposed || this.#commandConfigKey !== key) return;
490
+ this.#commandFetchedAt = Date.now();
491
+ this.#commandFailed = true;
492
+ this.#logCommandDiagnostic(key, "Status line command could not start", {
493
+ error: sanitizeStatusText(error instanceof Error ? error.message : String(error)).slice(0, 256),
494
+ });
495
+ this.#renderedRowsCache = undefined;
496
+ this.#notifyUpdate();
497
+ })
498
+ .finally(() => {
499
+ if (this.#commandInFlightKey !== key || this.#commandAbortController !== abortController) return;
500
+ this.#commandInFlightKey = undefined;
501
+ this.#commandAbortController = undefined;
502
+ this.#armCommandRefreshTimer(key, resolved.refreshMs);
503
+ });
504
+ return key;
505
+ }
506
+
309
507
  invalidate(): void {
310
508
  this.#invalidateGitCaches();
311
509
  }
@@ -502,6 +700,8 @@ export class StatusLineComponent implements Component {
502
700
  void fetcher
503
701
  .call(this.session)
504
702
  .then(reports => {
703
+ this.#cachedUsageReports = reports;
704
+ this.#cachedUsageProvider = this.#activeUsageProvider();
505
705
  this.#cachedUsage = this.#normalizeUsageReports(reports);
506
706
  this.#usageFetchedAt = Date.now();
507
707
  if (this.#onBranchChange) {
@@ -521,7 +721,9 @@ export class StatusLineComponent implements Component {
521
721
  }
522
722
 
523
723
  #normalizeUsageReports(reports: unknown): SegmentContext["usage"] {
524
- if (!Array.isArray(reports)) return null;
724
+ const activeProvider = this.#activeUsageProvider();
725
+
726
+ if (!activeProvider || !Array.isArray(reports)) return null;
525
727
  const windows: NonNullable<SegmentContext["usage"]>["windows"] = [];
526
728
  const seen = new Set<string>();
527
729
  const now = Date.now();
@@ -555,20 +757,24 @@ export class StatusLineComponent implements Component {
555
757
  for (const report of reports) {
556
758
  if (!report || typeof report !== "object") continue;
557
759
  const provider = (report as { provider?: unknown }).provider;
558
- const providerId = typeof provider === "string" ? provider : undefined;
760
+ const providerId = typeof provider === "string" ? resolveOAuthStorageProvider(provider) : undefined;
761
+ if (providerId !== activeProvider) continue;
559
762
  const limits = (report as { limits?: unknown }).limits;
560
763
  if (!Array.isArray(limits)) continue;
561
764
  for (const limit of limits) {
562
765
  if (!limit || typeof limit !== "object") continue;
563
766
  const l = limit as {
564
767
  id?: unknown;
565
- scope?: { windowId?: string; tier?: string; modelId?: string };
768
+ scope?: { provider?: unknown; windowId?: string; tier?: string; modelId?: string };
566
769
  window?: { id?: string; resetsAt?: number };
567
770
  amount?: { usedFraction?: number };
568
771
  };
569
772
  const fraction = l.amount?.usedFraction;
570
773
  if (typeof fraction !== "number") continue;
571
774
  const id = typeof l.id === "string" ? l.id : "";
775
+ const scopeProvider =
776
+ typeof l.scope?.provider === "string" ? resolveOAuthStorageProvider(l.scope.provider) : undefined;
777
+ if (scopeProvider !== undefined && scopeProvider !== providerId) continue;
572
778
  const windowId = l.scope?.windowId ?? l.window?.id;
573
779
  const tier = l.scope?.tier;
574
780
  const modelId = l.scope?.modelId;
@@ -584,9 +790,9 @@ export class StatusLineComponent implements Component {
584
790
  }
585
791
  } else if (windowId === "5h" && !tier) {
586
792
  pushWindow(`${providerId ?? "provider"}:5h`, "5h", fraction, resetsAt, "m");
587
- } else if (windowId === "7d" && !tier) {
793
+ } else if (windowId === "7d" && !tier && providerId !== "grok-build" && providerId !== "xai") {
588
794
  pushWindow(`${providerId ?? "provider"}:7d`, "7d", fraction, resetsAt, "h");
589
- } else if (providerId === "grok-build" && windowId === "weekly" && !tier) {
795
+ } else if (providerId === "grok-build" && id === "grok-build:weekly" && windowId === "weekly" && !tier) {
590
796
  pushWindow("grok-build:weekly", "weekly", fraction, resetsAt, "h");
591
797
  }
592
798
  }
@@ -595,17 +801,32 @@ export class StatusLineComponent implements Component {
595
801
  return windows.length > 0 ? { windows } : null;
596
802
  }
597
803
 
804
+ #activeUsageProvider(): string | undefined {
805
+ const model = this.session.state.model ?? this.session.model;
806
+ if (!model || typeof model !== "object") return undefined;
807
+ const provider = (model as { provider?: unknown }).provider;
808
+ return typeof provider === "string" && provider.length > 0 ? resolveOAuthStorageProvider(provider) : undefined;
809
+ }
810
+
811
+ #syncCachedUsageForActiveProvider(): void {
812
+ const activeProvider = this.#activeUsageProvider();
813
+ if (this.#cachedUsageProvider === activeProvider) return;
814
+ this.#cachedUsageProvider = activeProvider;
815
+ this.#cachedUsage = this.#normalizeUsageReports(this.#cachedUsageReports);
816
+ }
817
+
598
818
  #buildSegmentContext(
599
819
  width: number,
600
820
  effectiveSettings: Required<
601
821
  Pick<StatusLineSettings, "leftSegments" | "rightSegments" | "separator" | "segmentOptions">
602
822
  > &
603
823
  StatusLineSettings,
824
+ previewOnly = false,
604
825
  ): SegmentContext {
605
826
  const state = this.session.state;
606
827
 
607
- // Trigger background fetch (5-min TTL); render uses cached value
608
- this.refreshUsageInBackground();
828
+ this.#syncCachedUsageForActiveProvider();
829
+ if (!previewOnly) this.refreshUsageInBackground();
609
830
 
610
831
  // Get usage statistics
611
832
  const aggregateUsageStats = this.session.sessionManager?.getUsageStatistics() ?? {
@@ -633,6 +854,20 @@ export class StatusLineComponent implements Component {
633
854
  // treats a null value as hidden, so gating here is behavior-identical.
634
855
  const prSegmentActive =
635
856
  effectiveSettings.leftSegments.includes("pr") || effectiveSettings.rightSegments.includes("pr");
857
+ const gitSegmentActive =
858
+ effectiveSettings.leftSegments.includes("git") || effectiveSettings.rightSegments.includes("git");
859
+ const commandSegmentActive =
860
+ effectiveSettings.leftSegments.includes("command") || effectiveSettings.rightSegments.includes("command");
861
+ const commandOptions = commandSegmentActive ? this.#trustedCommandOptions() : undefined;
862
+ let commandKey: string | undefined = this.#commandConfigKey;
863
+ if (!previewOnly) {
864
+ if (commandOptions) {
865
+ commandKey = this.#refreshCommandInBackground(commandOptions);
866
+ } else {
867
+ this.#disableCommandSegment();
868
+ commandKey = undefined;
869
+ }
870
+ }
636
871
 
637
872
  return {
638
873
  session: this.session,
@@ -650,10 +885,20 @@ export class StatusLineComponent implements Component {
650
885
  sessionStartTime: this.#sessionStartTime,
651
886
  git: {
652
887
  branch: this.#getCurrentBranch(),
653
- status: this.#getGitStatus(),
654
- pr: prSegmentActive ? this.#lookupPr() : null,
888
+ status: gitSegmentActive ? this.#getGitStatus() : null,
889
+ pr: prSegmentActive ? (previewOnly ? (this.#cachedPr ?? null) : this.#lookupPr()) : null,
655
890
  },
656
891
  usage: this.#cachedUsage,
892
+ command:
893
+ commandSegmentActive && (previewOnly || commandKey !== undefined)
894
+ ? {
895
+ output: this.#commandOutput,
896
+ failed: this.#commandFailed,
897
+ pending: previewOnly
898
+ ? this.#commandOutput === null && !this.#commandFailed
899
+ : this.#commandInFlightKey === commandKey,
900
+ }
901
+ : undefined,
657
902
  };
658
903
  }
659
904
 
@@ -730,7 +975,14 @@ export class StatusLineComponent implements Component {
730
975
  if (available <= 0 || count <= 0) return "";
731
976
  const withCount = `…+${count}`;
732
977
  const text = visibleWidth(withCount) <= available ? withCount : "…";
733
- return theme.fg("dim", text);
978
+ return truncateToWidth(theme.fg("dim", text), available);
979
+ }
980
+
981
+ #renderPreviewHighlight(content: string, style: "focus" | "selected"): string {
982
+ const bgColor = style === "selected" ? "warning" : "text";
983
+ const bgAnsi = theme.getFgAnsi(bgColor).replace("\x1b[38;", "\x1b[48;");
984
+ const fgAnsi = theme.getBgAnsi("selectedBg").replace("\x1b[48;", "\x1b[38;");
985
+ return `${bgAnsi}${fgAnsi}> ${Bun.stripANSI(content)} <\x1b[0m`;
734
986
  }
735
987
 
736
988
  #renderStatusGroup(
@@ -795,8 +1047,9 @@ export class StatusLineComponent implements Component {
795
1047
  Pick<StatusLineSettings, "leftSegments" | "rightSegments" | "separator" | "segmentOptions">
796
1048
  > &
797
1049
  StatusLineSettings,
1050
+ previewOnly = false,
798
1051
  ): CollectedStatusSegments {
799
- const ctx = this.#buildSegmentContext(width, effectiveSettings);
1052
+ const ctx = this.#buildSegmentContext(width, effectiveSettings, previewOnly);
800
1053
  const separatorDef = getSeparator(effectiveSettings.separator ?? "powerline-thin", theme);
801
1054
 
802
1055
  // Use the subtle surface tone (the same elevated background as user-message
@@ -808,12 +1061,14 @@ export class StatusLineComponent implements Component {
808
1061
  const sepAnsi = theme.getFgAnsi("statusLineSep");
809
1062
 
810
1063
  const previewHighlightSegment = effectiveSettings.previewHighlightSegment;
1064
+ const previewHighlightStyle = effectiveSettings.previewHighlightStyle ?? "focus";
811
1065
  const highlightSegment = (segId: StatusLineSegmentId, content: string): string =>
812
- previewHighlightSegment === segId ? `\x1b[7m${content}\x1b[27m` : content;
1066
+ previewHighlightSegment === segId ? this.#renderPreviewHighlight(content, previewHighlightStyle) : content;
813
1067
 
814
1068
  const left: string[] = [];
815
1069
  const leftSegIds: StatusLineSegmentId[] = [];
816
1070
  for (const segId of effectiveSettings.leftSegments) {
1071
+ if (segId === "command" && !ctx.command) continue;
817
1072
  const rendered = renderSegment(segId, ctx);
818
1073
  if (rendered.visible && rendered.content) {
819
1074
  left.push(highlightSegment(segId, rendered.content));
@@ -834,6 +1089,7 @@ export class StatusLineComponent implements Component {
834
1089
  this.#keyDisplayContext,
835
1090
  );
836
1091
  for (const segId of effectiveSettings.rightSegments) {
1092
+ if (segId === "command" && !ctx.command) continue;
837
1093
  const rendered = renderSegment(segId, ctx);
838
1094
  if (rendered.visible && rendered.content) {
839
1095
  right.push(highlightSegment(segId, rendered.content));
@@ -869,6 +1125,7 @@ export class StatusLineComponent implements Component {
869
1125
  right,
870
1126
  rightSegIds,
871
1127
  previewHighlightSegment,
1128
+ previewHighlightStyle,
872
1129
  sessionAccent: effectiveSettings.sessionAccent,
873
1130
  leftSepWidth: visibleWidth(separatorDef.left),
874
1131
  rightSepWidth: visibleWidth(separatorDef.right),
@@ -965,7 +1222,7 @@ export class StatusLineComponent implements Component {
965
1222
 
966
1223
  #buildStatusLine(width: number, precollected?: CollectedStatusSegments): string {
967
1224
  const seg = precollected ?? this.#collectStatusSegments(width, this.#resolveSettings());
968
- const { ctx, separatorDef, bgAnsi, fgAnsi, sepAnsi, previewHighlightSegment } = seg;
1225
+ const { ctx, separatorDef, bgAnsi, fgAnsi, sepAnsi, previewHighlightSegment, previewHighlightStyle } = seg;
969
1226
  const { leftSepWidth, rightSepWidth, leftCapWidth, rightCapWidth } = seg;
970
1227
  const topFillWidth = Math.max(0, width);
971
1228
 
@@ -998,7 +1255,10 @@ export class StatusLineComponent implements Component {
998
1255
  const overflow = totalWidth() - budget;
999
1256
  const shrunk = this.#shrinkPathToWidth(left[pathIdx], ctx, overflow);
1000
1257
  if (shrunk !== null) {
1001
- left[pathIdx] = previewHighlightSegment === "path" ? `\x1b[7m${shrunk}\x1b[27m` : shrunk;
1258
+ left[pathIdx] =
1259
+ previewHighlightSegment === "path"
1260
+ ? this.#renderPreviewHighlight(shrunk, previewHighlightStyle)
1261
+ : shrunk;
1002
1262
  leftWidth = this.#groupWidth(left, leftCapWidth, leftSepWidth);
1003
1263
  }
1004
1264
  }
@@ -1087,9 +1347,9 @@ export class StatusLineComponent implements Component {
1087
1347
  * being dropped. Falls back to the polished justified single row whenever
1088
1348
  * everything fits on one line.
1089
1349
  */
1090
- #buildStatusRows(width: number, maxRows: number): string[] {
1350
+ #buildStatusRows(width: number, maxRows: number, previewOnly = false): string[] {
1091
1351
  const effectiveSettings = this.#resolveSettings();
1092
- const seg = this.#collectStatusSegments(width, effectiveSettings);
1352
+ const seg = this.#collectStatusSegments(width, effectiveSettings, previewOnly);
1093
1353
  const cacheKey = JSON.stringify({
1094
1354
  width,
1095
1355
  maxRows,
@@ -1124,14 +1384,14 @@ export class StatusLineComponent implements Component {
1124
1384
  .join(","),
1125
1385
  version: this.#version,
1126
1386
  });
1127
- if (this.#renderedRowsCache?.key === cacheKey) {
1387
+ if (!previewOnly && this.#renderedRowsCache?.key === cacheKey) {
1128
1388
  this.#renderedRowsCacheHits++;
1129
1389
  return [...this.#renderedRowsCache.rows];
1130
1390
  }
1131
- this.#renderedRowsCacheMisses++;
1391
+ if (!previewOnly) this.#renderedRowsCacheMisses++;
1132
1392
 
1133
1393
  if (seg.left.length === 0 && seg.right.length === 0) {
1134
- this.#renderedRowsCache = { key: cacheKey, rows: [] };
1394
+ if (!previewOnly) this.#renderedRowsCache = { key: cacheKey, rows: [] };
1135
1395
  return [];
1136
1396
  }
1137
1397
 
@@ -1209,7 +1469,7 @@ export class StatusLineComponent implements Component {
1209
1469
  if (dropped > 0) {
1210
1470
  const priorityRow = this.#priorityRowFor(seg, [], [], topFillWidth);
1211
1471
  if (priorityRow !== null) {
1212
- this.#renderedRowsCache = { key: cacheKey, rows: [priorityRow] };
1472
+ if (!previewOnly) this.#renderedRowsCache = { key: cacheKey, rows: [priorityRow] };
1213
1473
  return [priorityRow];
1214
1474
  }
1215
1475
  }
@@ -1249,7 +1509,7 @@ export class StatusLineComponent implements Component {
1249
1509
  if (packedRows.length === 0 && marker) rows = [marker];
1250
1510
  }
1251
1511
 
1252
- this.#renderedRowsCache = { key: cacheKey, rows };
1512
+ if (!previewOnly) this.#renderedRowsCache = { key: cacheKey, rows };
1253
1513
  return [...rows];
1254
1514
  }
1255
1515
 
@@ -1271,6 +1531,120 @@ export class StatusLineComponent implements Component {
1271
1531
  return this.#buildStatusRows(width, this.#resolveMaxRows()).join("\n");
1272
1532
  }
1273
1533
 
1534
+ getPreviewContentForSettings(width: number, previewSettings: StatusLineSettings): string {
1535
+ const previousSettings = this.#settings;
1536
+ this.#settings = { ...previousSettings, previewHighlightSegment: undefined, ...previewSettings };
1537
+ try {
1538
+ return this.#buildStatusRows(width, this.#resolveMaxRows(), true).join("\n");
1539
+ } finally {
1540
+ this.#settings = previousSettings;
1541
+ }
1542
+ }
1543
+
1544
+ #placePreviewSegments(
1545
+ seg: CollectedStatusSegments,
1546
+ width: number,
1547
+ ): {
1548
+ left: string[];
1549
+ leftIds: StatusLineSegmentId[];
1550
+ right: string[];
1551
+ rightIds: (StatusLineSegmentId | null)[];
1552
+ } {
1553
+ const {
1554
+ ctx,
1555
+ previewHighlightSegment,
1556
+ previewHighlightStyle,
1557
+ leftSepWidth,
1558
+ rightSepWidth,
1559
+ leftCapWidth,
1560
+ rightCapWidth,
1561
+ } = seg;
1562
+ const rank = this.#priorityRanker(seg);
1563
+ const layout = (budget: number) => {
1564
+ const left = [...seg.left];
1565
+ const leftIds = [...seg.leftSegIds];
1566
+ const right = [...seg.right];
1567
+ const rightIds = [...seg.rightSegIds];
1568
+ let leftWidth = this.#groupWidth(left, leftCapWidth, leftSepWidth);
1569
+ let rightWidth = this.#groupWidth(right, rightCapWidth, rightSepWidth);
1570
+ const totalWidth = () => leftWidth + rightWidth + (left.length > 0 && right.length > 0 ? 1 : 0);
1571
+ let dropped = 0;
1572
+
1573
+ const pathIdx = leftIds.indexOf("path");
1574
+ if (pathIdx >= 0 && totalWidth() > budget) {
1575
+ const shrunk = this.#shrinkPathToWidth(left[pathIdx], ctx, totalWidth() - budget);
1576
+ if (shrunk !== null) {
1577
+ left[pathIdx] =
1578
+ previewHighlightSegment === "path"
1579
+ ? this.#renderPreviewHighlight(shrunk, previewHighlightStyle)
1580
+ : shrunk;
1581
+ leftWidth = this.#groupWidth(left, leftCapWidth, leftSepWidth);
1582
+ }
1583
+ }
1584
+ while (totalWidth() > budget && (left.length > 0 || right.length > 0)) {
1585
+ let victimSide: "left" | "right" | null = null;
1586
+ let victimIndex = -1;
1587
+ let victimRank = Number.POSITIVE_INFINITY;
1588
+ for (let index = right.length - 1; index >= 0; index -= 1) {
1589
+ const candidate = rank(rightIds[index]);
1590
+ if (candidate < victimRank) {
1591
+ victimRank = candidate;
1592
+ victimSide = "right";
1593
+ victimIndex = index;
1594
+ }
1595
+ }
1596
+ for (let index = left.length - 1; index >= 0; index -= 1) {
1597
+ const candidate = rank(leftIds[index]);
1598
+ if (candidate < victimRank) {
1599
+ victimRank = candidate;
1600
+ victimSide = "left";
1601
+ victimIndex = index;
1602
+ }
1603
+ }
1604
+ if (victimSide === null) break;
1605
+ if (victimSide === "right") {
1606
+ right.splice(victimIndex, 1);
1607
+ rightIds.splice(victimIndex, 1);
1608
+ rightWidth = this.#groupWidth(right, rightCapWidth, rightSepWidth);
1609
+ } else {
1610
+ left.splice(victimIndex, 1);
1611
+ leftIds.splice(victimIndex, 1);
1612
+ leftWidth = this.#groupWidth(left, leftCapWidth, leftSepWidth);
1613
+ }
1614
+ dropped += 1;
1615
+ }
1616
+ return { left, leftIds, right, rightIds, dropped };
1617
+ };
1618
+
1619
+ let placed = layout(Math.max(0, width));
1620
+ let reserved = 0;
1621
+ if (placed.dropped > 0 && width > 0) {
1622
+ for (let pass = 0; pass < 4; pass += 1) {
1623
+ const needed = Math.min(width, visibleWidth(`…+${placed.dropped}`));
1624
+ if (needed <= reserved) break;
1625
+ reserved = needed;
1626
+ placed = layout(Math.max(0, width - reserved));
1627
+ }
1628
+ }
1629
+ return placed;
1630
+ }
1631
+
1632
+ getPreviewPartsForSettings(width: number, previewSettings: StatusLineSettings): StatusLinePreviewParts {
1633
+ const previousSettings = this.#settings;
1634
+ this.#settings = { ...previousSettings, ...previewSettings };
1635
+ this.#resolvedSettingsCache = undefined;
1636
+ this.#resolvedSettingsFingerprint = undefined;
1637
+ try {
1638
+ const seg = this.#collectStatusSegments(width, this.#resolveSettings(), true);
1639
+ const placed = this.#placePreviewSegments(seg, width);
1640
+ return { ...placed, separator: seg.separatorDef };
1641
+ } finally {
1642
+ this.#settings = previousSettings;
1643
+ this.#resolvedSettingsCache = undefined;
1644
+ this.#resolvedSettingsFingerprint = undefined;
1645
+ }
1646
+ }
1647
+
1274
1648
  getCacheStatsForTest(): { rowHits: number; rowMisses: number } {
1275
1649
  return { rowHits: this.#renderedRowsCacheHits, rowMisses: this.#renderedRowsCacheMisses };
1276
1650
  }
@@ -1043,6 +1043,7 @@ export class CommandController {
1043
1043
  this.ctx.ui.requestRender();
1044
1044
 
1045
1045
  prepareTranscriptRebuild(this.ctx.ui, "replace-identity");
1046
+ this.ctx.resetAssistantTextPresentation();
1046
1047
  this.ctx.chatContainer.clear();
1047
1048
  this.ctx.pendingMessagesContainer.clear();
1048
1049
  this.ctx.compactionQueuedMessages = [];
@@ -1083,6 +1084,7 @@ export class CommandController {
1083
1084
  this.ctx.ui.requestRender();
1084
1085
 
1085
1086
  prepareTranscriptRebuild(this.ctx.ui, "replace-identity");
1087
+ this.ctx.resetAssistantTextPresentation();
1086
1088
  this.ctx.chatContainer.clear();
1087
1089
  this.ctx.pendingMessagesContainer.clear();
1088
1090
  this.ctx.compactionQueuedMessages = [];
@@ -1231,6 +1233,7 @@ export class CommandController {
1231
1233
  const meta = outputMeta().truncationFromSummary(result, { direction: "tail" }).get();
1232
1234
  this.ctx.bashComponent.setComplete(result.exitCode, result.cancelled, {
1233
1235
  output: result.output,
1236
+ signal: result.signal,
1234
1237
  truncation: meta?.truncation,
1235
1238
  });
1236
1239
  }