pi-ui-extend 0.1.13 → 0.1.17

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 (111) hide show
  1. package/README.md +1 -1
  2. package/dist/app/app.d.ts +7 -0
  3. package/dist/app/app.js +102 -17
  4. package/dist/app/commands/command-controller.js +2 -0
  5. package/dist/app/commands/command-host.d.ts +5 -0
  6. package/dist/app/commands/command-model-actions.d.ts +2 -0
  7. package/dist/app/commands/command-model-actions.js +40 -4
  8. package/dist/app/commands/command-navigation-actions.d.ts +9 -0
  9. package/dist/app/commands/command-navigation-actions.js +62 -0
  10. package/dist/app/commands/command-registry.d.ts +2 -0
  11. package/dist/app/commands/command-registry.js +16 -0
  12. package/dist/app/constants.d.ts +0 -1
  13. package/dist/app/constants.js +0 -1
  14. package/dist/app/extensions/extension-ui-controller.d.ts +16 -5
  15. package/dist/app/extensions/extension-ui-controller.js +99 -61
  16. package/dist/app/icons.d.ts +1 -0
  17. package/dist/app/icons.js +2 -0
  18. package/dist/app/input/input-action-controller.d.ts +2 -0
  19. package/dist/app/input/input-action-controller.js +8 -1
  20. package/dist/app/logger.d.ts +25 -0
  21. package/dist/app/logger.js +90 -0
  22. package/dist/app/model/model-usage-status.js +30 -15
  23. package/dist/app/popup/menu-items-controller.d.ts +4 -0
  24. package/dist/app/popup/menu-items-controller.js +68 -6
  25. package/dist/app/popup/popup-action-controller.d.ts +2 -1
  26. package/dist/app/popup/popup-action-controller.js +7 -4
  27. package/dist/app/popup/popup-menu-controller.d.ts +36 -23
  28. package/dist/app/popup/popup-menu-controller.js +97 -326
  29. package/dist/app/rendering/conversation-entry-renderer.js +3 -3
  30. package/dist/app/rendering/conversation-viewport.d.ts +10 -2
  31. package/dist/app/rendering/conversation-viewport.js +157 -16
  32. package/dist/app/rendering/editor-panels.js +22 -9
  33. package/dist/app/rendering/popup-menu-renderer.d.ts +62 -0
  34. package/dist/app/rendering/popup-menu-renderer.js +405 -0
  35. package/dist/app/rendering/render-controller.js +30 -28
  36. package/dist/app/rendering/render-text.js +5 -2
  37. package/dist/app/rendering/status-line-renderer.d.ts +8 -1
  38. package/dist/app/rendering/status-line-renderer.js +217 -117
  39. package/dist/app/rendering/toast-controller.d.ts +12 -3
  40. package/dist/app/rendering/toast-controller.js +70 -12
  41. package/dist/app/runtime.d.ts +2 -1
  42. package/dist/app/runtime.js +20 -10
  43. package/dist/app/screen/mouse-controller.d.ts +2 -2
  44. package/dist/app/screen/mouse-controller.js +27 -48
  45. package/dist/app/screen/screen-styler.d.ts +1 -1
  46. package/dist/app/screen/screen-styler.js +9 -7
  47. package/dist/app/screen/scroll-controller.d.ts +12 -9
  48. package/dist/app/screen/scroll-controller.js +56 -45
  49. package/dist/app/screen/status-controller.js +2 -1
  50. package/dist/app/session/lazy-session-manager.d.ts +11 -0
  51. package/dist/app/session/lazy-session-manager.js +539 -0
  52. package/dist/app/session/pix-system-message.d.ts +16 -0
  53. package/dist/app/session/pix-system-message.js +64 -0
  54. package/dist/app/session/request-history.d.ts +4 -0
  55. package/dist/app/session/request-history.js +11 -0
  56. package/dist/app/session/session-event-controller.d.ts +11 -0
  57. package/dist/app/session/session-event-controller.js +58 -2
  58. package/dist/app/session/session-history.d.ts +18 -0
  59. package/dist/app/session/session-history.js +72 -3
  60. package/dist/app/session/session-lifecycle-controller.d.ts +6 -2
  61. package/dist/app/session/session-lifecycle-controller.js +7 -2
  62. package/dist/app/session/session-search.js +10 -0
  63. package/dist/app/session/tabs-controller.d.ts +17 -5
  64. package/dist/app/session/tabs-controller.js +308 -29
  65. package/dist/app/todo/todo-model.d.ts +4 -2
  66. package/dist/app/todo/todo-model.js +23 -13
  67. package/dist/app/types.d.ts +17 -6
  68. package/dist/app/workspace/workspace-actions-controller.d.ts +2 -0
  69. package/dist/app/workspace/workspace-actions-controller.js +12 -0
  70. package/dist/config.d.ts +6 -1
  71. package/dist/config.js +82 -25
  72. package/dist/default-pix-config.js +4 -0
  73. package/dist/fuzzy.d.ts +2 -0
  74. package/dist/fuzzy.js +27 -7
  75. package/dist/input-editor.d.ts +9 -0
  76. package/dist/input-editor.js +52 -0
  77. package/dist/schemas/pi-tools-suite-schema.d.ts +1 -0
  78. package/dist/schemas/pi-tools-suite-schema.js +1 -0
  79. package/dist/schemas/pix-schema.d.ts +3 -1
  80. package/dist/schemas/pix-schema.js +6 -4
  81. package/dist/terminal-width.d.ts +2 -0
  82. package/dist/terminal-width.js +64 -3
  83. package/dist/theme.js +6 -6
  84. package/dist/ui.d.ts +8 -0
  85. package/external/pi-tools-suite/README.md +3 -2
  86. package/external/pi-tools-suite/src/antigravity-auth/auth-store.ts +52 -8
  87. package/external/pi-tools-suite/src/antigravity-auth/commands.ts +3 -41
  88. package/external/pi-tools-suite/src/antigravity-auth/constants.ts +0 -2
  89. package/external/pi-tools-suite/src/antigravity-auth/index.ts +11 -18
  90. package/external/pi-tools-suite/src/antigravity-auth/oauth.ts +129 -61
  91. package/external/pi-tools-suite/src/antigravity-auth/status.ts +82 -3
  92. package/external/pi-tools-suite/src/antigravity-auth/stream.ts +20 -7
  93. package/external/pi-tools-suite/src/antigravity-auth/types.ts +21 -0
  94. package/external/pi-tools-suite/src/config.ts +8 -0
  95. package/external/pi-tools-suite/src/dcp/index.ts +16 -1
  96. package/external/pi-tools-suite/src/dcp/state.ts +35 -0
  97. package/external/pi-tools-suite/src/default-pi-tools-suite-config.ts +3 -0
  98. package/external/pi-tools-suite/src/todo/index.ts +123 -14
  99. package/external/pi-tools-suite/src/todo/state/persistence.ts +0 -1
  100. package/external/pi-tools-suite/src/todo/state/state-reducer.ts +26 -43
  101. package/external/pi-tools-suite/src/todo/todo.ts +12 -23
  102. package/external/pi-tools-suite/src/todo/tool/response-envelope.ts +34 -16
  103. package/external/pi-tools-suite/src/todo/tool/types.ts +7 -28
  104. package/external/pi-tools-suite/src/todo/view/format.ts +2 -3
  105. package/external/pi-tools-suite/src/tool-descriptions.ts +6 -4
  106. package/external/pi-tools-suite/src/usage/index.ts +5 -2
  107. package/external/pi-tools-suite/src/usage/lib/google.ts +53 -40
  108. package/external/pi-tools-suite/src/usage/lib/types.ts +12 -2
  109. package/package.json +1 -1
  110. package/schemas/pi-tools-suite.json +4 -0
  111. package/schemas/pix.json +11 -2
@@ -1,9 +1,12 @@
1
1
  import { compactProgressBarSegments, formatCompactProgressBar } from "../../context-progress-bar.js";
2
2
  import { padOrTrimPlain } from "./render-text.js";
3
- import { stringDisplayWidth } from "../../terminal-width.js";
3
+ import { displayIndexForColumn, stringDisplayWidth } from "../../terminal-width.js";
4
4
  import { APP_ICONS } from "../icons.js";
5
5
  import { resolveColor, resolveModelColor } from "../../config.js";
6
6
  const MODEL_USAGE_PROGRESS_BAR_WIDTH = stringDisplayWidth(formatCompactProgressBar(100));
7
+ const INPUT_BORDER_WIDGET_INSET = 2;
8
+ const INPUT_BORDER_WIDGET_SEPARATOR = "─";
9
+ const STATUS_ICON_BUTTON_WIDTH = 3;
7
10
  export class StatusLineRenderer {
8
11
  host;
9
12
  constructor(host) {
@@ -11,101 +14,149 @@ export class StatusLineRenderer {
11
14
  }
12
15
  layout(width) {
13
16
  const contentWidth = Math.max(1, width);
14
- const left = 0;
15
17
  const statusDot = APP_ICONS.record;
16
- const draftQueueButton = this.draftQueueWidgetText();
17
- const userJumpButton = APP_ICONS.user;
18
- const thinkingExpandButton = APP_ICONS.thinkingExpanded;
19
- const compactToolsButton = APP_ICONS.compactTools;
20
- const terminalBellSoundWidgetText = this.host.terminalBellSoundStatusWidgetText();
21
- const promptEnhancerWidgetText = this.host.promptEnhancerStatusWidgetText();
22
- const voiceWidgetText = this.host.voiceStatusWidgetText();
23
- const rightWidgetParts = [draftQueueButton, promptEnhancerWidgetText, userJumpButton, terminalBellSoundWidgetText, thinkingExpandButton, compactToolsButton, voiceWidgetText];
24
- const rightWidgetText = rightWidgetParts.filter((text) => text.length > 0).join(" ");
25
- const rightWidgetWidth = stringDisplayWidth(rightWidgetText);
26
- const leftWidth = rightWidgetWidth > 0 && contentWidth > rightWidgetWidth + 1 ? contentWidth - rightWidgetWidth - 1 : contentWidth;
27
18
  const baseStatus = this.host.currentStatus();
28
19
  const workspaceLabel = this.host.statusWorkspaceLabel();
29
20
  const modelUsageLabel = this.host.modelUsageStatusLabel();
30
21
  const workspaceDetailsLabel = modelUsageLabel ? `${workspaceLabel} ${modelUsageLabel}` : workspaceLabel;
31
- const contextBarLabel = this.contextBarLabel(baseStatus, leftWidth, workspaceDetailsLabel);
22
+ const contextBarLabel = this.contextBarLabel(baseStatus, contentWidth, workspaceDetailsLabel);
32
23
  const status = contextBarLabel ? `${baseStatus} ${contextBarLabel}` : baseStatus;
33
24
  const sessionLabel = "";
34
25
  const details = `${status} ${workspaceDetailsLabel}`;
35
- const leftText = padOrTrimPlain(`${statusDot} ${details}`, leftWidth);
36
- const innerText = leftWidth < contentWidth ? `${leftText} ${rightWidgetText}` : padOrTrimPlain(leftText, contentWidth);
37
- const text = padOrTrimPlain(innerText, width);
38
- let nextWidgetStartColumn = left + leftWidth + 2;
39
- let draftQueueWidget = leftWidth < contentWidth && draftQueueButton.length > 0
40
- ? this.widgetLayout(nextWidgetStartColumn, draftQueueButton)
41
- : undefined;
42
- if (draftQueueWidget)
43
- nextWidgetStartColumn = draftQueueWidget.endColumn + 1;
44
- let promptEnhancerWidget;
45
- let userJumpWidget;
46
- let terminalBellSoundWidget;
47
- let thinkingExpandWidget;
48
- let compactToolsWidget;
49
- const appendPromptEnhancerWidget = () => {
50
- promptEnhancerWidget = leftWidth < contentWidth && promptEnhancerWidgetText.length > 0
51
- ? this.widgetLayout(nextWidgetStartColumn, promptEnhancerWidgetText)
52
- : undefined;
53
- if (promptEnhancerWidget)
54
- nextWidgetStartColumn = promptEnhancerWidget.endColumn + 1;
55
- };
56
- const appendCoreStatusWidgets = () => {
57
- userJumpWidget = leftWidth < contentWidth
58
- ? this.widgetLayout(nextWidgetStartColumn, userJumpButton)
59
- : undefined;
60
- if (userJumpWidget)
61
- nextWidgetStartColumn = userJumpWidget.endColumn + 1;
62
- terminalBellSoundWidget = leftWidth < contentWidth && terminalBellSoundWidgetText.length > 0
63
- ? this.widgetLayout(nextWidgetStartColumn, terminalBellSoundWidgetText)
64
- : undefined;
65
- if (terminalBellSoundWidget)
66
- nextWidgetStartColumn = terminalBellSoundWidget.endColumn + 1;
67
- thinkingExpandWidget = leftWidth < contentWidth
68
- ? this.widgetLayout(nextWidgetStartColumn, thinkingExpandButton)
69
- : undefined;
70
- if (thinkingExpandWidget)
71
- nextWidgetStartColumn = thinkingExpandWidget.endColumn + 1;
72
- compactToolsWidget = leftWidth < contentWidth
73
- ? this.widgetLayout(nextWidgetStartColumn, compactToolsButton)
74
- : undefined;
75
- if (compactToolsWidget)
76
- nextWidgetStartColumn = compactToolsWidget.endColumn + 1;
77
- };
78
- appendPromptEnhancerWidget();
79
- appendCoreStatusWidgets();
80
- const voiceWidget = leftWidth < contentWidth && voiceWidgetText.length > 0 ? this.voiceWidgetLayout(nextWidgetStartColumn, voiceWidgetText) : undefined;
26
+ const text = padOrTrimPlain(`${statusDot} ${details}`, width);
81
27
  return {
82
28
  details,
83
29
  text,
84
30
  sessionLabel,
85
31
  workspaceLabel,
86
- ...(draftQueueWidget ? { draftQueueWidget } : {}),
87
- ...(userJumpWidget ? { userJumpWidget } : {}),
88
- ...(thinkingExpandWidget ? { thinkingExpandWidget } : {}),
89
- ...(compactToolsWidget ? { compactToolsWidget } : {}),
90
- ...(terminalBellSoundWidget ? { terminalBellSoundWidget } : {}),
91
32
  ...(modelUsageLabel ? { modelUsageLabel } : {}),
92
33
  ...(contextBarLabel ? { contextBarLabel } : {}),
93
- ...(promptEnhancerWidget ? { promptEnhancerWidget } : {}),
94
- ...(voiceWidget ? { voiceWidget } : {}),
95
34
  };
96
35
  }
36
+ inputBorderWidgetsLayout(width) {
37
+ const layout = {
38
+ details: "",
39
+ text: "",
40
+ sessionLabel: "",
41
+ workspaceLabel: "",
42
+ };
43
+ const widgets = [];
44
+ let hasParts = false;
45
+ const appendWidget = (text, assign) => {
46
+ if (text.length <= 0)
47
+ return;
48
+ widgets.push({ text, assign });
49
+ hasParts = true;
50
+ };
51
+ const draftQueueButton = this.draftQueueWidgetText();
52
+ appendWidget(draftQueueButton ? this.iconButtonText(draftQueueButton) : "", (column, text) => {
53
+ layout.draftQueueWidget = this.widgetLayout(column, text);
54
+ });
55
+ const promptEnhancerWidgetText = this.host.promptEnhancerStatusWidgetText();
56
+ appendWidget(promptEnhancerWidgetText ? this.iconButtonText(promptEnhancerWidgetText) : "", (column, text) => {
57
+ layout.promptEnhancerWidget = this.widgetLayout(column, text);
58
+ });
59
+ appendWidget(this.iconButtonText(APP_ICONS.user), (column, text) => {
60
+ layout.userJumpWidget = this.widgetLayout(column, text);
61
+ });
62
+ const terminalBellSoundWidgetText = this.host.terminalBellSoundStatusWidgetText();
63
+ appendWidget(terminalBellSoundWidgetText ? this.iconButtonText(terminalBellSoundWidgetText) : "", (column, text) => {
64
+ layout.terminalBellSoundWidget = this.widgetLayout(column, text);
65
+ });
66
+ appendWidget(this.iconButtonText(APP_ICONS.thinkingExpanded), (column, text) => {
67
+ layout.thinkingExpandWidget = this.widgetLayout(column, text);
68
+ });
69
+ appendWidget(this.iconButtonText(APP_ICONS.compactTools), (column, text) => {
70
+ layout.compactToolsWidget = this.widgetLayout(column, text);
71
+ });
72
+ const voiceWidgetText = this.host.voiceStatusWidgetText();
73
+ appendWidget(this.voiceBorderWidgetText(voiceWidgetText), (column, text) => {
74
+ layout.voiceWidget = this.voiceWidgetLayout(column, voiceWidgetText, text);
75
+ });
76
+ if (!hasParts)
77
+ return undefined;
78
+ const parts = [];
79
+ const totalWidth = widgets.reduce((total, widget, index) => total + (index > 0 ? 1 : 0) + stringDisplayWidth(widget.text), 0);
80
+ const endColumn = Math.max(1, width - INPUT_BORDER_WIDGET_INSET);
81
+ const startColumn = endColumn - totalWidth;
82
+ if (startColumn < 2)
83
+ return undefined;
84
+ layout.inputBorderWidgetStartColumn = startColumn;
85
+ let nextColumn = startColumn;
86
+ for (const [index, widget] of widgets.entries()) {
87
+ if (index > 0) {
88
+ parts.push(INPUT_BORDER_WIDGET_SEPARATOR);
89
+ nextColumn += 1;
90
+ }
91
+ parts.push(widget.text);
92
+ widget.assign(nextColumn, widget.text);
93
+ nextColumn += stringDisplayWidth(widget.text);
94
+ }
95
+ layout.text = parts.join("");
96
+ return layout;
97
+ }
97
98
  render(row, layout, width) {
98
99
  const colors = this.host.theme.colors;
99
100
  return this.host.screenStyler.styleLineSegments(row, layout.text, width, {
100
101
  foreground: colors.statusForeground,
101
102
  }, this.segments(layout.text, layout));
102
103
  }
104
+ renderInputBorderWidgets(row, layout, borderText, width) {
105
+ const startColumn = layout.inputBorderWidgetStartColumn ?? 1;
106
+ const text = overlayText(borderText, startColumn, layout.text);
107
+ return this.host.screenStyler.styleLineSegments(row, text, width, {
108
+ foreground: this.host.theme.colors.inputBorder,
109
+ }, this.inputBorderWidgetSegments(layout, text));
110
+ }
111
+ inputBorderWidgetSegments(layout, text) {
112
+ const colors = this.host.theme.colors;
113
+ const background = colors.inputBorder;
114
+ const segments = [];
115
+ const pushWidgetSegment = (widget, foreground) => {
116
+ if (!widget)
117
+ return;
118
+ segments.push({
119
+ start: displayIndexForColumn(text, widget.startColumn),
120
+ end: displayIndexForColumn(text, widget.endColumn),
121
+ foreground,
122
+ background,
123
+ });
124
+ };
125
+ pushWidgetSegment(layout.draftQueueWidget, colors.info);
126
+ pushWidgetSegment(layout.promptEnhancerWidget, this.host.promptEnhancerStatusWidgetActive()
127
+ ? colors.warning
128
+ : this.host.promptEnhancerStatusWidgetEnabled()
129
+ ? colors.info
130
+ : colors.muted);
131
+ pushWidgetSegment(layout.userJumpWidget, this.host.userMessageJumpMenuActive?.() ? colors.info : colors.muted);
132
+ pushWidgetSegment(layout.terminalBellSoundWidget, this.host.terminalBellSoundStatusWidgetEnabled() ? colors.info : colors.muted);
133
+ pushWidgetSegment(layout.thinkingExpandWidget, this.host.allThinkingExpandedActive?.() ? colors.info : colors.muted);
134
+ pushWidgetSegment(layout.compactToolsWidget, this.host.superCompactToolsActive?.() ? colors.info : colors.muted);
135
+ const voiceWidget = layout.voiceWidget;
136
+ if (voiceWidget) {
137
+ segments.push({
138
+ start: displayIndexForColumn(text, voiceWidget.startColumn),
139
+ end: displayIndexForColumn(text, voiceWidget.micEndColumn),
140
+ foreground: this.host.voiceStatusWidgetActive() ? colors.error : colors.muted,
141
+ background,
142
+ });
143
+ if (voiceWidget.languageEndColumn > voiceWidget.languageStartColumn) {
144
+ segments.push({
145
+ start: displayIndexForColumn(text, voiceWidget.languageStartColumn),
146
+ end: displayIndexForColumn(text, voiceWidget.languageEndColumn),
147
+ foreground: colors.statusForeground,
148
+ background,
149
+ });
150
+ }
151
+ }
152
+ return segments;
153
+ }
103
154
  modelTarget(statusText, row) {
104
155
  const session = this.host.session;
105
156
  if (!session)
106
157
  return undefined;
107
158
  const label = this.host.statusModelLabel(session);
108
- const marker = `${label} ${this.host.statusThinkingLabel(session)} `;
159
+ const marker = `${label} ${this.statusThinkingDisplayLabel(session)} `;
109
160
  const startIndex = statusText.indexOf(marker);
110
161
  if (startIndex < 0)
111
162
  return undefined;
@@ -115,7 +166,7 @@ export class StatusLineRenderer {
115
166
  const session = this.host.session;
116
167
  if (!session)
117
168
  return undefined;
118
- const label = this.host.statusThinkingLabel(session);
169
+ const label = this.statusThinkingDisplayLabel(session);
119
170
  const marker = ` ${label} ${this.host.formatContextUsagePercent(session)}`;
120
171
  const markerIndex = statusText.indexOf(marker);
121
172
  const startIndex = markerIndex >= 0 ? markerIndex + 1 : statusText.indexOf(label);
@@ -127,7 +178,7 @@ export class StatusLineRenderer {
127
178
  const session = this.host.session;
128
179
  if (!session)
129
180
  return undefined;
130
- const thinkingLabel = this.host.statusThinkingLabel(session);
181
+ const thinkingLabel = this.statusThinkingDisplayLabel(session);
131
182
  const contextLabel = this.host.formatContextUsagePercent(session);
132
183
  const marker = ` ${thinkingLabel} ${contextLabel}`;
133
184
  const markerIndex = statusText.indexOf(marker);
@@ -222,7 +273,7 @@ export class StatusLineRenderer {
222
273
  }] : [];
223
274
  this.pushDraftQueueWidgetSegment(segments, statusText);
224
275
  this.pushUserJumpWidgetSegment(segments, statusText);
225
- this.pushThinkingExpandWidgetSegment(segments, statusText);
276
+ this.pushThinkingExpandWidgetSegment(segments, statusText, layout);
226
277
  this.pushCompactToolsWidgetSegment(segments, statusText);
227
278
  this.pushTerminalBellSoundWidgetSegment(segments, statusText);
228
279
  this.pushWorkspaceSegments(segments, statusText, layout.workspaceLabel);
@@ -235,14 +286,15 @@ export class StatusLineRenderer {
235
286
  return segments;
236
287
  const modelLabel = this.host.statusModelLabel(session);
237
288
  const thinkingLabel = this.host.statusThinkingLabel(session);
289
+ const thinkingDisplayLabel = this.statusThinkingDisplayLabel(session);
238
290
  const contextLabel = this.host.formatContextUsagePercent(session);
239
- this.pushSegment(segments, statusText.indexOf(`${modelLabel} ${thinkingLabel} `), modelLabel.length, this.modelProviderColor(session));
240
- const thinkingMarkerStart = statusText.indexOf(` ${thinkingLabel} ${contextLabel}`);
291
+ this.pushSegment(segments, statusText.indexOf(`${modelLabel} ${thinkingDisplayLabel} `), modelLabel.length, this.modelProviderColor(session));
292
+ const thinkingMarkerStart = statusText.indexOf(` ${thinkingDisplayLabel} ${contextLabel}`);
241
293
  const thinkingStart = thinkingMarkerStart >= 0 ? thinkingMarkerStart + 1 : -1;
242
- this.pushSegment(segments, thinkingStart, thinkingLabel.length, this.thinkingLevelColor(thinkingLabel));
294
+ this.pushSegment(segments, thinkingStart, thinkingDisplayLabel.length, this.thinkingLevelColor(thinkingLabel));
243
295
  const contextPercent = this.host.roundedContextUsagePercent(session);
244
296
  if (contextPercent !== undefined && thinkingStart >= 0) {
245
- const contextStart = thinkingStart + thinkingLabel.length + 1;
297
+ const contextStart = thinkingStart + thinkingDisplayLabel.length + 1;
246
298
  this.pushSegment(segments, contextStart, contextLabel.length, this.host.contextUsagePercentColor(contextPercent));
247
299
  if (layout.contextBarLabel) {
248
300
  const barStart = contextStart + contextLabel.length + 1;
@@ -284,9 +336,13 @@ export class StatusLineRenderer {
284
336
  draftQueueWidgetText() {
285
337
  return this.host.queueableInputActive?.() ? APP_ICONS.timerSand : "";
286
338
  }
287
- pushThinkingExpandWidgetSegment(segments, statusText) {
339
+ pushThinkingExpandWidgetSegment(segments, statusText, layout) {
340
+ if (!layout.thinkingExpandWidget)
341
+ return;
288
342
  const buttonText = APP_ICONS.thinkingExpanded;
289
- const start = statusText.indexOf(buttonText);
343
+ const buttonMarker = ` ${buttonText} `;
344
+ const markerStart = statusText.indexOf(buttonMarker);
345
+ const start = markerStart >= 0 ? markerStart + 1 : -1;
290
346
  if (start < 0)
291
347
  return;
292
348
  this.pushSegment(segments, start, buttonText.length, this.host.allThinkingExpandedActive?.() ? this.host.theme.colors.info : this.host.theme.colors.muted);
@@ -307,18 +363,21 @@ export class StatusLineRenderer {
307
363
  }
308
364
  pushVoiceWidgetSegment(segments, statusText) {
309
365
  const widgetText = this.host.voiceStatusWidgetText();
366
+ if (widgetText.length <= 0)
367
+ return;
310
368
  const start = statusText.lastIndexOf(widgetText);
311
- if (start < 0 || widgetText.length <= 0)
369
+ const micStart = statusText.lastIndexOf(APP_ICONS.microphone);
370
+ if (start < 0 && micStart < 0)
312
371
  return;
313
372
  if (this.host.voiceStatusWidgetActive()) {
314
373
  const separatorIndex = widgetText.indexOf(" ");
315
374
  const micLength = separatorIndex >= 0 ? separatorIndex : widgetText.length;
316
- this.pushSegment(segments, start, micLength, this.host.theme.colors.error);
375
+ this.pushSegment(segments, micStart >= 0 ? micStart : start, micLength, this.host.theme.colors.error);
317
376
  return;
318
377
  }
319
378
  const separatorIndex = widgetText.indexOf(" ");
320
379
  const micLength = separatorIndex >= 0 ? separatorIndex : widgetText.length;
321
- this.pushSegment(segments, start, micLength, this.host.theme.colors.muted);
380
+ this.pushSegment(segments, micStart >= 0 ? micStart : start, micLength, this.host.theme.colors.muted);
322
381
  }
323
382
  pushWorkspaceSegments(segments, statusText, workspaceLabel) {
324
383
  const start = statusText.lastIndexOf(workspaceLabel);
@@ -350,16 +409,16 @@ export class StatusLineRenderer {
350
409
  const percentText = match[1];
351
410
  if (localStart === undefined || !percentText)
352
411
  continue;
353
- if (!prefixColored) {
354
- prefixColored = true;
355
- const prefixLength = modelUsageLabel.slice(0, localStart).trimEnd().length;
356
- this.pushSegment(segments, labelStart, prefixLength, this.host.theme.colors.selectionForeground);
357
- }
358
412
  const percent = Number.parseInt(percentText, 10);
359
413
  if (!Number.isFinite(percent))
360
414
  continue;
361
415
  const start = labelStart + localStart;
362
416
  const color = this.modelUsageProgressColor(percent);
417
+ if (!prefixColored) {
418
+ prefixColored = true;
419
+ const prefixLength = modelUsageLabel.slice(0, localStart).trimEnd().length;
420
+ this.pushSegment(segments, labelStart, prefixLength, color);
421
+ }
363
422
  this.pushSegment(segments, start, percentToken.length, color);
364
423
  const barStart = start + percentToken.length + 1;
365
424
  segments.push(...compactProgressBarSegments(barStart, percent, {
@@ -408,35 +467,15 @@ export class StatusLineRenderer {
408
467
  : modelProviderThemeColor(provider, this.host.theme.colors);
409
468
  }
410
469
  thinkingLevelColor(label) {
411
- const levels = this.availableThinkingLevels();
412
- const rank = levels.indexOf(label);
413
- if (rank >= 0)
414
- return this.thinkingRankColor(label, rank, levels.length);
415
- const fallbackLevels = ["off", "minimal", "low", "medium", "high", "xhigh"];
416
- const fallbackRank = fallbackLevels.indexOf(label);
417
- return fallbackRank >= 0
418
- ? this.thinkingRankColor(label, fallbackRank, fallbackLevels.length)
419
- : this.host.theme.colors.info;
470
+ return thinkingLevelThemeColor(label, this.host.theme.colors, this.availableThinkingLevels());
471
+ }
472
+ statusThinkingDisplayLabel(session) {
473
+ return `${APP_ICONS.lightbulb} ${this.host.statusThinkingLabel(session)}`;
420
474
  }
421
475
  availableThinkingLevels() {
422
476
  const levels = this.host.session?.getAvailableThinkingLevels();
423
477
  return Array.isArray(levels) && levels.length > 0 ? levels.map(String) : ["off", "minimal", "low", "medium", "high", "xhigh"];
424
478
  }
425
- thinkingRankColor(label, rank, count) {
426
- const baseColors = [
427
- this.host.theme.colors.muted,
428
- this.host.theme.colors.success,
429
- this.host.theme.colors.warning,
430
- this.host.theme.colors.toolMutation,
431
- this.host.theme.colors.error,
432
- this.host.theme.colors.thinkingXHigh,
433
- ];
434
- const colors = count > baseColors.length ? [this.host.theme.colors.statusForeground, ...baseColors] : baseColors;
435
- const fallbackLevels = ["off", "minimal", "low", "medium", "high", "xhigh"];
436
- const fallbackRank = fallbackLevels.indexOf(label);
437
- const colorIndex = count <= baseColors.length && fallbackRank >= 0 ? fallbackRank : rank;
438
- return colors[Math.max(0, Math.min(colors.length - 1, colorIndex))] ?? this.host.theme.colors.info;
439
- }
440
479
  contextBarLabel(status, width, workspaceLabel) {
441
480
  const session = this.host.session;
442
481
  if (!session)
@@ -457,21 +496,48 @@ export class StatusLineRenderer {
457
496
  endColumn: startColumn + stringDisplayWidth(widgetText),
458
497
  };
459
498
  }
460
- voiceWidgetLayout(startColumn, widgetText) {
461
- const separatorIndex = widgetText.indexOf(" ");
462
- const micText = separatorIndex >= 0 ? widgetText.slice(0, separatorIndex) : widgetText;
463
- const languageStartOffset = separatorIndex >= 0 ? stringDisplayWidth(widgetText.slice(0, separatorIndex + 1)) : stringDisplayWidth(widgetText);
464
- const afterLanguageIndex = widgetText.indexOf(" ", separatorIndex + 1);
465
- const languageTextEndIndex = afterLanguageIndex >= 0 ? afterLanguageIndex : widgetText.length;
466
- const languageEndOffset = stringDisplayWidth(widgetText.slice(0, languageTextEndIndex));
499
+ iconButtonText(icon) {
500
+ return padOrTrimPlain(` ${icon} `, STATUS_ICON_BUTTON_WIDTH);
501
+ }
502
+ voiceBorderWidgetText(widgetText) {
503
+ const parts = this.voiceBorderWidgetParts(widgetText);
504
+ if (!parts)
505
+ return "";
506
+ const micButton = this.iconButtonText(parts.buttonIconText);
507
+ if (parts.languageText) {
508
+ return `${micButton}${INPUT_BORDER_WIDGET_SEPARATOR}${parts.languageText}`;
509
+ }
510
+ return micButton;
511
+ }
512
+ voiceWidgetLayout(startColumn, sourceText, widgetText) {
513
+ const parts = this.voiceBorderWidgetParts(sourceText);
514
+ const languageStartOffset = parts?.languageText
515
+ ? STATUS_ICON_BUTTON_WIDTH + stringDisplayWidth(INPUT_BORDER_WIDGET_SEPARATOR)
516
+ : stringDisplayWidth(widgetText);
517
+ const languageEndOffset = parts?.languageText
518
+ ? languageStartOffset + stringDisplayWidth(parts.languageText)
519
+ : languageStartOffset;
467
520
  return {
468
521
  startColumn,
469
- micEndColumn: startColumn + stringDisplayWidth(micText),
522
+ micEndColumn: startColumn + STATUS_ICON_BUTTON_WIDTH,
470
523
  languageStartColumn: startColumn + languageStartOffset,
471
524
  languageEndColumn: startColumn + languageEndOffset,
472
525
  endColumn: startColumn + stringDisplayWidth(widgetText),
473
526
  };
474
527
  }
528
+ voiceBorderWidgetParts(widgetText) {
529
+ const tokens = widgetText.trim().split(/\s+/u).filter((token) => token.length > 0);
530
+ const iconText = tokens[0];
531
+ if (!iconText)
532
+ return undefined;
533
+ const maybeLanguage = tokens[1] ?? "";
534
+ const hasLanguage = /^[A-Z][A-Z0-9_-]*$/u.test(maybeLanguage);
535
+ const suffixText = tokens.slice(hasLanguage ? 2 : 1).join(" ");
536
+ return {
537
+ buttonIconText: suffixText || iconText,
538
+ languageText: hasLanguage ? maybeLanguage : "",
539
+ };
540
+ }
475
541
  statusDotColor() {
476
542
  switch (this.host.sessionActivity) {
477
543
  case "thinking":
@@ -483,6 +549,40 @@ export class StatusLineRenderer {
483
549
  }
484
550
  }
485
551
  }
552
+ function overlayText(text, startColumn, overlay) {
553
+ const start = Math.max(0, startColumn - 1);
554
+ const overlayWidth = stringDisplayWidth(overlay);
555
+ const startIndex = displayIndexForColumn(text, start + 1);
556
+ const endIndex = displayIndexForColumn(text, start + overlayWidth + 1);
557
+ const padded = text.padEnd(startIndex, " ");
558
+ return `${padded.slice(0, startIndex)}${overlay}${padded.slice(endIndex)}`;
559
+ }
560
+ export function thinkingLevelThemeColor(label, colors, availableLevels) {
561
+ const levels = availableLevels && availableLevels.length > 0 ? availableLevels.map(String) : ["off", "minimal", "low", "medium", "high", "xhigh"];
562
+ const rank = levels.indexOf(label);
563
+ if (rank >= 0)
564
+ return thinkingRankThemeColor(label, rank, levels.length, colors);
565
+ const fallbackLevels = ["off", "minimal", "low", "medium", "high", "xhigh"];
566
+ const fallbackRank = fallbackLevels.indexOf(label);
567
+ return fallbackRank >= 0
568
+ ? thinkingRankThemeColor(label, fallbackRank, fallbackLevels.length, colors)
569
+ : colors.info;
570
+ }
571
+ function thinkingRankThemeColor(label, rank, count, colors) {
572
+ const baseColors = [
573
+ colors.muted,
574
+ colors.success,
575
+ colors.modelOpenAI,
576
+ colors.warning,
577
+ colors.error,
578
+ colors.thinkingXHigh,
579
+ ];
580
+ const palette = count > baseColors.length ? [colors.statusForeground, ...baseColors] : baseColors;
581
+ const fallbackLevels = ["off", "minimal", "low", "medium", "high", "xhigh"];
582
+ const fallbackRank = fallbackLevels.indexOf(label);
583
+ const colorIndex = count <= baseColors.length && fallbackRank >= 0 ? fallbackRank : rank;
584
+ return palette[Math.max(0, Math.min(palette.length - 1, colorIndex))] ?? colors.info;
585
+ }
486
586
  export function modelProviderThemeColor(provider, colors) {
487
587
  const palette = modelProviderThemePalette(colors);
488
588
  const hash = hashString(provider.trim().toLowerCase());
@@ -1,16 +1,25 @@
1
- import { Toast, type ToastKind, type ToastVariant } from "../../ui.js";
1
+ import { type ToastEntry, type ToastKind, type ToastVariant } from "../../ui.js";
2
2
  export type AppToastControllerHost = {
3
+ activeScope?(): string | undefined;
3
4
  render(): void;
4
5
  };
5
6
  export declare class AppToastController {
6
7
  private readonly host;
7
- readonly toast: Toast;
8
+ private readonly toastsByScope;
8
9
  private readonly timers;
9
10
  constructor(host: AppToastControllerHost);
10
11
  showToast(message: string, kind?: ToastKind, options?: {
11
12
  durationMs?: number;
12
13
  variant?: ToastVariant;
14
+ scopeKey?: string;
13
15
  }): void;
14
- dismissToast(toastId: number): void;
16
+ dismissToast(toastId: number, scopeKey?: string): void;
17
+ dismissActiveDialog(scopeKey?: string): boolean;
18
+ visibleStates(scopeKey?: string): readonly ToastEntry[];
19
+ entry(toastId: number, scopeKey?: string): ToastEntry | undefined;
15
20
  clearToastTimers(): void;
21
+ private toastForScope;
22
+ private timersForScope;
23
+ private deleteScopeIfEmpty;
24
+ private normalizeScopeKey;
16
25
  }
@@ -2,13 +2,15 @@ import { Toast } from "../../ui.js";
2
2
  import { TOAST_DURATION_MS } from "../constants.js";
3
3
  export class AppToastController {
4
4
  host;
5
- toast = new Toast();
5
+ toastsByScope = new Map();
6
6
  timers = new Map();
7
7
  constructor(host) {
8
8
  this.host = host;
9
9
  }
10
10
  showToast(message, kind = "info", options = {}) {
11
- const toastId = this.toast.show(message, kind, options.variant ? { variant: options.variant } : {});
11
+ const scopeKey = this.normalizeScopeKey(options.scopeKey ?? this.host.activeScope?.());
12
+ const toast = this.toastForScope(scopeKey);
13
+ const toastId = toast.show(message, kind, options.variant ? { variant: options.variant } : {});
12
14
  if (kind === "error" || options.variant === "dialog") {
13
15
  this.host.render();
14
16
  return;
@@ -17,27 +19,83 @@ export class AppToastController {
17
19
  ? Math.floor(options.durationMs)
18
20
  : TOAST_DURATION_MS;
19
21
  const timer = setTimeout(() => {
20
- this.toast.hide(toastId);
21
- this.timers.delete(toastId);
22
+ toast.hide(toastId);
23
+ this.timers.get(scopeKey)?.delete(toastId);
24
+ this.deleteScopeIfEmpty(scopeKey);
22
25
  this.host.render();
23
26
  }, durationMs);
24
- this.timers.set(toastId, timer);
27
+ this.timersForScope(scopeKey).set(toastId, timer);
25
28
  timer.unref();
26
29
  this.host.render();
27
30
  }
28
- dismissToast(toastId) {
29
- const timer = this.timers.get(toastId);
31
+ dismissToast(toastId, scopeKey = this.normalizeScopeKey(this.host.activeScope?.())) {
32
+ const timers = this.timers.get(scopeKey);
33
+ const timer = timers?.get(toastId);
30
34
  if (timer) {
31
35
  clearTimeout(timer);
32
- this.timers.delete(toastId);
36
+ timers?.delete(toastId);
33
37
  }
34
- this.toast.hide(toastId);
38
+ this.toastsByScope.get(scopeKey)?.hide(toastId);
39
+ this.deleteScopeIfEmpty(scopeKey);
35
40
  this.host.render();
36
41
  }
42
+ dismissActiveDialog(scopeKey = this.normalizeScopeKey(this.host.activeScope?.())) {
43
+ const states = this.visibleStates(scopeKey);
44
+ let dialog;
45
+ for (let index = states.length - 1; index >= 0; index -= 1) {
46
+ const toast = states[index];
47
+ if (!toast)
48
+ continue;
49
+ if (toast.variant !== "dialog")
50
+ continue;
51
+ dialog = toast;
52
+ break;
53
+ }
54
+ if (!dialog)
55
+ return false;
56
+ this.dismissToast(dialog.id, scopeKey);
57
+ return true;
58
+ }
59
+ visibleStates(scopeKey = this.normalizeScopeKey(this.host.activeScope?.())) {
60
+ return this.toastsByScope.get(scopeKey)?.visibleStates ?? [];
61
+ }
62
+ entry(toastId, scopeKey = this.normalizeScopeKey(this.host.activeScope?.())) {
63
+ return this.toastsByScope.get(scopeKey)?.entry(toastId);
64
+ }
37
65
  clearToastTimers() {
38
- for (const timer of this.timers.values())
39
- clearTimeout(timer);
66
+ for (const timers of this.timers.values()) {
67
+ for (const timer of timers.values())
68
+ clearTimeout(timer);
69
+ }
40
70
  this.timers.clear();
41
- this.toast.hide();
71
+ for (const toast of this.toastsByScope.values())
72
+ toast.hide();
73
+ this.toastsByScope.clear();
74
+ }
75
+ toastForScope(scopeKey) {
76
+ let toast = this.toastsByScope.get(scopeKey);
77
+ if (!toast) {
78
+ toast = new Toast();
79
+ this.toastsByScope.set(scopeKey, toast);
80
+ }
81
+ return toast;
82
+ }
83
+ timersForScope(scopeKey) {
84
+ let timers = this.timers.get(scopeKey);
85
+ if (!timers) {
86
+ timers = new Map();
87
+ this.timers.set(scopeKey, timers);
88
+ }
89
+ return timers;
90
+ }
91
+ deleteScopeIfEmpty(scopeKey) {
92
+ const timers = this.timers.get(scopeKey);
93
+ if (timers && timers.size === 0)
94
+ this.timers.delete(scopeKey);
95
+ if (!this.toastsByScope.get(scopeKey)?.visible)
96
+ this.toastsByScope.delete(scopeKey);
97
+ }
98
+ normalizeScopeKey(scopeKey) {
99
+ return scopeKey ?? "";
42
100
  }
43
101
  }
@@ -1,6 +1,6 @@
1
1
  import { SessionManager, type EventBus, type AgentSessionRuntime, type LoadExtensionsResult, type SessionEntry } from "@earendil-works/pi-coding-agent";
2
2
  import { type PixConfig } from "../config.js";
3
- import type { AppOptions } from "./types.js";
3
+ import type { AppOptions, ThinkingLevel } from "./types.js";
4
4
  export type PiToolsSuiteInstallAction = "installed" | "already-installed" | "existing-kept" | "missing-source";
5
5
  export type PiToolsSuiteInstallResult = {
6
6
  action: PiToolsSuiteInstallAction;
@@ -39,6 +39,7 @@ export type CreatePixRuntimeOptions = {
39
39
  };
40
40
  type RuntimeSessionManagerModelState = Pick<SessionManager, "getEntries" | "getBranch">;
41
41
  export declare function resolvePixRuntimeModelRef(options: Pick<AppOptions, "modelRef">, sessionManager: RuntimeSessionManagerModelState, config?: PixConfig): string | undefined;
42
+ export declare function resolvePixRuntimeInitialThinkingLevel(options: Pick<AppOptions, "modelRef">, sessionManager: RuntimeSessionManagerModelState, config: PixConfig): ThinkingLevel | undefined;
42
43
  export declare function resolveSessionModelRefFromTail(entries: readonly SessionEntry[]): string | undefined;
43
44
  export declare function createPixRuntime(options: AppOptions, runtimeOptions?: CreatePixRuntimeOptions): Promise<AgentSessionRuntime>;
44
45
  export {};