pi-ui-extend 0.1.13 → 0.1.15

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 (92) hide show
  1. package/README.md +1 -1
  2. package/dist/app/app.d.ts +5 -0
  3. package/dist/app/app.js +82 -12
  4. package/dist/app/commands/command-controller.js +1 -0
  5. package/dist/app/commands/command-host.d.ts +3 -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.js +3 -0
  9. package/dist/app/commands/command-registry.d.ts +1 -0
  10. package/dist/app/commands/command-registry.js +8 -0
  11. package/dist/app/extensions/extension-ui-controller.d.ts +16 -5
  12. package/dist/app/extensions/extension-ui-controller.js +99 -61
  13. package/dist/app/input/input-action-controller.d.ts +1 -0
  14. package/dist/app/input/input-action-controller.js +8 -2
  15. package/dist/app/logger.d.ts +25 -0
  16. package/dist/app/logger.js +90 -0
  17. package/dist/app/model/model-usage-status.js +30 -15
  18. package/dist/app/popup/menu-items-controller.d.ts +2 -0
  19. package/dist/app/popup/menu-items-controller.js +45 -6
  20. package/dist/app/popup/popup-action-controller.d.ts +2 -1
  21. package/dist/app/popup/popup-action-controller.js +7 -4
  22. package/dist/app/popup/popup-menu-controller.d.ts +36 -23
  23. package/dist/app/popup/popup-menu-controller.js +68 -322
  24. package/dist/app/rendering/conversation-entry-renderer.js +3 -3
  25. package/dist/app/rendering/conversation-viewport.d.ts +10 -2
  26. package/dist/app/rendering/conversation-viewport.js +157 -16
  27. package/dist/app/rendering/editor-panels.js +4 -2
  28. package/dist/app/rendering/popup-menu-renderer.d.ts +50 -0
  29. package/dist/app/rendering/popup-menu-renderer.js +307 -0
  30. package/dist/app/rendering/render-controller.js +5 -13
  31. package/dist/app/rendering/status-line-renderer.d.ts +1 -1
  32. package/dist/app/rendering/status-line-renderer.js +27 -24
  33. package/dist/app/rendering/toast-controller.d.ts +11 -3
  34. package/dist/app/rendering/toast-controller.js +53 -12
  35. package/dist/app/runtime.d.ts +2 -1
  36. package/dist/app/runtime.js +20 -10
  37. package/dist/app/screen/mouse-controller.d.ts +2 -2
  38. package/dist/app/screen/mouse-controller.js +27 -48
  39. package/dist/app/screen/screen-styler.d.ts +1 -1
  40. package/dist/app/screen/screen-styler.js +9 -7
  41. package/dist/app/screen/scroll-controller.d.ts +11 -9
  42. package/dist/app/screen/scroll-controller.js +50 -45
  43. package/dist/app/session/lazy-session-manager.d.ts +11 -0
  44. package/dist/app/session/lazy-session-manager.js +539 -0
  45. package/dist/app/session/pix-system-message.d.ts +16 -0
  46. package/dist/app/session/pix-system-message.js +64 -0
  47. package/dist/app/session/session-event-controller.d.ts +11 -0
  48. package/dist/app/session/session-event-controller.js +58 -2
  49. package/dist/app/session/session-history.d.ts +18 -0
  50. package/dist/app/session/session-history.js +72 -3
  51. package/dist/app/session/session-lifecycle-controller.d.ts +6 -2
  52. package/dist/app/session/session-lifecycle-controller.js +7 -2
  53. package/dist/app/session/tabs-controller.d.ts +13 -1
  54. package/dist/app/session/tabs-controller.js +248 -27
  55. package/dist/app/todo/todo-model.d.ts +3 -1
  56. package/dist/app/todo/todo-model.js +14 -2
  57. package/dist/app/types.d.ts +5 -2
  58. package/dist/app/workspace/workspace-actions-controller.d.ts +2 -0
  59. package/dist/app/workspace/workspace-actions-controller.js +12 -0
  60. package/dist/config.d.ts +5 -1
  61. package/dist/config.js +73 -25
  62. package/dist/default-pix-config.js +2 -0
  63. package/dist/schemas/pi-tools-suite-schema.d.ts +1 -0
  64. package/dist/schemas/pi-tools-suite-schema.js +1 -0
  65. package/dist/schemas/pix-schema.d.ts +2 -1
  66. package/dist/schemas/pix-schema.js +5 -4
  67. package/dist/terminal-width.d.ts +2 -0
  68. package/dist/terminal-width.js +64 -3
  69. package/external/pi-tools-suite/README.md +1 -0
  70. package/external/pi-tools-suite/src/antigravity-auth/auth-store.ts +12 -3
  71. package/external/pi-tools-suite/src/antigravity-auth/commands.ts +2 -4
  72. package/external/pi-tools-suite/src/antigravity-auth/constants.ts +2 -2
  73. package/external/pi-tools-suite/src/antigravity-auth/index.ts +8 -2
  74. package/external/pi-tools-suite/src/antigravity-auth/oauth.ts +102 -50
  75. package/external/pi-tools-suite/src/antigravity-auth/status.ts +81 -2
  76. package/external/pi-tools-suite/src/antigravity-auth/stream.ts +29 -8
  77. package/external/pi-tools-suite/src/config.ts +8 -0
  78. package/external/pi-tools-suite/src/dcp/index.ts +16 -1
  79. package/external/pi-tools-suite/src/dcp/state.ts +35 -0
  80. package/external/pi-tools-suite/src/default-pi-tools-suite-config.ts +3 -0
  81. package/external/pi-tools-suite/src/todo/index.ts +181 -11
  82. package/external/pi-tools-suite/src/todo/state/state-reducer.ts +23 -10
  83. package/external/pi-tools-suite/src/todo/todo.ts +10 -5
  84. package/external/pi-tools-suite/src/todo/tool/response-envelope.ts +33 -6
  85. package/external/pi-tools-suite/src/todo/tool/types.ts +9 -1
  86. package/external/pi-tools-suite/src/todo/view/format.ts +2 -1
  87. package/external/pi-tools-suite/src/tool-descriptions.ts +2 -1
  88. package/external/pi-tools-suite/src/usage/index.ts +5 -2
  89. package/external/pi-tools-suite/src/usage/lib/google.ts +6 -13
  90. package/package.json +1 -1
  91. package/schemas/pi-tools-suite.json +4 -0
  92. package/schemas/pix.json +6 -2
@@ -57,7 +57,6 @@ export class AppRenderController {
57
57
  const underTabsOverlayStartRow = Math.min(rows, topReservedRows + 1);
58
58
  const underTabsOverlayLines = menuLines.slice(0, Math.max(0, statusRow - underTabsOverlayStartRow));
59
59
  const { lines: visible, metrics: scrollMetrics } = this.deps.scrollController.conversationView(columns, bodyHeight);
60
- const scrollBar = this.deps.scrollController.scrollBarForMetrics(scrollMetrics);
61
60
  const conversationColumns = Math.max(1, Math.min(columns, scrollMetrics.viewportColumns));
62
61
  this.deps.mouseController.syncConversationSelectionForRender(scrollMetrics.start, bodyHeight, topReservedRows, conversationColumns);
63
62
  this.deps.mouseController.renderedTargets.clear();
@@ -130,17 +129,6 @@ export class AppRenderController {
130
129
  setRenderedBackground(row, rendered?.backgroundOverride);
131
130
  appendFrameOutput("conversation", row, this.renderFrameRow(row, this.deps.screenStyler.styleBaseLine(row, rendered, conversationColumns)));
132
131
  }
133
- if (scrollBar && columns > 0) {
134
- for (let layoutRow = 1; layoutRow <= bodyHeight; layoutRow += 1) {
135
- const row = toScreenRow(layoutRow);
136
- const isThumb = layoutRow >= scrollBar.thumbStartRow && layoutRow <= scrollBar.thumbEndRow;
137
- const marker = isThumb ? " " : "│";
138
- appendFrameOutput("conversation", row, `\x1b[${row};${columns}H${colorize(marker, {
139
- foreground: this.deps.theme.colors.inputBorder,
140
- ...(isThumb ? { background: this.deps.theme.colors.inputBorder } : {}),
141
- })}`);
142
- }
143
- }
144
132
  const aboveEditorStartRow = inputSeparatorRow + 1;
145
133
  for (let index = 0; index < aboveEditorLines.length; index += 1) {
146
134
  const rendered = frameRenderedLine(aboveEditorLines[index], columns, this.deps.theme, this.deps.screenStyler);
@@ -245,7 +233,7 @@ export class AppRenderController {
245
233
  appendFrameOutput(regionForOverlayRow(row), row, this.renderFrameRow(row, this.deps.popupMenus.styleOverlayLine(row, line ?? { text: "" }, columns)));
246
234
  }
247
235
  }
248
- for (const toastOverlay of renderToastOverlays(this.deps.toastController.toast.visibleStates, columns, Math.max(0, statusRow - topReservedRows - 1), this.deps.theme)) {
236
+ for (const toastOverlay of renderToastOverlays(visibleToastStates(this.deps.toastController), columns, Math.max(0, statusRow - topReservedRows - 1), this.deps.theme)) {
249
237
  const row = topReservedRows + toastOverlay.row;
250
238
  const rowText = this.deps.mouseController.renderedRowTexts.get(row) ?? "";
251
239
  if (toastOverlay.target)
@@ -326,6 +314,10 @@ export class AppRenderController {
326
314
  return { row: Math.min(2, rows - 1), text, output };
327
315
  }
328
316
  }
317
+ function visibleToastStates(toastController) {
318
+ const candidate = toastController;
319
+ return typeof candidate.visibleStates === "function" ? candidate.visibleStates() : candidate.toast?.visibleStates ?? [];
320
+ }
329
321
  function inputFrameLine(width, edge) {
330
322
  if (width <= 0)
331
323
  return "";
@@ -67,10 +67,10 @@ export declare class StatusLineRenderer {
67
67
  private modelProviderColor;
68
68
  private thinkingLevelColor;
69
69
  private availableThinkingLevels;
70
- private thinkingRankColor;
71
70
  private contextBarLabel;
72
71
  private widgetLayout;
73
72
  private voiceWidgetLayout;
74
73
  private statusDotColor;
75
74
  }
75
+ export declare function thinkingLevelThemeColor(label: string, colors: Theme["colors"], availableLevels?: readonly string[]): string;
76
76
  export declare function modelProviderThemeColor(provider: string, colors: Theme["colors"]): string;
@@ -408,35 +408,12 @@ export class StatusLineRenderer {
408
408
  : modelProviderThemeColor(provider, this.host.theme.colors);
409
409
  }
410
410
  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;
411
+ return thinkingLevelThemeColor(label, this.host.theme.colors, this.availableThinkingLevels());
420
412
  }
421
413
  availableThinkingLevels() {
422
414
  const levels = this.host.session?.getAvailableThinkingLevels();
423
415
  return Array.isArray(levels) && levels.length > 0 ? levels.map(String) : ["off", "minimal", "low", "medium", "high", "xhigh"];
424
416
  }
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
417
  contextBarLabel(status, width, workspaceLabel) {
441
418
  const session = this.host.session;
442
419
  if (!session)
@@ -483,6 +460,32 @@ export class StatusLineRenderer {
483
460
  }
484
461
  }
485
462
  }
463
+ export function thinkingLevelThemeColor(label, colors, availableLevels) {
464
+ const levels = availableLevels && availableLevels.length > 0 ? availableLevels.map(String) : ["off", "minimal", "low", "medium", "high", "xhigh"];
465
+ const rank = levels.indexOf(label);
466
+ if (rank >= 0)
467
+ return thinkingRankThemeColor(label, rank, levels.length, colors);
468
+ const fallbackLevels = ["off", "minimal", "low", "medium", "high", "xhigh"];
469
+ const fallbackRank = fallbackLevels.indexOf(label);
470
+ return fallbackRank >= 0
471
+ ? thinkingRankThemeColor(label, fallbackRank, fallbackLevels.length, colors)
472
+ : colors.info;
473
+ }
474
+ function thinkingRankThemeColor(label, rank, count, colors) {
475
+ const baseColors = [
476
+ colors.muted,
477
+ colors.success,
478
+ colors.warning,
479
+ colors.toolMutation,
480
+ colors.error,
481
+ colors.thinkingXHigh,
482
+ ];
483
+ const palette = count > baseColors.length ? [colors.statusForeground, ...baseColors] : baseColors;
484
+ const fallbackLevels = ["off", "minimal", "low", "medium", "high", "xhigh"];
485
+ const fallbackRank = fallbackLevels.indexOf(label);
486
+ const colorIndex = count <= baseColors.length && fallbackRank >= 0 ? fallbackRank : rank;
487
+ return palette[Math.max(0, Math.min(palette.length - 1, colorIndex))] ?? colors.info;
488
+ }
486
489
  export function modelProviderThemeColor(provider, colors) {
487
490
  const palette = modelProviderThemePalette(colors);
488
491
  const hash = hashString(provider.trim().toLowerCase());
@@ -1,16 +1,24 @@
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
+ visibleStates(scopeKey?: string): readonly ToastEntry[];
18
+ entry(toastId: number, scopeKey?: string): ToastEntry | undefined;
15
19
  clearToastTimers(): void;
20
+ private toastForScope;
21
+ private timersForScope;
22
+ private deleteScopeIfEmpty;
23
+ private normalizeScopeKey;
16
24
  }
@@ -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,66 @@ 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
+ visibleStates(scopeKey = this.normalizeScopeKey(this.host.activeScope?.())) {
43
+ return this.toastsByScope.get(scopeKey)?.visibleStates ?? [];
44
+ }
45
+ entry(toastId, scopeKey = this.normalizeScopeKey(this.host.activeScope?.())) {
46
+ return this.toastsByScope.get(scopeKey)?.entry(toastId);
47
+ }
37
48
  clearToastTimers() {
38
- for (const timer of this.timers.values())
39
- clearTimeout(timer);
49
+ for (const timers of this.timers.values()) {
50
+ for (const timer of timers.values())
51
+ clearTimeout(timer);
52
+ }
40
53
  this.timers.clear();
41
- this.toast.hide();
54
+ for (const toast of this.toastsByScope.values())
55
+ toast.hide();
56
+ this.toastsByScope.clear();
57
+ }
58
+ toastForScope(scopeKey) {
59
+ let toast = this.toastsByScope.get(scopeKey);
60
+ if (!toast) {
61
+ toast = new Toast();
62
+ this.toastsByScope.set(scopeKey, toast);
63
+ }
64
+ return toast;
65
+ }
66
+ timersForScope(scopeKey) {
67
+ let timers = this.timers.get(scopeKey);
68
+ if (!timers) {
69
+ timers = new Map();
70
+ this.timers.set(scopeKey, timers);
71
+ }
72
+ return timers;
73
+ }
74
+ deleteScopeIfEmpty(scopeKey) {
75
+ const timers = this.timers.get(scopeKey);
76
+ if (timers && timers.size === 0)
77
+ this.timers.delete(scopeKey);
78
+ if (!this.toastsByScope.get(scopeKey)?.visible)
79
+ this.toastsByScope.delete(scopeKey);
80
+ }
81
+ normalizeScopeKey(scopeKey) {
82
+ return scopeKey ?? "";
42
83
  }
43
84
  }
@@ -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 {};
@@ -7,6 +7,7 @@ import { createAgentSessionFromServices, createAgentSessionRuntime, createAgentS
7
7
  import { loadPixConfig, resolveDefaultModelRef } from "../config.js";
8
8
  import { PI_FAVORITE_MODEL_REFS } from "./constants.js";
9
9
  import { isThinkingLevel, parseModelRef, parseScopedModelRef } from "./model/model-ref.js";
10
+ import { openLazySessionManager } from "./session/lazy-session-manager.js";
10
11
  const BUNDLED_QUESTION_EXTENSION_NAME = "question";
11
12
  const PI_TOOLS_SUITE_EXTENSION_NAME = "pi-tools-suite";
12
13
  const BUNDLED_EXTENSIONS_DIR = resolve(dirname(fileURLToPath(import.meta.url)), "../..", "extensions");
@@ -140,6 +141,11 @@ export function resolvePixRuntimeModelRef(options, sessionManager, config = load
140
141
  return resolveSessionModelRefFromTail(sessionManager.getBranch());
141
142
  return resolveDefaultModelRef(config);
142
143
  }
144
+ export function resolvePixRuntimeInitialThinkingLevel(options, sessionManager, config) {
145
+ const effectiveModelRef = resolvePixRuntimeModelRef(options, sessionManager, config);
146
+ const parsedModel = effectiveModelRef ? parseModelRef(effectiveModelRef) : undefined;
147
+ return parsedModel?.thinkingLevel;
148
+ }
143
149
  export function resolveSessionModelRefFromTail(entries) {
144
150
  let modelRef;
145
151
  let thinkingLevel;
@@ -166,8 +172,10 @@ export function resolveSessionModelRefFromTail(entries) {
166
172
  export async function createPixRuntime(options, runtimeOptions = {}) {
167
173
  const agentDir = getAgentDir();
168
174
  const createRuntime = async ({ cwd, sessionManager, sessionStartEvent }) => {
169
- const effectiveModelRef = resolvePixRuntimeModelRef(options, sessionManager);
175
+ const config = loadPixConfig(cwd);
176
+ const effectiveModelRef = resolvePixRuntimeModelRef(options, sessionManager, config);
170
177
  const parsedModel = effectiveModelRef ? parseModelRef(effectiveModelRef) : undefined;
178
+ const initialThinkingLevel = resolvePixRuntimeInitialThinkingLevel(options, sessionManager, config);
171
179
  await ensureBundledSkillsInstalled();
172
180
  await ensurePiToolsSuiteExtensionInstalled({ agentDir });
173
181
  const bundledExtensionPaths = getBundledExtensionPaths();
@@ -175,6 +183,7 @@ export async function createPixRuntime(options, runtimeOptions = {}) {
175
183
  cwd,
176
184
  agentDir,
177
185
  resourceLoaderOptions: {
186
+ ...(config.ignoreContextFiles ? { noContextFiles: true } : {}),
178
187
  ...(runtimeOptions.eventBus === undefined ? {} : { eventBus: runtimeOptions.eventBus }),
179
188
  ...(bundledExtensionPaths.length === 0 ? {} : {
180
189
  additionalExtensionPaths: bundledExtensionPaths,
@@ -203,15 +212,16 @@ export async function createPixRuntime(options, runtimeOptions = {}) {
203
212
  },
204
213
  ];
205
214
  });
215
+ const created = await createAgentSessionFromServices({
216
+ services,
217
+ sessionManager,
218
+ ...(sessionStartEvent === undefined ? {} : { sessionStartEvent }),
219
+ ...(model === undefined ? {} : { model }),
220
+ ...(initialThinkingLevel === undefined ? {} : { thinkingLevel: initialThinkingLevel }),
221
+ ...(scopedModels.length === 0 ? {} : { scopedModels }),
222
+ });
206
223
  return {
207
- ...(await createAgentSessionFromServices({
208
- services,
209
- sessionManager,
210
- ...(sessionStartEvent === undefined ? {} : { sessionStartEvent }),
211
- ...(model === undefined ? {} : { model }),
212
- ...(parsedModel?.thinkingLevel === undefined ? {} : { thinkingLevel: parsedModel.thinkingLevel }),
213
- ...(scopedModels.length === 0 ? {} : { scopedModels }),
214
- })),
224
+ ...created,
215
225
  services,
216
226
  diagnostics: services.diagnostics,
217
227
  };
@@ -222,7 +232,7 @@ export async function createPixRuntime(options, runtimeOptions = {}) {
222
232
  sessionManager: options.noSession
223
233
  ? SessionManager.inMemory(options.cwd)
224
234
  : options.sessionPath
225
- ? SessionManager.open(options.sessionPath, undefined, options.cwd)
235
+ ? openLazySessionManager(options.sessionPath, { cwdOverride: options.cwd })
226
236
  : SessionManager.create(options.cwd),
227
237
  });
228
238
  }
@@ -56,6 +56,7 @@ export type AppMouseControllerHost = {
56
56
  }): void;
57
57
  dismissToast(toastId: number): void;
58
58
  refreshModelUsageStatus(): void | Promise<void>;
59
+ refreshUserMessageJumpMenuItems?(): Promise<void>;
59
60
  queueInputFromStatus?(): void | Promise<void>;
60
61
  toggleAllThinkingExpanded?(): void;
61
62
  toggleSuperCompactTools?(): void;
@@ -111,7 +112,6 @@ export declare class AppMouseController {
111
112
  statusVoiceLanguageTarget: StatusVoiceLanguageTarget | undefined;
112
113
  readonly tabLineTargets: TabLineMouseTarget[];
113
114
  mouseSelection: MouseSelection | undefined;
114
- private scrollBarDragActive;
115
115
  private inputScrollBarDragActive;
116
116
  private autoScrollTimer;
117
117
  private autoScrollDirection;
@@ -142,7 +142,6 @@ export declare class AppMouseController {
142
142
  private statusTargetAt;
143
143
  private handleImageClick;
144
144
  private handleFileLinkClick;
145
- private handleConversationScrollBar;
146
145
  private handleInputScrollBar;
147
146
  private finishInputScrollBarDrag;
148
147
  private handleInputWheel;
@@ -155,6 +154,7 @@ export declare class AppMouseController {
155
154
  private handleStatusContextClick;
156
155
  private handleStatusModelUsageClick;
157
156
  private handleStatusUserJumpClick;
157
+ private openStatusUserJumpMenu;
158
158
  private handleStatusDraftQueueClick;
159
159
  private handleStatusThinkingExpandClick;
160
160
  private handleStatusCompactToolsClick;
@@ -3,7 +3,7 @@ import { stringifyUnknown } from "../rendering/message-content.js";
3
3
  import { horizontalPaddingLayout } from "../rendering/render-text.js";
4
4
  import { orderedSelection, samePoint } from "./screen-selection.js";
5
5
  import { openImageContent as openSystemImageContent } from "./image-opener.js";
6
- import { stringDisplayWidth } from "../../terminal-width.js";
6
+ import { sliceByDisplayColumns, stringDisplayWidth } from "../../terminal-width.js";
7
7
  import { formatDcpStatsToast } from "../rendering/dcp-stats.js";
8
8
  import { detectFileLinks } from "./file-links.js";
9
9
  import { openFileLink as openDetectedFileLink } from "./file-link-opener.js";
@@ -34,7 +34,6 @@ export class AppMouseController {
34
34
  statusVoiceLanguageTarget;
35
35
  tabLineTargets = [];
36
36
  mouseSelection;
37
- scrollBarDragActive = false;
38
37
  inputScrollBarDragActive = false;
39
38
  autoScrollTimer;
40
39
  autoScrollDirection;
@@ -57,8 +56,6 @@ export class AppMouseController {
57
56
  handleMouse(event) {
58
57
  if (this.handleInputScrollBar(event))
59
58
  return;
60
- if (this.handleConversationScrollBar(event))
61
- return;
62
59
  this.showClickFlashOnPress(event);
63
60
  if (this.handleMouseSelection(event))
64
61
  return;
@@ -339,39 +336,6 @@ export class AppMouseController {
339
336
  this.host.showToast("Could not open file link. Install the Zed CLI or set ZED_CLI.", "warning");
340
337
  return true;
341
338
  }
342
- handleConversationScrollBar(event) {
343
- if (!this.scrollBarDragActive && this.tabLineTargetAt(event))
344
- return false;
345
- const columns = this.host.terminalColumns();
346
- const terminalRows = this.host.terminalRows();
347
- const tabPanelRows = this.host.tabPanelRows(terminalRows);
348
- const rows = editorLayoutRows(terminalRows, tabPanelRows);
349
- const topOffset = editorLayoutTopOffset(tabPanelRows);
350
- if (columns <= 0 || rows <= 0)
351
- return false;
352
- const { bodyHeight } = this.host.editorLayoutRenderer().computeLayout(columns, rows);
353
- const localY = event.y - topOffset;
354
- const insideBody = localY >= 1 && localY <= bodyHeight;
355
- const onScrollBar = insideBody && event.x === columns && !!this.scrollController.scrollBarMetrics(columns, bodyHeight);
356
- const baseButton = event.button & 3;
357
- const draggingLeftButton = (event.button & 32) !== 0 && baseButton === 0;
358
- if (event.released) {
359
- if (!this.scrollBarDragActive)
360
- return false;
361
- this.scrollBarDragActive = false;
362
- return true;
363
- }
364
- if (event.button === 0 && onScrollBar) {
365
- this.scrollBarDragActive = true;
366
- this.scrollController.scrollToScrollbarPosition(localY - 1);
367
- return true;
368
- }
369
- if (!draggingLeftButton || !this.scrollBarDragActive)
370
- return false;
371
- const bodyRow = Math.max(0, Math.min(Math.max(0, bodyHeight - 1), localY - 1));
372
- this.scrollController.scrollToScrollbarPosition(bodyRow);
373
- return true;
374
- }
375
339
  handleInputScrollBar(event) {
376
340
  if (!this.inputScrollBarDragActive && this.tabLineTargetAt(event))
377
341
  return false;
@@ -509,10 +473,24 @@ export class AppMouseController {
509
473
  return false;
510
474
  if (event.y !== target.row || event.x < target.startColumn || event.x >= target.endColumn)
511
475
  return false;
512
- this.popupMenus.openDirectPopupMenu("user-message-jump", { preserveStatus: true });
513
- this.host.render();
476
+ void this.openStatusUserJumpMenu();
514
477
  return true;
515
478
  }
479
+ async openStatusUserJumpMenu() {
480
+ try {
481
+ if (this.host.refreshUserMessageJumpMenuItems) {
482
+ this.host.setStatus("scanning session messages…");
483
+ this.host.render();
484
+ await this.host.refreshUserMessageJumpMenuItems();
485
+ }
486
+ this.popupMenus.openDirectPopupMenu("user-message-jump", { preserveStatus: true });
487
+ this.host.render();
488
+ }
489
+ catch (error) {
490
+ this.host.showToast(`Could not load jump messages: ${error instanceof Error ? error.message : stringifyUnknown(error)}`, "error");
491
+ this.host.render();
492
+ }
493
+ }
516
494
  handleStatusDraftQueueClick(event) {
517
495
  const target = this.statusDraftQueueTarget;
518
496
  if (!target)
@@ -829,9 +807,7 @@ export class AppMouseController {
829
807
  const line = range.start.line + index;
830
808
  const startColumn = line === range.start.line ? range.start.x : 1;
831
809
  const endColumn = line === range.end.line ? range.end.x : text.length + 1;
832
- const startIndex = Math.max(0, Math.min(text.length, startColumn - 1));
833
- const endIndex = Math.max(startIndex, Math.min(text.length, endColumn - 1));
834
- lines.push(text.slice(startIndex, endIndex).trimEnd());
810
+ lines.push(sliceByDisplayColumns(text, startColumn, endColumn).trimEnd());
835
811
  }
836
812
  return lines.join("\n").replace(/\s+$/u, "");
837
813
  }
@@ -839,10 +815,10 @@ export class AppMouseController {
839
815
  const area = this.conversationArea();
840
816
  if (!area || area.bodyHeight <= 0)
841
817
  return undefined;
842
- if (!clampToViewport && (event.y < area.topRow || event.y > area.bottomRow || event.x === this.host.terminalColumns()))
818
+ if (!clampToViewport && (event.y < area.topRow || event.y > area.bottomRow))
843
819
  return undefined;
844
820
  const screenY = Math.max(area.topRow, Math.min(area.bottomRow, event.y));
845
- const screenX = Math.max(1, Math.min(area.viewportColumns + 1, event.x));
821
+ const screenX = viewportSelectionColumn(event.x, area.viewportColumns);
846
822
  return {
847
823
  conversation: {
848
824
  line: area.metrics.start + (screenY - area.topRow),
@@ -945,7 +921,7 @@ export class AppMouseController {
945
921
  if (!area)
946
922
  return;
947
923
  const screenY = this.autoScrollDirection < 0 ? area.topRow : area.bottomRow;
948
- const screenX = Math.max(1, Math.min(area.viewportColumns + 1, this.autoScrollCursorX));
924
+ const screenX = viewportSelectionColumn(this.autoScrollCursorX, area.viewportColumns);
949
925
  selection.current = { x: screenX, y: screenY };
950
926
  selection.conversationCurrent = {
951
927
  line: area.metrics.start + (screenY - area.topRow),
@@ -971,13 +947,16 @@ export function screenSelectionLineText(row, text, startColumn, endColumn, input
971
947
  copyStartColumn = Math.max(copyStartColumn, inputFrame.contentStartColumn);
972
948
  copyEndColumn = Math.min(copyEndColumn, inputFrame.contentEndColumn);
973
949
  }
974
- const startIndex = Math.max(0, Math.min(text.length, copyStartColumn - 1));
975
- const endIndex = Math.max(startIndex, Math.min(text.length, copyEndColumn - 1));
976
- return text.slice(startIndex, endIndex);
950
+ return sliceByDisplayColumns(text, copyStartColumn, copyEndColumn);
977
951
  }
978
952
  function sameConversationPoint(left, right) {
979
953
  return !!left && left.line === right.line && left.x === right.x;
980
954
  }
955
+ function viewportSelectionColumn(mouseX, viewportColumns) {
956
+ if (mouseX >= viewportColumns)
957
+ return viewportColumns + 1;
958
+ return Math.max(1, Math.min(viewportColumns + 1, mouseX));
959
+ }
981
960
  function toastTargetContainsEvent(target, event) {
982
961
  if (target.startColumn === undefined || target.endColumn === undefined)
983
962
  return true;
@@ -29,7 +29,7 @@ export declare class ScreenStyler {
29
29
  end: number;
30
30
  }[] | undefined, width: number, tagColor: string, suggestionColor: string, frameColor?: string): string;
31
31
  private styleAnsiLine;
32
- selectionRangeForRow(row: number, width: number): {
32
+ selectionRangeForRow(row: number, width: number, text?: string): {
33
33
  startIndex: number;
34
34
  endIndex: number;
35
35
  } | undefined;
@@ -1,6 +1,7 @@
1
1
  import { ANSI_RESET, colorize } from "../../theme.js";
2
2
  import { renderMarkdownLine } from "../../markdown-format.js";
3
3
  import { syntaxHighlightSegmentsForLine } from "../../syntax-highlight.js";
4
+ import { displayIndexForColumn } from "../../terminal-width.js";
4
5
  import { padOrTrimPlain } from "../rendering/render-text.js";
5
6
  import { orderedSelection } from "./screen-selection.js";
6
7
  export class ScreenStyler {
@@ -19,7 +20,7 @@ export class ScreenStyler {
19
20
  ? renderMarkdownDisplayLine(line.text, width, line.syntaxHighlight.start)
20
21
  : undefined;
21
22
  const text = markdownLine?.text ?? line?.text ?? "";
22
- if (line?.syntaxHighlight && !this.selectionRangeForRow(row, width)) {
23
+ if (line?.syntaxHighlight && !this.selectionRangeForRow(row, width, text)) {
23
24
  const syntaxHighlight = markdownLine ? { ...line.syntaxHighlight, start: Math.min(line.syntaxHighlight.start, markdownLine.text.length) } : line.syntaxHighlight;
24
25
  const segments = [
25
26
  ...syntaxHighlightSegmentsForLine(text, syntaxHighlight, colors),
@@ -35,7 +36,7 @@ export class ScreenStyler {
35
36
  return this.styleLine(row, text, width, options);
36
37
  }
37
38
  styleLineSegments(row, text, width, baseOptions, segments) {
38
- if (this.selectionRangeForRow(row, width))
39
+ if (this.selectionRangeForRow(row, width, text))
39
40
  return this.styleLine(row, text, width, baseOptions);
40
41
  const plain = padOrTrimPlain(text, width);
41
42
  const chunks = [];
@@ -56,7 +57,7 @@ export class ScreenStyler {
56
57
  }
57
58
  styleLine(row, text, width, options) {
58
59
  const plain = padOrTrimPlain(text, width);
59
- const range = this.selectionRangeForRow(row, width);
60
+ const range = this.selectionRangeForRow(row, width, plain);
60
61
  if (!range)
61
62
  return colorize(plain, options);
62
63
  const before = plain.slice(0, range.startIndex);
@@ -75,7 +76,7 @@ export class ScreenStyler {
75
76
  styleInputLine(row, text, tagSpans, suggestionSpans, width, tagColor, suggestionColor, frameColor) {
76
77
  const colors = this.host.theme.colors;
77
78
  const baseOptions = { foreground: colors.inputForeground };
78
- if (this.selectionRangeForRow(row, width))
79
+ if (this.selectionRangeForRow(row, width, text))
79
80
  return this.styleLine(row, text, width, baseOptions);
80
81
  const plain = padOrTrimPlain(text, width);
81
82
  const frameSpans = inputFrameSpans(plain, width, frameColor);
@@ -109,7 +110,7 @@ export class ScreenStyler {
109
110
  return text;
110
111
  return `${prefix}${text.replaceAll(ANSI_RESET, `${ANSI_RESET}${prefix}`)}${ANSI_RESET}`;
111
112
  }
112
- selectionRangeForRow(row, width) {
113
+ selectionRangeForRow(row, width, text) {
113
114
  if (!this.host.mouseSelection)
114
115
  return undefined;
115
116
  const anchor = this.host.mouseSelection.screenAnchor ?? this.host.mouseSelection.anchor;
@@ -119,8 +120,9 @@ export class ScreenStyler {
119
120
  return undefined;
120
121
  const startColumn = row === start.y ? start.x : 1;
121
122
  const endColumn = row === end.y ? end.x : width + 1;
122
- const startIndex = Math.max(0, Math.min(width, startColumn - 1));
123
- const endIndex = Math.max(startIndex, Math.min(width, endColumn - 1));
123
+ const plain = text ?? " ".repeat(Math.max(0, width));
124
+ const startIndex = Math.max(0, Math.min(plain.length, displayIndexForColumn(plain, startColumn)));
125
+ const endIndex = Math.max(startIndex, Math.min(plain.length, displayIndexForColumn(plain, endColumn)));
124
126
  return endIndex > startIndex ? { startIndex, endIndex } : undefined;
125
127
  }
126
128
  baseLineForeground(variant) {
@@ -1,6 +1,6 @@
1
1
  import type { ConversationViewport } from "../rendering/conversation-viewport.js";
2
2
  import type { EditorLayoutRenderer } from "../rendering/editor-layout-renderer.js";
3
- import type { RenderedLine } from "../types.js";
3
+ import type { Entry, RenderedLine } from "../types.js";
4
4
  export type AppScrollMetrics = {
5
5
  bodyHeight: number;
6
6
  viewportColumns: number;
@@ -8,10 +8,6 @@ export type AppScrollMetrics = {
8
8
  maxScroll: number;
9
9
  start: number;
10
10
  };
11
- export type AppScrollBarMetrics = {
12
- thumbStartRow: number;
13
- thumbEndRow: number;
14
- };
15
11
  export type ConversationTextScrollTarget = {
16
12
  entryId?: string;
17
13
  needles: readonly string[];
@@ -22,12 +18,19 @@ export type AppScrollControllerHost = {
22
18
  terminalColumns(): number;
23
19
  terminalRows(): number;
24
20
  tabPanelRows(terminalRows: number): number;
21
+ hasOlderSessionHistory?(): boolean;
22
+ isLoadingOlderSessionHistory?(): boolean;
23
+ loadOlderSessionHistory?(options?: {
24
+ render?: boolean;
25
+ onPrependedEntries?: (entries: readonly Entry[]) => void;
26
+ }): Promise<boolean>;
25
27
  render(): void;
26
28
  };
27
29
  export declare class AppScrollController {
28
30
  private readonly host;
29
31
  private scrollFromBottom;
30
32
  private detachedScrollStart;
33
+ private readonly olderHistoryThresholdLines;
31
34
  constructor(host: AppScrollControllerHost);
32
35
  reset(): void;
33
36
  conversationView(columns: number, bodyHeight: number): {
@@ -36,16 +39,15 @@ export declare class AppScrollController {
36
39
  };
37
40
  visibleConversationLines(columns: number, bodyHeight: number): RenderedLine[];
38
41
  scrollMetrics(columns: number, bodyHeight: number): AppScrollMetrics;
39
- scrollBarForMetrics(metrics: AppScrollMetrics): AppScrollBarMetrics | undefined;
40
- scrollBarMetrics(columns: number, bodyHeight: number): AppScrollBarMetrics | undefined;
41
42
  scrollByPage(direction: -1 | 1): void;
42
43
  scrollByLines(delta: number, options?: {
43
44
  render?: boolean;
44
45
  }): boolean;
45
- scrollToScrollbarPosition(bodyRow: number): boolean;
46
+ private shouldLoadOlderHistory;
47
+ private loadOlderHistoryAnchored;
48
+ adjustForHistoryWindowPrune(edge: "top" | "bottom", lineCount: number): void;
46
49
  scrollToConversationEntry(entryId: string): boolean;
47
50
  scrollToConversationText(target: ConversationTextScrollTarget): boolean;
48
- private scrollToStart;
49
51
  private setScrollStart;
50
52
  private viewportColumns;
51
53
  }