@oh-my-pi/pi-coding-agent 13.15.3 → 13.16.1
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 +30 -16
- package/package.json +7 -7
- package/src/commit/agentic/tools/analyze-file.ts +1 -0
- package/src/config/model-registry.ts +215 -57
- package/src/config/settings-schema.ts +27 -0
- package/src/extensibility/custom-tools/types.ts +3 -0
- package/src/extensibility/extensions/runner.ts +7 -0
- package/src/extensibility/extensions/types.ts +10 -1
- package/src/extensibility/hooks/types.ts +1 -1
- package/src/internal-urls/docs-index.generated.ts +1 -1
- package/src/ipy/cancellation.ts +28 -0
- package/src/ipy/executor.ts +252 -77
- package/src/ipy/kernel.ts +181 -35
- package/src/ipy/modules.ts +39 -4
- package/src/modes/acp/acp-agent.ts +1 -0
- package/src/modes/components/hook-editor.ts +57 -8
- package/src/modes/components/model-selector.ts +48 -29
- package/src/modes/components/settings-defs.ts +10 -1
- package/src/modes/components/settings-selector.ts +92 -5
- package/src/modes/controllers/extension-ui-controller.ts +35 -4
- package/src/modes/controllers/input-controller.ts +4 -3
- package/src/modes/controllers/selector-controller.ts +2 -2
- package/src/modes/interactive-mode.ts +7 -2
- package/src/modes/print-mode.ts +1 -0
- package/src/modes/prompt-action-autocomplete.ts +5 -3
- package/src/modes/rpc/rpc-mode.ts +79 -30
- package/src/modes/rpc/rpc-types.ts +9 -1
- package/src/modes/theme/theme.ts +70 -0
- package/src/modes/types.ts +6 -1
- package/src/prompts/system/custom-system-prompt.md +5 -0
- package/src/prompts/system/system-prompt.md +6 -0
- package/src/prompts/tools/ask.md +1 -0
- package/src/prompts/tools/grep.md +1 -1
- package/src/prompts/tools/hashline.md +20 -5
- package/src/sdk.ts +26 -2
- package/src/session/agent-session.ts +18 -11
- package/src/system-prompt.ts +63 -2
- package/src/task/executor.ts +4 -0
- package/src/task/index.ts +2 -0
- package/src/tools/ask.ts +109 -61
- package/src/tools/ast-edit.ts +2 -16
- package/src/tools/ast-grep.ts +2 -17
- package/src/tools/browser.ts +35 -17
- package/src/tools/find.ts +1 -0
- package/src/tools/grep.ts +25 -34
- package/src/tools/index.ts +3 -0
- package/src/tools/path-utils.ts +7 -0
- package/src/tools/python.ts +3 -2
- package/src/tools/render-utils.ts +27 -0
- package/src/tui/tree-list.ts +51 -22
|
@@ -12,7 +12,8 @@ import {
|
|
|
12
12
|
type TUI,
|
|
13
13
|
visibleWidth,
|
|
14
14
|
} from "@oh-my-pi/pi-tui";
|
|
15
|
-
import {
|
|
15
|
+
import type { ModelRegistry } from "../../config/model-registry";
|
|
16
|
+
import { getKnownRoleIds, getRoleInfo, MODEL_ROLE_IDS, MODEL_ROLES } from "../../config/model-registry";
|
|
16
17
|
import { resolveModelRoleValue } from "../../config/model-resolver";
|
|
17
18
|
import type { Settings } from "../../config/settings";
|
|
18
19
|
import { type ThemeColor, theme } from "../../modes/theme/theme";
|
|
@@ -43,22 +44,13 @@ interface RoleAssignment {
|
|
|
43
44
|
thinkingLevel: ThinkingLevel;
|
|
44
45
|
}
|
|
45
46
|
|
|
46
|
-
type RoleSelectCallback = (model: Model, role:
|
|
47
|
+
type RoleSelectCallback = (model: Model, role: string | null, thinkingLevel?: ThinkingLevel) => void;
|
|
47
48
|
type CancelCallback = () => void;
|
|
48
49
|
interface MenuRoleAction {
|
|
49
50
|
label: string;
|
|
50
|
-
role:
|
|
51
|
+
role: string; // now accepts custom role strings
|
|
51
52
|
}
|
|
52
53
|
|
|
53
|
-
const MENU_ROLE_ACTIONS: MenuRoleAction[] = MODEL_ROLE_IDS.map(role => {
|
|
54
|
-
const roleInfo = MODEL_ROLES[role];
|
|
55
|
-
const roleLabel = roleInfo.tag ? `${roleInfo.tag} (${roleInfo.name})` : roleInfo.name;
|
|
56
|
-
return {
|
|
57
|
-
label: `Set as ${roleLabel}`,
|
|
58
|
-
role,
|
|
59
|
-
};
|
|
60
|
-
});
|
|
61
|
-
|
|
62
54
|
const ALL_TAB = "ALL";
|
|
63
55
|
|
|
64
56
|
/**
|
|
@@ -77,7 +69,7 @@ export class ModelSelectorComponent extends Container {
|
|
|
77
69
|
#allModels: ModelItem[] = [];
|
|
78
70
|
#filteredModels: ModelItem[] = [];
|
|
79
71
|
#selectedIndex: number = 0;
|
|
80
|
-
#roles = {} as Record<
|
|
72
|
+
#roles = {} as Record<string, RoleAssignment | undefined>;
|
|
81
73
|
#settings = null as unknown as Settings;
|
|
82
74
|
#modelRegistry = null as unknown as ModelRegistry;
|
|
83
75
|
#onSelectCallback = (() => {}) as RoleSelectCallback;
|
|
@@ -87,6 +79,8 @@ export class ModelSelectorComponent extends Container {
|
|
|
87
79
|
#scopedModels: ReadonlyArray<ScopedModelItem>;
|
|
88
80
|
#temporaryOnly: boolean;
|
|
89
81
|
|
|
82
|
+
#menuRoleActions: MenuRoleAction[] = [];
|
|
83
|
+
|
|
90
84
|
// Tab state
|
|
91
85
|
#providers: string[] = [ALL_TAB];
|
|
92
86
|
#activeTabIndex: number = 0;
|
|
@@ -95,7 +89,7 @@ export class ModelSelectorComponent extends Container {
|
|
|
95
89
|
#isMenuOpen: boolean = false;
|
|
96
90
|
#menuSelectedIndex: number = 0;
|
|
97
91
|
#menuStep: "role" | "thinking" = "role";
|
|
98
|
-
#menuSelectedRole:
|
|
92
|
+
#menuSelectedRole: string | null = null;
|
|
99
93
|
|
|
100
94
|
constructor(
|
|
101
95
|
tui: TUI,
|
|
@@ -103,7 +97,7 @@ export class ModelSelectorComponent extends Container {
|
|
|
103
97
|
settings: Settings,
|
|
104
98
|
modelRegistry: ModelRegistry,
|
|
105
99
|
scopedModels: ReadonlyArray<ScopedModelItem>,
|
|
106
|
-
onSelect: (model: Model, role:
|
|
100
|
+
onSelect: (model: Model, role: string | null, thinkingLevel?: ThinkingLevel) => void,
|
|
107
101
|
onCancel: () => void,
|
|
108
102
|
options?: { temporaryOnly?: boolean; initialSearchInput?: string },
|
|
109
103
|
) {
|
|
@@ -118,6 +112,9 @@ export class ModelSelectorComponent extends Container {
|
|
|
118
112
|
this.#temporaryOnly = options?.temporaryOnly ?? false;
|
|
119
113
|
const initialSearchInput = options?.initialSearchInput;
|
|
120
114
|
|
|
115
|
+
// Initialize menu role actions (built-in + custom from settings)
|
|
116
|
+
this.#buildMenuRoleActions();
|
|
117
|
+
|
|
121
118
|
// Load current role assignments from settings
|
|
122
119
|
this.#loadRoleModels();
|
|
123
120
|
|
|
@@ -184,22 +181,35 @@ export class ModelSelectorComponent extends Container {
|
|
|
184
181
|
});
|
|
185
182
|
}
|
|
186
183
|
|
|
184
|
+
#buildMenuRoleActions(): void {
|
|
185
|
+
this.#menuRoleActions = getKnownRoleIds(this.#settings).map(role => {
|
|
186
|
+
const roleInfo = getRoleInfo(role, this.#settings);
|
|
187
|
+
const roleLabel = roleInfo.tag ? `${roleInfo.tag} (${roleInfo.name})` : roleInfo.name;
|
|
188
|
+
return {
|
|
189
|
+
label: `Set as ${roleLabel}`,
|
|
190
|
+
role,
|
|
191
|
+
};
|
|
192
|
+
});
|
|
193
|
+
}
|
|
194
|
+
|
|
187
195
|
#loadRoleModels(): void {
|
|
188
196
|
const allModels = this.#modelRegistry.getAll();
|
|
189
197
|
const matchPreferences = { usageOrder: this.#settings.getStorage()?.getModelUsageOrder() };
|
|
190
|
-
for (const role of
|
|
198
|
+
for (const role of getKnownRoleIds(this.#settings)) {
|
|
191
199
|
const roleValue = this.#settings.getModelRole(role);
|
|
192
200
|
if (!roleValue) continue;
|
|
193
201
|
|
|
194
|
-
const
|
|
202
|
+
const resolved = resolveModelRoleValue(roleValue, allModels, {
|
|
195
203
|
settings: this.#settings,
|
|
196
204
|
matchPreferences,
|
|
197
205
|
});
|
|
198
|
-
if (model) {
|
|
206
|
+
if (resolved.model) {
|
|
199
207
|
this.#roles[role] = {
|
|
200
|
-
model,
|
|
208
|
+
model: resolved.model,
|
|
201
209
|
thinkingLevel:
|
|
202
|
-
explicitThinkingLevel && thinkingLevel !== undefined
|
|
210
|
+
resolved.explicitThinkingLevel && resolved.thinkingLevel !== undefined
|
|
211
|
+
? resolved.thinkingLevel
|
|
212
|
+
: ThinkingLevel.Inherit,
|
|
203
213
|
};
|
|
204
214
|
}
|
|
205
215
|
}
|
|
@@ -470,7 +480,7 @@ export class ModelSelectorComponent extends Container {
|
|
|
470
480
|
// Build role badges (inverted: color as background, black text)
|
|
471
481
|
const roleBadgeTokens: string[] = [];
|
|
472
482
|
for (const role of MODEL_ROLE_IDS) {
|
|
473
|
-
const { tag, color } =
|
|
483
|
+
const { tag, color } = getRoleInfo(role, this.#settings);
|
|
474
484
|
const assigned = this.#roles[role];
|
|
475
485
|
if (!tag || !assigned || !modelsAreEqual(assigned.model, item.model)) continue;
|
|
476
486
|
|
|
@@ -478,6 +488,15 @@ export class ModelSelectorComponent extends Container {
|
|
|
478
488
|
const thinkingLabel = getThinkingLevelMetadata(assigned.thinkingLevel).label;
|
|
479
489
|
roleBadgeTokens.push(`${badge} ${theme.fg("dim", `(${thinkingLabel})`)}`);
|
|
480
490
|
}
|
|
491
|
+
// Custom role badges
|
|
492
|
+
for (const [role, assigned] of Object.entries(this.#roles)) {
|
|
493
|
+
if (role in MODEL_ROLES || !assigned || !modelsAreEqual(assigned.model, item.model)) continue;
|
|
494
|
+
const roleInfo = getRoleInfo(role, this.#settings);
|
|
495
|
+
const badgeLabel = roleInfo.tag ?? roleInfo.name;
|
|
496
|
+
const badge = makeInvertedBadge(badgeLabel, roleInfo.color ?? "muted");
|
|
497
|
+
const thinkingLabel = getThinkingLevelMetadata(assigned.thinkingLevel).label;
|
|
498
|
+
roleBadgeTokens.push(`${badge} ${theme.fg("dim", `(${thinkingLabel})`)}`);
|
|
499
|
+
}
|
|
481
500
|
const badgeText = roleBadgeTokens.length > 0 ? ` ${roleBadgeTokens.join(" ")}` : "";
|
|
482
501
|
|
|
483
502
|
let line = "";
|
|
@@ -527,11 +546,11 @@ export class ModelSelectorComponent extends Container {
|
|
|
527
546
|
return [ThinkingLevel.Inherit, ThinkingLevel.Off, ...getSupportedEfforts(model)];
|
|
528
547
|
}
|
|
529
548
|
|
|
530
|
-
#getCurrentRoleThinkingLevel(role:
|
|
549
|
+
#getCurrentRoleThinkingLevel(role: string): ThinkingLevel {
|
|
531
550
|
return this.#roles[role]?.thinkingLevel ?? ThinkingLevel.Inherit;
|
|
532
551
|
}
|
|
533
552
|
|
|
534
|
-
#getThinkingPreselectIndex(role:
|
|
553
|
+
#getThinkingPreselectIndex(role: string, model: Model): number {
|
|
535
554
|
const options = this.#getThinkingLevelsForModel(model);
|
|
536
555
|
const currentLevel = this.#getCurrentRoleThinkingLevel(role);
|
|
537
556
|
const foundIndex = options.indexOf(currentLevel);
|
|
@@ -569,12 +588,12 @@ export class ModelSelectorComponent extends Container {
|
|
|
569
588
|
const label = getThinkingLevelMetadata(thinkingLevel).label;
|
|
570
589
|
return `${prefix}${label}`;
|
|
571
590
|
})
|
|
572
|
-
:
|
|
591
|
+
: this.#menuRoleActions.map((action, index) => {
|
|
573
592
|
const prefix = index === this.#menuSelectedIndex ? ` ${theme.nav.cursor} ` : " ";
|
|
574
593
|
return `${prefix}${action.label}`;
|
|
575
594
|
});
|
|
576
595
|
|
|
577
|
-
const selectedRoleName = this.#menuSelectedRole ?
|
|
596
|
+
const selectedRoleName = this.#menuSelectedRole ? getRoleInfo(this.#menuSelectedRole, this.#settings).name : "";
|
|
578
597
|
const headerText =
|
|
579
598
|
showingThinking && this.#menuSelectedRole
|
|
580
599
|
? ` Thinking for: ${selectedRoleName} (${selectedModel.id})`
|
|
@@ -674,7 +693,7 @@ export class ModelSelectorComponent extends Container {
|
|
|
674
693
|
const optionCount =
|
|
675
694
|
this.#menuStep === "thinking" && this.#menuSelectedRole !== null
|
|
676
695
|
? this.#getThinkingLevelsForModel(selectedModel.model).length
|
|
677
|
-
:
|
|
696
|
+
: this.#menuRoleActions.length;
|
|
678
697
|
if (optionCount === 0) return;
|
|
679
698
|
|
|
680
699
|
if (matchesKey(keyData, "up")) {
|
|
@@ -691,7 +710,7 @@ export class ModelSelectorComponent extends Container {
|
|
|
691
710
|
|
|
692
711
|
if (matchesKey(keyData, "enter") || matchesKey(keyData, "return") || keyData === "\n") {
|
|
693
712
|
if (this.#menuStep === "role") {
|
|
694
|
-
const action =
|
|
713
|
+
const action = this.#menuRoleActions[this.#menuSelectedIndex];
|
|
695
714
|
if (!action) return;
|
|
696
715
|
this.#menuSelectedRole = action.role;
|
|
697
716
|
this.#menuStep = "thinking";
|
|
@@ -712,7 +731,7 @@ export class ModelSelectorComponent extends Container {
|
|
|
712
731
|
if (getKeybindings().matches(keyData, "tui.select.cancel")) {
|
|
713
732
|
if (this.#menuStep === "thinking" && this.#menuSelectedRole !== null) {
|
|
714
733
|
this.#menuStep = "role";
|
|
715
|
-
const roleIndex =
|
|
734
|
+
const roleIndex = this.#menuRoleActions.findIndex(action => action.role === this.#menuSelectedRole);
|
|
716
735
|
this.#menuSelectedRole = null;
|
|
717
736
|
this.#menuSelectedIndex = roleIndex >= 0 ? roleIndex : 0;
|
|
718
737
|
this.#updateMenu();
|
|
@@ -728,7 +747,7 @@ export class ModelSelectorComponent extends Container {
|
|
|
728
747
|
if (thinkingLevel === ThinkingLevel.Inherit) return modelKey;
|
|
729
748
|
return `${modelKey}:${thinkingLevel}`;
|
|
730
749
|
}
|
|
731
|
-
#handleSelect(model: Model, role:
|
|
750
|
+
#handleSelect(model: Model, role: string | null, thinkingLevel?: ThinkingLevel): void {
|
|
732
751
|
// For temporary role, don't save to settings - just notify caller
|
|
733
752
|
if (role === null) {
|
|
734
753
|
this.#onSelectCallback(model, null);
|
|
@@ -51,7 +51,11 @@ export interface SubmenuSettingDef extends BaseSettingDef {
|
|
|
51
51
|
onPreviewCancel?: (originalValue: string) => void;
|
|
52
52
|
}
|
|
53
53
|
|
|
54
|
-
export
|
|
54
|
+
export interface TextInputSettingDef extends BaseSettingDef {
|
|
55
|
+
type: "text";
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export type SettingDef = BooleanSettingDef | EnumSettingDef | SubmenuSettingDef | TextInputSettingDef;
|
|
55
59
|
|
|
56
60
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
57
61
|
// Condition Functions
|
|
@@ -465,6 +469,11 @@ function pathToSettingDef(path: SettingPath): SettingDef | null {
|
|
|
465
469
|
return createSubmenuSettingDef(base, []);
|
|
466
470
|
}
|
|
467
471
|
|
|
472
|
+
// Plain string setting — free-text input field
|
|
473
|
+
if (schemaType === "string") {
|
|
474
|
+
return { ...base, type: "text" };
|
|
475
|
+
}
|
|
476
|
+
|
|
468
477
|
return null;
|
|
469
478
|
}
|
|
470
479
|
|
|
@@ -2,6 +2,7 @@ import type { ThinkingLevel } from "@oh-my-pi/pi-agent-core";
|
|
|
2
2
|
import type { Effort } from "@oh-my-pi/pi-ai";
|
|
3
3
|
import {
|
|
4
4
|
Container,
|
|
5
|
+
Input,
|
|
5
6
|
matchesKey,
|
|
6
7
|
type SelectItem,
|
|
7
8
|
SelectList,
|
|
@@ -31,6 +32,52 @@ import { getPreset } from "./status-line/presets";
|
|
|
31
32
|
/**
|
|
32
33
|
* A submenu component for selecting from a list of options.
|
|
33
34
|
*/
|
|
35
|
+
/**
|
|
36
|
+
* Submenu component for free-text string settings.
|
|
37
|
+
* Mirrors the ConfigInputSubmenu pattern from plugin-settings.ts.
|
|
38
|
+
*/
|
|
39
|
+
class TextInputSubmenu extends Container {
|
|
40
|
+
#input: Input;
|
|
41
|
+
|
|
42
|
+
constructor(
|
|
43
|
+
label: string,
|
|
44
|
+
description: string,
|
|
45
|
+
currentValue: string,
|
|
46
|
+
private readonly onSubmit: (value: string) => void,
|
|
47
|
+
private readonly onCancel: () => void,
|
|
48
|
+
) {
|
|
49
|
+
super();
|
|
50
|
+
|
|
51
|
+
this.addChild(new Text(theme.bold(theme.fg("accent", label)), 0, 0));
|
|
52
|
+
if (description) {
|
|
53
|
+
this.addChild(new Spacer(1));
|
|
54
|
+
this.addChild(new Text(theme.fg("muted", description), 0, 0));
|
|
55
|
+
}
|
|
56
|
+
this.addChild(new Spacer(1));
|
|
57
|
+
|
|
58
|
+
this.#input = new Input();
|
|
59
|
+
if (currentValue) {
|
|
60
|
+
this.#input.setValue(currentValue);
|
|
61
|
+
// Move cursor to end of pre-filled value (ctrl+e = cursorLineEnd).
|
|
62
|
+
this.#input.handleInput("\x05");
|
|
63
|
+
}
|
|
64
|
+
this.#input.onSubmit = value => {
|
|
65
|
+
this.onSubmit(value); // empty string clears the setting
|
|
66
|
+
};
|
|
67
|
+
this.addChild(this.#input);
|
|
68
|
+
this.addChild(new Spacer(1));
|
|
69
|
+
this.addChild(new Text(theme.fg("dim", " Enter to save · Esc to cancel · Clear field to unset"), 0, 0));
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
handleInput(data: string): void {
|
|
73
|
+
if (data === "\x1b" || data === "\x1b\x1b") {
|
|
74
|
+
this.onCancel();
|
|
75
|
+
return;
|
|
76
|
+
}
|
|
77
|
+
this.#input.handleInput(data);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
34
81
|
class SelectSubmenu extends Container {
|
|
35
82
|
#selectList: SelectList;
|
|
36
83
|
#previewText: Text | null = null;
|
|
@@ -180,6 +227,7 @@ export class SettingsSelectorComponent extends Container {
|
|
|
180
227
|
#statusPreviewContainer: Container | null = null;
|
|
181
228
|
#statusPreviewText: Text | null = null;
|
|
182
229
|
#currentTabId: SettingTab | "plugins" = "appearance";
|
|
230
|
+
#textInputActive = false;
|
|
183
231
|
|
|
184
232
|
constructor(
|
|
185
233
|
private readonly context: SettingsRuntimeContext,
|
|
@@ -277,6 +325,15 @@ export class SettingsSelectorComponent extends Container {
|
|
|
277
325
|
currentValue: this.#getSubmenuCurrentValue(def.path, currentValue),
|
|
278
326
|
submenu: (cv, done) => this.#createSubmenu(def, cv, done),
|
|
279
327
|
};
|
|
328
|
+
|
|
329
|
+
case "text":
|
|
330
|
+
return {
|
|
331
|
+
id: def.path,
|
|
332
|
+
label: def.label,
|
|
333
|
+
description: def.description,
|
|
334
|
+
currentValue: (currentValue as string) ?? "",
|
|
335
|
+
submenu: (cv, done) => this.#createTextInput(def, cv, done),
|
|
336
|
+
};
|
|
280
337
|
}
|
|
281
338
|
}
|
|
282
339
|
|
|
@@ -389,6 +446,34 @@ export class SettingsSelectorComponent extends Container {
|
|
|
389
446
|
);
|
|
390
447
|
}
|
|
391
448
|
|
|
449
|
+
/**
|
|
450
|
+
* Create a text input submenu for a plain string setting.
|
|
451
|
+
*/
|
|
452
|
+
#createTextInput(
|
|
453
|
+
def: SettingDef & { type: "text" },
|
|
454
|
+
currentValue: string,
|
|
455
|
+
done: (value?: string) => void,
|
|
456
|
+
): Container {
|
|
457
|
+
this.#textInputActive = true;
|
|
458
|
+
const wrappedDone = (value?: string) => {
|
|
459
|
+
this.#textInputActive = false;
|
|
460
|
+
done(value);
|
|
461
|
+
};
|
|
462
|
+
return new TextInputSubmenu(
|
|
463
|
+
def.label,
|
|
464
|
+
def.description,
|
|
465
|
+
currentValue,
|
|
466
|
+
value => {
|
|
467
|
+
// Empty string clears the setting; undefined-typed string settings
|
|
468
|
+
// store "" which the browser.ts expandPath ignores (no-op fallback).
|
|
469
|
+
this.#setSettingValue(def.path, value);
|
|
470
|
+
this.callbacks.onChange(def.path, value);
|
|
471
|
+
wrappedDone(value);
|
|
472
|
+
},
|
|
473
|
+
() => wrappedDone(),
|
|
474
|
+
);
|
|
475
|
+
}
|
|
476
|
+
|
|
392
477
|
/**
|
|
393
478
|
* Set a setting value, handling type conversion.
|
|
394
479
|
*/
|
|
@@ -510,12 +595,14 @@ export class SettingsSelectorComponent extends Container {
|
|
|
510
595
|
}
|
|
511
596
|
|
|
512
597
|
handleInput(data: string): void {
|
|
513
|
-
// Handle tab switching
|
|
598
|
+
// Handle tab switching — but NOT when a text input is active, since
|
|
599
|
+
// arrow keys must reach the cursor and Tab must not switch tabs.
|
|
514
600
|
if (
|
|
515
|
-
|
|
516
|
-
matchesKey(data, "
|
|
517
|
-
|
|
518
|
-
|
|
601
|
+
!this.#textInputActive &&
|
|
602
|
+
(matchesKey(data, "tab") ||
|
|
603
|
+
matchesKey(data, "shift+tab") ||
|
|
604
|
+
matchesKey(data, "left") ||
|
|
605
|
+
matchesKey(data, "right"))
|
|
519
606
|
) {
|
|
520
607
|
this.#tabBar.handleInput(data);
|
|
521
608
|
return;
|
|
@@ -50,7 +50,8 @@ export class ExtensionUiController {
|
|
|
50
50
|
this.ctx.editor.handleInput(`\x1b[200~${text}\x1b[201~`);
|
|
51
51
|
},
|
|
52
52
|
getEditorText: () => this.ctx.editor.getText(),
|
|
53
|
-
editor: (title, prefill
|
|
53
|
+
editor: (title, prefill, dialogOptions, editorOptions) =>
|
|
54
|
+
this.showHookEditor(title, prefill, dialogOptions, editorOptions),
|
|
54
55
|
get theme() {
|
|
55
56
|
return theme;
|
|
56
57
|
},
|
|
@@ -122,6 +123,7 @@ export class ExtensionUiController {
|
|
|
122
123
|
};
|
|
123
124
|
const contextActions: ExtensionContextActions = {
|
|
124
125
|
getModel: () => this.ctx.session.model,
|
|
126
|
+
getSearchDb: () => this.ctx.session.searchDb,
|
|
125
127
|
isIdle: () => !this.ctx.session.isStreaming,
|
|
126
128
|
abort: () => this.ctx.session.abort(),
|
|
127
129
|
hasPendingMessages: () => this.ctx.session.queuedMessageCount > 0,
|
|
@@ -383,6 +385,7 @@ export class ExtensionUiController {
|
|
|
383
385
|
};
|
|
384
386
|
const contextActions: ExtensionContextActions = {
|
|
385
387
|
getModel: () => this.ctx.session.model,
|
|
388
|
+
getSearchDb: () => this.ctx.session.searchDb,
|
|
386
389
|
isIdle: () => !this.ctx.session.isStreaming,
|
|
387
390
|
abort: () => this.ctx.session.abort(),
|
|
388
391
|
hasPendingMessages: () => this.ctx.session.queuedMessageCount > 0,
|
|
@@ -580,6 +583,7 @@ export class ExtensionUiController {
|
|
|
580
583
|
sessionManager: this.ctx.session.sessionManager,
|
|
581
584
|
modelRegistry: this.ctx.session.modelRegistry,
|
|
582
585
|
model: this.ctx.session.model,
|
|
586
|
+
searchDb: this.ctx.session.searchDb,
|
|
583
587
|
isIdle: () => !this.ctx.session.isStreaming,
|
|
584
588
|
hasPendingMessages: () => this.ctx.session.queuedMessageCount > 0,
|
|
585
589
|
hasQueuedMessages: () => this.ctx.session.queuedMessageCount > 0,
|
|
@@ -783,26 +787,53 @@ export class ExtensionUiController {
|
|
|
783
787
|
/**
|
|
784
788
|
* Show a multi-line editor for hooks (with Ctrl+G support).
|
|
785
789
|
*/
|
|
786
|
-
showHookEditor(
|
|
790
|
+
showHookEditor(
|
|
791
|
+
title: string,
|
|
792
|
+
prefill?: string,
|
|
793
|
+
dialogOptions?: ExtensionUIDialogOptions,
|
|
794
|
+
editorOptions?: { promptStyle?: boolean },
|
|
795
|
+
): Promise<string | undefined> {
|
|
787
796
|
const { promise, resolve } = Promise.withResolvers<string | undefined>();
|
|
797
|
+
let settled = false;
|
|
798
|
+
const onAbort = () => {
|
|
799
|
+
this.hideHookEditor();
|
|
800
|
+
if (!settled) {
|
|
801
|
+
settled = true;
|
|
802
|
+
resolve(undefined);
|
|
803
|
+
}
|
|
804
|
+
};
|
|
805
|
+
const finish = (value: string | undefined) => {
|
|
806
|
+
if (settled) return;
|
|
807
|
+
settled = true;
|
|
808
|
+
dialogOptions?.signal?.removeEventListener("abort", onAbort);
|
|
809
|
+
resolve(value);
|
|
810
|
+
};
|
|
788
811
|
this.ctx.hookEditor = new HookEditorComponent(
|
|
789
812
|
this.ctx.ui,
|
|
790
813
|
title,
|
|
791
814
|
prefill,
|
|
792
815
|
value => {
|
|
793
816
|
this.hideHookEditor();
|
|
794
|
-
|
|
817
|
+
finish(value);
|
|
795
818
|
},
|
|
796
819
|
() => {
|
|
797
820
|
this.hideHookEditor();
|
|
798
|
-
|
|
821
|
+
finish(undefined);
|
|
799
822
|
},
|
|
823
|
+
editorOptions,
|
|
800
824
|
);
|
|
801
825
|
|
|
802
826
|
this.ctx.editorContainer.clear();
|
|
803
827
|
this.ctx.editorContainer.addChild(this.ctx.hookEditor);
|
|
804
828
|
this.ctx.ui.setFocus(this.ctx.hookEditor);
|
|
805
829
|
this.ctx.ui.requestRender();
|
|
830
|
+
if (dialogOptions?.signal) {
|
|
831
|
+
if (dialogOptions.signal.aborted) {
|
|
832
|
+
onAbort();
|
|
833
|
+
} else {
|
|
834
|
+
dialogOptions.signal.addEventListener("abort", onAbort, { once: true });
|
|
835
|
+
}
|
|
836
|
+
}
|
|
806
837
|
return promise;
|
|
807
838
|
}
|
|
808
839
|
|
|
@@ -550,6 +550,7 @@ export class InputController {
|
|
|
550
550
|
return createPromptActionAutocompleteProvider({
|
|
551
551
|
commands,
|
|
552
552
|
basePath,
|
|
553
|
+
searchDb: this.ctx.session.searchDb,
|
|
553
554
|
keybindings: this.ctx.keybindings,
|
|
554
555
|
copyCurrentLine: () => this.handleCopyCurrentLine(),
|
|
555
556
|
copyPrompt: () => this.handleCopyPrompt(),
|
|
@@ -608,8 +609,8 @@ export class InputController {
|
|
|
608
609
|
|
|
609
610
|
async cycleRoleModel(options?: { temporary?: boolean }): Promise<void> {
|
|
610
611
|
try {
|
|
611
|
-
const
|
|
612
|
-
const result = await this.ctx.session.cycleRoleModels(
|
|
612
|
+
const cycleOrder = settings.get("cycleOrder");
|
|
613
|
+
const result = await this.ctx.session.cycleRoleModels(cycleOrder, options);
|
|
613
614
|
if (!result) {
|
|
614
615
|
this.ctx.showStatus("Only one role model available");
|
|
615
616
|
return;
|
|
@@ -625,7 +626,7 @@ export class InputController {
|
|
|
625
626
|
: "";
|
|
626
627
|
const tempLabel = options?.temporary ? " (temporary)" : "";
|
|
627
628
|
const cycleSeparator = theme.fg("dim", " > ");
|
|
628
|
-
const cycleLabel =
|
|
629
|
+
const cycleLabel = cycleOrder
|
|
629
630
|
.map(role => {
|
|
630
631
|
if (role === result.role) {
|
|
631
632
|
return theme.bold(theme.fg("accent", role));
|
|
@@ -3,7 +3,7 @@ import { getOAuthProviders, type OAuthProvider } from "@oh-my-pi/pi-ai";
|
|
|
3
3
|
import type { Component } from "@oh-my-pi/pi-tui";
|
|
4
4
|
import { Input, Loader, Spacer, Text } from "@oh-my-pi/pi-tui";
|
|
5
5
|
import { getAgentDbPath, getProjectDir } from "@oh-my-pi/pi-utils";
|
|
6
|
-
import {
|
|
6
|
+
import { getRoleInfo } from "../../config/model-registry";
|
|
7
7
|
import { settings } from "../../config/settings";
|
|
8
8
|
import { DebugSelectorComponent } from "../../debug";
|
|
9
9
|
import { disableProvider, enableProvider } from "../../discovery";
|
|
@@ -406,7 +406,7 @@ export class SelectorController {
|
|
|
406
406
|
// Don't call done() - selector stays open for role assignment
|
|
407
407
|
} else {
|
|
408
408
|
// Other roles (smol, slow): just update settings, not current model
|
|
409
|
-
const roleInfo =
|
|
409
|
+
const roleInfo = getRoleInfo(role, settings);
|
|
410
410
|
const roleLabel = roleInfo?.name ?? role;
|
|
411
411
|
this.ctx.showStatus(`${roleLabel} model: ${model.id}`);
|
|
412
412
|
// Don't call done() - selector stays open
|
|
@@ -1399,8 +1399,13 @@ export class InteractiveMode implements InteractiveModeContext {
|
|
|
1399
1399
|
this.#extensionUiController.hideHookInput();
|
|
1400
1400
|
}
|
|
1401
1401
|
|
|
1402
|
-
showHookEditor(
|
|
1403
|
-
|
|
1402
|
+
showHookEditor(
|
|
1403
|
+
title: string,
|
|
1404
|
+
prefill?: string,
|
|
1405
|
+
dialogOptions?: ExtensionUIDialogOptions,
|
|
1406
|
+
editorOptions?: { promptStyle?: boolean },
|
|
1407
|
+
): Promise<string | undefined> {
|
|
1408
|
+
return this.#extensionUiController.showHookEditor(title, prefill, dialogOptions, editorOptions);
|
|
1404
1409
|
}
|
|
1405
1410
|
|
|
1406
1411
|
hideHookEditor(): void {
|
package/src/modes/print-mode.ts
CHANGED
|
@@ -76,6 +76,7 @@ export async function runPrintMode(session: AgentSession, options: PrintModeOpti
|
|
|
76
76
|
// ExtensionContextActions
|
|
77
77
|
{
|
|
78
78
|
getModel: () => session.model,
|
|
79
|
+
getSearchDb: () => session.searchDb,
|
|
79
80
|
isIdle: () => !session.isStreaming,
|
|
80
81
|
abort: () => session.abort(),
|
|
81
82
|
hasPendingMessages: () => session.queuedMessageCount > 0,
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { SearchDb } from "@oh-my-pi/pi-natives";
|
|
1
2
|
import {
|
|
2
3
|
type AutocompleteItem,
|
|
3
4
|
type AutocompleteProvider,
|
|
@@ -23,6 +24,7 @@ interface PromptActionAutocompleteItem extends AutocompleteItem {
|
|
|
23
24
|
interface PromptActionAutocompleteOptions {
|
|
24
25
|
commands: SlashCommand[];
|
|
25
26
|
basePath: string;
|
|
27
|
+
searchDb?: SearchDb;
|
|
26
28
|
keybindings: KeybindingsManager;
|
|
27
29
|
copyCurrentLine: () => void;
|
|
28
30
|
copyPrompt: () => void;
|
|
@@ -90,8 +92,8 @@ export class PromptActionAutocompleteProvider implements AutocompleteProvider {
|
|
|
90
92
|
#baseProvider: CombinedAutocompleteProvider;
|
|
91
93
|
#actions: PromptActionDefinition[];
|
|
92
94
|
|
|
93
|
-
constructor(commands: SlashCommand[], basePath: string, actions: PromptActionDefinition[]) {
|
|
94
|
-
this.#baseProvider = new CombinedAutocompleteProvider(commands, basePath);
|
|
95
|
+
constructor(commands: SlashCommand[], basePath: string, actions: PromptActionDefinition[], searchDb?: SearchDb) {
|
|
96
|
+
this.#baseProvider = new CombinedAutocompleteProvider(commands, basePath, searchDb);
|
|
95
97
|
this.#actions = actions;
|
|
96
98
|
}
|
|
97
99
|
|
|
@@ -227,5 +229,5 @@ export function createPromptActionAutocompleteProvider(
|
|
|
227
229
|
},
|
|
228
230
|
];
|
|
229
231
|
|
|
230
|
-
return new PromptActionAutocompleteProvider(options.commands, options.basePath, actions);
|
|
232
|
+
return new PromptActionAutocompleteProvider(options.commands, options.basePath, actions, options.searchDb);
|
|
231
233
|
}
|