pi-ui-extend 0.1.19 → 0.1.21

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 (38) hide show
  1. package/dist/app/app.d.ts +3 -0
  2. package/dist/app/app.js +68 -8
  3. package/dist/app/constants.js +1 -1
  4. package/dist/app/extensions/extension-ui-controller.js +2 -2
  5. package/dist/app/input/voice-controller.d.ts +3 -2
  6. package/dist/app/input/voice-controller.js +9 -0
  7. package/dist/app/rendering/conversation-entry-renderer.js +39 -9
  8. package/dist/app/rendering/conversation-tool-renderer.js +1 -1
  9. package/dist/app/rendering/conversation-viewport.d.ts +1 -5
  10. package/dist/app/rendering/conversation-viewport.js +9 -16
  11. package/dist/app/rendering/editor-layout-renderer.js +5 -5
  12. package/dist/app/rendering/render-controller.js +14 -24
  13. package/dist/app/rendering/status-line-renderer.d.ts +2 -0
  14. package/dist/app/rendering/status-line-renderer.js +75 -29
  15. package/dist/app/rendering/tool-block-renderer.d.ts +2 -0
  16. package/dist/app/rendering/tool-block-renderer.js +13 -1
  17. package/dist/app/runtime.d.ts +2 -0
  18. package/dist/app/runtime.js +27 -4
  19. package/dist/app/screen/mouse-controller.d.ts +1 -1
  20. package/dist/app/screen/mouse-controller.js +9 -3
  21. package/dist/app/screen/screen-styler.js +3 -3
  22. package/dist/app/session/session-lifecycle-controller.d.ts +2 -0
  23. package/dist/app/session/session-lifecycle-controller.js +43 -16
  24. package/dist/app/session/tabs-controller.d.ts +1 -1
  25. package/dist/app/session/tabs-controller.js +3 -7
  26. package/dist/app/types.d.ts +1 -1
  27. package/dist/config.d.ts +1 -0
  28. package/dist/config.js +19 -7
  29. package/dist/markdown-format.d.ts +2 -0
  30. package/dist/markdown-format.js +5 -2
  31. package/dist/syntax-highlight.js +3 -1
  32. package/dist/theme.d.ts +11 -0
  33. package/dist/theme.js +56 -15
  34. package/extensions/question/tui.ts +1 -1
  35. package/external/pi-tools-suite/src/antigravity-auth/auth-store.ts +6 -1
  36. package/external/pi-tools-suite/src/antigravity-auth/oauth.ts +1 -1
  37. package/external/pi-tools-suite/src/glm-coding-discipline/index.ts +123 -20
  38. package/package.json +1 -1
package/dist/app/app.d.ts CHANGED
@@ -60,6 +60,9 @@ export declare class PiUiExtendApp {
60
60
  private resumeLoading;
61
61
  constructor(options: AppOptions);
62
62
  private createRuntime;
63
+ private loadStartupConfig;
64
+ private applyPixConfig;
65
+ private updateOutputFilters;
63
66
  start(): Promise<void>;
64
67
  private checkPixUpdateOnStartup;
65
68
  private bindCurrentSession;
package/dist/app/app.js CHANGED
@@ -1,6 +1,6 @@
1
1
  import { THEMES } from "../theme.js";
2
2
  import { InputEditor } from "../input-editor.js";
3
- import { compileOutputFilterPatterns, loadPixConfig, resolveToolRule, } from "../config.js";
3
+ import { compileOutputFilterPatterns, defaultPixConfig, loadPixConfig, resolveToolRule, } from "../config.js";
4
4
  import { AppCommandController } from "./commands/command-controller.js";
5
5
  import { ConversationViewport } from "./rendering/conversation-viewport.js";
6
6
  import { EditorLayoutRenderer } from "./rendering/editor-layout-renderer.js";
@@ -68,7 +68,7 @@ export class PiUiExtendApp {
68
68
  extensionUiController;
69
69
  extensionActions;
70
70
  pixConfig;
71
- outputFilters;
71
+ outputFilters = [];
72
72
  commandController;
73
73
  inputActions;
74
74
  inputController;
@@ -127,7 +127,7 @@ export class PiUiExtendApp {
127
127
  constructor(options) {
128
128
  this.options = options;
129
129
  this.theme = THEMES[options.themeName];
130
- this.pixConfig = loadPixConfig(this.options.cwd);
130
+ this.pixConfig = defaultPixConfig();
131
131
  setAppIconTheme(this.pixConfig.iconTheme.name);
132
132
  const app = this;
133
133
  this.blinkController = new AppBlinkController({
@@ -160,7 +160,7 @@ export class PiUiExtendApp {
160
160
  });
161
161
  this.tabsController = new AppTabsController({
162
162
  options: this.options,
163
- maxProjectSessions: this.pixConfig.maxProjectSessions,
163
+ maxProjectSessions: () => this.pixConfig.maxProjectSessions,
164
164
  blinkController: this.blinkController,
165
165
  runtime: () => this.runtime,
166
166
  createRuntimeForNewSession: () => this.createRuntime(newTabRuntimeOptions(this.options)),
@@ -398,18 +398,16 @@ export class PiUiExtendApp {
398
398
  get subagentsWidgetState() { return app.subagentsWidgetController.widgetState; },
399
399
  get voicePartialText() { return app.voicePartialText; },
400
400
  get autocompleteSuggestion() { return app.autocompleteController.suggestionText(); },
401
- get queuedMessageWidgetEntries() { return app.queuedMessages.deferredQueuedEntries(); },
401
+ get queuedMessageWidgetEntries() { return app.queuedMessages.queuedEntries(); },
402
402
  renderExtensionInputComponent: (width) => this.extensionUiController.renderActiveCustomUi(width),
403
403
  extensionInputUsesEditor: () => this.extensionUiController.activeCustomUiUsesEditor(),
404
404
  widgetTuiHandle: () => this.extensionUiController.widgetTuiHandle(),
405
405
  createExtensionTheme: () => this.extensionUiController.createExtensionTheme(),
406
406
  suppressExtensionWidget: (key) => this.extensionUiController.suppressWidget(key),
407
407
  });
408
- this.outputFilters = compileOutputFilterPatterns(this.pixConfig.outputFilters.patterns);
408
+ this.updateOutputFilters();
409
409
  this.conversationViewport = new ConversationViewport({
410
410
  get entries() { return app.entries; },
411
- get session() { return app.runtime?.session; },
412
- get deferredUserMessages() { return app.queuedMessages.deferredUserMessages; },
413
411
  get entryRenderVersions() { return app.sessionEvents.entryRenderVersions; },
414
412
  get superCompactTools() { return app.superCompactTools; },
415
413
  get allThinkingExpanded() { return app.allThinkingExpanded; },
@@ -700,6 +698,7 @@ export class PiUiExtendApp {
700
698
  inputEditor: () => this.inputEditor,
701
699
  enableTerminal: () => this.terminalController.enableTerminal(),
702
700
  disposeRuntimeForQuit: (runtime) => this.terminalController.disposeRuntimeForQuit(runtime),
701
+ loadStartupConfig: () => this.loadStartupConfig(),
703
702
  loadRequestHistory: () => this.requestHistory.load(),
704
703
  startSubagentsPolling: () => this.subagentsWidgetController.startPolling(),
705
704
  closeSdkMenuForBind: () => this.popupMenus.closeSdkMenu(undefined, { render: false, restoreStatus: false }),
@@ -750,8 +749,35 @@ export class PiUiExtendApp {
750
749
  createRuntime(options) {
751
750
  return createPixRuntime(options, {
752
751
  eventBus: this.createExtensionEventBus(),
752
+ config: this.pixConfig,
753
753
  });
754
754
  }
755
+ async loadStartupConfig() {
756
+ await yieldToEventLoop();
757
+ this.applyPixConfig(loadPixConfig(this.options.cwd));
758
+ }
759
+ applyPixConfig(config) {
760
+ replaceRecord(this.pixConfig.toolRenderer, config.toolRenderer);
761
+ replaceRecord(this.pixConfig.outputFilters, config.outputFilters);
762
+ replaceRecord(this.pixConfig.promptEnhancer, config.promptEnhancer);
763
+ replaceRecord(this.pixConfig.autocomplete, config.autocomplete);
764
+ replaceRecord(this.pixConfig.modelColors, config.modelColors);
765
+ replaceRecord(this.pixConfig.iconTheme, config.iconTheme);
766
+ replaceRecord(this.pixConfig.dictation, config.dictation);
767
+ if (config.defaultModel === undefined)
768
+ delete this.pixConfig.defaultModel;
769
+ else
770
+ this.pixConfig.defaultModel = { ...config.defaultModel };
771
+ this.pixConfig.ignoreContextFiles = config.ignoreContextFiles;
772
+ this.pixConfig.maxProjectSessions = config.maxProjectSessions;
773
+ this.updateOutputFilters();
774
+ this.voiceController.updateDictationConfig(this.pixConfig.dictation);
775
+ setAppIconTheme(this.pixConfig.iconTheme.name);
776
+ this.conversationViewport.clear();
777
+ }
778
+ updateOutputFilters() {
779
+ this.outputFilters.splice(0, this.outputFilters.length, ...compileOutputFilterPatterns(this.pixConfig.outputFilters.patterns));
780
+ }
755
781
  async start() {
756
782
  await this.sessionLifecycle.start();
757
783
  this.modelUsageController.startPolling();
@@ -1053,3 +1079,37 @@ function newTabRuntimeOptions(options) {
1053
1079
  ...(options.modelRef === undefined ? {} : { modelRef: options.modelRef }),
1054
1080
  };
1055
1081
  }
1082
+ async function yieldToEventLoop() {
1083
+ await new Promise((resolve) => { setTimeout(resolve, 0); });
1084
+ }
1085
+ function replaceRecord(target, source) {
1086
+ for (const key of Object.keys(target)) {
1087
+ if (!(key in source))
1088
+ delete target[key];
1089
+ }
1090
+ for (const [key, value] of Object.entries(source)) {
1091
+ const existing = target[key];
1092
+ if (isMutableRecord(existing) && isMutableRecord(value)) {
1093
+ replaceRecord(existing, value);
1094
+ }
1095
+ else {
1096
+ target[key] = cloneConfigValue(value);
1097
+ }
1098
+ }
1099
+ }
1100
+ function cloneConfigValue(value) {
1101
+ if (Array.isArray(value))
1102
+ return value.map(cloneConfigValue);
1103
+ if (isMutableRecord(value))
1104
+ return cloneConfigRecord(value);
1105
+ return value;
1106
+ }
1107
+ function cloneConfigRecord(value) {
1108
+ const cloned = {};
1109
+ for (const [key, nested] of Object.entries(value))
1110
+ cloned[key] = cloneConfigValue(nested);
1111
+ return cloned;
1112
+ }
1113
+ function isMutableRecord(value) {
1114
+ return typeof value === "object" && value !== null && !Array.isArray(value);
1115
+ }
@@ -73,7 +73,7 @@ export const SUBAGENTS_WIDGET_MAX_ROWS = 8;
73
73
  export const DEFAULT_THINKING_TOOL_RULE = {
74
74
  previewLines: 0,
75
75
  direction: "head",
76
- color: "accent",
76
+ color: "thinkingForeground",
77
77
  };
78
78
  export const TERMINAL_COMMAND_MODIFIER_FLAG = 8;
79
79
  export const GIT_BRANCH_CACHE_MS = 30_000;
@@ -1,4 +1,4 @@
1
- import { ANSI_RESET, colorize, THEMES } from "../../theme.js";
1
+ import { ANSI_RESET, ansiStylePrefix, colorize, THEMES } from "../../theme.js";
2
2
  import { isToastKind } from "../../ui.js";
3
3
  import { ellipsizeDisplay, sanitizeText } from "../rendering/render-text.js";
4
4
  const CUSTOM_UI_WIDGET_KEY = "pix-custom-ui";
@@ -20,7 +20,7 @@ export class ExtensionUiController {
20
20
  const colors = this.host.theme.colors;
21
21
  const foreground = (color) => extensionForegroundColor(colors, String(color));
22
22
  const background = (color) => extensionBackgroundColor(colors, String(color));
23
- const prefix = (options) => (colorize("", options).replace(new RegExp(`${escapeRegExp(ANSI_RESET)}$`), ""));
23
+ const prefix = (options) => ansiStylePrefix(options);
24
24
  return {
25
25
  fg: (color, text) => colorize(text, { foreground: foreground(color) }),
26
26
  bg: (color, text) => colorize(text, { background: background(color) }),
@@ -54,8 +54,8 @@ type VoiceControllerTestDeps = {
54
54
  export declare function setVoiceControllerTestDeps(overrides?: Partial<VoiceControllerTestDeps>): void;
55
55
  export declare class AppVoiceController {
56
56
  private readonly host;
57
- private readonly modelDefinitions;
58
- private readonly languages;
57
+ private modelDefinitions;
58
+ private languages;
59
59
  private language;
60
60
  private state;
61
61
  private readonly modelCache;
@@ -69,6 +69,7 @@ export declare class AppVoiceController {
69
69
  private partialTranscriptTimer;
70
70
  private startGeneration;
71
71
  constructor(host: AppVoiceControllerHost, dictationConfig: DictationConfig);
72
+ updateDictationConfig(dictationConfig: DictationConfig): void;
72
73
  statusWidgetText(): string;
73
74
  showLanguageSwitcher(): boolean;
74
75
  statusWidgetActive(): boolean;
@@ -51,6 +51,15 @@ export class AppVoiceController {
51
51
  this.languages = Object.keys(this.modelDefinitions);
52
52
  this.language = this.initialLanguage(dictationConfig.language);
53
53
  }
54
+ updateDictationConfig(dictationConfig) {
55
+ this.modelDefinitions = dictationConfig.languages;
56
+ this.languages = Object.keys(this.modelDefinitions);
57
+ this.language = this.initialLanguage(dictationConfig.language ?? this.language);
58
+ for (const language of this.modelCache.keys()) {
59
+ if (!this.modelDefinitions[language])
60
+ this.modelCache.delete(language);
61
+ }
62
+ }
54
63
  statusWidgetText() {
55
64
  const languageLabel = this.showLanguageSwitcher() ? ` ${this.language.toUpperCase()}` : "";
56
65
  switch (this.state) {
@@ -1,5 +1,6 @@
1
1
  import { applyOutputFilters } from "../../config.js";
2
2
  import { renderMarkdownTextLines } from "../../markdown-format.js";
3
+ import { stringDisplayWidth } from "../../terminal-width.js";
3
4
  import { attachImageClickTargets } from "../screen/image-click-targets.js";
4
5
  import { APP_ICONS } from "../icons.js";
5
6
  import { horizontalPaddingLayout, padHorizontalText, wrapText } from "./render-text.js";
@@ -7,16 +8,39 @@ import { renderConversationShellEntry } from "./conversation-shell-renderer.js";
7
8
  import { renderConversationToolEntry, renderThinkingEntry } from "./conversation-tool-renderer.js";
8
9
  export function renderConversationEntry(entry, width, options) {
9
10
  const { left: userContentLeft, contentWidth: userContentWidth } = horizontalPaddingLayout(width);
10
- const userLine = (text, entryId, syntaxHighlight, segments) => ({
11
- text: padHorizontalText(text, width),
12
- colorOverride: options.colors.warning,
13
- ...(segments && segments.length > 0 ? { segments: segments.map((segment) => ({ ...segment, start: segment.start + userContentLeft, end: segment.end + userContentLeft })) } : {}),
14
- ...(syntaxHighlight === undefined ? {} : { syntaxHighlight }),
15
- ...(entryId === undefined ? {} : { target: { kind: "user-message", id: entryId } }),
16
- });
11
+ const userLine = (text, entryId, syntaxHighlight, segments) => {
12
+ const textWidth = stringDisplayWidth(text);
13
+ const padding = Math.max(0, width - textWidth);
14
+ const paddedText = " ".repeat(padding) + text;
15
+ const offset = padding;
16
+ return {
17
+ text: paddedText,
18
+ colorOverride: options.colors.userForeground,
19
+ backgroundOverride: options.colors.userMessageBackground,
20
+ ...(segments && segments.length > 0
21
+ ? {
22
+ segments: segments.map((segment) => ({
23
+ ...segment,
24
+ start: segment.start + offset,
25
+ end: segment.end + offset,
26
+ foreground: options.colors.userForeground,
27
+ })),
28
+ }
29
+ : {}),
30
+ ...(syntaxHighlight === undefined
31
+ ? {}
32
+ : {
33
+ syntaxHighlight: {
34
+ ...syntaxHighlight,
35
+ start: syntaxHighlight.start + offset,
36
+ },
37
+ }),
38
+ ...(entryId === undefined ? {} : { target: { kind: "user-message", id: entryId } }),
39
+ };
40
+ };
17
41
  const queuedLine = (text, entryId, segments) => ({
18
42
  text,
19
- colorOverride: options.colors.warning,
43
+ colorOverride: options.colors.userForeground,
20
44
  ...(segments && segments.length > 0 ? { segments } : {}),
21
45
  target: { kind: "queue-message", id: entryId },
22
46
  });
@@ -69,10 +93,16 @@ function renderAssistantLines(text, width, options) {
69
93
  return [];
70
94
  const lines = [];
71
95
  for (const line of contentLines) {
96
+ const headingSegment = line.heading
97
+ ? { start: contentLeft, end: contentLeft + line.text.length, foreground: options.colors.assistantForeground, bold: true }
98
+ : undefined;
99
+ const existingSegments = line.segments?.map((segment) => ({ ...segment, start: segment.start + contentLeft, end: segment.end + contentLeft })) ?? [];
100
+ const allSegments = headingSegment ? [headingSegment, ...existingSegments] : existingSegments;
72
101
  lines.push({
73
102
  text: padHorizontalText(line.text, width),
74
103
  colorOverride: options.colors.assistantForeground,
75
- ...(line.segments && line.segments.length > 0 ? { segments: line.segments.map((segment) => ({ ...segment, start: segment.start + contentLeft, end: segment.end + contentLeft })) } : {}),
104
+ backgroundOverride: options.colors.assistantMessageBackground,
105
+ ...(allSegments.length > 0 ? { segments: allSegments } : {}),
76
106
  ...(line.syntaxHighlight ? { syntaxHighlight: line.syntaxHighlight } : {}),
77
107
  });
78
108
  }
@@ -63,7 +63,7 @@ export function renderThinkingEntry(entry, width, options) {
63
63
  expandedText: compactExpandedText || "(empty)",
64
64
  bodyWrap: "word",
65
65
  syntaxHighlight: compactExpandedText ? markdownSyntaxHighlightsForText(compactExpandedText) : undefined,
66
- }, rule, width, options.colors, { superCompact: Boolean(options.superCompactTools && !forceExpanded) });
66
+ }, rule, width, options.colors, { superCompact: Boolean(options.superCompactTools && !forceExpanded), backgroundOverride: options.colors.thinkingMessageBackground, skipHeaderBackground: true });
67
67
  }
68
68
  function trimTrailingBlankLines(text) {
69
69
  return text.replace(/(?:\r?\n[ \t]*)+$/u, "");
@@ -1,12 +1,9 @@
1
- import type { AgentSession } from "@earendil-works/pi-coding-agent";
2
1
  import { type PixConfig } from "../../config.js";
3
2
  import type { Theme } from "../../theme.js";
4
3
  import { type InlineUserMessageMenuContext } from "./conversation-entry-renderer.js";
5
- import type { ConversationBlockCache, Entry, RenderedLine, SubmittedUserMessage } from "../types.js";
4
+ import type { ConversationBlockCache, Entry, RenderedLine } from "../types.js";
6
5
  export type ConversationViewportHost = {
7
6
  readonly entries: readonly Entry[];
8
- readonly session: AgentSession | undefined;
9
- readonly deferredUserMessages: readonly SubmittedUserMessage[];
10
7
  readonly entryRenderVersions: ReadonlyMap<string, number>;
11
8
  readonly cwd: string;
12
9
  readonly colors: Theme["colors"];
@@ -40,7 +37,6 @@ export declare class ConversationViewport {
40
37
  blockForEntry(entry: Entry, width: number): ConversationBlockCache;
41
38
  entryBlockPositions(width: number): ConversationEntryBlockPosition[];
42
39
  measuredLineCountForEntries(width: number, entryIds: readonly string[]): number;
43
- private queuedEntries;
44
40
  private layoutForWidth;
45
41
  private buildLayout;
46
42
  private previousMeasuredLineCount;
@@ -2,7 +2,6 @@ import { resolveToolRule } from "../../config.js";
2
2
  import { stringDisplayWidth } from "../../terminal-width.js";
3
3
  import { renderConversationEntry as renderConversationEntryLines } from "./conversation-entry-renderer.js";
4
4
  import { horizontalPaddingLayout } from "./render-text.js";
5
- import { sdkQueuedMessageEntries } from "../session/queued-message-entries.js";
6
5
  export class ConversationViewport {
7
6
  host;
8
7
  blockCachesByWidth = new Map();
@@ -63,8 +62,7 @@ export class ConversationViewport {
63
62
  return { lines: visible, changed: false };
64
63
  }
65
64
  entries() {
66
- const queued = this.queuedEntries();
67
- return queued.length === 0 ? [...this.host.entries] : [...this.host.entries, ...queued];
65
+ return [...this.host.entries];
68
66
  }
69
67
  blockForEntry(entry, width) {
70
68
  const blockCache = this.blockCacheForWidth(width);
@@ -119,21 +117,16 @@ export class ConversationViewport {
119
117
  }
120
118
  return lineCount;
121
119
  }
122
- queuedEntries() {
123
- return sdkQueuedMessageEntries(this.host.session);
124
- }
125
120
  layoutForWidth(width) {
126
- const queued = this.queuedEntries();
127
- const entries = queued.length === 0 ? this.host.entries : [...this.host.entries, ...queued];
128
- const queuedSignature = queued.map((entry) => entry.id).join("\n");
121
+ const entries = this.host.entries;
129
122
  const superCompactTools = Boolean(this.host.superCompactTools);
130
123
  const allThinkingExpanded = Boolean(this.host.allThinkingExpanded);
131
124
  let layout = this.layoutCachesByWidth.get(width);
132
- if (!layout || this.layoutStructureChanged(layout, entries, queuedSignature, superCompactTools, allThinkingExpanded)) {
133
- const previousLayout = layout && layout.queuedSignature === queuedSignature && layout.superCompactTools === superCompactTools && layout.allThinkingExpanded === allThinkingExpanded
125
+ if (!layout || this.layoutStructureChanged(layout, entries, superCompactTools, allThinkingExpanded)) {
126
+ const previousLayout = layout && layout.superCompactTools === superCompactTools && layout.allThinkingExpanded === allThinkingExpanded
134
127
  ? layout
135
128
  : undefined;
136
- layout = this.buildLayout(entries, width, queuedSignature, superCompactTools, allThinkingExpanded, previousLayout);
129
+ layout = this.buildLayout(entries, width, superCompactTools, allThinkingExpanded, previousLayout);
137
130
  this.layoutCachesByWidth.set(width, layout);
138
131
  }
139
132
  else {
@@ -144,7 +137,7 @@ export class ConversationViewport {
144
137
  }
145
138
  return layout;
146
139
  }
147
- buildLayout(entries, width, queuedSignature, superCompactTools, allThinkingExpanded, previousLayout) {
140
+ buildLayout(entries, width, superCompactTools, allThinkingExpanded, previousLayout) {
148
141
  const entryIds = [];
149
142
  const lineCounts = [];
150
143
  const measuredLineCounts = [];
@@ -163,7 +156,7 @@ export class ConversationViewport {
163
156
  totalLineCount += lineCount;
164
157
  }
165
158
  offsets.push(totalLineCount);
166
- return { entries, entryIds, lineCounts, measuredLineCounts, offsets, positions, dirtyEntryIds: new Set(), totalLineCount, queuedSignature, superCompactTools, allThinkingExpanded };
159
+ return { entries, entryIds, lineCounts, measuredLineCounts, offsets, positions, dirtyEntryIds: new Set(), totalLineCount, superCompactTools, allThinkingExpanded };
167
160
  }
168
161
  previousMeasuredLineCount(previousLayout, entries, index, entry) {
169
162
  const previousIndex = previousLayout?.positions.get(entry.id);
@@ -179,8 +172,8 @@ export class ConversationViewport {
179
172
  return undefined;
180
173
  return previousLayout.lineCounts[previousIndex];
181
174
  }
182
- layoutStructureChanged(layout, entries, queuedSignature, superCompactTools, allThinkingExpanded) {
183
- if (layout.entries.length !== entries.length || layout.queuedSignature !== queuedSignature || layout.superCompactTools !== superCompactTools || layout.allThinkingExpanded !== allThinkingExpanded)
175
+ layoutStructureChanged(layout, entries, superCompactTools, allThinkingExpanded) {
176
+ if (layout.entries.length !== entries.length || layout.superCompactTools !== superCompactTools || layout.allThinkingExpanded !== allThinkingExpanded)
184
177
  return true;
185
178
  if (layout.entries.length === 0)
186
179
  return false;
@@ -8,7 +8,7 @@ export class EditorLayoutRenderer {
8
8
  this.host = host;
9
9
  }
10
10
  computeLayout(width, rows) {
11
- const maxAvailableInputRows = Math.max(1, rows - 5);
11
+ const maxAvailableInputRows = Math.max(1, rows - 4);
12
12
  const renderedInput = this.renderInput(width, Math.min(INPUT_MAX_ROWS, maxAvailableInputRows), maxAvailableInputRows);
13
13
  const maxEntityRows = Math.max(0, rows - renderedInput.lines.length - 4);
14
14
  const editorEntityWidth = inputFrameContentWidth(width);
@@ -18,9 +18,9 @@ export class EditorLayoutRenderer {
18
18
  aboveEditorLines = [...aboveEditorLines, { text: "", variant: "normal" }];
19
19
  }
20
20
  const belowEditorLines = this.limitEntityLines(this.renderExtensionWidgets("belowEditor", editorEntityWidth), maxEntityRows - aboveEditorLines.length);
21
- const inputBottomSeparatorRow = rows - 1;
22
- const belowEditorStartRow = inputBottomSeparatorRow - belowEditorLines.length;
23
- const inputStartRow = belowEditorStartRow - renderedInput.lines.length;
21
+ const belowEditorStartRow = rows - belowEditorLines.length;
22
+ const inputBottomSeparatorRow = belowEditorStartRow - 1;
23
+ const inputStartRow = inputBottomSeparatorRow - renderedInput.lines.length;
24
24
  const inputSeparatorRow = inputStartRow - aboveEditorLines.length - 1;
25
25
  return {
26
26
  renderedInput,
@@ -111,7 +111,7 @@ export class EditorLayoutRenderer {
111
111
  for (const [index, text] of wrapped.entries()) {
112
112
  lines.push({
113
113
  text: padHorizontalText(text, width),
114
- colorOverride: this.host.theme.colors.warning,
114
+ colorOverride: this.host.theme.colors.userForeground,
115
115
  target: { kind: "queue-message", id: entry.id },
116
116
  ...(index === 0 ? { segments: [{ start: 0, end: icon.length, foreground: this.host.theme.colors.info }] } : {}),
117
117
  });
@@ -150,7 +150,7 @@ export class AppRenderController {
150
150
  if (row < statusRow) {
151
151
  this.deps.mouseController.renderedRowTexts.set(row, separatorText);
152
152
  appendFrameOutput("inputStatus", row, this.renderFrameRow(row, this.deps.screenStyler.styleLine(row, separatorText, columns, {
153
- foreground: this.deps.theme.colors.inputBorder,
153
+ foreground: this.deps.theme.colors.tabBorder,
154
154
  })));
155
155
  }
156
156
  }
@@ -176,7 +176,15 @@ export class AppRenderController {
176
176
  })}`);
177
177
  }
178
178
  }
179
- const belowEditorStartRow = inputStartRow + renderedInput.lines.length;
179
+ if (inputBottomSeparatorRow && inputBottomSeparatorRow > inputSeparatorRow && inputBottomSeparatorRow < statusRow) {
180
+ const separatorText = inputFrameLine(columns, "bottom");
181
+ const row = toScreenRow(inputBottomSeparatorRow);
182
+ this.deps.mouseController.renderedRowTexts.set(row, separatorText);
183
+ appendFrameOutput("inputStatus", row, this.renderFrameRow(row, this.deps.screenStyler.styleLine(row, separatorText, columns, {
184
+ foreground: this.deps.theme.colors.tabBorder,
185
+ })));
186
+ }
187
+ const belowEditorStartRow = (inputBottomSeparatorRow ?? (inputStartRow + renderedInput.lines.length - 1)) + 1;
180
188
  for (let index = 0; index < belowEditorLines.length; index += 1) {
181
189
  const rendered = frameRenderedLine(belowEditorLines[index], columns, this.deps.theme, this.deps.screenStyler);
182
190
  const row = toScreenRow(belowEditorStartRow + index);
@@ -191,25 +199,7 @@ export class AppRenderController {
191
199
  appendFrameOutput("inputStatus", row, this.renderFrameRow(row, rendered.output(row)));
192
200
  }
193
201
  const statusLayout = this.deps.statusLineRenderer.layout(columns);
194
- const statusLineRenderer = this.deps.statusLineRenderer;
195
- const inputBorderWidgetsLayout = statusLineRenderer.inputBorderWidgetsLayout?.(columns);
196
- if (inputBottomSeparatorRow > 1) {
197
- const separatorText = inputFrameLine(columns, "bottom");
198
- const row = toScreenRow(inputBottomSeparatorRow);
199
- if (row < statusRow) {
200
- const text = inputBorderWidgetsLayout
201
- ? overlayText(separatorText, inputBorderWidgetsLayout.inputBorderWidgetStartColumn ?? 1, inputBorderWidgetsLayout.text)
202
- : separatorText;
203
- this.deps.mouseController.renderedRowTexts.set(row, text);
204
- const output = inputBorderWidgetsLayout && statusLineRenderer.renderInputBorderWidgets
205
- ? statusLineRenderer.renderInputBorderWidgets(row, inputBorderWidgetsLayout, separatorText, columns)
206
- : this.deps.screenStyler.styleLine(row, separatorText, columns, {
207
- foreground: this.deps.theme.colors.inputBorder,
208
- });
209
- appendFrameOutput("inputStatus", row, this.renderFrameRow(row, output));
210
- }
211
- }
212
- this.updateStatusMouseState(statusLayout, statusRow, inputBorderWidgetsLayout, toScreenRow(inputBottomSeparatorRow));
202
+ this.updateStatusMouseState(statusLayout, statusRow);
213
203
  appendFrameOutput("inputStatus", statusRow, this.renderFrameRow(statusRow, this.deps.statusLineRenderer.render(statusRow, statusLayout, columns)));
214
204
  const voiceProgressOverlay = this.renderVoiceProgressOverlay(this.deps.voiceProgressOverlayText(), columns, statusRow);
215
205
  if (voiceProgressOverlay) {
@@ -287,9 +277,9 @@ export class AppRenderController {
287
277
  const text = fixedCellText(flash.text, width);
288
278
  return `\x1b[${flash.y};${flash.startColumn}H\x1b[7m${text}${ANSI_RESET}`;
289
279
  }
290
- updateStatusMouseState(statusLayout, statusRow, inputBorderWidgetsLayout, inputBorderWidgetsRow) {
291
- const widgetLayout = inputBorderWidgetsLayout;
292
- const widgetRow = inputBorderWidgetsRow ?? statusRow;
280
+ updateStatusMouseState(statusLayout, statusRow) {
281
+ const widgetLayout = statusLayout;
282
+ const widgetRow = statusRow;
293
283
  this.deps.mouseController.statusModelTarget = this.deps.statusLineRenderer.modelTarget(statusLayout.text, statusRow);
294
284
  this.deps.mouseController.statusThinkingTarget = this.deps.statusLineRenderer.thinkingTarget(statusLayout.text, statusRow);
295
285
  this.deps.mouseController.statusContextTarget = this.deps.statusLineRenderer.contextTarget(statusLayout.text, statusRow, statusLayout);
@@ -53,6 +53,8 @@ export declare class StatusLineRenderer {
53
53
  terminalBellSoundTarget(layout: StatusLineLayout, row: number): StatusTerminalBellSoundTarget | undefined;
54
54
  sessionTarget(statusText: string, row: number, label: string, workspaceLabel: string): StatusSessionTarget | undefined;
55
55
  private segments;
56
+ private fitWorkspaceLabel;
57
+ private pushStatusWidgetSegments;
56
58
  private pushPromptEnhancerWidgetSegment;
57
59
  private pushUserJumpWidgetSegment;
58
60
  private pushDraftQueueWidgetSegment;