@oh-my-pi/pi-coding-agent 15.9.5 → 15.10.0
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.
- package/CHANGELOG.md +98 -1
- package/dist/types/cli/args.d.ts +1 -1
- package/dist/types/cli/gallery-cli.d.ts +43 -0
- package/dist/types/cli/gallery-fixtures/agentic.d.ts +2 -0
- package/dist/types/cli/gallery-fixtures/codeintel.d.ts +3 -0
- package/dist/types/cli/gallery-fixtures/edit.d.ts +3 -0
- package/dist/types/cli/gallery-fixtures/fs.d.ts +2 -0
- package/dist/types/cli/gallery-fixtures/index.d.ts +4 -0
- package/dist/types/cli/gallery-fixtures/interaction.d.ts +3 -0
- package/dist/types/cli/gallery-fixtures/memory.d.ts +2 -0
- package/dist/types/cli/gallery-fixtures/misc.d.ts +3 -0
- package/dist/types/cli/gallery-fixtures/search.d.ts +3 -0
- package/dist/types/cli/gallery-fixtures/shell.d.ts +3 -0
- package/dist/types/cli/gallery-fixtures/types.d.ts +44 -0
- package/dist/types/cli/gallery-fixtures/web.d.ts +2 -0
- package/dist/types/cli/gallery-screenshot.d.ts +35 -0
- package/dist/types/commands/gallery.d.ts +47 -0
- package/dist/types/config/keybindings.d.ts +10 -2
- package/dist/types/config/model-id-affixes.d.ts +2 -0
- package/dist/types/config/model-registry.d.ts +8 -1
- package/dist/types/config/settings-schema.d.ts +43 -7
- package/dist/types/edit/file-snapshot-store.d.ts +1 -1
- package/dist/types/eval/backend.d.ts +6 -6
- package/dist/types/eval/bridge-timeout.d.ts +27 -0
- package/dist/types/eval/idle-timeout.d.ts +16 -14
- package/dist/types/eval/js/executor.d.ts +3 -3
- package/dist/types/eval/py/executor.d.ts +2 -2
- package/dist/types/eval/py/spawn-options.d.ts +58 -0
- package/dist/types/extensibility/plugins/marketplace-auto-update.d.ts +8 -0
- package/dist/types/lsp/types.d.ts +10 -0
- package/dist/types/main.d.ts +3 -2
- package/dist/types/memory-backend/index.d.ts +2 -1
- package/dist/types/memory-backend/resolve.d.ts +1 -1
- package/dist/types/memory-backend/types.d.ts +1 -1
- package/dist/types/modes/components/assistant-message.d.ts +5 -0
- package/dist/types/modes/components/copy-selector.d.ts +22 -0
- package/dist/types/modes/components/custom-editor.d.ts +2 -1
- package/dist/types/modes/components/model-selector.d.ts +1 -0
- package/dist/types/modes/components/tool-execution.d.ts +18 -0
- package/dist/types/modes/controllers/command-controller.d.ts +0 -1
- package/dist/types/modes/controllers/selector-controller.d.ts +2 -1
- package/dist/types/modes/index.d.ts +5 -4
- package/dist/types/modes/interactive-mode.d.ts +2 -2
- package/dist/types/modes/setup-version.d.ts +11 -0
- package/dist/types/modes/setup-wizard/index.d.ts +2 -1
- package/dist/types/modes/setup-wizard/scenes/web-search.d.ts +2 -1
- package/dist/types/modes/types.d.ts +2 -2
- package/dist/types/modes/utils/copy-targets.d.ts +53 -0
- package/dist/types/sdk.d.ts +1 -1
- package/dist/types/task/executor.d.ts +7 -0
- package/dist/types/telemetry-export.d.ts +1 -1
- package/dist/types/tools/eval-render.d.ts +1 -0
- package/dist/types/tools/fetch.d.ts +15 -7
- package/dist/types/tools/render-utils.d.ts +33 -0
- package/dist/types/tools/renderers.d.ts +16 -2
- package/dist/types/tools/search.d.ts +1 -1
- package/dist/types/tools/write.d.ts +2 -0
- package/dist/types/tui/code-cell.d.ts +6 -0
- package/dist/types/tui/output-block.d.ts +11 -0
- package/dist/types/web/scrapers/github.d.ts +22 -0
- package/dist/types/web/search/providers/perplexity.d.ts +8 -1
- package/dist/types/web/search/types.d.ts +1 -1
- package/package.json +9 -9
- package/scripts/dev-launch +42 -0
- package/scripts/dev-launch-preload.ts +19 -0
- package/src/autoresearch/dashboard.ts +11 -21
- package/src/cli/args.ts +2 -2
- package/src/cli/claude-trace-cli.ts +13 -1
- package/src/cli/gallery-cli.ts +223 -0
- package/src/cli/gallery-fixtures/agentic.ts +292 -0
- package/src/cli/gallery-fixtures/codeintel.ts +188 -0
- package/src/cli/gallery-fixtures/edit.ts +194 -0
- package/src/cli/gallery-fixtures/fs.ts +153 -0
- package/src/cli/gallery-fixtures/index.ts +40 -0
- package/src/cli/gallery-fixtures/interaction.ts +49 -0
- package/src/cli/gallery-fixtures/memory.ts +81 -0
- package/src/cli/gallery-fixtures/misc.ts +221 -0
- package/src/cli/gallery-fixtures/search.ts +213 -0
- package/src/cli/gallery-fixtures/shell.ts +167 -0
- package/src/cli/gallery-fixtures/types.ts +41 -0
- package/src/cli/gallery-fixtures/web.ts +158 -0
- package/src/cli/gallery-screenshot.ts +279 -0
- package/src/cli-commands.ts +1 -0
- package/src/commands/gallery.ts +52 -0
- package/src/commands/launch.ts +1 -1
- package/src/config/keybindings.ts +68 -2
- package/src/config/model-equivalence.ts +35 -12
- package/src/config/model-id-affixes.ts +39 -22
- package/src/config/model-registry.ts +16 -16
- package/src/config/settings-schema.ts +29 -6
- package/src/config/settings.ts +11 -0
- package/src/dap/client.ts +14 -16
- package/src/debug/raw-sse.ts +18 -4
- package/src/edit/file-snapshot-store.ts +1 -1
- package/src/edit/index.ts +1 -1
- package/src/edit/renderer.ts +43 -55
- package/src/edit/streaming.ts +1 -1
- package/src/eval/__tests__/agent-bridge.test.ts +102 -58
- package/src/eval/__tests__/bridge-timeout.test.ts +64 -0
- package/src/eval/__tests__/idle-timeout.test.ts +26 -12
- package/src/eval/__tests__/kernel-spawn.test.ts +103 -0
- package/src/eval/__tests__/llm-bridge.test.ts +10 -10
- package/src/eval/agent-bridge.ts +38 -12
- package/src/eval/backend.ts +6 -6
- package/src/eval/bridge-timeout.ts +44 -0
- package/src/eval/idle-timeout.ts +33 -15
- package/src/eval/js/executor.ts +10 -10
- package/src/eval/llm-bridge.ts +4 -5
- package/src/eval/py/executor.ts +6 -6
- package/src/eval/py/kernel.ts +11 -1
- package/src/eval/py/spawn-options.ts +126 -0
- package/src/export/ttsr.ts +9 -0
- package/src/extensibility/extensions/runner.ts +3 -0
- package/src/extensibility/plugins/doctor.ts +0 -1
- package/src/extensibility/plugins/marketplace-auto-update.ts +49 -0
- package/src/goals/tools/goal-tool.ts +2 -2
- package/src/internal-urls/docs-index.generated.ts +7 -6
- package/src/lsp/client.ts +179 -52
- package/src/lsp/index.ts +38 -4
- package/src/lsp/render.ts +3 -3
- package/src/lsp/types.ts +10 -0
- package/src/main.ts +47 -52
- package/src/memory-backend/index.ts +13 -1
- package/src/memory-backend/resolve.ts +3 -5
- package/src/memory-backend/types.ts +1 -1
- package/src/modes/components/agent-dashboard.ts +13 -4
- package/src/modes/components/assistant-message.ts +22 -1
- package/src/modes/components/copy-selector.ts +249 -0
- package/src/modes/components/custom-editor.ts +10 -1
- package/src/modes/components/extensions/extension-list.ts +17 -8
- package/src/modes/components/history-search.ts +19 -11
- package/src/modes/components/model-selector.ts +125 -29
- package/src/modes/components/oauth-selector.ts +28 -12
- package/src/modes/components/session-observer-overlay.ts +13 -15
- package/src/modes/components/session-selector.ts +24 -13
- package/src/modes/components/status-line.ts +3 -5
- package/src/modes/components/tool-execution.ts +83 -24
- package/src/modes/components/tree-selector.ts +19 -7
- package/src/modes/components/user-message-selector.ts +25 -14
- package/src/modes/controllers/command-controller.ts +13 -118
- package/src/modes/controllers/event-controller.ts +26 -10
- package/src/modes/controllers/input-controller.ts +11 -3
- package/src/modes/controllers/selector-controller.ts +40 -3
- package/src/modes/index.ts +5 -4
- package/src/modes/interactive-mode.ts +21 -7
- package/src/modes/setup-version.ts +11 -0
- package/src/modes/setup-wizard/index.ts +3 -2
- package/src/modes/setup-wizard/scenes/web-search.ts +3 -2
- package/src/modes/theme/theme.ts +46 -10
- package/src/modes/types.ts +2 -2
- package/src/modes/utils/context-usage.ts +10 -6
- package/src/modes/utils/copy-targets.ts +254 -0
- package/src/modes/utils/hotkeys-markdown.ts +1 -0
- package/src/prompts/tools/ast-edit.md +1 -1
- package/src/prompts/tools/ast-grep.md +1 -1
- package/src/prompts/tools/read.md +1 -1
- package/src/prompts/tools/search.md +1 -1
- package/src/sdk.ts +21 -23
- package/src/session/agent-session.ts +13 -9
- package/src/slash-commands/builtin-registry.ts +4 -12
- package/src/slash-commands/helpers/usage-report.ts +2 -0
- package/src/task/executor.ts +20 -2
- package/src/task/render.ts +37 -11
- package/src/telemetry-export.ts +25 -7
- package/src/tools/bash.ts +18 -8
- package/src/tools/browser/render.ts +5 -4
- package/src/tools/debug.ts +3 -3
- package/src/tools/eval-backends.ts +6 -17
- package/src/tools/eval-render.ts +28 -10
- package/src/tools/eval.ts +19 -23
- package/src/tools/fetch.ts +99 -89
- package/src/tools/read.ts +7 -7
- package/src/tools/render-utils.ts +63 -3
- package/src/tools/renderers.ts +16 -1
- package/src/tools/report-tool-issue.ts +1 -1
- package/src/tools/search.ts +173 -81
- package/src/tools/ssh.ts +21 -8
- package/src/tools/todo.ts +20 -7
- package/src/tools/write.ts +39 -9
- package/src/tui/code-cell.ts +19 -4
- package/src/tui/output-block.ts +14 -0
- package/src/web/scrapers/github.ts +255 -3
- package/src/web/scrapers/youtube.ts +3 -2
- package/src/web/search/providers/perplexity.ts +199 -51
- package/src/web/search/render.ts +42 -57
- package/src/web/search/types.ts +5 -1
- package/dist/types/eval/heartbeat.d.ts +0 -45
- package/src/eval/__tests__/heartbeat.test.ts +0 -84
- package/src/eval/__tests__/shared-executors.test.ts +0 -609
- package/src/eval/heartbeat.ts +0 -74
- /package/dist/types/eval/__tests__/{heartbeat.test.d.ts → bridge-timeout.test.d.ts} +0 -0
- /package/dist/types/eval/__tests__/{shared-executors.test.d.ts → kernel-spawn.test.d.ts} +0 -0
|
@@ -6,6 +6,7 @@ import {
|
|
|
6
6
|
getKeybindings,
|
|
7
7
|
Input,
|
|
8
8
|
matchesKey,
|
|
9
|
+
ScrollView,
|
|
9
10
|
Spacer,
|
|
10
11
|
type Tab,
|
|
11
12
|
TabBar,
|
|
@@ -13,6 +14,7 @@ import {
|
|
|
13
14
|
type TUI,
|
|
14
15
|
visibleWidth,
|
|
15
16
|
} from "@oh-my-pi/pi-tui";
|
|
17
|
+
import { formatNumber } from "@oh-my-pi/pi-utils";
|
|
16
18
|
import type { ModelRegistry } from "../../config/model-registry";
|
|
17
19
|
import { getKnownRoleIds, getRoleInfo, MODEL_ROLE_IDS, MODEL_ROLES } from "../../config/model-registry";
|
|
18
20
|
import { resolveModelRoleValue } from "../../config/model-resolver";
|
|
@@ -147,6 +149,7 @@ export class ModelSelectorComponent extends Container {
|
|
|
147
149
|
#tui: TUI;
|
|
148
150
|
#scopedModels: ReadonlyArray<ScopedModelItem>;
|
|
149
151
|
#temporaryOnly: boolean;
|
|
152
|
+
#currentContextTokens: number;
|
|
150
153
|
|
|
151
154
|
#menuRoleActions: MenuRoleAction[] = [];
|
|
152
155
|
|
|
@@ -172,7 +175,7 @@ export class ModelSelectorComponent extends Container {
|
|
|
172
175
|
scopedModels: ReadonlyArray<ScopedModelItem>,
|
|
173
176
|
onSelect: RoleSelectCallback,
|
|
174
177
|
onCancel: () => void,
|
|
175
|
-
options?: { temporaryOnly?: boolean; initialSearchInput?: string },
|
|
178
|
+
options?: { temporaryOnly?: boolean; initialSearchInput?: string; currentContextTokens?: number },
|
|
176
179
|
) {
|
|
177
180
|
super();
|
|
178
181
|
|
|
@@ -183,6 +186,9 @@ export class ModelSelectorComponent extends Container {
|
|
|
183
186
|
this.#onSelectCallback = onSelect;
|
|
184
187
|
this.#onCancelCallback = onCancel;
|
|
185
188
|
this.#temporaryOnly = options?.temporaryOnly ?? false;
|
|
189
|
+
const currentContextTokens = options?.currentContextTokens ?? 0;
|
|
190
|
+
this.#currentContextTokens =
|
|
191
|
+
Number.isFinite(currentContextTokens) && currentContextTokens > 0 ? Math.floor(currentContextTokens) : 0;
|
|
186
192
|
const initialSearchInput = options?.initialSearchInput;
|
|
187
193
|
|
|
188
194
|
// Initialize menu role actions (built-in + custom from settings)
|
|
@@ -215,8 +221,8 @@ export class ModelSelectorComponent extends Container {
|
|
|
215
221
|
this.#searchInput.setValue(initialSearchInput);
|
|
216
222
|
}
|
|
217
223
|
this.#searchInput.onSubmit = () => {
|
|
218
|
-
// Enter on search input opens menu if we have
|
|
219
|
-
if (this.#
|
|
224
|
+
// Enter on search input opens menu if we have an enabled selection
|
|
225
|
+
if (this.#getSelectedItem()) {
|
|
220
226
|
this.#openMenu();
|
|
221
227
|
}
|
|
222
228
|
};
|
|
@@ -460,7 +466,11 @@ export class ModelSelectorComponent extends Container {
|
|
|
460
466
|
this.#filteredModels = models;
|
|
461
467
|
this.#canonicalModels = canonicalModels;
|
|
462
468
|
this.#filteredCanonicalModels = canonicalModels;
|
|
463
|
-
|
|
469
|
+
const visibleModels = this.#isCanonicalTab() ? canonicalModels : models;
|
|
470
|
+
this.#selectedIndex = this.#coerceSelectedIndex(
|
|
471
|
+
Math.min(this.#selectedIndex, Math.max(0, visibleModels.length - 1)),
|
|
472
|
+
visibleModels,
|
|
473
|
+
);
|
|
464
474
|
}
|
|
465
475
|
|
|
466
476
|
async #loadModels(): Promise<void> {
|
|
@@ -626,6 +636,74 @@ export class ModelSelectorComponent extends Container {
|
|
|
626
636
|
return this.#getActiveTabId() === CANONICAL_TAB;
|
|
627
637
|
}
|
|
628
638
|
|
|
639
|
+
#isModelOverContextLimit(model: Model): boolean {
|
|
640
|
+
const contextWindow = model.contextWindow ?? 0;
|
|
641
|
+
return this.#currentContextTokens > 0 && contextWindow > 0 && this.#currentContextTokens > contextWindow;
|
|
642
|
+
}
|
|
643
|
+
|
|
644
|
+
#isItemDisabled(item: ModelItem | CanonicalModelItem): boolean {
|
|
645
|
+
return this.#isModelOverContextLimit(item.model);
|
|
646
|
+
}
|
|
647
|
+
|
|
648
|
+
#formatContextLimitSuffix(model: Model): string {
|
|
649
|
+
if (!this.#isModelOverContextLimit(model)) {
|
|
650
|
+
return "";
|
|
651
|
+
}
|
|
652
|
+
return ` ${theme.status.disabled} context>${formatNumber(model.contextWindow).toLowerCase()}`;
|
|
653
|
+
}
|
|
654
|
+
|
|
655
|
+
#getVisibleItems(): ReadonlyArray<ModelItem | CanonicalModelItem> {
|
|
656
|
+
return this.#isCanonicalTab() ? this.#filteredCanonicalModels : this.#filteredModels;
|
|
657
|
+
}
|
|
658
|
+
|
|
659
|
+
#coerceSelectedIndex(
|
|
660
|
+
index: number,
|
|
661
|
+
visibleItems: ReadonlyArray<ModelItem | CanonicalModelItem> = this.#getVisibleItems(),
|
|
662
|
+
): number {
|
|
663
|
+
const maxIndex = visibleItems.length - 1;
|
|
664
|
+
if (maxIndex < 0) {
|
|
665
|
+
return 0;
|
|
666
|
+
}
|
|
667
|
+
const clamped = Math.max(0, Math.min(index, maxIndex));
|
|
668
|
+
const clampedItem = visibleItems[clamped];
|
|
669
|
+
if (clampedItem && !this.#isItemDisabled(clampedItem)) {
|
|
670
|
+
return clamped;
|
|
671
|
+
}
|
|
672
|
+
for (let i = clamped + 1; i <= maxIndex; i++) {
|
|
673
|
+
const item = visibleItems[i];
|
|
674
|
+
if (item && !this.#isItemDisabled(item)) {
|
|
675
|
+
return i;
|
|
676
|
+
}
|
|
677
|
+
}
|
|
678
|
+
for (let i = clamped - 1; i >= 0; i--) {
|
|
679
|
+
const item = visibleItems[i];
|
|
680
|
+
if (item && !this.#isItemDisabled(item)) {
|
|
681
|
+
return i;
|
|
682
|
+
}
|
|
683
|
+
}
|
|
684
|
+
return clamped;
|
|
685
|
+
}
|
|
686
|
+
|
|
687
|
+
#moveSelection(delta: number): void {
|
|
688
|
+
const visibleItems = this.#getVisibleItems();
|
|
689
|
+
const count = visibleItems.length;
|
|
690
|
+
if (count === 0) {
|
|
691
|
+
return;
|
|
692
|
+
}
|
|
693
|
+
let index = this.#selectedIndex;
|
|
694
|
+
for (let step = 0; step < count; step++) {
|
|
695
|
+
index = (index + delta + count) % count;
|
|
696
|
+
const item = visibleItems[index];
|
|
697
|
+
if (item && !this.#isItemDisabled(item)) {
|
|
698
|
+
this.#selectedIndex = index;
|
|
699
|
+
this.#updateList();
|
|
700
|
+
return;
|
|
701
|
+
}
|
|
702
|
+
}
|
|
703
|
+
this.#selectedIndex = this.#coerceSelectedIndex(this.#selectedIndex, visibleItems);
|
|
704
|
+
this.#updateList();
|
|
705
|
+
}
|
|
706
|
+
|
|
629
707
|
#filterModels(query: string): void {
|
|
630
708
|
const activeTabId = this.#getActiveTabId();
|
|
631
709
|
const activeProviderId = this.#getActiveProviderId();
|
|
@@ -696,8 +774,11 @@ export class ModelSelectorComponent extends Container {
|
|
|
696
774
|
this.#filteredCanonicalModels = baseCanonicalModels;
|
|
697
775
|
}
|
|
698
776
|
|
|
699
|
-
const
|
|
700
|
-
this.#selectedIndex =
|
|
777
|
+
const visibleItems = isCanonicalTab ? this.#filteredCanonicalModels : this.#filteredModels;
|
|
778
|
+
this.#selectedIndex = this.#coerceSelectedIndex(
|
|
779
|
+
Math.min(this.#selectedIndex, Math.max(0, visibleItems.length - 1)),
|
|
780
|
+
visibleItems,
|
|
781
|
+
);
|
|
701
782
|
this.#updateList();
|
|
702
783
|
}
|
|
703
784
|
|
|
@@ -778,6 +859,7 @@ export class ModelSelectorComponent extends Container {
|
|
|
778
859
|
|
|
779
860
|
const showProvider = this.#getActiveTabId() === ALL_TAB;
|
|
780
861
|
|
|
862
|
+
const rows: string[] = [];
|
|
781
863
|
// Show visible slice of filtered models
|
|
782
864
|
for (let i = startIndex; i < endIndex; i++) {
|
|
783
865
|
const item = visibleItems[i];
|
|
@@ -786,6 +868,8 @@ export class ModelSelectorComponent extends Container {
|
|
|
786
868
|
const providerItem = isCanonicalTab ? undefined : (item as ModelItem);
|
|
787
869
|
|
|
788
870
|
const isSelected = i === this.#selectedIndex;
|
|
871
|
+
const isDisabled = this.#isItemDisabled(item);
|
|
872
|
+
const disabledSuffix = this.#formatContextLimitSuffix(item.model);
|
|
789
873
|
|
|
790
874
|
// Build role badges (inverted: color as background, black text)
|
|
791
875
|
const roleBadgeTokens: string[] = [];
|
|
@@ -815,34 +899,42 @@ export class ModelSelectorComponent extends Container {
|
|
|
815
899
|
if (isCanonicalTab) {
|
|
816
900
|
const variants = theme.fg("dim", ` [${canonicalItem?.variantCount ?? 0}]`);
|
|
817
901
|
const backing = theme.fg("dim", ` -> ${item.model.provider}/${item.model.id}`);
|
|
818
|
-
line = `${prefix}${theme.fg("accent", item.id)}${variants}${backing}${badgeText}`;
|
|
902
|
+
line = `${prefix}${theme.fg("accent", item.id)}${variants}${backing}${badgeText}${disabledSuffix}`;
|
|
819
903
|
} else if (showProvider) {
|
|
820
904
|
const providerPrefix = theme.fg("dim", `${providerItem?.provider ?? ""}/`);
|
|
821
|
-
line = `${prefix}${providerPrefix}${theme.fg("accent", providerItem?.id ?? item.id)}${badgeText}`;
|
|
905
|
+
line = `${prefix}${providerPrefix}${theme.fg("accent", providerItem?.id ?? item.id)}${badgeText}${disabledSuffix}`;
|
|
822
906
|
} else {
|
|
823
|
-
line = `${prefix}${theme.fg("accent", item.id)}${badgeText}`;
|
|
907
|
+
line = `${prefix}${theme.fg("accent", item.id)}${badgeText}${disabledSuffix}`;
|
|
824
908
|
}
|
|
825
909
|
} else {
|
|
826
910
|
const prefix = " ";
|
|
827
911
|
if (isCanonicalTab) {
|
|
828
912
|
const variants = theme.fg("dim", ` [${canonicalItem?.variantCount ?? 0}]`);
|
|
829
913
|
const backing = theme.fg("dim", ` -> ${item.model.provider}/${item.model.id}`);
|
|
830
|
-
line = `${prefix}${item.id}${variants}${backing}${badgeText}`;
|
|
914
|
+
line = `${prefix}${item.id}${variants}${backing}${badgeText}${disabledSuffix}`;
|
|
831
915
|
} else if (showProvider) {
|
|
832
916
|
const providerPrefix = theme.fg("dim", `${providerItem?.provider ?? ""}/`);
|
|
833
|
-
line = `${prefix}${providerPrefix}${providerItem?.id ?? item.id}${badgeText}`;
|
|
917
|
+
line = `${prefix}${providerPrefix}${providerItem?.id ?? item.id}${badgeText}${disabledSuffix}`;
|
|
834
918
|
} else {
|
|
835
|
-
line = `${prefix}${item.id}${badgeText}`;
|
|
919
|
+
line = `${prefix}${item.id}${badgeText}${disabledSuffix}`;
|
|
836
920
|
}
|
|
837
921
|
}
|
|
838
922
|
|
|
839
|
-
|
|
923
|
+
if (isDisabled) {
|
|
924
|
+
line = theme.fg("dim", Bun.stripANSI(line));
|
|
925
|
+
}
|
|
926
|
+
rows.push(line);
|
|
840
927
|
}
|
|
841
928
|
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
929
|
+
if (rows.length > 0) {
|
|
930
|
+
const sv = new ScrollView(rows, {
|
|
931
|
+
height: rows.length,
|
|
932
|
+
scrollbar: "auto",
|
|
933
|
+
totalRows: visibleItems.length,
|
|
934
|
+
theme: { track: t => theme.fg("muted", t), thumb: t => theme.fg("accent", t) },
|
|
935
|
+
});
|
|
936
|
+
sv.setScrollOffset(startIndex);
|
|
937
|
+
this.#listContainer.addChild(sv);
|
|
846
938
|
}
|
|
847
939
|
|
|
848
940
|
// Show error message or "no results" if empty
|
|
@@ -863,8 +955,14 @@ export class ModelSelectorComponent extends Container {
|
|
|
863
955
|
const suffix = isCanonicalTab
|
|
864
956
|
? ` (${selected.model.provider}/${selected.model.id}, ${(selected as CanonicalModelItem).variantCount} variants)`
|
|
865
957
|
: "";
|
|
958
|
+
const limitWarning = this.#isItemDisabled(selected)
|
|
959
|
+
? theme.fg(
|
|
960
|
+
"dim",
|
|
961
|
+
` — current context ${formatNumber(this.#currentContextTokens).toLowerCase()} > ${formatNumber(selected.model.contextWindow).toLowerCase()} limit`,
|
|
962
|
+
)
|
|
963
|
+
: "";
|
|
866
964
|
this.#listContainer.addChild(
|
|
867
|
-
new Text(theme.fg("muted", ` Model Name: ${selected.model.name}${suffix}`), 0, 0),
|
|
965
|
+
new Text(theme.fg("muted", ` Model Name: ${selected.model.name}${suffix}`) + limitWarning, 0, 0),
|
|
868
966
|
);
|
|
869
967
|
}
|
|
870
968
|
}
|
|
@@ -890,7 +988,8 @@ export class ModelSelectorComponent extends Container {
|
|
|
890
988
|
}
|
|
891
989
|
|
|
892
990
|
#openMenu(): void {
|
|
893
|
-
|
|
991
|
+
const selectedItem = this.#getSelectedItem();
|
|
992
|
+
if (!selectedItem || this.#isItemDisabled(selectedItem)) return;
|
|
894
993
|
|
|
895
994
|
this.#isMenuOpen = true;
|
|
896
995
|
this.#menuStep = "role";
|
|
@@ -978,26 +1077,20 @@ export class ModelSelectorComponent extends Container {
|
|
|
978
1077
|
|
|
979
1078
|
// Up arrow - navigate list (wrap to bottom when at top)
|
|
980
1079
|
if (matchesSelectUp(keyData)) {
|
|
981
|
-
|
|
982
|
-
if (itemCount === 0) return;
|
|
983
|
-
this.#selectedIndex = this.#selectedIndex === 0 ? itemCount - 1 : this.#selectedIndex - 1;
|
|
984
|
-
this.#updateList();
|
|
1080
|
+
this.#moveSelection(-1);
|
|
985
1081
|
return;
|
|
986
1082
|
}
|
|
987
1083
|
|
|
988
1084
|
// Down arrow - navigate list (wrap to top when at bottom)
|
|
989
1085
|
if (matchesSelectDown(keyData)) {
|
|
990
|
-
|
|
991
|
-
if (itemCount === 0) return;
|
|
992
|
-
this.#selectedIndex = this.#selectedIndex === itemCount - 1 ? 0 : this.#selectedIndex + 1;
|
|
993
|
-
this.#updateList();
|
|
1086
|
+
this.#moveSelection(1);
|
|
994
1087
|
return;
|
|
995
1088
|
}
|
|
996
1089
|
|
|
997
1090
|
// Enter - open context menu or select directly in temporary mode
|
|
998
1091
|
if (matchesKey(keyData, "enter") || matchesKey(keyData, "return") || keyData === "\n") {
|
|
999
1092
|
const selectedItem = this.#getSelectedItem();
|
|
1000
|
-
if (selectedItem) {
|
|
1093
|
+
if (selectedItem && !this.#isItemDisabled(selectedItem)) {
|
|
1001
1094
|
if (this.#temporaryOnly) {
|
|
1002
1095
|
// In temporary mode, skip menu and select directly
|
|
1003
1096
|
this.#handleSelect(selectedItem, null);
|
|
@@ -1020,7 +1113,7 @@ export class ModelSelectorComponent extends Container {
|
|
|
1020
1113
|
}
|
|
1021
1114
|
#handleMenuInput(keyData: string): void {
|
|
1022
1115
|
const selectedItem = this.#getSelectedItem();
|
|
1023
|
-
if (!selectedItem) return;
|
|
1116
|
+
if (!selectedItem || this.#isItemDisabled(selectedItem)) return;
|
|
1024
1117
|
|
|
1025
1118
|
const optionCount =
|
|
1026
1119
|
this.#menuStep === "thinking" && this.#menuSelectedRole !== null
|
|
@@ -1079,6 +1172,9 @@ export class ModelSelectorComponent extends Container {
|
|
|
1079
1172
|
role: string | null,
|
|
1080
1173
|
thinkingLevel?: ConfiguredThinkingLevel,
|
|
1081
1174
|
): void {
|
|
1175
|
+
if (this.#isItemDisabled(item)) {
|
|
1176
|
+
return;
|
|
1177
|
+
}
|
|
1082
1178
|
// For temporary role, don't save to settings - just notify caller
|
|
1083
1179
|
if (role === null) {
|
|
1084
1180
|
this.#onSelectCallback(item.model, null, undefined, item.selector);
|
|
@@ -1,6 +1,14 @@
|
|
|
1
1
|
import { getOAuthProviders } from "@oh-my-pi/pi-ai/utils/oauth";
|
|
2
2
|
import type { OAuthProviderInfo } from "@oh-my-pi/pi-ai/utils/oauth/types";
|
|
3
|
-
import {
|
|
3
|
+
import {
|
|
4
|
+
Container,
|
|
5
|
+
extractPrintableText,
|
|
6
|
+
fuzzyFilter,
|
|
7
|
+
matchesKey,
|
|
8
|
+
ScrollView,
|
|
9
|
+
Spacer,
|
|
10
|
+
TruncatedText,
|
|
11
|
+
} from "@oh-my-pi/pi-tui";
|
|
4
12
|
import { theme } from "../../modes/theme/theme";
|
|
5
13
|
import { matchesSelectCancel, matchesSelectDown, matchesSelectUp } from "../../modes/utils/keybinding-matchers";
|
|
6
14
|
import type { AuthStorage } from "../../session/auth-storage";
|
|
@@ -162,14 +170,10 @@ export class OAuthSelectorComponent extends Container {
|
|
|
162
170
|
return this.#isSearchEnabled() || this.#searchQuery.length > 0;
|
|
163
171
|
}
|
|
164
172
|
|
|
165
|
-
#renderStatusLine(
|
|
166
|
-
const
|
|
167
|
-
const
|
|
168
|
-
|
|
169
|
-
? `${selectedCount}/${total} of ${this.#allProviders.length}`
|
|
170
|
-
: `${selectedCount}/${total}`;
|
|
171
|
-
const suffix = this.#searchQuery.trim() ? ` Search: ${this.#searchQuery}` : " Type to search";
|
|
172
|
-
return theme.fg("muted", ` (${count})${suffix}`);
|
|
173
|
+
#renderStatusLine(_total: number): string {
|
|
174
|
+
const query = this.#searchQuery.trim();
|
|
175
|
+
const suffix = query ? `Search: ${this.#searchQuery}` : "Type to search";
|
|
176
|
+
return theme.fg("muted", ` ${suffix}`);
|
|
173
177
|
}
|
|
174
178
|
|
|
175
179
|
#getProviderSearchText(provider: OAuthProviderInfo): string {
|
|
@@ -223,6 +227,7 @@ export class OAuthSelectorComponent extends Container {
|
|
|
223
227
|
: Math.max(0, Math.min(this.#selectedIndex - Math.floor(maxVisible / 2), total - maxVisible));
|
|
224
228
|
const endIndex = Math.min(startIndex + maxVisible, total);
|
|
225
229
|
|
|
230
|
+
const rows: string[] = [];
|
|
226
231
|
for (let i = startIndex; i < endIndex; i++) {
|
|
227
232
|
const provider = this.#filteredProviders[i];
|
|
228
233
|
if (!provider) continue;
|
|
@@ -239,11 +244,22 @@ export class OAuthSelectorComponent extends Container {
|
|
|
239
244
|
const text = isAvailable ? ` ${provider.name}` : theme.fg("dim", ` ${provider.name}`);
|
|
240
245
|
line = text + statusIndicator;
|
|
241
246
|
}
|
|
242
|
-
|
|
247
|
+
rows.push(line);
|
|
243
248
|
}
|
|
244
249
|
|
|
245
|
-
|
|
246
|
-
|
|
250
|
+
if (rows.length > 0) {
|
|
251
|
+
const sv = new ScrollView(rows, {
|
|
252
|
+
height: rows.length,
|
|
253
|
+
scrollbar: "auto",
|
|
254
|
+
totalRows: total,
|
|
255
|
+
theme: { track: t => theme.fg("muted", t), thumb: t => theme.fg("accent", t) },
|
|
256
|
+
});
|
|
257
|
+
sv.setScrollOffset(startIndex);
|
|
258
|
+
this.#listContainer.addChild(sv);
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
// Search status line (scrollbar covers overflow indication)
|
|
262
|
+
if (this.#shouldRenderSearchStatus()) {
|
|
247
263
|
this.#listContainer.addChild(new TruncatedText(this.#renderStatusLine(total), 0, 0));
|
|
248
264
|
}
|
|
249
265
|
|
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
* - Enter on main session -> close overlay (jump back)
|
|
16
16
|
*/
|
|
17
17
|
import type { ToolResultMessage } from "@oh-my-pi/pi-ai";
|
|
18
|
-
import { Container, Markdown, type MarkdownTheme, matchesKey } from "@oh-my-pi/pi-tui";
|
|
18
|
+
import { Container, Markdown, type MarkdownTheme, matchesKey, ScrollView } from "@oh-my-pi/pi-tui";
|
|
19
19
|
import { formatDuration, formatNumber, logger } from "@oh-my-pi/pi-utils";
|
|
20
20
|
import type { KeyId } from "../../config/keybindings";
|
|
21
21
|
import { isSilentAbort } from "../../session/messages";
|
|
@@ -230,23 +230,21 @@ export class SessionObserverOverlayComponent extends Container {
|
|
|
230
230
|
lines.push(...new DynamicBorder().render(width));
|
|
231
231
|
|
|
232
232
|
// --- Scrolled content viewport ---
|
|
233
|
-
const
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
233
|
+
const sv = new ScrollView(
|
|
234
|
+
this.#renderedLines.slice(this.#scrollOffset, this.#scrollOffset + this.#viewportHeight),
|
|
235
|
+
{
|
|
236
|
+
height: this.#viewportHeight,
|
|
237
|
+
scrollbar: "auto",
|
|
238
|
+
totalRows: this.#renderedLines.length,
|
|
239
|
+
theme: { track: t => theme.fg("dim", t), thumb: t => theme.fg("accent", t) },
|
|
240
|
+
},
|
|
241
|
+
);
|
|
242
|
+
sv.setScrollOffset(this.#scrollOffset);
|
|
243
|
+
for (const row of sv.render(Math.max(1, width - 1))) lines.push(` ${row}`);
|
|
242
244
|
|
|
243
245
|
// --- Footer ---
|
|
244
|
-
const scrollInfo =
|
|
245
|
-
this.#renderedLines.length > this.#viewportHeight
|
|
246
|
-
? ` ${theme.fg("dim", `[${this.#scrollOffset + 1}-${Math.min(this.#scrollOffset + this.#viewportHeight, this.#renderedLines.length)}/${this.#renderedLines.length}]`)}`
|
|
247
|
-
: "";
|
|
248
246
|
lines.push("");
|
|
249
|
-
lines.push(` ${this.#viewerFooterLines[0] ?? ""}
|
|
247
|
+
lines.push(` ${this.#viewerFooterLines[0] ?? ""}`);
|
|
250
248
|
for (let i = 1; i < this.#viewerFooterLines.length; i++) {
|
|
251
249
|
lines.push(` ${this.#viewerFooterLines[i]}`);
|
|
252
250
|
}
|
|
@@ -6,6 +6,7 @@ import {
|
|
|
6
6
|
matchesKey,
|
|
7
7
|
padding,
|
|
8
8
|
replaceTabs,
|
|
9
|
+
ScrollView,
|
|
9
10
|
Spacer,
|
|
10
11
|
Text,
|
|
11
12
|
truncateToWidth,
|
|
@@ -247,6 +248,11 @@ class SessionList implements Component {
|
|
|
247
248
|
const endIndex = Math.min(startIndex + maxVisible, this.#filteredSessions.length);
|
|
248
249
|
|
|
249
250
|
// Render visible sessions (3 lines, or 4 when a title adds a preview line).
|
|
251
|
+
// Each session block is built into sessionLines, then wrapped by ScrollView
|
|
252
|
+
// so the right-edge scrollbar is proportional at the physical-line level.
|
|
253
|
+
const sessionLines: string[] = [];
|
|
254
|
+
const overflow = this.#filteredSessions.length > maxVisible;
|
|
255
|
+
const rowWidth = Math.max(0, width - (overflow ? 1 : 0));
|
|
250
256
|
for (let i = startIndex; i < endIndex; i++) {
|
|
251
257
|
const session = this.#filteredSessions[i];
|
|
252
258
|
const isSelected = i === this.#selectedIndex;
|
|
@@ -258,22 +264,22 @@ class SessionList implements Component {
|
|
|
258
264
|
const cursorSymbol = `${theme.nav.cursor} `;
|
|
259
265
|
const cursorWidth = visibleWidth(cursorSymbol);
|
|
260
266
|
const cursor = isSelected ? theme.fg("accent", cursorSymbol) : padding(cursorWidth);
|
|
261
|
-
const maxWidth =
|
|
267
|
+
const maxWidth = rowWidth - cursorWidth; // Account for cursor width
|
|
262
268
|
|
|
263
269
|
if (session.title) {
|
|
264
270
|
// Has title: show title on first line, dimmed first message on second line
|
|
265
271
|
const truncatedTitle = truncateToWidth(session.title, maxWidth);
|
|
266
272
|
const titleLine = cursor + (isSelected ? theme.bold(truncatedTitle) : truncatedTitle);
|
|
267
|
-
|
|
273
|
+
sessionLines.push(titleLine);
|
|
268
274
|
|
|
269
275
|
// Second line: dimmed first message preview
|
|
270
276
|
const truncatedPreview = truncateToWidth(normalizedMessage, maxWidth);
|
|
271
|
-
|
|
277
|
+
sessionLines.push(` ${theme.fg("dim", truncatedPreview)}`);
|
|
272
278
|
} else {
|
|
273
279
|
// No title: show first message as main line
|
|
274
280
|
const truncatedMsg = truncateToWidth(normalizedMessage, maxWidth);
|
|
275
281
|
const messageLine = cursor + (isSelected ? theme.bold(truncatedMsg) : truncatedMsg);
|
|
276
|
-
|
|
282
|
+
sessionLines.push(messageLine);
|
|
277
283
|
}
|
|
278
284
|
|
|
279
285
|
// Metadata line: date + file size + lifecycle status (+ project dir in
|
|
@@ -290,18 +296,23 @@ class SessionList implements Component {
|
|
|
290
296
|
if (this.#showCwd && session.cwd) {
|
|
291
297
|
metadata += ` ${dot} ${dim(shortenPath(session.cwd))}`;
|
|
292
298
|
}
|
|
293
|
-
const metadataLine = truncateToWidth(metadata,
|
|
299
|
+
const metadataLine = truncateToWidth(metadata, rowWidth);
|
|
294
300
|
|
|
295
|
-
|
|
296
|
-
|
|
301
|
+
sessionLines.push(metadataLine);
|
|
302
|
+
sessionLines.push(""); // Blank line between sessions
|
|
297
303
|
}
|
|
298
304
|
|
|
299
|
-
//
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
+
// Wrap the rendered window in a ScrollView for a proportional right-edge bar.
|
|
306
|
+
const visibleCount = endIndex - startIndex;
|
|
307
|
+
const linesPerItem = visibleCount > 0 ? sessionLines.length / visibleCount : 1;
|
|
308
|
+
const sv = new ScrollView(sessionLines, {
|
|
309
|
+
height: sessionLines.length,
|
|
310
|
+
scrollbar: "auto",
|
|
311
|
+
totalRows: Math.round(this.#filteredSessions.length * linesPerItem),
|
|
312
|
+
theme: { track: t => theme.fg("muted", t), thumb: t => theme.fg("accent", t) },
|
|
313
|
+
});
|
|
314
|
+
sv.setScrollOffset(Math.round(startIndex * linesPerItem));
|
|
315
|
+
lines.push(...sv.render(width));
|
|
305
316
|
|
|
306
317
|
// Add keybinding hint
|
|
307
318
|
lines.push("");
|
|
@@ -546,7 +546,7 @@ export class StatusLineComponent implements Component {
|
|
|
546
546
|
return `${modelId}|${sp.length}:${sp[0]?.length ?? 0}|${tools.length}|${skills.length}`;
|
|
547
547
|
}
|
|
548
548
|
|
|
549
|
-
#buildSegmentContext(width: number): SegmentContext {
|
|
549
|
+
#buildSegmentContext(width: number, segmentOptions: StatusLineSettings["segmentOptions"]): SegmentContext {
|
|
550
550
|
const state = this.session.state;
|
|
551
551
|
|
|
552
552
|
// Trigger background fetch (5-min TTL); render uses cached value
|
|
@@ -575,7 +575,7 @@ export class StatusLineComponent implements Component {
|
|
|
575
575
|
return {
|
|
576
576
|
session: this.session,
|
|
577
577
|
width,
|
|
578
|
-
options:
|
|
578
|
+
options: segmentOptions ?? {},
|
|
579
579
|
planMode: this.#planModeStatus,
|
|
580
580
|
loopMode: this.#loopModeStatus,
|
|
581
581
|
goalMode: this.#goalModeStatus,
|
|
@@ -632,8 +632,8 @@ export class StatusLineComponent implements Component {
|
|
|
632
632
|
}
|
|
633
633
|
|
|
634
634
|
#buildStatusLine(width: number): string {
|
|
635
|
-
const ctx = this.#buildSegmentContext(width);
|
|
636
635
|
const effectiveSettings = this.#resolveSettings();
|
|
636
|
+
const ctx = this.#buildSegmentContext(width, effectiveSettings.segmentOptions);
|
|
637
637
|
const separatorDef = getSeparator(effectiveSettings.separator ?? "powerline-thin", theme);
|
|
638
638
|
|
|
639
639
|
const bgAnsi = theme.getBgAnsi("statusLineBg");
|
|
@@ -759,8 +759,6 @@ export class StatusLineComponent implements Component {
|
|
|
759
759
|
return leftGroup + (leftGroup && rightGroup ? " " : "") + rightGroup;
|
|
760
760
|
}
|
|
761
761
|
|
|
762
|
-
leftWidth = groupWidth(left, leftCapWidth, leftSepWidth);
|
|
763
|
-
rightWidth = groupWidth(right, rightCapWidth, rightSepWidth);
|
|
764
762
|
const gapWidth = Math.max(1, topFillWidth - leftWidth - rightWidth);
|
|
765
763
|
const sessionName =
|
|
766
764
|
effectiveSettings.sessionAccent !== false ? this.session.sessionManager?.getSessionName() : undefined;
|