@oh-my-pi/pi-coding-agent 1.341.0 → 2.0.1337
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 +73 -0
- package/README.md +1 -1
- package/examples/custom-tools/subagent/index.ts +1 -1
- package/package.json +5 -3
- package/src/cli/args.ts +5 -6
- package/src/cli/file-processor.ts +3 -3
- package/src/cli/list-models.ts +2 -2
- package/src/cli/plugin-cli.ts +1 -1
- package/src/cli/session-picker.ts +2 -2
- package/src/cli.ts +1 -1
- package/src/config.ts +3 -3
- package/src/core/agent-session.ts +157 -15
- package/src/core/bash-executor.ts +50 -10
- package/src/core/compaction/branch-summarization.ts +5 -5
- package/src/core/compaction/compaction.ts +3 -3
- package/src/core/compaction/index.ts +3 -3
- package/src/core/custom-commands/bundled/review/index.ts +156 -0
- package/src/core/custom-commands/index.ts +15 -0
- package/src/core/custom-commands/loader.ts +232 -0
- package/src/core/custom-commands/types.ts +112 -0
- package/src/core/custom-tools/index.ts +3 -3
- package/src/core/custom-tools/loader.ts +10 -8
- package/src/core/custom-tools/types.ts +11 -6
- package/src/core/custom-tools/wrapper.ts +2 -1
- package/src/core/exec.ts +22 -12
- package/src/core/export-html/index.ts +5 -5
- package/src/core/file-mentions.ts +54 -0
- package/src/core/hooks/index.ts +5 -5
- package/src/core/hooks/loader.ts +21 -16
- package/src/core/hooks/runner.ts +6 -6
- package/src/core/hooks/tool-wrapper.ts +2 -2
- package/src/core/hooks/types.ts +12 -15
- package/src/core/index.ts +6 -6
- package/src/core/logger.ts +112 -0
- package/src/core/mcp/client.ts +3 -3
- package/src/core/mcp/config.ts +1 -1
- package/src/core/mcp/index.ts +12 -12
- package/src/core/mcp/loader.ts +2 -2
- package/src/core/mcp/manager.ts +6 -6
- package/src/core/mcp/tool-bridge.ts +3 -3
- package/src/core/mcp/transports/http.ts +1 -1
- package/src/core/mcp/transports/index.ts +2 -2
- package/src/core/mcp/transports/stdio.ts +1 -1
- package/src/core/messages.ts +22 -0
- package/src/core/model-registry.ts +2 -2
- package/src/core/model-resolver.ts +2 -2
- package/src/core/plugins/doctor.ts +1 -1
- package/src/core/plugins/index.ts +6 -6
- package/src/core/plugins/installer.ts +4 -4
- package/src/core/plugins/loader.ts +4 -9
- package/src/core/plugins/manager.ts +5 -5
- package/src/core/plugins/paths.ts +3 -3
- package/src/core/sdk.ts +77 -35
- package/src/core/session-manager.ts +6 -6
- package/src/core/settings-manager.ts +16 -3
- package/src/core/skills.ts +5 -5
- package/src/core/slash-commands.ts +60 -45
- package/src/core/system-prompt.ts +6 -6
- package/src/core/title-generator.ts +2 -2
- package/src/core/tools/bash.ts +32 -155
- package/src/core/tools/context.ts +2 -2
- package/src/core/tools/edit-diff.ts +3 -3
- package/src/core/tools/edit.ts +18 -5
- package/src/core/tools/exa/company.ts +3 -3
- package/src/core/tools/exa/index.ts +16 -17
- package/src/core/tools/exa/linkedin.ts +3 -3
- package/src/core/tools/exa/mcp-client.ts +9 -9
- package/src/core/tools/exa/render.ts +5 -5
- package/src/core/tools/exa/researcher.ts +3 -3
- package/src/core/tools/exa/search.ts +6 -5
- package/src/core/tools/exa/types.ts +5 -6
- package/src/core/tools/exa/websets.ts +3 -3
- package/src/core/tools/find.ts +3 -3
- package/src/core/tools/grep.ts +3 -3
- package/src/core/tools/index.ts +48 -34
- package/src/core/tools/ls.ts +4 -4
- package/src/core/tools/lsp/client.ts +161 -90
- package/src/core/tools/lsp/config.ts +1 -1
- package/src/core/tools/lsp/edits.ts +2 -2
- package/src/core/tools/lsp/index.ts +15 -13
- package/src/core/tools/lsp/render.ts +2 -2
- package/src/core/tools/lsp/rust-analyzer.ts +3 -3
- package/src/core/tools/lsp/utils.ts +1 -1
- package/src/core/tools/notebook.ts +1 -1
- package/src/core/tools/output.ts +175 -0
- package/src/core/tools/read.ts +7 -7
- package/src/core/tools/renderers.ts +92 -13
- package/src/core/tools/review.ts +268 -0
- package/src/core/tools/task/agents.ts +1 -1
- package/src/core/tools/task/bundled-agents/reviewer.md +52 -37
- package/src/core/tools/task/discovery.ts +2 -2
- package/src/core/tools/task/executor.ts +145 -28
- package/src/core/tools/task/index.ts +78 -30
- package/src/core/tools/task/model-resolver.ts +30 -20
- package/src/core/tools/task/parallel.ts +1 -1
- package/src/core/tools/task/render.ts +219 -30
- package/src/core/tools/task/subprocess-tool-registry.ts +89 -0
- package/src/core/tools/task/types.ts +36 -2
- package/src/core/tools/web-fetch.ts +5 -3
- package/src/core/tools/web-search/auth.ts +1 -1
- package/src/core/tools/web-search/index.ts +17 -15
- package/src/core/tools/web-search/providers/anthropic.ts +2 -2
- package/src/core/tools/web-search/providers/exa.ts +3 -5
- package/src/core/tools/web-search/providers/perplexity.ts +1 -1
- package/src/core/tools/web-search/render.ts +3 -3
- package/src/core/tools/write.ts +4 -4
- package/src/index.ts +29 -18
- package/src/main.ts +37 -32
- package/src/migrations.ts +3 -3
- package/src/modes/index.ts +5 -5
- package/src/modes/interactive/components/armin.ts +1 -1
- package/src/modes/interactive/components/assistant-message.ts +1 -1
- package/src/modes/interactive/components/bash-execution.ts +4 -4
- package/src/modes/interactive/components/bordered-loader.ts +2 -2
- package/src/modes/interactive/components/branch-summary-message.ts +2 -2
- package/src/modes/interactive/components/compaction-summary-message.ts +2 -2
- package/src/modes/interactive/components/diff.ts +1 -1
- package/src/modes/interactive/components/dynamic-border.ts +1 -1
- package/src/modes/interactive/components/footer.ts +5 -5
- package/src/modes/interactive/components/hook-editor.ts +2 -2
- package/src/modes/interactive/components/hook-input.ts +2 -2
- package/src/modes/interactive/components/hook-message.ts +3 -3
- package/src/modes/interactive/components/hook-selector.ts +2 -2
- package/src/modes/interactive/components/model-selector.ts +281 -59
- package/src/modes/interactive/components/oauth-selector.ts +3 -3
- package/src/modes/interactive/components/plugin-settings.ts +4 -4
- package/src/modes/interactive/components/queue-mode-selector.ts +2 -2
- package/src/modes/interactive/components/session-selector.ts +4 -4
- package/src/modes/interactive/components/settings-defs.ts +1 -1
- package/src/modes/interactive/components/settings-selector.ts +5 -5
- package/src/modes/interactive/components/show-images-selector.ts +2 -2
- package/src/modes/interactive/components/theme-selector.ts +2 -2
- package/src/modes/interactive/components/thinking-selector.ts +2 -2
- package/src/modes/interactive/components/tool-execution.ts +26 -8
- package/src/modes/interactive/components/tree-selector.ts +3 -3
- package/src/modes/interactive/components/user-message-selector.ts +2 -2
- package/src/modes/interactive/components/user-message.ts +1 -1
- package/src/modes/interactive/components/welcome.ts +2 -2
- package/src/modes/interactive/interactive-mode.ts +85 -41
- package/src/modes/interactive/theme/theme.ts +8 -7
- package/src/modes/print-mode.ts +4 -3
- package/src/modes/rpc/rpc-client.ts +4 -4
- package/src/modes/rpc/rpc-mode.ts +21 -11
- package/src/modes/rpc/rpc-types.ts +3 -3
- package/src/utils/changelog.ts +2 -2
- package/src/utils/clipboard.ts +1 -1
- package/src/utils/shell-snapshot.ts +218 -0
- package/src/utils/shell.ts +93 -13
- package/src/utils/tools-manager.ts +1 -1
- package/examples/custom-tools/subagent/agents/reviewer.md +0 -35
- package/src/core/tools/exa/logger.ts +0 -56
|
@@ -1,11 +1,25 @@
|
|
|
1
1
|
import { type Model, modelsAreEqual } from "@oh-my-pi/pi-ai";
|
|
2
|
-
import {
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
2
|
+
import {
|
|
3
|
+
Container,
|
|
4
|
+
Input,
|
|
5
|
+
isArrowDown,
|
|
6
|
+
isArrowLeft,
|
|
7
|
+
isArrowRight,
|
|
8
|
+
isArrowUp,
|
|
9
|
+
isEnter,
|
|
10
|
+
isEscape,
|
|
11
|
+
isShiftTab,
|
|
12
|
+
isTab,
|
|
13
|
+
Spacer,
|
|
14
|
+
Text,
|
|
15
|
+
type TUI,
|
|
16
|
+
} from "@oh-my-pi/pi-tui";
|
|
17
|
+
import type { ModelRegistry } from "../../../core/model-registry";
|
|
18
|
+
import { parseModelString } from "../../../core/model-resolver";
|
|
19
|
+
import type { SettingsManager } from "../../../core/settings-manager";
|
|
20
|
+
import { fuzzyFilter } from "../../../utils/fuzzy";
|
|
21
|
+
import { theme } from "../theme/theme";
|
|
22
|
+
import { DynamicBorder } from "./dynamic-border";
|
|
9
23
|
|
|
10
24
|
interface ModelItem {
|
|
11
25
|
provider: string;
|
|
@@ -18,16 +32,33 @@ interface ScopedModelItem {
|
|
|
18
32
|
thinkingLevel: string;
|
|
19
33
|
}
|
|
20
34
|
|
|
35
|
+
type ModelRole = "default" | "smol" | "slow";
|
|
36
|
+
|
|
37
|
+
interface MenuAction {
|
|
38
|
+
label: string;
|
|
39
|
+
role: ModelRole;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
const MENU_ACTIONS: MenuAction[] = [
|
|
43
|
+
{ label: "Set as Default", role: "default" },
|
|
44
|
+
{ label: "Set as Smol (Fast)", role: "smol" },
|
|
45
|
+
{ label: "Set as Slow (Thinking)", role: "slow" },
|
|
46
|
+
];
|
|
47
|
+
|
|
48
|
+
const ALL_TAB = "ALL";
|
|
49
|
+
|
|
21
50
|
/**
|
|
22
|
-
* Component that renders a model selector with
|
|
23
|
-
* -
|
|
24
|
-
* -
|
|
25
|
-
* -
|
|
26
|
-
* - Escape: Close selector
|
|
51
|
+
* Component that renders a model selector with provider tabs and context menu.
|
|
52
|
+
* - Tab/Arrow Left/Right: Switch between provider tabs
|
|
53
|
+
* - Arrow Up/Down: Navigate model list
|
|
54
|
+
* - Enter: Open context menu to select action
|
|
55
|
+
* - Escape: Close menu or selector
|
|
27
56
|
*/
|
|
28
57
|
export class ModelSelectorComponent extends Container {
|
|
29
58
|
private searchInput: Input;
|
|
59
|
+
private headerContainer: Container;
|
|
30
60
|
private listContainer: Container;
|
|
61
|
+
private menuContainer: Container;
|
|
31
62
|
private allModels: ModelItem[] = [];
|
|
32
63
|
private filteredModels: ModelItem[] = [];
|
|
33
64
|
private selectedIndex: number = 0;
|
|
@@ -43,6 +74,14 @@ export class ModelSelectorComponent extends Container {
|
|
|
43
74
|
private tui: TUI;
|
|
44
75
|
private scopedModels: ReadonlyArray<ScopedModelItem>;
|
|
45
76
|
|
|
77
|
+
// Tab state
|
|
78
|
+
private providers: string[] = [ALL_TAB];
|
|
79
|
+
private activeTabIndex: number = 0;
|
|
80
|
+
|
|
81
|
+
// Context menu state
|
|
82
|
+
private isMenuOpen: boolean = false;
|
|
83
|
+
private menuSelectedIndex: number = 0;
|
|
84
|
+
|
|
46
85
|
constructor(
|
|
47
86
|
tui: TUI,
|
|
48
87
|
currentModel: Model<any> | undefined,
|
|
@@ -69,21 +108,26 @@ export class ModelSelectorComponent extends Container {
|
|
|
69
108
|
this.addChild(new DynamicBorder());
|
|
70
109
|
this.addChild(new Spacer(1));
|
|
71
110
|
|
|
72
|
-
// Add hint about model filtering
|
|
111
|
+
// Add hint about model filtering
|
|
73
112
|
const hintText =
|
|
74
113
|
scopedModels.length > 0
|
|
75
114
|
? "Showing models from --models scope"
|
|
76
115
|
: "Only showing models with configured API keys (see README for details)";
|
|
77
116
|
this.addChild(new Text(theme.fg("warning", hintText), 0, 0));
|
|
78
|
-
this.addChild(new
|
|
117
|
+
this.addChild(new Spacer(1));
|
|
118
|
+
|
|
119
|
+
// Create header container for tab bar
|
|
120
|
+
this.headerContainer = new Container();
|
|
121
|
+
this.addChild(this.headerContainer);
|
|
122
|
+
|
|
79
123
|
this.addChild(new Spacer(1));
|
|
80
124
|
|
|
81
125
|
// Create search input
|
|
82
126
|
this.searchInput = new Input();
|
|
83
127
|
this.searchInput.onSubmit = () => {
|
|
84
|
-
// Enter on search input
|
|
128
|
+
// Enter on search input opens menu if we have a selection
|
|
85
129
|
if (this.filteredModels[this.selectedIndex]) {
|
|
86
|
-
this.
|
|
130
|
+
this.openMenu();
|
|
87
131
|
}
|
|
88
132
|
};
|
|
89
133
|
this.addChild(this.searchInput);
|
|
@@ -94,6 +138,10 @@ export class ModelSelectorComponent extends Container {
|
|
|
94
138
|
this.listContainer = new Container();
|
|
95
139
|
this.addChild(this.listContainer);
|
|
96
140
|
|
|
141
|
+
// Create menu container (hidden by default)
|
|
142
|
+
this.menuContainer = new Container();
|
|
143
|
+
this.addChild(this.menuContainer);
|
|
144
|
+
|
|
97
145
|
this.addChild(new Spacer(1));
|
|
98
146
|
|
|
99
147
|
// Add bottom border
|
|
@@ -101,6 +149,8 @@ export class ModelSelectorComponent extends Container {
|
|
|
101
149
|
|
|
102
150
|
// Load models and do initial render
|
|
103
151
|
this.loadModels().then(() => {
|
|
152
|
+
this.buildProviderTabs();
|
|
153
|
+
this.updateTabBar();
|
|
104
154
|
this.updateList();
|
|
105
155
|
// Request re-render after models are loaded
|
|
106
156
|
this.tui.requestRender();
|
|
@@ -175,13 +225,15 @@ export class ModelSelectorComponent extends Container {
|
|
|
175
225
|
}
|
|
176
226
|
}
|
|
177
227
|
|
|
178
|
-
// Sort: current model first, then by provider
|
|
228
|
+
// Sort: current model first, then by provider, then by id
|
|
179
229
|
models.sort((a, b) => {
|
|
180
230
|
const aIsCurrent = modelsAreEqual(this.currentModel, a.model);
|
|
181
231
|
const bIsCurrent = modelsAreEqual(this.currentModel, b.model);
|
|
182
232
|
if (aIsCurrent && !bIsCurrent) return -1;
|
|
183
233
|
if (!aIsCurrent && bIsCurrent) return 1;
|
|
184
|
-
|
|
234
|
+
const providerCmp = a.provider.localeCompare(b.provider);
|
|
235
|
+
if (providerCmp !== 0) return providerCmp;
|
|
236
|
+
return a.id.localeCompare(b.id);
|
|
185
237
|
});
|
|
186
238
|
|
|
187
239
|
this.allModels = models;
|
|
@@ -189,12 +241,81 @@ export class ModelSelectorComponent extends Container {
|
|
|
189
241
|
this.selectedIndex = Math.min(this.selectedIndex, Math.max(0, models.length - 1));
|
|
190
242
|
}
|
|
191
243
|
|
|
244
|
+
private buildProviderTabs(): void {
|
|
245
|
+
// Extract unique providers from models
|
|
246
|
+
const providerSet = new Set<string>();
|
|
247
|
+
for (const item of this.allModels) {
|
|
248
|
+
providerSet.add(item.provider.toUpperCase());
|
|
249
|
+
}
|
|
250
|
+
// Sort providers alphabetically
|
|
251
|
+
const sortedProviders = Array.from(providerSet).sort();
|
|
252
|
+
this.providers = [ALL_TAB, ...sortedProviders];
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
private updateTabBar(): void {
|
|
256
|
+
this.headerContainer.clear();
|
|
257
|
+
|
|
258
|
+
// Build tab bar line
|
|
259
|
+
const parts: string[] = [];
|
|
260
|
+
parts.push(theme.fg("muted", "Provider:"));
|
|
261
|
+
parts.push(" ");
|
|
262
|
+
|
|
263
|
+
for (let i = 0; i < this.providers.length; i++) {
|
|
264
|
+
const provider = this.providers[i]!;
|
|
265
|
+
const isActive = i === this.activeTabIndex;
|
|
266
|
+
|
|
267
|
+
if (isActive) {
|
|
268
|
+
parts.push(theme.fg("accent", `[ ${provider} ]`));
|
|
269
|
+
} else {
|
|
270
|
+
parts.push(theme.fg("muted", ` ${provider} `));
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
if (i < this.providers.length - 1) {
|
|
274
|
+
parts.push(" ");
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
parts.push(" ");
|
|
279
|
+
parts.push(theme.fg("dim", "(←/→ or Tab to switch)"));
|
|
280
|
+
|
|
281
|
+
this.headerContainer.addChild(new Text(parts.join(""), 0, 0));
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
private getActiveProvider(): string {
|
|
285
|
+
return this.providers[this.activeTabIndex] ?? ALL_TAB;
|
|
286
|
+
}
|
|
287
|
+
|
|
192
288
|
private filterModels(query: string): void {
|
|
193
|
-
|
|
289
|
+
const activeProvider = this.getActiveProvider();
|
|
290
|
+
|
|
291
|
+
// Start with all models or filter by provider
|
|
292
|
+
let baseModels = this.allModels;
|
|
293
|
+
if (activeProvider !== ALL_TAB) {
|
|
294
|
+
baseModels = this.allModels.filter((m) => m.provider.toUpperCase() === activeProvider);
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
// Apply fuzzy filter if query is present
|
|
298
|
+
if (query.trim()) {
|
|
299
|
+
// If user is searching, auto-switch to ALL tab to show global results
|
|
300
|
+
if (activeProvider !== ALL_TAB) {
|
|
301
|
+
this.activeTabIndex = 0;
|
|
302
|
+
this.updateTabBar();
|
|
303
|
+
baseModels = this.allModels;
|
|
304
|
+
}
|
|
305
|
+
this.filteredModels = fuzzyFilter(baseModels, query, ({ id, provider }) => `${id} ${provider}`);
|
|
306
|
+
} else {
|
|
307
|
+
this.filteredModels = baseModels;
|
|
308
|
+
}
|
|
309
|
+
|
|
194
310
|
this.selectedIndex = Math.min(this.selectedIndex, Math.max(0, this.filteredModels.length - 1));
|
|
195
311
|
this.updateList();
|
|
196
312
|
}
|
|
197
313
|
|
|
314
|
+
private applyTabFilter(): void {
|
|
315
|
+
const query = this.searchInput.getValue();
|
|
316
|
+
this.filterModels(query);
|
|
317
|
+
}
|
|
318
|
+
|
|
198
319
|
private updateList(): void {
|
|
199
320
|
this.listContainer.clear();
|
|
200
321
|
|
|
@@ -205,6 +326,9 @@ export class ModelSelectorComponent extends Container {
|
|
|
205
326
|
);
|
|
206
327
|
const endIndex = Math.min(startIndex + maxVisible, this.filteredModels.length);
|
|
207
328
|
|
|
329
|
+
const activeProvider = this.getActiveProvider();
|
|
330
|
+
const showProvider = activeProvider === ALL_TAB;
|
|
331
|
+
|
|
208
332
|
// Show visible slice of filtered models
|
|
209
333
|
for (let i = startIndex; i < endIndex; i++) {
|
|
210
334
|
const item = this.filteredModels[i];
|
|
@@ -215,22 +339,31 @@ export class ModelSelectorComponent extends Container {
|
|
|
215
339
|
const isSmol = modelsAreEqual(this.smolModel, item.model);
|
|
216
340
|
const isSlow = modelsAreEqual(this.slowModel, item.model);
|
|
217
341
|
|
|
218
|
-
// Build role
|
|
219
|
-
|
|
220
|
-
if (isDefault)
|
|
221
|
-
if (isSmol)
|
|
222
|
-
if (isSlow)
|
|
342
|
+
// Build role badges (right-aligned style)
|
|
343
|
+
const badges: string[] = [];
|
|
344
|
+
if (isDefault) badges.push(theme.fg("success", "[ DEFAULT ]"));
|
|
345
|
+
if (isSmol) badges.push(theme.fg("warning", "[ SMOL ]"));
|
|
346
|
+
if (isSlow) badges.push(theme.fg("accent", "[ SLOW ]"));
|
|
347
|
+
const badgeText = badges.length > 0 ? ` ${badges.join(" ")}` : "";
|
|
223
348
|
|
|
224
349
|
let line = "";
|
|
225
350
|
if (isSelected) {
|
|
226
351
|
const prefix = theme.fg("accent", "→ ");
|
|
227
|
-
const modelText =
|
|
228
|
-
|
|
229
|
-
|
|
352
|
+
const modelText = item.id;
|
|
353
|
+
if (showProvider) {
|
|
354
|
+
const providerBadge = theme.fg("muted", `[${item.provider}]`);
|
|
355
|
+
line = `${prefix}${theme.fg("accent", modelText)} ${providerBadge}${badgeText}`;
|
|
356
|
+
} else {
|
|
357
|
+
line = `${prefix}${theme.fg("accent", modelText)}${badgeText}`;
|
|
358
|
+
}
|
|
230
359
|
} else {
|
|
231
|
-
const
|
|
232
|
-
|
|
233
|
-
|
|
360
|
+
const prefix = " ";
|
|
361
|
+
if (showProvider) {
|
|
362
|
+
const providerBadge = theme.fg("muted", `[${item.provider}]`);
|
|
363
|
+
line = `${prefix}${item.id} ${providerBadge}${badgeText}`;
|
|
364
|
+
} else {
|
|
365
|
+
line = `${prefix}${item.id}${badgeText}`;
|
|
366
|
+
}
|
|
234
367
|
}
|
|
235
368
|
|
|
236
369
|
this.listContainer.addChild(new Text(line, 0, 0));
|
|
@@ -244,7 +377,6 @@ export class ModelSelectorComponent extends Container {
|
|
|
244
377
|
|
|
245
378
|
// Show error message or "no results" if empty
|
|
246
379
|
if (this.errorMessage) {
|
|
247
|
-
// Show error in red
|
|
248
380
|
const errorLines = this.errorMessage.split("\n");
|
|
249
381
|
for (const line of errorLines) {
|
|
250
382
|
this.listContainer.addChild(new Text(theme.fg("error", line), 0, 0));
|
|
@@ -254,52 +386,142 @@ export class ModelSelectorComponent extends Container {
|
|
|
254
386
|
}
|
|
255
387
|
}
|
|
256
388
|
|
|
389
|
+
private openMenu(): void {
|
|
390
|
+
if (this.filteredModels.length === 0) return;
|
|
391
|
+
|
|
392
|
+
this.isMenuOpen = true;
|
|
393
|
+
this.menuSelectedIndex = 0;
|
|
394
|
+
this.updateMenu();
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
private closeMenu(): void {
|
|
398
|
+
this.isMenuOpen = false;
|
|
399
|
+
this.menuContainer.clear();
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
private updateMenu(): void {
|
|
403
|
+
this.menuContainer.clear();
|
|
404
|
+
|
|
405
|
+
const selectedModel = this.filteredModels[this.selectedIndex];
|
|
406
|
+
if (!selectedModel) return;
|
|
407
|
+
|
|
408
|
+
// Menu header
|
|
409
|
+
this.menuContainer.addChild(new Spacer(1));
|
|
410
|
+
this.menuContainer.addChild(new Text(theme.fg("border", "─".repeat(40)), 0, 0));
|
|
411
|
+
this.menuContainer.addChild(new Text(theme.fg("text", ` Action for: ${theme.bold(selectedModel.id)}`), 0, 0));
|
|
412
|
+
this.menuContainer.addChild(new Spacer(1));
|
|
413
|
+
|
|
414
|
+
// Menu options
|
|
415
|
+
for (let i = 0; i < MENU_ACTIONS.length; i++) {
|
|
416
|
+
const action = MENU_ACTIONS[i]!;
|
|
417
|
+
const isSelected = i === this.menuSelectedIndex;
|
|
418
|
+
|
|
419
|
+
let line: string;
|
|
420
|
+
if (isSelected) {
|
|
421
|
+
line = theme.fg("accent", ` → ${action.label}`);
|
|
422
|
+
} else {
|
|
423
|
+
line = theme.fg("muted", ` ${action.label}`);
|
|
424
|
+
}
|
|
425
|
+
this.menuContainer.addChild(new Text(line, 0, 0));
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
this.menuContainer.addChild(new Spacer(1));
|
|
429
|
+
this.menuContainer.addChild(new Text(theme.fg("dim", " Enter: confirm Esc: cancel"), 0, 0));
|
|
430
|
+
this.menuContainer.addChild(new Text(theme.fg("border", "─".repeat(40)), 0, 0));
|
|
431
|
+
}
|
|
432
|
+
|
|
257
433
|
handleInput(keyData: string): void {
|
|
258
|
-
|
|
434
|
+
if (this.isMenuOpen) {
|
|
435
|
+
this.handleMenuInput(keyData);
|
|
436
|
+
return;
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
// Tab bar navigation: Left/Right arrows or Tab/Shift+Tab
|
|
440
|
+
if (isArrowLeft(keyData) || isShiftTab(keyData)) {
|
|
441
|
+
this.activeTabIndex = (this.activeTabIndex - 1 + this.providers.length) % this.providers.length;
|
|
442
|
+
this.updateTabBar();
|
|
443
|
+
this.selectedIndex = 0;
|
|
444
|
+
this.applyTabFilter();
|
|
445
|
+
return;
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
if (isArrowRight(keyData) || isTab(keyData)) {
|
|
449
|
+
this.activeTabIndex = (this.activeTabIndex + 1) % this.providers.length;
|
|
450
|
+
this.updateTabBar();
|
|
451
|
+
this.selectedIndex = 0;
|
|
452
|
+
this.applyTabFilter();
|
|
453
|
+
return;
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
// Up arrow - navigate list (wrap to bottom when at top)
|
|
259
457
|
if (isArrowUp(keyData)) {
|
|
260
458
|
if (this.filteredModels.length === 0) return;
|
|
261
459
|
this.selectedIndex = this.selectedIndex === 0 ? this.filteredModels.length - 1 : this.selectedIndex - 1;
|
|
262
460
|
this.updateList();
|
|
461
|
+
return;
|
|
263
462
|
}
|
|
264
|
-
|
|
265
|
-
|
|
463
|
+
|
|
464
|
+
// Down arrow - navigate list (wrap to top when at bottom)
|
|
465
|
+
if (isArrowDown(keyData)) {
|
|
266
466
|
if (this.filteredModels.length === 0) return;
|
|
267
467
|
this.selectedIndex = this.selectedIndex === this.filteredModels.length - 1 ? 0 : this.selectedIndex + 1;
|
|
268
468
|
this.updateList();
|
|
469
|
+
return;
|
|
269
470
|
}
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
if (
|
|
274
|
-
this.
|
|
471
|
+
|
|
472
|
+
// Enter - open context menu
|
|
473
|
+
if (isEnter(keyData)) {
|
|
474
|
+
if (this.filteredModels[this.selectedIndex]) {
|
|
475
|
+
this.openMenu();
|
|
275
476
|
}
|
|
477
|
+
return;
|
|
276
478
|
}
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
479
|
+
|
|
480
|
+
// Escape - close selector
|
|
481
|
+
if (isEscape(keyData)) {
|
|
482
|
+
this.onCancelCallback();
|
|
483
|
+
return;
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
// Pass everything else to search input
|
|
487
|
+
this.searchInput.handleInput(keyData);
|
|
488
|
+
this.filterModels(this.searchInput.getValue());
|
|
489
|
+
}
|
|
490
|
+
|
|
491
|
+
private handleMenuInput(keyData: string): void {
|
|
492
|
+
// Up arrow - navigate menu
|
|
493
|
+
if (isArrowUp(keyData)) {
|
|
494
|
+
this.menuSelectedIndex = (this.menuSelectedIndex - 1 + MENU_ACTIONS.length) % MENU_ACTIONS.length;
|
|
495
|
+
this.updateMenu();
|
|
496
|
+
return;
|
|
497
|
+
}
|
|
498
|
+
|
|
499
|
+
// Down arrow - navigate menu
|
|
500
|
+
if (isArrowDown(keyData)) {
|
|
501
|
+
this.menuSelectedIndex = (this.menuSelectedIndex + 1) % MENU_ACTIONS.length;
|
|
502
|
+
this.updateMenu();
|
|
503
|
+
return;
|
|
283
504
|
}
|
|
284
|
-
|
|
285
|
-
|
|
505
|
+
|
|
506
|
+
// Enter - confirm selection
|
|
507
|
+
if (isEnter(keyData)) {
|
|
286
508
|
const selectedModel = this.filteredModels[this.selectedIndex];
|
|
287
|
-
|
|
288
|
-
|
|
509
|
+
const action = MENU_ACTIONS[this.menuSelectedIndex];
|
|
510
|
+
if (selectedModel && action) {
|
|
511
|
+
this.handleSelect(selectedModel.model, action.role);
|
|
512
|
+
this.closeMenu();
|
|
289
513
|
}
|
|
514
|
+
return;
|
|
290
515
|
}
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
else {
|
|
297
|
-
this.searchInput.handleInput(keyData);
|
|
298
|
-
this.filterModels(this.searchInput.getValue());
|
|
516
|
+
|
|
517
|
+
// Escape - close menu only
|
|
518
|
+
if (isEscape(keyData)) {
|
|
519
|
+
this.closeMenu();
|
|
520
|
+
return;
|
|
299
521
|
}
|
|
300
522
|
}
|
|
301
523
|
|
|
302
|
-
private handleSelect(model: Model<any>, role:
|
|
524
|
+
private handleSelect(model: Model<any>, role: ModelRole): void {
|
|
303
525
|
// Save to settings
|
|
304
526
|
this.settingsManager.setModelRole(role, `${model.provider}/${model.id}`);
|
|
305
527
|
|
|
@@ -315,7 +537,7 @@ export class ModelSelectorComponent extends Container {
|
|
|
315
537
|
// Notify caller (for updating agent state if needed)
|
|
316
538
|
this.onSelectCallback(model, role);
|
|
317
539
|
|
|
318
|
-
// Update list to show new
|
|
540
|
+
// Update list to show new badges
|
|
319
541
|
this.updateList();
|
|
320
542
|
}
|
|
321
543
|
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { getOAuthProviders, type OAuthProviderInfo } from "@oh-my-pi/pi-ai";
|
|
2
2
|
import { Container, isArrowDown, isArrowUp, isEnter, isEscape, Spacer, TruncatedText } from "@oh-my-pi/pi-tui";
|
|
3
|
-
import type { AuthStorage } from "../../../core/auth-storage
|
|
4
|
-
import { theme } from "../theme/theme
|
|
5
|
-
import { DynamicBorder } from "./dynamic-border
|
|
3
|
+
import type { AuthStorage } from "../../../core/auth-storage";
|
|
4
|
+
import { theme } from "../theme/theme";
|
|
5
|
+
import { DynamicBorder } from "./dynamic-border";
|
|
6
6
|
|
|
7
7
|
/**
|
|
8
8
|
* Component that renders an OAuth provider selector
|
|
@@ -18,10 +18,10 @@ import {
|
|
|
18
18
|
Spacer,
|
|
19
19
|
Text,
|
|
20
20
|
} from "@oh-my-pi/pi-tui";
|
|
21
|
-
import { PluginManager } from "../../../core/plugins/manager
|
|
22
|
-
import type { InstalledPlugin, PluginSettingSchema } from "../../../core/plugins/types
|
|
23
|
-
import { getSelectListTheme, getSettingsListTheme, theme } from "../theme/theme
|
|
24
|
-
import { DynamicBorder } from "./dynamic-border
|
|
21
|
+
import { PluginManager } from "../../../core/plugins/manager";
|
|
22
|
+
import type { InstalledPlugin, PluginSettingSchema } from "../../../core/plugins/types";
|
|
23
|
+
import { getSelectListTheme, getSettingsListTheme, theme } from "../theme/theme";
|
|
24
|
+
import { DynamicBorder } from "./dynamic-border";
|
|
25
25
|
|
|
26
26
|
// =============================================================================
|
|
27
27
|
// Plugin List Component
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { Container, type SelectItem, SelectList } from "@oh-my-pi/pi-tui";
|
|
2
|
-
import { getSelectListTheme } from "../theme/theme
|
|
3
|
-
import { DynamicBorder } from "./dynamic-border
|
|
2
|
+
import { getSelectListTheme } from "../theme/theme";
|
|
3
|
+
import { DynamicBorder } from "./dynamic-border";
|
|
4
4
|
|
|
5
5
|
/**
|
|
6
6
|
* Component that renders a queue mode selector with borders
|
|
@@ -11,10 +11,10 @@ import {
|
|
|
11
11
|
Text,
|
|
12
12
|
truncateToWidth,
|
|
13
13
|
} from "@oh-my-pi/pi-tui";
|
|
14
|
-
import type { SessionInfo } from "../../../core/session-manager
|
|
15
|
-
import { fuzzyFilter } from "../../../utils/fuzzy
|
|
16
|
-
import { theme } from "../theme/theme
|
|
17
|
-
import { DynamicBorder } from "./dynamic-border
|
|
14
|
+
import type { SessionInfo } from "../../../core/session-manager";
|
|
15
|
+
import { fuzzyFilter } from "../../../utils/fuzzy";
|
|
16
|
+
import { theme } from "../theme/theme";
|
|
17
|
+
import { DynamicBorder } from "./dynamic-border";
|
|
18
18
|
|
|
19
19
|
/**
|
|
20
20
|
* Custom session list component with multi-line items and search
|
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
|
|
11
11
|
import type { ThinkingLevel } from "@oh-my-pi/pi-agent-core";
|
|
12
12
|
import { getCapabilities } from "@oh-my-pi/pi-tui";
|
|
13
|
-
import type { SettingsManager } from "../../../core/settings-manager
|
|
13
|
+
import type { SettingsManager } from "../../../core/settings-manager";
|
|
14
14
|
|
|
15
15
|
// Setting value types
|
|
16
16
|
export type SettingValue = boolean | string;
|
|
@@ -16,11 +16,11 @@ import {
|
|
|
16
16
|
type TabBarTheme,
|
|
17
17
|
Text,
|
|
18
18
|
} from "@oh-my-pi/pi-tui";
|
|
19
|
-
import type { SettingsManager } from "../../../core/settings-manager
|
|
20
|
-
import { getSelectListTheme, getSettingsListTheme, theme } from "../theme/theme
|
|
21
|
-
import { DynamicBorder } from "./dynamic-border
|
|
22
|
-
import { PluginSettingsComponent } from "./plugin-settings
|
|
23
|
-
import { getSettingsForTab, type SettingDef } from "./settings-defs
|
|
19
|
+
import type { SettingsManager } from "../../../core/settings-manager";
|
|
20
|
+
import { getSelectListTheme, getSettingsListTheme, theme } from "../theme/theme";
|
|
21
|
+
import { DynamicBorder } from "./dynamic-border";
|
|
22
|
+
import { PluginSettingsComponent } from "./plugin-settings";
|
|
23
|
+
import { getSettingsForTab, type SettingDef } from "./settings-defs";
|
|
24
24
|
|
|
25
25
|
function getTabBarTheme(): TabBarTheme {
|
|
26
26
|
return {
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { Container, type SelectItem, SelectList } from "@oh-my-pi/pi-tui";
|
|
2
|
-
import { getSelectListTheme } from "../theme/theme
|
|
3
|
-
import { DynamicBorder } from "./dynamic-border
|
|
2
|
+
import { getSelectListTheme } from "../theme/theme";
|
|
3
|
+
import { DynamicBorder } from "./dynamic-border";
|
|
4
4
|
|
|
5
5
|
/**
|
|
6
6
|
* Component that renders a show images selector with borders
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { Container, type SelectItem, SelectList } from "@oh-my-pi/pi-tui";
|
|
2
|
-
import { getAvailableThemes, getSelectListTheme } from "../theme/theme
|
|
3
|
-
import { DynamicBorder } from "./dynamic-border
|
|
2
|
+
import { getAvailableThemes, getSelectListTheme } from "../theme/theme";
|
|
3
|
+
import { DynamicBorder } from "./dynamic-border";
|
|
4
4
|
|
|
5
5
|
/**
|
|
6
6
|
* Component that renders a theme selector
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { ThinkingLevel } from "@oh-my-pi/pi-agent-core";
|
|
2
2
|
import { Container, type SelectItem, SelectList } from "@oh-my-pi/pi-tui";
|
|
3
|
-
import { getSelectListTheme } from "../theme/theme
|
|
4
|
-
import { DynamicBorder } from "./dynamic-border
|
|
3
|
+
import { getSelectListTheme } from "../theme/theme";
|
|
4
|
+
import { DynamicBorder } from "./dynamic-border";
|
|
5
5
|
|
|
6
6
|
const LEVEL_DESCRIPTIONS: Record<ThinkingLevel, string> = {
|
|
7
7
|
off: "No reasoning",
|
|
@@ -11,14 +11,14 @@ import {
|
|
|
11
11
|
type TUI,
|
|
12
12
|
} from "@oh-my-pi/pi-tui";
|
|
13
13
|
import stripAnsi from "strip-ansi";
|
|
14
|
-
import type { CustomTool } from "../../../core/custom-tools/types
|
|
15
|
-
import { computeEditDiff, type EditDiffError, type EditDiffResult } from "../../../core/tools/edit-diff
|
|
16
|
-
import { toolRenderers } from "../../../core/tools/renderers
|
|
17
|
-
import { DEFAULT_MAX_BYTES, DEFAULT_MAX_LINES, formatSize } from "../../../core/tools/truncate
|
|
18
|
-
import { sanitizeBinaryOutput } from "../../../utils/shell
|
|
19
|
-
import { getLanguageFromPath, highlightCode, theme } from "../theme/theme
|
|
20
|
-
import { renderDiff } from "./diff
|
|
21
|
-
import { truncateToVisualLines } from "./visual-truncate
|
|
14
|
+
import type { CustomTool } from "../../../core/custom-tools/types";
|
|
15
|
+
import { computeEditDiff, type EditDiffError, type EditDiffResult } from "../../../core/tools/edit-diff";
|
|
16
|
+
import { toolRenderers } from "../../../core/tools/renderers";
|
|
17
|
+
import { DEFAULT_MAX_BYTES, DEFAULT_MAX_LINES, formatSize } from "../../../core/tools/truncate";
|
|
18
|
+
import { sanitizeBinaryOutput } from "../../../utils/shell";
|
|
19
|
+
import { getLanguageFromPath, highlightCode, theme } from "../theme/theme";
|
|
20
|
+
import { renderDiff } from "./diff";
|
|
21
|
+
import { truncateToVisualLines } from "./visual-truncate";
|
|
22
22
|
|
|
23
23
|
// Preview line limit for bash when not expanded
|
|
24
24
|
const BASH_PREVIEW_LINES = 5;
|
|
@@ -550,6 +550,24 @@ export class ToolExecutionComponent extends Container {
|
|
|
550
550
|
text += `\n\n${renderDiff(this.editDiffPreview.diff, { filePath: rawPath })}`;
|
|
551
551
|
}
|
|
552
552
|
}
|
|
553
|
+
|
|
554
|
+
// Show LSP diagnostics if available
|
|
555
|
+
if (this.result?.details?.diagnostics?.available) {
|
|
556
|
+
const diag = this.result.details.diagnostics;
|
|
557
|
+
if (diag.diagnostics.length > 0) {
|
|
558
|
+
const icon = diag.hasErrors ? theme.fg("error", "●") : theme.fg("warning", "●");
|
|
559
|
+
text += `\n\n${icon} ${theme.fg("toolTitle", "LSP Diagnostics")} ${theme.fg("dim", `(${diag.summary})`)}`;
|
|
560
|
+
const maxDiags = this.expanded ? diag.diagnostics.length : 5;
|
|
561
|
+
const displayDiags = diag.diagnostics.slice(0, maxDiags);
|
|
562
|
+
for (const d of displayDiags) {
|
|
563
|
+
const color = d.includes("[error]") ? "error" : d.includes("[warning]") ? "warning" : "dim";
|
|
564
|
+
text += `\n ${theme.fg(color, d)}`;
|
|
565
|
+
}
|
|
566
|
+
if (diag.diagnostics.length > maxDiags) {
|
|
567
|
+
text += theme.fg("dim", `\n ... (${diag.diagnostics.length - maxDiags} more)`);
|
|
568
|
+
}
|
|
569
|
+
}
|
|
570
|
+
}
|
|
553
571
|
} else if (this.toolName === "ls") {
|
|
554
572
|
const path = shortenPath(this.args?.path || ".");
|
|
555
573
|
const limit = this.args?.limit;
|
|
@@ -17,9 +17,9 @@ import {
|
|
|
17
17
|
TruncatedText,
|
|
18
18
|
truncateToWidth,
|
|
19
19
|
} from "@oh-my-pi/pi-tui";
|
|
20
|
-
import type { SessionTreeNode } from "../../../core/session-manager
|
|
21
|
-
import { theme } from "../theme/theme
|
|
22
|
-
import { DynamicBorder } from "./dynamic-border
|
|
20
|
+
import type { SessionTreeNode } from "../../../core/session-manager";
|
|
21
|
+
import { theme } from "../theme/theme";
|
|
22
|
+
import { DynamicBorder } from "./dynamic-border";
|
|
23
23
|
|
|
24
24
|
/** Gutter info: position (displayIndent where connector was) and whether to show │ */
|
|
25
25
|
interface GutterInfo {
|
|
@@ -10,8 +10,8 @@ import {
|
|
|
10
10
|
Text,
|
|
11
11
|
truncateToWidth,
|
|
12
12
|
} from "@oh-my-pi/pi-tui";
|
|
13
|
-
import { theme } from "../theme/theme
|
|
14
|
-
import { DynamicBorder } from "./dynamic-border
|
|
13
|
+
import { theme } from "../theme/theme";
|
|
14
|
+
import { DynamicBorder } from "./dynamic-border";
|
|
15
15
|
|
|
16
16
|
interface UserMessageItem {
|
|
17
17
|
id: string; // Entry ID in the session
|