pi-ui-extend 0.1.32 → 0.1.34

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 (95) hide show
  1. package/README.md +1 -1
  2. package/dist/app/app.d.ts +2 -0
  3. package/dist/app/app.js +28 -0
  4. package/dist/app/commands/command-session-actions.js +29 -1
  5. package/dist/app/constants.d.ts +1 -1
  6. package/dist/app/constants.js +2 -2
  7. package/dist/app/icons.d.ts +4 -9
  8. package/dist/app/icons.js +12 -35
  9. package/dist/app/model/model-usage-status.d.ts +2 -1
  10. package/dist/app/model/model-usage-status.js +33 -25
  11. package/dist/app/rendering/conversation-entry-renderer.d.ts +1 -0
  12. package/dist/app/rendering/conversation-tool-renderer.d.ts +1 -0
  13. package/dist/app/rendering/conversation-tool-renderer.js +12 -18
  14. package/dist/app/rendering/conversation-viewport.d.ts +4 -0
  15. package/dist/app/rendering/conversation-viewport.js +144 -13
  16. package/dist/app/rendering/dcp-stats.js +42 -16
  17. package/dist/app/rendering/render-controller.js +4 -0
  18. package/dist/app/rendering/status-line-renderer.d.ts +8 -1
  19. package/dist/app/rendering/status-line-renderer.js +36 -1
  20. package/dist/app/rendering/tab-line-renderer.js +2 -2
  21. package/dist/app/rendering/tool-block-renderer.d.ts +1 -0
  22. package/dist/app/rendering/tool-block-renderer.js +37 -11
  23. package/dist/app/runtime.js +1 -1
  24. package/dist/app/screen/mouse-controller.d.ts +5 -1
  25. package/dist/app/screen/mouse-controller.js +16 -0
  26. package/dist/app/screen/scroll-controller.d.ts +20 -0
  27. package/dist/app/screen/scroll-controller.js +127 -10
  28. package/dist/app/session/lazy-session-manager.js +35 -5
  29. package/dist/app/session/pix-system-message.d.ts +1 -0
  30. package/dist/app/session/pix-system-message.js +14 -3
  31. package/dist/app/session/queued-message-controller.d.ts +11 -4
  32. package/dist/app/session/queued-message-controller.js +74 -59
  33. package/dist/app/session/queued-message-entries.d.ts +2 -1
  34. package/dist/app/session/queued-message-entries.js +12 -1
  35. package/dist/app/session/session-event-controller.d.ts +42 -1
  36. package/dist/app/session/session-event-controller.js +500 -31
  37. package/dist/app/session/session-history.js +23 -4
  38. package/dist/app/session/tabs-controller.d.ts +11 -1
  39. package/dist/app/session/tabs-controller.js +102 -21
  40. package/dist/app/types.d.ts +14 -1
  41. package/dist/bundled-extensions/question/contract.d.ts +25 -0
  42. package/dist/bundled-extensions/question/contract.js +94 -0
  43. package/dist/bundled-extensions/question/index.d.ts +7 -0
  44. package/dist/bundled-extensions/question/index.js +28 -0
  45. package/dist/bundled-extensions/question/render.d.ts +4 -0
  46. package/dist/bundled-extensions/question/render.js +27 -0
  47. package/dist/bundled-extensions/question/result.d.ts +6 -0
  48. package/dist/bundled-extensions/question/result.js +84 -0
  49. package/dist/bundled-extensions/question/tool-description.d.ts +7 -0
  50. package/dist/bundled-extensions/question/tool-description.js +11 -0
  51. package/dist/bundled-extensions/question/tui.d.ts +2 -0
  52. package/dist/bundled-extensions/question/tui.js +577 -0
  53. package/dist/bundled-extensions/question/types.d.ts +103 -0
  54. package/dist/bundled-extensions/question/types.js +1 -0
  55. package/dist/bundled-extensions/session-title/config.d.ts +17 -0
  56. package/dist/bundled-extensions/session-title/config.js +150 -0
  57. package/dist/bundled-extensions/session-title/index.d.ts +5 -0
  58. package/dist/bundled-extensions/session-title/index.js +384 -0
  59. package/dist/bundled-extensions/session-title/title-generation.d.ts +26 -0
  60. package/dist/bundled-extensions/session-title/title-generation.js +141 -0
  61. package/dist/bundled-extensions/terminal-bell/index.d.ts +14 -0
  62. package/dist/bundled-extensions/terminal-bell/index.js +491 -0
  63. package/dist/config.d.ts +1 -1
  64. package/dist/config.js +2 -1
  65. package/dist/default-pix-config.js +2 -1
  66. package/dist/icon-theme.d.ts +7 -0
  67. package/dist/icon-theme.js +36 -0
  68. package/dist/schemas/pi-tools-suite-schema.d.ts +4 -0
  69. package/dist/schemas/pi-tools-suite-schema.js +5 -0
  70. package/dist/schemas/pix-schema.d.ts +1 -0
  71. package/dist/schemas/pix-schema.js +1 -0
  72. package/external/pi-tools-suite/README.md +7 -7
  73. package/external/pi-tools-suite/src/async-subagents/async-subagents.sample.jsonc +16 -16
  74. package/external/pi-tools-suite/src/async-subagents/core/state.ts +18 -4
  75. package/external/pi-tools-suite/src/async-subagents/core/types.ts +4 -0
  76. package/external/pi-tools-suite/src/async-subagents/tools/result.ts +14 -26
  77. package/external/pi-tools-suite/src/async-subagents/tools/subagents.ts +0 -1
  78. package/external/pi-tools-suite/src/dcp/config.ts +14 -14
  79. package/external/pi-tools-suite/src/dcp/index.ts +31 -43
  80. package/external/pi-tools-suite/src/dcp/state-persistence.ts +151 -0
  81. package/external/pi-tools-suite/src/default-pi-tools-suite-config.ts +25 -18
  82. package/external/pi-tools-suite/src/tool-descriptions.ts +34 -54
  83. package/package.json +3 -2
  84. package/schemas/pi-tools-suite.json +14 -0
  85. package/schemas/pix.json +7 -0
  86. package/extensions/question/contract.ts +0 -100
  87. package/extensions/question/index.ts +0 -34
  88. package/extensions/question/render.ts +0 -28
  89. package/extensions/question/result.ts +0 -86
  90. package/extensions/question/tool-description.ts +0 -11
  91. package/extensions/question/tui.ts +0 -629
  92. package/extensions/question/types.ts +0 -123
  93. package/extensions/session-title/config.ts +0 -164
  94. package/extensions/session-title/index.ts +0 -502
  95. package/extensions/terminal-bell/index.ts +0 -345
package/README.md CHANGED
@@ -212,7 +212,7 @@ Type `/` in the prompt to open the command picker. Commands that accept argument
212
212
  | `/import` | `<path.jsonl>` | Import and resume a session from a JSONL file. |
213
213
  | `/share` | — | Share the session as a private GitHub gist (requires `gh` CLI). |
214
214
  | `/copy` | — | Copy the last agent message to the clipboard. |
215
- | `/name` | `[name]` | Show or set the session display name. |
215
+ | `/name` | `[name]` | Set the session display name. Without arguments, generate one automatically using the session-title logic. |
216
216
  | `/session` | — | Show session info: message counts, token usage, and cost. |
217
217
  | `/usage` | — | Show local account quota usage and context window utilization. |
218
218
  | `/changelog` | — | Show the Pi package changelog. |
package/dist/app/app.d.ts CHANGED
@@ -109,6 +109,8 @@ export declare class PiUiExtendApp {
109
109
  private render;
110
110
  private scheduleRender;
111
111
  private syncScrollAfterInputEditorChange;
112
+ private conversationQuickScrollDirections;
113
+ private scrollConversationQuick;
112
114
  private renderStatusLine;
113
115
  private terminalColumns;
114
116
  private terminalRows;
package/dist/app/app.js CHANGED
@@ -183,10 +183,13 @@ export class PiUiExtendApp {
183
183
  loadSessionHistoryAsync: (options) => this.loadSessionHistoryAsync(options),
184
184
  captureSessionView: () => this.captureSessionView(),
185
185
  restoreSessionView: (view) => this.restoreSessionView(view),
186
+ restoreScrollState: (state) => this.scrollController.restoreState(state),
186
187
  syncUserSessionEntryMetadata: () => this.workspaceActions.syncUserSessionEntryMetadata(),
187
188
  captureInputState: () => this.inputEditor.draftState,
188
189
  restoreInputState: (state) => this.restoreTabInputState(state),
189
190
  closeMenusForTabSwitch: () => this.popupMenus.closeMenusForTabSwitch(),
191
+ captureAutoUserMessages: () => this.queuedMessages.captureAutoUserMessages(),
192
+ restoreAutoUserMessages: (messages) => this.queuedMessages.restoreAutoUserMessages(messages),
190
193
  captureDeferredUserMessages: () => this.queuedMessages.captureDeferredUserMessages(),
191
194
  restoreDeferredUserMessages: (messages) => this.queuedMessages.restoreDeferredUserMessages(messages),
192
195
  addEntry: (entry) => this.addEntry(entry),
@@ -283,6 +286,7 @@ export class PiUiExtendApp {
283
286
  terminalBellSoundStatusWidgetEnabled: () => this.terminalBellSoundController.isEnabled(),
284
287
  voiceStatusWidgetText: () => this.voiceController.statusWidgetText(),
285
288
  voiceStatusWidgetActive: () => this.voiceController.statusWidgetActive(),
289
+ conversationQuickScrollDirections: () => this.conversationQuickScrollDirections(),
286
290
  queueableInputActive: () => this.inputEditor.promptText.trimEnd().length > 0 || this.inputEditor.images.length > 0,
287
291
  userMessageJumpMenuActive: () => this.popupMenus.directMenu === "user-message-jump",
288
292
  allThinkingExpandedActive: () => this.allThinkingExpanded,
@@ -364,6 +368,7 @@ export class PiUiExtendApp {
364
368
  setSessionStatus: (session) => this.setSessionStatus(session),
365
369
  setSessionActivity: (activity) => this.setSessionActivity(activity),
366
370
  updateQueuedMessageStatus: () => this.queuedMessages.updateQueuedMessageStatus(),
371
+ flushAutoUserMessages: () => { void this.queuedMessages.flushAutoUserMessages(); },
367
372
  prepareWorkspaceMutation: (toolName, args) => this.workspaceActions.prepareWorkspaceMutation(toolName, args),
368
373
  workspaceMutationFromToolExecution: (input) => this.workspaceActions.workspaceMutationFromToolExecution(input),
369
374
  recordWorkspaceMutationForUserEntry: (entryId, mutation) => this.workspaceActions.recordWorkspaceMutationForUserEntry(entryId, mutation),
@@ -420,6 +425,7 @@ export class PiUiExtendApp {
420
425
  colors: this.theme.colors,
421
426
  pixConfig: this.pixConfig,
422
427
  outputFilters: this.outputFilters,
428
+ availableThinkingLevels: () => this.runtime?.session.getAvailableThinkingLevels(),
423
429
  hasDynamicConversationBlock: () => this.popupMenus.hasDynamicConversationBlock(),
424
430
  isDynamicConversationBlock: (entry) => this.popupMenus.isDynamicConversationBlock(entry),
425
431
  renderInlineUserMessageMenu: (entry, context) => this.popupMenus.renderInlineUserMessageMenu(entry, context),
@@ -433,6 +439,9 @@ export class PiUiExtendApp {
433
439
  hasOlderSessionHistory: () => this.sessionEvents.hasOlderSessionHistory(),
434
440
  isLoadingOlderSessionHistory: () => this.sessionEvents.isLoadingOlderSessionHistory(),
435
441
  loadOlderSessionHistory: (options) => this.sessionEvents.loadOlderSessionHistory(options),
442
+ hasNewerSessionHistory: () => this.sessionEvents.hasNewerSessionHistory(),
443
+ isLoadingNewerSessionHistory: () => this.sessionEvents.isLoadingNewerSessionHistory(),
444
+ loadNewerSessionHistory: (options) => this.sessionEvents.loadNewerSessionHistory(options),
436
445
  render: () => this.render(),
437
446
  });
438
447
  this.commandController = new AppCommandController({
@@ -564,6 +573,7 @@ export class PiUiExtendApp {
564
573
  this.render();
565
574
  });
566
575
  },
576
+ scrollConversationQuick: (direction) => this.scrollConversationQuick(direction),
567
577
  toggleAllThinkingExpanded: () => {
568
578
  this.allThinkingExpanded = !this.allThinkingExpanded;
569
579
  this.render();
@@ -904,12 +914,14 @@ export class PiUiExtendApp {
904
914
  return {
905
915
  entries: [...this.entries],
906
916
  eventState: this.sessionEvents.snapshotState(),
917
+ scrollState: this.scrollController.captureState(),
907
918
  };
908
919
  }
909
920
  restoreSessionView(view) {
910
921
  this.entries.splice(0, this.entries.length, ...view.entries);
911
922
  this.sessionEvents.restoreState(view.eventState);
912
923
  this.conversationViewport.clear();
924
+ this.scrollController.restoreState(view.scrollState);
913
925
  this.workspaceActions.syncUserSessionEntryMetadata();
914
926
  }
915
927
  loadSessionHistory() {
@@ -1109,6 +1121,22 @@ export class PiUiExtendApp {
1109
1121
  this.lastInputEditorContentVersion = contentVersion;
1110
1122
  this.scrollController.scrollToBottom();
1111
1123
  }
1124
+ conversationQuickScrollDirections() {
1125
+ const columns = this.terminalColumns();
1126
+ const terminalRows = this.terminalRows();
1127
+ const rows = Math.max(1, terminalRows - this.tabLineRenderer.panelRows(terminalRows));
1128
+ const { bodyHeight } = this.editorLayoutRenderer.computeLayout(columns, rows);
1129
+ if (bodyHeight <= 0)
1130
+ return { up: false, down: false };
1131
+ return this.scrollController.quickScrollDirections(columns, bodyHeight);
1132
+ }
1133
+ async scrollConversationQuick(direction) {
1134
+ const changed = direction === "up"
1135
+ ? await this.scrollController.scrollToAbsoluteTop()
1136
+ : await this.scrollController.scrollToAbsoluteBottom();
1137
+ if (changed)
1138
+ this.render();
1139
+ }
1112
1140
  renderStatusLine() {
1113
1141
  this.renderController.renderStatusLine();
1114
1142
  }
@@ -10,6 +10,8 @@ import { copyTextToClipboard } from "../screen/clipboard.js";
10
10
  import { formatAccountUsageReport, queryAccountUsageReport } from "../model/model-usage-status.js";
11
11
  import { checkPixUpdate, formatPixUpdateCheck, parsePixUpdateArgs, pixUpdateUsage } from "../cli/update.js";
12
12
  import { createStartupInfoMessage } from "../cli/startup-info.js";
13
+ import { loadSessionTitleConfig } from "../../bundled-extensions/session-title/config.js";
14
+ import { fallbackSessionTitleFromInput, firstUserMessageText, generateSessionTitle, sessionTitleModelRefs, } from "../../bundled-extensions/session-title/title-generation.js";
13
15
  export class SessionCommandActions {
14
16
  host;
15
17
  constructor(host) {
@@ -100,8 +102,34 @@ export class SessionCommandActions {
100
102
  return;
101
103
  const name = argumentsText.trim();
102
104
  if (!name) {
103
- this.host.addEntry({ id: createId("system"), kind: "system", text: `Session name: ${runtime.session.sessionName ?? "(none)"}` });
105
+ const branch = runtime.session.sessionManager.getBranch();
106
+ const firstPrompt = firstUserMessageText(branch);
107
+ if (!firstPrompt)
108
+ throw new Error("Cannot auto-name this session yet: no user messages found.");
109
+ const config = loadSessionTitleConfig(runtime.cwd);
110
+ const fallbackTitle = fallbackSessionTitleFromInput(firstPrompt, config.maxTitleChars);
111
+ const modelRefs = sessionTitleModelRefs(config);
112
+ let generatedName;
113
+ this.host.setStatus("generating session name");
114
+ this.host.render();
115
+ if (config.enabled) {
116
+ for (const modelRef of modelRefs) {
117
+ for (let attempt = 0; attempt < config.generationAttempts; attempt++) {
118
+ generatedName = await generateSessionTitle(firstPrompt.slice(0, config.maxInputChars).trim(), runtime.services.modelRegistry, config, modelRef, AbortSignal.timeout(config.timeoutMs));
119
+ if (generatedName)
120
+ break;
121
+ }
122
+ if (generatedName)
123
+ break;
124
+ }
125
+ }
126
+ const nextName = generatedName ?? fallbackTitle;
127
+ if (!nextName)
128
+ throw new Error("Could not generate a session name for this session.");
129
+ runtime.session.setSessionName(nextName);
130
+ this.host.addEntry({ id: createId("system"), kind: "system", text: `Session name set: ${nextName}` });
104
131
  this.host.setSessionStatus(runtime.session);
132
+ this.host.render();
105
133
  return;
106
134
  }
107
135
  runtime.session.setSessionName(name);
@@ -1,7 +1,7 @@
1
1
  import type { ResolvedToolRule } from "../config.js";
2
2
  export declare const THINKING_LEVELS: readonly ["off", "minimal", "low", "medium", "high", "xhigh"];
3
3
  export declare const THINKING_MENU_MAX_ROWS: number;
4
- export declare const PI_FAVORITE_MODEL_REFS: readonly ["amazon-bedrock/us.anthropic.claude-opus-4-6-v1", "anthropic/claude-opus-4-8", "openai/gpt-5.4", "azure-openai-responses/gpt-5.4", "openai-codex/gpt-5.5", "deepseek/deepseek-v4-pro", "google/gemini-3.1-pro-preview", "google-vertex/gemini-3.1-pro-preview", "github-copilot/gpt-5.4", "openrouter/moonshotai/kimi-k2.6", "vercel-ai-gateway/zai/glm-5.1", "xai/grok-4.20-0309-reasoning", "groq/openai/gpt-oss-120b", "cerebras/zai-glm-4.7", "zai/glm-5.1", "mistral/devstral-medium-latest", "minimax/MiniMax-M2.7", "minimax-cn/MiniMax-M2.7", "moonshotai/kimi-k2.6", "moonshotai-cn/kimi-k2.6", "huggingface/moonshotai/Kimi-K2.6", "fireworks/accounts/fireworks/models/kimi-k2p6", "together/moonshotai/Kimi-K2.6", "opencode/kimi-k2.6", "opencode-go/kimi-k2.6", "kimi-coding/kimi-for-coding", "cloudflare-workers-ai/@cf/moonshotai/kimi-k2.6", "cloudflare-ai-gateway/workers-ai/@cf/moonshotai/kimi-k2.6", "xiaomi/mimo-v2.5-pro", "xiaomi-token-plan-cn/mimo-v2.5-pro", "xiaomi-token-plan-ams/mimo-v2.5-pro", "xiaomi-token-plan-sgp/mimo-v2.5-pro"];
4
+ export declare const PI_FAVORITE_MODEL_REFS: readonly ["amazon-bedrock/us.anthropic.claude-opus-4-6-v1", "anthropic/claude-opus-4-8", "openai/gpt-5.4", "azure-openai-responses/gpt-5.4", "openai-codex/gpt-5.5", "deepseek/deepseek-v4-pro", "google/gemini-3.1-pro-preview", "google-vertex/gemini-3.1-pro-preview", "github-copilot/gpt-5.4", "openrouter/moonshotai/kimi-k2.6", "vercel-ai-gateway/zai/glm-5.2", "xai/grok-4.20-0309-reasoning", "groq/openai/gpt-oss-120b", "cerebras/zai-glm-4.7", "zai/glm-5.2", "mistral/devstral-medium-latest", "minimax/MiniMax-M2.7", "minimax-cn/MiniMax-M2.7", "moonshotai/kimi-k2.6", "moonshotai-cn/kimi-k2.6", "huggingface/moonshotai/Kimi-K2.6", "fireworks/accounts/fireworks/models/kimi-k2p6", "together/moonshotai/Kimi-K2.6", "opencode/kimi-k2.6", "opencode-go/kimi-k2.6", "kimi-coding/kimi-for-coding", "cloudflare-workers-ai/@cf/moonshotai/kimi-k2.6", "cloudflare-ai-gateway/workers-ai/@cf/moonshotai/kimi-k2.6", "xiaomi/mimo-v2.5-pro", "xiaomi-token-plan-cn/mimo-v2.5-pro", "xiaomi-token-plan-ams/mimo-v2.5-pro", "xiaomi-token-plan-sgp/mimo-v2.5-pro"];
5
5
  export declare const SLASH_COMMAND_MENU_MAX_ROWS = 6;
6
6
  export declare const RESUME_MENU_MAX_ROWS = 20;
7
7
  export declare const RESUME_MENU_INITIAL_SESSION_ROWS = 30;
@@ -12,11 +12,11 @@ export const PI_FAVORITE_MODEL_REFS = [
12
12
  "google-vertex/gemini-3.1-pro-preview",
13
13
  "github-copilot/gpt-5.4",
14
14
  "openrouter/moonshotai/kimi-k2.6",
15
- "vercel-ai-gateway/zai/glm-5.1",
15
+ "vercel-ai-gateway/zai/glm-5.2",
16
16
  "xai/grok-4.20-0309-reasoning",
17
17
  "groq/openai/gpt-oss-120b",
18
18
  "cerebras/zai-glm-4.7",
19
- "zai/glm-5.1",
19
+ "zai/glm-5.2",
20
20
  "mistral/devstral-medium-latest",
21
21
  "minimax/MiniMax-M2.7",
22
22
  "minimax-cn/MiniMax-M2.7",
@@ -1,5 +1,5 @@
1
- export declare const PIX_ICON_THEME_ENV = "PIX_ICON_THEME";
2
- export declare const PIX_USE_FALLBACK_ICONS_ENV = "PIX_USE_FALLBACK_ICONS";
1
+ import { type AppIconThemeName } from "../icon-theme.js";
2
+ export { appIconThemeFromFallbackFlag, appIconThemeOverrideFromEnv, parseAppIconThemeName, resolveAppIconThemeNameFromEnv, PIX_ICON_THEME_ENV, PIX_USE_FALLBACK_ICONS_ENV, type AppIconThemeName, } from "../icon-theme.js";
3
3
  declare const NERD_FONT_ICONS: {
4
4
  readonly alert: "󰀦";
5
5
  readonly autoFix: "󰁨";
@@ -28,17 +28,12 @@ declare const NERD_FONT_ICONS: {
28
28
  readonly toolBodyEnd: "└";
29
29
  readonly toolBodyGutter: "│";
30
30
  readonly toolPreviewTruncated: "⊞";
31
- readonly down: "󰁅";
31
+ readonly up: "";
32
+ readonly down: "↓";
32
33
  };
33
34
  export type AppIconName = keyof typeof NERD_FONT_ICONS;
34
35
  export type AppIconMap = Record<AppIconName, string>;
35
- export type AppIconThemeName = "nerdFont" | "fallback";
36
36
  export declare const APP_ICON_THEMES: Record<AppIconThemeName, AppIconMap>;
37
37
  export declare const APP_ICONS: AppIconMap;
38
38
  export declare function currentAppIconTheme(): AppIconThemeName;
39
- export declare function parseAppIconThemeName(value: unknown): AppIconThemeName | undefined;
40
- export declare function appIconThemeFromFallbackFlag(value: unknown): AppIconThemeName | undefined;
41
- export declare function resolveAppIconThemeNameFromEnv(env?: NodeJS.ProcessEnv): AppIconThemeName;
42
- export declare function appIconThemeOverrideFromEnv(env?: NodeJS.ProcessEnv): AppIconThemeName | undefined;
43
39
  export declare function setAppIconTheme(themeName: AppIconThemeName): void;
44
- export {};
package/dist/app/icons.js CHANGED
@@ -4,8 +4,13 @@
4
4
  //
5
5
  // Use codepoint escapes for private-use characters so editors cannot silently
6
6
  // substitute visually similar glyphs.
7
- export const PIX_ICON_THEME_ENV = "PIX_ICON_THEME";
8
- export const PIX_USE_FALLBACK_ICONS_ENV = "PIX_USE_FALLBACK_ICONS";
7
+ //
8
+ // Theme-name parsing/resolution lives in the dependency-free src/icon-theme.ts so
9
+ // configuration loading can resolve the theme without importing this app/ module
10
+ // (breaks the src/config.ts <-> src/app/icons.ts import cycle).
11
+ import { resolveAppIconThemeNameFromEnv, } from "../icon-theme.js";
12
+ // Re-exported for existing importers; the canonical home is src/icon-theme.ts.
13
+ export { appIconThemeFromFallbackFlag, appIconThemeOverrideFromEnv, parseAppIconThemeName, resolveAppIconThemeNameFromEnv, PIX_ICON_THEME_ENV, PIX_USE_FALLBACK_ICONS_ENV, } from "../icon-theme.js";
9
14
  const NERD_FONT_ICONS = {
10
15
  alert: "\u{f0026}",
11
16
  autoFix: "\u{f0068}",
@@ -34,10 +39,11 @@ const NERD_FONT_ICONS = {
34
39
  toolBodyEnd: "└",
35
40
  toolBodyGutter: "│",
36
41
  toolPreviewTruncated: "⊞",
37
- down: "\u{f0045}",
42
+ up: "",
43
+ down: "↓",
38
44
  };
39
45
  const FALLBACK_ICONS = {
40
- alert: "!",
46
+ alert: "",
41
47
  autoFix: "*",
42
48
  check: "✓",
43
49
  checkCircle: "✓",
@@ -64,7 +70,8 @@ const FALLBACK_ICONS = {
64
70
  toolBodyEnd: "`",
65
71
  toolBodyGutter: "|",
66
72
  toolPreviewTruncated: "+",
67
- down: "v",
73
+ up: "",
74
+ down: "↓",
68
75
  };
69
76
  export const APP_ICON_THEMES = {
70
77
  nerdFont: NERD_FONT_ICONS,
@@ -75,36 +82,6 @@ export const APP_ICONS = { ...APP_ICON_THEMES[currentAppIconThemeName] };
75
82
  export function currentAppIconTheme() {
76
83
  return currentAppIconThemeName;
77
84
  }
78
- export function parseAppIconThemeName(value) {
79
- if (typeof value !== "string")
80
- return undefined;
81
- const normalized = value.trim().toLowerCase().replace(/[\s_-]+/gu, "");
82
- if (normalized === "fallback" || normalized === "plain" || normalized === "ascii")
83
- return "fallback";
84
- if (normalized === "nerdfont" || normalized === "font" || normalized === "icons")
85
- return "nerdFont";
86
- return undefined;
87
- }
88
- export function appIconThemeFromFallbackFlag(value) {
89
- if (typeof value === "boolean")
90
- return value ? "fallback" : "nerdFont";
91
- if (typeof value !== "string")
92
- return undefined;
93
- const normalized = value.trim().toLowerCase();
94
- if (["1", "true", "yes", "on", "fallback"].includes(normalized))
95
- return "fallback";
96
- if (["0", "false", "no", "off", "nerdfont", "nerd-font"].includes(normalized))
97
- return "nerdFont";
98
- return undefined;
99
- }
100
- export function resolveAppIconThemeNameFromEnv(env = process.env) {
101
- return appIconThemeOverrideFromEnv(env) ?? "nerdFont";
102
- }
103
- export function appIconThemeOverrideFromEnv(env = process.env) {
104
- return appIconThemeFromFallbackFlag(env[PIX_USE_FALLBACK_ICONS_ENV])
105
- ?? parseAppIconThemeName(env[PIX_ICON_THEME_ENV])
106
- ?? undefined;
107
- }
108
85
  export function setAppIconTheme(themeName) {
109
86
  currentAppIconThemeName = themeName;
110
87
  Object.assign(APP_ICONS, APP_ICON_THEMES[themeName]);
@@ -7,13 +7,14 @@ export type ModelUsageDescriptor = BaseModelUsageDescriptor & ({
7
7
  } | {
8
8
  readonly kind: "google-antigravity";
9
9
  readonly quotaModelKey: string;
10
- readonly account: AntigravityQuotaAccount;
10
+ readonly account?: AntigravityQuotaAccount;
11
11
  readonly accounts?: readonly AntigravityQuotaAccount[];
12
12
  });
13
13
  export type ModelUsageLimitWindow = {
14
14
  readonly remainingPercent: number;
15
15
  readonly resetAt: number;
16
16
  readonly windowSeconds: number;
17
+ readonly hasKnownWindowDuration?: boolean;
17
18
  };
18
19
  export type ModelUsageStatus = {
19
20
  readonly modelKey: string;
@@ -1,9 +1,9 @@
1
1
  import { createHash } from "node:crypto";
2
- import { readFileSync } from "node:fs";
3
2
  import { readFile } from "node:fs/promises";
4
3
  import { homedir } from "node:os";
5
4
  import { join } from "node:path";
6
5
  import { formatCompactProgressBar } from "../../context-progress-bar.js";
6
+ import { APP_ICONS } from "../icons.js";
7
7
  const OPENAI_USAGE_URL = "https://chatgpt.com/backend-api/wham/usage";
8
8
  const ZAI_QUOTA_URL = "https://api.z.ai/api/monitor/usage/quota/limit";
9
9
  const ZHIPU_QUOTA_URL = "https://bigmodel.cn/api/monitor/usage/quota/limit";
@@ -35,16 +35,12 @@ export function modelUsageDescriptor(model) {
35
35
  }
36
36
  if (ANTIGRAVITY_QUOTA_PROVIDERS.has(provider)) {
37
37
  const quotaModelKey = resolveAntigravityQuotaModelKey(model);
38
- const accounts = readAllAntigravityQuotaAccounts();
39
- const account = readActiveAntigravityQuotaAccount(accounts);
40
- if (!quotaModelKey || !account)
38
+ if (!quotaModelKey)
41
39
  return undefined;
42
40
  return {
43
41
  kind: "google-antigravity",
44
- modelKey: `${model.provider}/${model.id}@all:${accounts.map((item) => item.cacheKey).join(",")}`,
42
+ modelKey: `${model.provider}/${model.id}`,
45
43
  quotaModelKey,
46
- account,
47
- accounts,
48
44
  };
49
45
  }
50
46
  return undefined;
@@ -395,7 +391,7 @@ export function googleAntigravityUsageStatusFromResponse(data, descriptor, now =
395
391
  modelKey: descriptor.modelKey,
396
392
  provider: "google-antigravity",
397
393
  updatedAt: now,
398
- ...(descriptor.account.email ? { accountEmail: descriptor.account.email } : {}),
394
+ ...(descriptor.account?.email ? { accountEmail: descriptor.account.email } : {}),
399
395
  ...(weekly ? { weekly } : {}),
400
396
  ...(hourly ? { hourly } : {}),
401
397
  };
@@ -413,7 +409,9 @@ function googleAntigravityWindowFromResponse(data, quotaModelKey, now) {
413
409
  }
414
410
  async function queryGoogleAntigravityModelUsage(descriptor) {
415
411
  const now = Date.now();
416
- const accounts = descriptor.accounts?.length ? descriptor.accounts : [descriptor.account];
412
+ const accounts = await readAllAntigravityQuotaAccounts();
413
+ if (accounts.length === 0)
414
+ return undefined;
417
415
  const windows = (await Promise.all(accounts.map(async (account) => {
418
416
  try {
419
417
  const response = await fetchGoogleAntigravityQuotaForAccount(account, now);
@@ -451,7 +449,7 @@ const GOOGLE_ACCOUNT_QUOTA_WINDOWS = [
451
449
  { label: "G3 Pro", quotaModelKey: "gemini-3.1-pro-low" },
452
450
  ];
453
451
  async function queryGoogleAntigravityAccountUsage(now) {
454
- const accounts = readAllAntigravityQuotaAccounts();
452
+ const accounts = await readAllAntigravityQuotaAccounts();
455
453
  const results = await Promise.all(accounts.map(async (account) => {
456
454
  const accountLabel = account.email ?? maskCredential(account.refreshToken);
457
455
  try {
@@ -474,12 +472,8 @@ async function queryGoogleAntigravityAccountUsage(now) {
474
472
  }));
475
473
  return results;
476
474
  }
477
- function readActiveAntigravityQuotaAccount(accounts = readAllAntigravityQuotaAccounts()) {
478
- const credential = readPiAuthSync().antigravity;
479
- return accounts[clampAccountIndex(credential?.activeIndex, accounts.length)];
480
- }
481
- function readAllAntigravityQuotaAccounts() {
482
- const credential = readPiAuthSync().antigravity;
475
+ async function readAllAntigravityQuotaAccounts() {
476
+ const credential = (await readPiAuth()).antigravity;
483
477
  if (!credential)
484
478
  return [];
485
479
  const credentialClient = getGoogleOAuthClientCredentials(credential);
@@ -504,14 +498,6 @@ function readAllAntigravityQuotaAccounts() {
504
498
  }) : undefined;
505
499
  return account ? [account] : [];
506
500
  }
507
- function readPiAuthSync() {
508
- try {
509
- return JSON.parse(readFileSync(getPiAuthPath(), "utf8"));
510
- }
511
- catch {
512
- return {};
513
- }
514
- }
515
501
  function getAccountRefreshToken(account) {
516
502
  if (account.refreshToken)
517
503
  return account.refreshToken;
@@ -844,6 +830,7 @@ function modelUsageWindow(window, now) {
844
830
  remainingPercent: clampPercent(Math.round(100 - window.used_percent)),
845
831
  resetAt: now + Math.max(0, Math.round(window.reset_after_seconds)) * 1000,
846
832
  windowSeconds: Math.max(0, Math.round(window.limit_window_seconds)),
833
+ hasKnownWindowDuration: true,
847
834
  };
848
835
  }
849
836
  function accountWindowFromRateLimit(window, now) {
@@ -930,5 +917,26 @@ function maskCredential(value) {
930
917
  return `${visible.slice(0, 4)}****${visible.slice(-4)}`;
931
918
  }
932
919
  function formatUsageWindow(_prefix, window, now) {
933
- return `${window.remainingPercent}% ${formatCompactProgressBar(window.remainingPercent)} ${formatDurationShort(window.resetAt, now)}`;
920
+ const warning = modelUsageWindowWillExhaustBeforeReset(window, now) ? ` ${APP_ICONS.alert}` : "";
921
+ return `${window.remainingPercent}% ${formatCompactProgressBar(window.remainingPercent)}${warning} ${formatDurationShort(window.resetAt, now)}`;
922
+ }
923
+ function modelUsageWindowWillExhaustBeforeReset(window, now) {
924
+ if (!window.hasKnownWindowDuration)
925
+ return false;
926
+ if (window.windowSeconds <= DAY_SECONDS)
927
+ return false;
928
+ if (window.remainingPercent <= 0)
929
+ return false;
930
+ const timeUntilResetSeconds = Math.max(0, (window.resetAt - now) / 1000);
931
+ const elapsedSeconds = Math.max(0, window.windowSeconds - timeUntilResetSeconds);
932
+ if (elapsedSeconds <= 0)
933
+ return false;
934
+ const total = 100;
935
+ const used = total - window.remainingPercent;
936
+ if (used <= 0)
937
+ return false;
938
+ const remaining = total - used;
939
+ const averageRate = used / elapsedSeconds;
940
+ const projectedSecondsUntilExhaustion = remaining / averageRate;
941
+ return projectedSecondsUntilExhaustion < timeUntilResetSeconds;
934
942
  }
@@ -11,6 +11,7 @@ export type ConversationEntryRenderOptions = {
11
11
  colors: Theme["colors"];
12
12
  pixConfig: PixConfig;
13
13
  outputFilters: readonly RegExp[];
14
+ availableThinkingLevels?: readonly string[];
14
15
  superCompactTools?: boolean;
15
16
  allThinkingExpanded?: boolean;
16
17
  renderInlineUserMessageMenu: (entry: Extract<Entry, {
@@ -5,6 +5,7 @@ export type ConversationToolRenderOptions = {
5
5
  cwd: string;
6
6
  pixConfig: PixConfig;
7
7
  colors: Theme["colors"];
8
+ availableThinkingLevels?: readonly string[];
8
9
  superCompactTools?: boolean;
9
10
  allThinkingExpanded?: boolean;
10
11
  };
@@ -1,12 +1,13 @@
1
1
  import { resolveColor, resolveToolRule } from "../../config.js";
2
2
  import { formatMarkdownTables, markdownSyntaxHighlightsForText } from "../../markdown-format.js";
3
3
  import { renderToolDisplay } from "../../tool-renderers/index.js";
4
- import { DEFAULT_THINKING_TOOL_RULE, SUBAGENT_STATUSES, THINKING_TOOL_NAME, TODO_TOOL_NAME } from "../constants.js";
4
+ import { SUBAGENT_STATUSES, THINKING_TOOL_NAME, TODO_TOOL_NAME } from "../constants.js";
5
5
  import { attachImageClickTargets } from "../screen/image-click-targets.js";
6
6
  import { formatStructuredText } from "./message-content.js";
7
7
  import { formatSubagentTimestamp, isSubagentRunRenderDetails, isSubagentsToolName, subagentRunName, subagentStatusIcon, taskPreviewMap, } from "../subagents/subagents-model.js";
8
8
  import { formatTodoTaskLine, isTodoDetails, visibleTodoTasks } from "../todo/todo-model.js";
9
9
  import { renderToolBlock } from "./tool-block-renderer.js";
10
+ import { thinkingLevelThemeColor } from "./status-line-renderer.js";
10
11
  export function renderConversationToolEntry(entry, width, options) {
11
12
  const todoLines = renderTodoToolEntry(entry, width, options);
12
13
  if (todoLines)
@@ -46,12 +47,15 @@ export function renderConversationToolEntry(entry, width, options) {
46
47
  return attachImageClickTargets(lines, entry.id, entry.images, { foreground: options.colors.info, underline: true });
47
48
  }
48
49
  export function renderThinkingEntry(entry, width, options) {
49
- const rule = resolveThinkingToolRule(options.pixConfig);
50
+ const rule = resolveToolRule(THINKING_TOOL_NAME, options.pixConfig.toolRenderer);
50
51
  const markdownText = entry.text ? formatMarkdownTables(entry.text, Math.max(1, width - 2)) : "";
51
52
  const expandedText = trimTrailingBlankLines(markdownText);
52
53
  const forceExpanded = Boolean(options.allThinkingExpanded);
53
54
  const compactExpandedText = options.superCompactTools && forceExpanded ? removeBlankLines(expandedText) : expandedText;
54
55
  const expanded = forceExpanded || (entry.expanded && expandedText.trim().length > 0);
56
+ const headerColorOverride = entry.level
57
+ ? thinkingLevelThemeColor(entry.level, options.colors, options.availableThinkingLevels)
58
+ : undefined;
55
59
  return renderToolBlock({
56
60
  id: entry.id,
57
61
  toolName: THINKING_TOOL_NAME,
@@ -63,7 +67,12 @@ export function renderThinkingEntry(entry, width, options) {
63
67
  expandedText: compactExpandedText || "(empty)",
64
68
  bodyWrap: "word",
65
69
  syntaxHighlight: compactExpandedText ? markdownSyntaxHighlightsForText(compactExpandedText) : undefined,
66
- }, rule, width, options.colors, { superCompact: Boolean(options.superCompactTools && !forceExpanded), backgroundOverride: options.colors.thinkingMessageBackground, skipHeaderBackground: true, showGutter: false });
70
+ }, rule, width, options.colors, {
71
+ superCompact: Boolean(options.superCompactTools && !forceExpanded),
72
+ backgroundOverride: options.colors.thinkingMessageBackground,
73
+ showGutter: true,
74
+ ...(headerColorOverride === undefined ? {} : { headerColorOverride }),
75
+ });
67
76
  }
68
77
  function trimTrailingBlankLines(text) {
69
78
  return text.replace(/(?:\r?\n[ \t]*)+$/u, "");
@@ -199,18 +208,3 @@ function formatSubagentToolLine(agent, preview) {
199
208
  parts.push(`retry:${agent.retryCount}`);
200
209
  return parts.join(" ");
201
210
  }
202
- function resolveThinkingToolRule(pixConfig) {
203
- const configured = pixConfig.toolRenderer.tools[THINKING_TOOL_NAME];
204
- if (!configured)
205
- return DEFAULT_THINKING_TOOL_RULE;
206
- const rule = {
207
- previewLines: configured.previewLines ?? DEFAULT_THINKING_TOOL_RULE.previewLines,
208
- direction: configured.direction ?? DEFAULT_THINKING_TOOL_RULE.direction,
209
- color: configured.color ?? DEFAULT_THINKING_TOOL_RULE.color,
210
- };
211
- if (configured.compactHidden != null)
212
- rule.compactHidden = configured.compactHidden;
213
- if (configured.hidden != null)
214
- rule.hidden = configured.hidden;
215
- return rule;
216
- }
@@ -9,6 +9,7 @@ export type ConversationViewportHost = {
9
9
  readonly colors: Theme["colors"];
10
10
  readonly pixConfig: PixConfig;
11
11
  readonly outputFilters: readonly RegExp[];
12
+ availableThinkingLevels?(): readonly string[] | undefined;
12
13
  readonly superCompactTools?: boolean;
13
14
  readonly allThinkingExpanded?: boolean;
14
15
  hasDynamicConversationBlock?(): boolean;
@@ -36,11 +37,14 @@ export declare class ConversationViewport {
36
37
  entries(): Entry[];
37
38
  blockForEntry(entry: Entry, width: number): ConversationBlockCache;
38
39
  entryBlockPositions(width: number): ConversationEntryBlockPosition[];
40
+ entryBlockPositionById(width: number, entryId: string): ConversationEntryBlockPosition | undefined;
39
41
  measuredLineCountForEntries(width: number, entryIds: readonly string[]): number;
40
42
  private layoutForWidth;
41
43
  private buildLayout;
42
44
  private previousMeasuredLineCount;
43
45
  private layoutStructureChanged;
46
+ private syncLayoutStructure;
47
+ private rebuildOffsets;
44
48
  private refreshDirtyLayoutEntries;
45
49
  private blockCacheForWidth;
46
50
  private refreshDynamicLayoutEntries;