@oh-my-pi/pi-coding-agent 3.30.0 → 3.32.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 +85 -0
- package/package.json +5 -5
- package/src/cli/args.ts +4 -0
- package/src/core/agent-session.ts +29 -2
- package/src/core/bash-executor.ts +2 -1
- package/src/core/custom-commands/bundled/review/index.ts +367 -14
- package/src/core/custom-commands/bundled/wt/index.ts +1 -1
- package/src/core/sdk.ts +10 -2
- package/src/core/session-manager.ts +158 -246
- package/src/core/session-storage.ts +379 -0
- package/src/core/settings-manager.ts +155 -4
- package/src/core/slash-commands.ts +39 -13
- package/src/core/system-prompt.ts +62 -64
- package/src/core/tools/ask.ts +5 -4
- package/src/core/tools/bash-interceptor.ts +26 -61
- package/src/core/tools/bash.ts +13 -8
- package/src/core/tools/edit-diff.ts +11 -4
- package/src/core/tools/edit.ts +7 -13
- package/src/core/tools/find.ts +111 -50
- package/src/core/tools/gemini-image.ts +128 -147
- package/src/core/tools/grep.ts +397 -415
- package/src/core/tools/index.test.ts +5 -1
- package/src/core/tools/index.ts +8 -4
- package/src/core/tools/ls.ts +12 -10
- package/src/core/tools/lsp/client.ts +84 -19
- package/src/core/tools/lsp/config.ts +205 -656
- package/src/core/tools/lsp/defaults.json +465 -0
- package/src/core/tools/lsp/index.ts +72 -35
- package/src/core/tools/lsp/rust-analyzer.ts +49 -10
- package/src/core/tools/lsp/types.ts +1 -0
- package/src/core/tools/lsp/utils.ts +1 -1
- package/src/core/tools/read.ts +150 -74
- package/src/core/tools/render-utils.ts +70 -10
- package/src/core/tools/review.ts +38 -126
- package/src/core/tools/task/artifacts.ts +5 -4
- package/src/core/tools/task/commands.ts +4 -0
- package/src/core/tools/task/executor.ts +94 -83
- package/src/core/tools/task/index.ts +130 -92
- package/src/core/tools/task/parallel.ts +30 -3
- package/src/core/tools/task/render.ts +85 -39
- package/src/core/tools/task/types.ts +15 -6
- package/src/core/tools/task/worker.ts +124 -89
- package/src/core/tools/web-fetch.ts +112 -377
- package/src/core/tools/{web-fetch-handlers → web-scrapers}/artifacthub.ts +6 -1
- package/src/core/tools/{web-fetch-handlers → web-scrapers}/arxiv.ts +8 -4
- package/src/core/tools/{web-fetch-handlers → web-scrapers}/aur.ts +6 -2
- package/src/core/tools/{web-fetch-handlers → web-scrapers}/biorxiv.ts +6 -1
- package/src/core/tools/{web-fetch-handlers → web-scrapers}/bluesky.ts +10 -3
- package/src/core/tools/{web-fetch-handlers → web-scrapers}/brew.ts +6 -2
- package/src/core/tools/{web-fetch-handlers → web-scrapers}/cheatsh.ts +6 -1
- package/src/core/tools/{web-fetch-handlers → web-scrapers}/chocolatey.ts +6 -1
- package/src/core/tools/web-scrapers/choosealicense.ts +110 -0
- package/src/core/tools/web-scrapers/cisa-kev.ts +100 -0
- package/src/core/tools/web-scrapers/clojars.ts +180 -0
- package/src/core/tools/{web-fetch-handlers → web-scrapers}/coingecko.ts +6 -1
- package/src/core/tools/{web-fetch-handlers → web-scrapers}/crates-io.ts +7 -2
- package/src/core/tools/web-scrapers/crossref.ts +149 -0
- package/src/core/tools/{web-fetch-handlers → web-scrapers}/devto.ts +8 -4
- package/src/core/tools/{web-fetch-handlers → web-scrapers}/discogs.ts +6 -1
- package/src/core/tools/web-scrapers/discourse.ts +221 -0
- package/src/core/tools/{web-fetch-handlers → web-scrapers}/dockerhub.ts +7 -3
- package/src/core/tools/web-scrapers/fdroid.ts +158 -0
- package/src/core/tools/web-scrapers/firefox-addons.ts +214 -0
- package/src/core/tools/web-scrapers/flathub.ts +239 -0
- package/src/core/tools/{web-fetch-handlers → web-scrapers}/github-gist.ts +6 -2
- package/src/core/tools/{web-fetch-handlers → web-scrapers}/github.ts +63 -32
- package/src/core/tools/{web-fetch-handlers → web-scrapers}/gitlab.ts +31 -19
- package/src/core/tools/{web-fetch-handlers → web-scrapers}/go-pkg.ts +8 -4
- package/src/core/tools/{web-fetch-handlers → web-scrapers}/hackage.ts +6 -1
- package/src/core/tools/{web-fetch-handlers → web-scrapers}/hackernews.ts +18 -18
- package/src/core/tools/{web-fetch-handlers → web-scrapers}/hex.ts +3 -3
- package/src/core/tools/{web-fetch-handlers → web-scrapers}/huggingface.ts +10 -10
- package/src/core/tools/{web-fetch-handlers → web-scrapers}/iacr.ts +8 -4
- package/src/core/tools/web-scrapers/index.ts +250 -0
- package/src/core/tools/web-scrapers/jetbrains-marketplace.ts +169 -0
- package/src/core/tools/web-scrapers/lemmy.ts +220 -0
- package/src/core/tools/{web-fetch-handlers → web-scrapers}/lobsters.ts +3 -3
- package/src/core/tools/{web-fetch-handlers → web-scrapers}/mastodon.ts +11 -3
- package/src/core/tools/{web-fetch-handlers → web-scrapers}/maven.ts +6 -1
- package/src/core/tools/{web-fetch-handlers → web-scrapers}/mdn.ts +2 -2
- package/src/core/tools/{web-fetch-handlers → web-scrapers}/metacpan.ts +13 -7
- package/src/core/tools/web-scrapers/musicbrainz.ts +273 -0
- package/src/core/tools/{web-fetch-handlers → web-scrapers}/npm.ts +12 -5
- package/src/core/tools/{web-fetch-handlers → web-scrapers}/nuget.ts +9 -5
- package/src/core/tools/{web-fetch-handlers → web-scrapers}/nvd.ts +6 -1
- package/src/core/tools/web-scrapers/ollama.ts +267 -0
- package/src/core/tools/web-scrapers/open-vsx.ts +119 -0
- package/src/core/tools/{web-fetch-handlers → web-scrapers}/opencorporates.ts +2 -0
- package/src/core/tools/{web-fetch-handlers → web-scrapers}/openlibrary.ts +18 -12
- package/src/core/tools/web-scrapers/orcid.ts +299 -0
- package/src/core/tools/{web-fetch-handlers → web-scrapers}/osv.ts +6 -1
- package/src/core/tools/{web-fetch-handlers → web-scrapers}/packagist.ts +6 -2
- package/src/core/tools/{web-fetch-handlers → web-scrapers}/pub-dev.ts +3 -3
- package/src/core/tools/{web-fetch-handlers → web-scrapers}/pubmed.ts +8 -4
- package/src/core/tools/{web-fetch-handlers → web-scrapers}/pypi.ts +7 -3
- package/src/core/tools/web-scrapers/rawg.ts +124 -0
- package/src/core/tools/{web-fetch-handlers → web-scrapers}/readthedocs.ts +7 -3
- package/src/core/tools/{web-fetch-handlers → web-scrapers}/reddit.ts +6 -2
- package/src/core/tools/{web-fetch-handlers → web-scrapers}/repology.ts +6 -1
- package/src/core/tools/{web-fetch-handlers → web-scrapers}/rfc.ts +7 -3
- package/src/core/tools/{web-fetch-handlers → web-scrapers}/rubygems.ts +6 -1
- package/src/core/tools/web-scrapers/searchcode.ts +217 -0
- package/src/core/tools/{web-fetch-handlers → web-scrapers}/sec-edgar.ts +6 -1
- package/src/core/tools/{web-fetch-handlers → web-scrapers}/semantic-scholar.ts +2 -2
- package/src/core/tools/web-scrapers/snapcraft.ts +200 -0
- package/src/core/tools/web-scrapers/sourcegraph.ts +373 -0
- package/src/core/tools/web-scrapers/spdx.ts +121 -0
- package/src/core/tools/{web-fetch-handlers → web-scrapers}/spotify.ts +3 -3
- package/src/core/tools/{web-fetch-handlers → web-scrapers}/stackoverflow.ts +3 -2
- package/src/core/tools/{web-fetch-handlers → web-scrapers}/terraform.ts +11 -3
- package/src/core/tools/{web-fetch-handlers → web-scrapers}/tldr.ts +6 -2
- package/src/core/tools/{web-fetch-handlers → web-scrapers}/twitter.ts +15 -3
- package/src/core/tools/{web-fetch-handlers → web-scrapers}/types.ts +98 -27
- package/src/core/tools/web-scrapers/utils.ts +162 -0
- package/src/core/tools/{web-fetch-handlers → web-scrapers}/vimeo.ts +3 -3
- package/src/core/tools/web-scrapers/vscode-marketplace.ts +195 -0
- package/src/core/tools/web-scrapers/w3c.ts +163 -0
- package/src/core/tools/{web-fetch-handlers → web-scrapers}/wikidata.ts +13 -5
- package/src/core/tools/{web-fetch-handlers → web-scrapers}/wikipedia.ts +7 -3
- package/src/core/tools/{web-fetch-handlers → web-scrapers}/youtube.ts +72 -20
- package/src/core/tools/write.ts +21 -18
- package/src/core/voice.ts +3 -2
- package/src/lib/worktree/collapse.ts +2 -1
- package/src/lib/worktree/git.ts +2 -18
- package/src/main.ts +59 -3
- package/src/modes/interactive/components/extensions/extension-dashboard.ts +33 -19
- package/src/modes/interactive/components/extensions/extension-list.ts +15 -8
- package/src/modes/interactive/components/hook-editor.ts +2 -1
- package/src/modes/interactive/components/model-selector.ts +19 -4
- package/src/modes/interactive/interactive-mode.ts +41 -63
- package/src/modes/interactive/theme/theme.ts +58 -58
- package/src/modes/rpc/rpc-mode.ts +10 -9
- package/src/prompts/review-request.md +27 -0
- package/src/prompts/reviewer.md +64 -68
- package/src/prompts/tools/output.md +22 -3
- package/src/prompts/tools/task.md +32 -33
- package/src/utils/clipboard.ts +2 -1
- package/examples/extensions/subagent/agents/reviewer.md +0 -35
- package/src/core/tools/web-fetch-handlers/index.ts +0 -69
- package/src/core/tools/web-fetch-handlers/utils.ts +0 -91
- /package/src/core/tools/{web-fetch-handlers → web-scrapers}/academic.test.ts +0 -0
- /package/src/core/tools/{web-fetch-handlers → web-scrapers}/business.test.ts +0 -0
- /package/src/core/tools/{web-fetch-handlers → web-scrapers}/dev-platforms.test.ts +0 -0
- /package/src/core/tools/{web-fetch-handlers → web-scrapers}/documentation.test.ts +0 -0
- /package/src/core/tools/{web-fetch-handlers → web-scrapers}/finance-media.test.ts +0 -0
- /package/src/core/tools/{web-fetch-handlers → web-scrapers}/git-hosting.test.ts +0 -0
- /package/src/core/tools/{web-fetch-handlers → web-scrapers}/media.test.ts +0 -0
- /package/src/core/tools/{web-fetch-handlers → web-scrapers}/package-managers-2.test.ts +0 -0
- /package/src/core/tools/{web-fetch-handlers → web-scrapers}/package-managers.test.ts +0 -0
- /package/src/core/tools/{web-fetch-handlers → web-scrapers}/package-registries.test.ts +0 -0
- /package/src/core/tools/{web-fetch-handlers → web-scrapers}/research.test.ts +0 -0
- /package/src/core/tools/{web-fetch-handlers → web-scrapers}/security.test.ts +0 -0
- /package/src/core/tools/{web-fetch-handlers → web-scrapers}/social-extended.test.ts +0 -0
- /package/src/core/tools/{web-fetch-handlers → web-scrapers}/social.test.ts +0 -0
- /package/src/core/tools/{web-fetch-handlers → web-scrapers}/stackexchange.test.ts +0 -0
- /package/src/core/tools/{web-fetch-handlers → web-scrapers}/standards.test.ts +0 -0
- /package/src/core/tools/{web-fetch-handlers → web-scrapers}/wikipedia.test.ts +0 -0
- /package/src/core/tools/{web-fetch-handlers → web-scrapers}/youtube.test.ts +0 -0
|
@@ -38,30 +38,40 @@ export class ExtensionDashboard extends Container {
|
|
|
38
38
|
private inspector: InspectorPanel;
|
|
39
39
|
private settingsManager: SettingsManager | null;
|
|
40
40
|
private cwd: string;
|
|
41
|
+
private terminalHeight: number;
|
|
41
42
|
|
|
42
43
|
public onClose?: () => void;
|
|
43
44
|
|
|
44
|
-
constructor(cwd: string, settingsManager: SettingsManager | null = null) {
|
|
45
|
+
constructor(cwd: string, settingsManager: SettingsManager | null = null, terminalHeight?: number) {
|
|
45
46
|
super();
|
|
46
47
|
this.cwd = cwd;
|
|
47
48
|
this.settingsManager = settingsManager;
|
|
49
|
+
this.terminalHeight = terminalHeight ?? process.stdout.rows ?? 24;
|
|
48
50
|
const disabledIds = settingsManager?.getDisabledExtensions() ?? [];
|
|
49
51
|
this.state = createInitialState(cwd, disabledIds);
|
|
50
52
|
|
|
53
|
+
// Calculate max visible items based on terminal height
|
|
54
|
+
// Reserve ~10 lines for header, tabs, help text, borders
|
|
55
|
+
const maxVisible = Math.max(5, Math.floor((this.terminalHeight - 10) / 2));
|
|
56
|
+
|
|
51
57
|
// Create main list - always focused
|
|
52
|
-
this.mainList = new ExtensionList(
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
58
|
+
this.mainList = new ExtensionList(
|
|
59
|
+
this.state.searchFiltered,
|
|
60
|
+
{
|
|
61
|
+
onSelectionChange: (ext) => {
|
|
62
|
+
this.state.selected = ext;
|
|
63
|
+
this.inspector.setExtension(ext);
|
|
64
|
+
},
|
|
65
|
+
onToggle: (extensionId, enabled) => {
|
|
66
|
+
this.handleExtensionToggle(extensionId, enabled);
|
|
67
|
+
},
|
|
68
|
+
onMasterToggle: (providerId) => {
|
|
69
|
+
this.handleProviderToggle(providerId);
|
|
70
|
+
},
|
|
71
|
+
masterSwitchProvider: this.getActiveProviderId(),
|
|
62
72
|
},
|
|
63
|
-
|
|
64
|
-
|
|
73
|
+
maxVisible,
|
|
74
|
+
);
|
|
65
75
|
this.mainList.setFocused(true);
|
|
66
76
|
|
|
67
77
|
// Create inspector
|
|
@@ -91,9 +101,10 @@ export class ExtensionDashboard extends Container {
|
|
|
91
101
|
this.addChild(new Text(this.renderTabBar(), 0, 0));
|
|
92
102
|
this.addChild(new Spacer(1));
|
|
93
103
|
|
|
94
|
-
//
|
|
95
|
-
//
|
|
96
|
-
|
|
104
|
+
// 2-column body with height limit
|
|
105
|
+
// Reserve ~8 lines for header, tabs, help text, borders
|
|
106
|
+
const bodyMaxHeight = Math.max(5, this.terminalHeight - 8);
|
|
107
|
+
this.addChild(new TwoColumnBody(this.mainList, this.inspector, bodyMaxHeight));
|
|
97
108
|
|
|
98
109
|
this.addChild(new Spacer(1));
|
|
99
110
|
this.addChild(new Text(theme.fg("dim", " ↑/↓: navigate Space: toggle Tab: next provider Esc: close"), 0, 0));
|
|
@@ -262,10 +273,12 @@ export class ExtensionDashboard extends Container {
|
|
|
262
273
|
class TwoColumnBody implements Component {
|
|
263
274
|
private leftPane: ExtensionList;
|
|
264
275
|
private rightPane: InspectorPanel;
|
|
276
|
+
private maxHeight: number;
|
|
265
277
|
|
|
266
|
-
constructor(left: ExtensionList, right: InspectorPanel) {
|
|
278
|
+
constructor(left: ExtensionList, right: InspectorPanel, maxHeight: number) {
|
|
267
279
|
this.leftPane = left;
|
|
268
280
|
this.rightPane = right;
|
|
281
|
+
this.maxHeight = maxHeight;
|
|
269
282
|
}
|
|
270
283
|
|
|
271
284
|
render(width: number): string[] {
|
|
@@ -275,11 +288,12 @@ class TwoColumnBody implements Component {
|
|
|
275
288
|
const leftLines = this.leftPane.render(leftWidth);
|
|
276
289
|
const rightLines = this.rightPane.render(rightWidth);
|
|
277
290
|
|
|
278
|
-
|
|
291
|
+
// Limit to maxHeight lines
|
|
292
|
+
const numLines = Math.min(this.maxHeight, Math.max(leftLines.length, rightLines.length));
|
|
279
293
|
const combined: string[] = [];
|
|
280
294
|
const separator = theme.fg("dim", ` ${theme.boxSharp.vertical} `);
|
|
281
295
|
|
|
282
|
-
for (let i = 0; i <
|
|
296
|
+
for (let i = 0; i < numLines; i++) {
|
|
283
297
|
const left = truncateToWidth(leftLines[i] ?? "", leftWidth);
|
|
284
298
|
const leftPadded = left + " ".repeat(Math.max(0, leftWidth - visibleWidth(left)));
|
|
285
299
|
const right = truncateToWidth(rightLines[i] ?? "", rightWidth);
|
|
@@ -31,7 +31,7 @@ export interface ExtensionListCallbacks {
|
|
|
31
31
|
masterSwitchProvider?: string | null;
|
|
32
32
|
}
|
|
33
33
|
|
|
34
|
-
const
|
|
34
|
+
const DEFAULT_MAX_VISIBLE = 15;
|
|
35
35
|
|
|
36
36
|
/** Flattened list item for rendering */
|
|
37
37
|
type ListItem =
|
|
@@ -48,14 +48,21 @@ export class ExtensionList implements Component {
|
|
|
48
48
|
private focused = false;
|
|
49
49
|
private callbacks: ExtensionListCallbacks;
|
|
50
50
|
private masterSwitchProvider: string | null = null;
|
|
51
|
+
private maxVisible: number;
|
|
51
52
|
|
|
52
|
-
constructor(extensions: Extension[], callbacks: ExtensionListCallbacks = {}) {
|
|
53
|
+
constructor(extensions: Extension[], callbacks: ExtensionListCallbacks = {}, maxVisible?: number) {
|
|
53
54
|
this.extensions = extensions;
|
|
54
55
|
this.callbacks = callbacks;
|
|
55
56
|
this.masterSwitchProvider = callbacks.masterSwitchProvider ?? null;
|
|
57
|
+
this.maxVisible = maxVisible ?? DEFAULT_MAX_VISIBLE;
|
|
56
58
|
this.rebuildList();
|
|
57
59
|
}
|
|
58
60
|
|
|
61
|
+
setMaxVisible(maxVisible: number): void {
|
|
62
|
+
this.maxVisible = maxVisible;
|
|
63
|
+
this.clampSelection();
|
|
64
|
+
}
|
|
65
|
+
|
|
59
66
|
setExtensions(extensions: Extension[]): void {
|
|
60
67
|
this.extensions = extensions;
|
|
61
68
|
this.rebuildList();
|
|
@@ -126,7 +133,7 @@ export class ExtensionList implements Component {
|
|
|
126
133
|
|
|
127
134
|
// Calculate visible range
|
|
128
135
|
const startIdx = this.scrollOffset;
|
|
129
|
-
const endIdx = Math.min(startIdx +
|
|
136
|
+
const endIdx = Math.min(startIdx + this.maxVisible, this.listItems.length);
|
|
130
137
|
|
|
131
138
|
// Render visible items
|
|
132
139
|
for (let i = startIdx; i < endIdx; i++) {
|
|
@@ -143,7 +150,7 @@ export class ExtensionList implements Component {
|
|
|
143
150
|
}
|
|
144
151
|
|
|
145
152
|
// Scroll indicator
|
|
146
|
-
if (this.listItems.length >
|
|
153
|
+
if (this.listItems.length > this.maxVisible) {
|
|
147
154
|
const indicator = theme.fg("muted", ` (${this.selectedIndex + 1}/${this.listItems.length})`);
|
|
148
155
|
lines.push(indicator);
|
|
149
156
|
}
|
|
@@ -388,8 +395,8 @@ export class ExtensionList implements Component {
|
|
|
388
395
|
// Adjust scroll offset
|
|
389
396
|
if (this.selectedIndex < this.scrollOffset) {
|
|
390
397
|
this.scrollOffset = this.selectedIndex;
|
|
391
|
-
} else if (this.selectedIndex >= this.scrollOffset +
|
|
392
|
-
this.scrollOffset = this.selectedIndex -
|
|
398
|
+
} else if (this.selectedIndex >= this.scrollOffset + this.maxVisible) {
|
|
399
|
+
this.scrollOffset = this.selectedIndex - this.maxVisible + 1;
|
|
393
400
|
}
|
|
394
401
|
}
|
|
395
402
|
|
|
@@ -470,8 +477,8 @@ export class ExtensionList implements Component {
|
|
|
470
477
|
private moveSelectionDown(): void {
|
|
471
478
|
if (this.selectedIndex < this.listItems.length - 1) {
|
|
472
479
|
this.selectedIndex++;
|
|
473
|
-
if (this.selectedIndex >= this.scrollOffset +
|
|
474
|
-
this.scrollOffset = this.selectedIndex -
|
|
480
|
+
if (this.selectedIndex >= this.scrollOffset + this.maxVisible) {
|
|
481
|
+
this.scrollOffset = this.selectedIndex - this.maxVisible + 1;
|
|
475
482
|
}
|
|
476
483
|
this.notifySelectionChange();
|
|
477
484
|
}
|
|
@@ -7,6 +7,7 @@ import * as fs from "node:fs";
|
|
|
7
7
|
import * as os from "node:os";
|
|
8
8
|
import * as path from "node:path";
|
|
9
9
|
import { Container, Editor, isCtrlG, isEscape, Spacer, Text, type TUI } from "@oh-my-pi/pi-tui";
|
|
10
|
+
import { nanoid } from "nanoid";
|
|
10
11
|
import { getEditorTheme, theme } from "../theme/theme";
|
|
11
12
|
import { DynamicBorder } from "./dynamic-border";
|
|
12
13
|
|
|
@@ -90,7 +91,7 @@ export class HookEditorComponent extends Container {
|
|
|
90
91
|
}
|
|
91
92
|
|
|
92
93
|
const currentText = this.editor.getText();
|
|
93
|
-
const tmpFile = path.join(os.tmpdir(), `omp-hook-editor-${
|
|
94
|
+
const tmpFile = path.join(os.tmpdir(), `omp-hook-editor-${nanoid()}.md`);
|
|
94
95
|
|
|
95
96
|
try {
|
|
96
97
|
fs.writeFileSync(tmpFile, currentText, "utf-8");
|
|
@@ -34,7 +34,7 @@ interface ScopedModelItem {
|
|
|
34
34
|
thinkingLevel: string;
|
|
35
35
|
}
|
|
36
36
|
|
|
37
|
-
type ModelRole = "default" | "smol" | "slow";
|
|
37
|
+
type ModelRole = "default" | "smol" | "slow" | "temporary";
|
|
38
38
|
|
|
39
39
|
interface MenuAction {
|
|
40
40
|
label: string;
|
|
@@ -75,6 +75,7 @@ export class ModelSelectorComponent extends Container {
|
|
|
75
75
|
private errorMessage?: string;
|
|
76
76
|
private tui: TUI;
|
|
77
77
|
private scopedModels: ReadonlyArray<ScopedModelItem>;
|
|
78
|
+
private temporaryOnly: boolean;
|
|
78
79
|
|
|
79
80
|
// Tab state
|
|
80
81
|
private providers: string[] = [ALL_TAB];
|
|
@@ -92,6 +93,7 @@ export class ModelSelectorComponent extends Container {
|
|
|
92
93
|
scopedModels: ReadonlyArray<ScopedModelItem>,
|
|
93
94
|
onSelect: (model: Model<any>, role: string) => void,
|
|
94
95
|
onCancel: () => void,
|
|
96
|
+
options?: { temporaryOnly?: boolean },
|
|
95
97
|
) {
|
|
96
98
|
super();
|
|
97
99
|
|
|
@@ -102,6 +104,7 @@ export class ModelSelectorComponent extends Container {
|
|
|
102
104
|
this.scopedModels = scopedModels;
|
|
103
105
|
this.onSelectCallback = onSelect;
|
|
104
106
|
this.onCancelCallback = onCancel;
|
|
107
|
+
this.temporaryOnly = options?.temporaryOnly ?? false;
|
|
105
108
|
|
|
106
109
|
// Load current role assignments from settings
|
|
107
110
|
this._loadRoleModels();
|
|
@@ -483,10 +486,16 @@ export class ModelSelectorComponent extends Container {
|
|
|
483
486
|
return;
|
|
484
487
|
}
|
|
485
488
|
|
|
486
|
-
// Enter - open context menu
|
|
489
|
+
// Enter - open context menu or select directly in temporary mode
|
|
487
490
|
if (isEnter(keyData)) {
|
|
488
|
-
|
|
489
|
-
|
|
491
|
+
const selectedModel = this.filteredModels[this.selectedIndex];
|
|
492
|
+
if (selectedModel) {
|
|
493
|
+
if (this.temporaryOnly) {
|
|
494
|
+
// In temporary mode, skip menu and select directly
|
|
495
|
+
this.handleSelect(selectedModel.model, "temporary");
|
|
496
|
+
} else {
|
|
497
|
+
this.openMenu();
|
|
498
|
+
}
|
|
490
499
|
}
|
|
491
500
|
return;
|
|
492
501
|
}
|
|
@@ -536,6 +545,12 @@ export class ModelSelectorComponent extends Container {
|
|
|
536
545
|
}
|
|
537
546
|
|
|
538
547
|
private handleSelect(model: Model<any>, role: ModelRole): void {
|
|
548
|
+
// For temporary role, don't save to settings - just notify caller
|
|
549
|
+
if (role === "temporary") {
|
|
550
|
+
this.onSelectCallback(model, role);
|
|
551
|
+
return;
|
|
552
|
+
}
|
|
553
|
+
|
|
539
554
|
// Save to settings
|
|
540
555
|
this.settingsManager.setModelRole(role, `${model.provider}/${model.id}`);
|
|
541
556
|
|
|
@@ -23,6 +23,7 @@ import {
|
|
|
23
23
|
TUI,
|
|
24
24
|
visibleWidth,
|
|
25
25
|
} from "@oh-my-pi/pi-tui";
|
|
26
|
+
import { nanoid } from "nanoid";
|
|
26
27
|
import { getAuthPath, getDebugLogPath } from "../../config";
|
|
27
28
|
import type { AgentSession, AgentSessionEvent } from "../../core/agent-session";
|
|
28
29
|
import type { ExtensionUIContext } from "../../core/extensions/index";
|
|
@@ -941,9 +942,9 @@ export class InteractiveMode {
|
|
|
941
942
|
this.editor.onCtrlD = () => this.handleCtrlD();
|
|
942
943
|
this.editor.onCtrlZ = () => this.handleCtrlZ();
|
|
943
944
|
this.editor.onShiftTab = () => this.cycleThinkingLevel();
|
|
944
|
-
this.editor.onCtrlP = () => this.
|
|
945
|
-
this.editor.onShiftCtrlP = () => this.
|
|
946
|
-
this.editor.onCtrlY = () => this.
|
|
945
|
+
this.editor.onCtrlP = () => this.cycleRoleModel();
|
|
946
|
+
this.editor.onShiftCtrlP = () => this.cycleRoleModel({ temporary: true });
|
|
947
|
+
this.editor.onCtrlY = () => this.showModelSelector({ temporaryOnly: true });
|
|
947
948
|
|
|
948
949
|
// Global debug handler on TUI (works regardless of focus)
|
|
949
950
|
this.ui.onDebug = () => this.handleDebugCommand();
|
|
@@ -951,9 +952,6 @@ export class InteractiveMode {
|
|
|
951
952
|
this.editor.onCtrlO = () => this.toggleToolOutputExpansion();
|
|
952
953
|
this.editor.onCtrlT = () => this.toggleThinkingBlockVisibility();
|
|
953
954
|
this.editor.onCtrlG = () => this.openExternalEditor();
|
|
954
|
-
this.editor.onCtrlY = () => {
|
|
955
|
-
void this.toggleVoiceListening();
|
|
956
|
-
};
|
|
957
955
|
this.editor.onQuestionMark = () => this.handleHotkeysCommand();
|
|
958
956
|
this.editor.onCtrlV = () => this.handleImagePaste();
|
|
959
957
|
|
|
@@ -991,6 +989,14 @@ export class InteractiveMode {
|
|
|
991
989
|
private setupEditorSubmitHandler(): void {
|
|
992
990
|
this.editor.onSubmit = async (text: string) => {
|
|
993
991
|
text = text.trim();
|
|
992
|
+
|
|
993
|
+
// Empty submit while streaming with queued messages: flush queues immediately
|
|
994
|
+
if (!text && this.session.isStreaming && this.session.queuedMessageCount > 0) {
|
|
995
|
+
// Abort current stream and let queued messages be processed
|
|
996
|
+
await this.session.abort();
|
|
997
|
+
return;
|
|
998
|
+
}
|
|
999
|
+
|
|
994
1000
|
if (!text) return;
|
|
995
1001
|
|
|
996
1002
|
// Handle slash commands
|
|
@@ -1900,31 +1906,6 @@ export class InteractiveMode {
|
|
|
1900
1906
|
this.voiceSupervisor.notifyProgress(text);
|
|
1901
1907
|
}
|
|
1902
1908
|
|
|
1903
|
-
private async toggleVoiceListening(): Promise<void> {
|
|
1904
|
-
if (!this.settingsManager.getVoiceEnabled()) {
|
|
1905
|
-
this.settingsManager.setVoiceEnabled(true);
|
|
1906
|
-
this.showStatus("Voice mode enabled.");
|
|
1907
|
-
}
|
|
1908
|
-
|
|
1909
|
-
if (this.voiceAutoModeEnabled) {
|
|
1910
|
-
this.voiceAutoModeEnabled = false;
|
|
1911
|
-
this.stopVoiceProgressTimer();
|
|
1912
|
-
await this.voiceSupervisor.stop();
|
|
1913
|
-
this.setVoiceStatus(undefined);
|
|
1914
|
-
this.showStatus("Voice mode disabled.");
|
|
1915
|
-
return;
|
|
1916
|
-
}
|
|
1917
|
-
|
|
1918
|
-
this.voiceAutoModeEnabled = true;
|
|
1919
|
-
try {
|
|
1920
|
-
await this.voiceSupervisor.start();
|
|
1921
|
-
} catch (error) {
|
|
1922
|
-
this.voiceAutoModeEnabled = false;
|
|
1923
|
-
this.setVoiceStatus(undefined);
|
|
1924
|
-
this.showError(error instanceof Error ? error.message : String(error));
|
|
1925
|
-
}
|
|
1926
|
-
}
|
|
1927
|
-
|
|
1928
1909
|
private async submitVoiceText(text: string): Promise<void> {
|
|
1929
1910
|
const cleaned = text.trim();
|
|
1930
1911
|
if (!cleaned) {
|
|
@@ -1994,27 +1975,9 @@ export class InteractiveMode {
|
|
|
1994
1975
|
}
|
|
1995
1976
|
}
|
|
1996
1977
|
|
|
1997
|
-
private async
|
|
1978
|
+
private async cycleRoleModel(options?: { temporary?: boolean }): Promise<void> {
|
|
1998
1979
|
try {
|
|
1999
|
-
const result = await this.session.
|
|
2000
|
-
if (result === undefined) {
|
|
2001
|
-
const msg = this.session.scopedModels.length > 0 ? "Only one model in scope" : "Only one model available";
|
|
2002
|
-
this.showStatus(msg);
|
|
2003
|
-
} else {
|
|
2004
|
-
this.statusLine.invalidate();
|
|
2005
|
-
this.updateEditorBorderColor();
|
|
2006
|
-
const thinkingStr =
|
|
2007
|
-
result.model.reasoning && result.thinkingLevel !== "off" ? ` (thinking: ${result.thinkingLevel})` : "";
|
|
2008
|
-
this.showStatus(`Switched to ${result.model.name || result.model.id}${thinkingStr}`);
|
|
2009
|
-
}
|
|
2010
|
-
} catch (error) {
|
|
2011
|
-
this.showError(error instanceof Error ? error.message : String(error));
|
|
2012
|
-
}
|
|
2013
|
-
}
|
|
2014
|
-
|
|
2015
|
-
private async cycleRoleModel(): Promise<void> {
|
|
2016
|
-
try {
|
|
2017
|
-
const result = await this.session.cycleRoleModels(["slow", "default", "smol"]);
|
|
1980
|
+
const result = await this.session.cycleRoleModels(["slow", "default", "smol"], options);
|
|
2018
1981
|
if (!result) {
|
|
2019
1982
|
this.showStatus("Only one role model available");
|
|
2020
1983
|
return;
|
|
@@ -2025,7 +1988,8 @@ export class InteractiveMode {
|
|
|
2025
1988
|
const roleLabel = result.role === "default" ? "default" : result.role;
|
|
2026
1989
|
const thinkingStr =
|
|
2027
1990
|
result.model.reasoning && result.thinkingLevel !== "off" ? ` (thinking: ${result.thinkingLevel})` : "";
|
|
2028
|
-
|
|
1991
|
+
const tempLabel = options?.temporary ? " (temporary)" : "";
|
|
1992
|
+
this.showStatus(`Switched to ${roleLabel}: ${result.model.name || result.model.id}${thinkingStr}${tempLabel}`);
|
|
2029
1993
|
} catch (error) {
|
|
2030
1994
|
this.showError(error instanceof Error ? error.message : String(error));
|
|
2031
1995
|
}
|
|
@@ -2068,7 +2032,7 @@ export class InteractiveMode {
|
|
|
2068
2032
|
}
|
|
2069
2033
|
|
|
2070
2034
|
const currentText = this.editor.getText();
|
|
2071
|
-
const tmpFile = path.join(os.tmpdir(), `omp-editor-${
|
|
2035
|
+
const tmpFile = path.join(os.tmpdir(), `omp-editor-${nanoid()}.omp.md`);
|
|
2072
2036
|
|
|
2073
2037
|
try {
|
|
2074
2038
|
// Write current content to temp file
|
|
@@ -2255,7 +2219,7 @@ export class InteractiveMode {
|
|
|
2255
2219
|
*/
|
|
2256
2220
|
private showExtensionsDashboard(): void {
|
|
2257
2221
|
this.showSelector((done) => {
|
|
2258
|
-
const dashboard = new ExtensionDashboard(process.cwd(), this.settingsManager);
|
|
2222
|
+
const dashboard = new ExtensionDashboard(process.cwd(), this.settingsManager, this.ui.terminal.rows);
|
|
2259
2223
|
dashboard.onClose = () => {
|
|
2260
2224
|
done();
|
|
2261
2225
|
this.ui.requestRender();
|
|
@@ -2379,7 +2343,7 @@ export class InteractiveMode {
|
|
|
2379
2343
|
}
|
|
2380
2344
|
}
|
|
2381
2345
|
|
|
2382
|
-
private showModelSelector(): void {
|
|
2346
|
+
private showModelSelector(options?: { temporaryOnly?: boolean }): void {
|
|
2383
2347
|
this.showSelector((done) => {
|
|
2384
2348
|
const selector = new ModelSelectorComponent(
|
|
2385
2349
|
this.ui,
|
|
@@ -2389,24 +2353,36 @@ export class InteractiveMode {
|
|
|
2389
2353
|
this.session.scopedModels,
|
|
2390
2354
|
async (model, role) => {
|
|
2391
2355
|
try {
|
|
2392
|
-
|
|
2393
|
-
|
|
2356
|
+
if (role === "temporary") {
|
|
2357
|
+
// Temporary: update agent state but don't persist to settings
|
|
2358
|
+
await this.session.setModelTemporary(model);
|
|
2359
|
+
this.statusLine.invalidate();
|
|
2360
|
+
this.updateEditorBorderColor();
|
|
2361
|
+
this.showStatus(`Temporary model: ${model.id}`);
|
|
2362
|
+
done();
|
|
2363
|
+
this.ui.requestRender();
|
|
2364
|
+
} else if (role === "default") {
|
|
2365
|
+
// Default: update agent state and persist
|
|
2394
2366
|
await this.session.setModel(model, role);
|
|
2395
2367
|
this.statusLine.invalidate();
|
|
2396
2368
|
this.updateEditorBorderColor();
|
|
2369
|
+
this.showStatus(`Default model: ${model.id}`);
|
|
2370
|
+
// Don't call done() - selector stays open for role assignment
|
|
2371
|
+
} else {
|
|
2372
|
+
// Other roles (smol, slow): just update settings, not current model
|
|
2373
|
+
const roleLabel = role === "smol" ? "Smol" : role;
|
|
2374
|
+
this.showStatus(`${roleLabel} model: ${model.id}`);
|
|
2375
|
+
// Don't call done() - selector stays open
|
|
2397
2376
|
}
|
|
2398
|
-
// For other roles (small), just show status - settings already updated by selector
|
|
2399
|
-
const roleLabel = role === "default" ? "Default" : role === "smol" ? "Smol" : role;
|
|
2400
|
-
this.showStatus(`${roleLabel} model: ${model.id}`);
|
|
2401
2377
|
} catch (error) {
|
|
2402
2378
|
this.showError(error instanceof Error ? error.message : String(error));
|
|
2403
2379
|
}
|
|
2404
|
-
// Don't call done() - selector stays open
|
|
2405
2380
|
},
|
|
2406
2381
|
() => {
|
|
2407
2382
|
done();
|
|
2408
2383
|
this.ui.requestRender();
|
|
2409
2384
|
},
|
|
2385
|
+
options,
|
|
2410
2386
|
);
|
|
2411
2387
|
return { component: selector, focus: selector };
|
|
2412
2388
|
});
|
|
@@ -2994,8 +2970,10 @@ export class InteractiveMode {
|
|
|
2994
2970
|
| \`Ctrl+D\` | Exit (when editor is empty) |
|
|
2995
2971
|
| \`Ctrl+Z\` | Suspend to background |
|
|
2996
2972
|
| \`Shift+Tab\` | Cycle thinking level |
|
|
2997
|
-
| \`Ctrl+P\` | Cycle models |
|
|
2998
|
-
| \`Ctrl+
|
|
2973
|
+
| \`Ctrl+P\` | Cycle role models (slow/default/smol) |
|
|
2974
|
+
| \`Shift+Ctrl+P\` | Cycle role models (temporary) |
|
|
2975
|
+
| \`Ctrl+Y\` | Select model (temporary) |
|
|
2976
|
+
| \`Ctrl+L\` | Select model (set roles) |
|
|
2999
2977
|
| \`Ctrl+O\` | Toggle tool output expansion |
|
|
3000
2978
|
| \`Ctrl+T\` | Toggle thinking block visibility |
|
|
3001
2979
|
| \`Ctrl+G\` | Edit message in external editor |
|
|
@@ -315,18 +315,18 @@ const UNICODE_SYMBOLS: SymbolMap = {
|
|
|
315
315
|
"icon.rewind": "↩",
|
|
316
316
|
// pick: ⚡ | alt: ✨ ✦
|
|
317
317
|
"icon.auto": "⚡",
|
|
318
|
-
// pick:
|
|
319
|
-
"icon.extensionSkill": "
|
|
320
|
-
// pick:
|
|
321
|
-
"icon.extensionTool": "
|
|
318
|
+
// pick: ✧ | alt: ⚙ SK 🧠
|
|
319
|
+
"icon.extensionSkill": "✧",
|
|
320
|
+
// pick: ⚒ | alt: ⛭ TL 🛠
|
|
321
|
+
"icon.extensionTool": "⚒",
|
|
322
322
|
// pick: / | alt: ⌘ ⌥
|
|
323
323
|
"icon.extensionSlashCommand": "/",
|
|
324
|
-
// pick:
|
|
325
|
-
"icon.extensionMcp": "
|
|
326
|
-
// pick:
|
|
327
|
-
"icon.extensionRule": "
|
|
328
|
-
// pick:
|
|
329
|
-
"icon.extensionHook": "
|
|
324
|
+
// pick: ◈ | alt: ⧫ MCP 🔌
|
|
325
|
+
"icon.extensionMcp": "◈",
|
|
326
|
+
// pick: § | alt: ⚖ RL 📏
|
|
327
|
+
"icon.extensionRule": "§",
|
|
328
|
+
// pick: ↪ | alt: ⚓ HK 🪝
|
|
329
|
+
"icon.extensionHook": "↪",
|
|
330
330
|
// pick: PR | alt: 💬 ✎
|
|
331
331
|
"icon.extensionPrompt": "PR",
|
|
332
332
|
// pick: CF | alt: 📄 📎
|
|
@@ -356,10 +356,10 @@ const UNICODE_SYMBOLS: SymbolMap = {
|
|
|
356
356
|
"format.bullet": "•",
|
|
357
357
|
// pick: – | alt: — ― -
|
|
358
358
|
"format.dash": "–",
|
|
359
|
-
// pick:
|
|
360
|
-
"format.bracketLeft": "
|
|
361
|
-
// pick:
|
|
362
|
-
"format.bracketRight": "
|
|
359
|
+
// pick: ⟨ | alt: [ ⟦
|
|
360
|
+
"format.bracketLeft": "⟨",
|
|
361
|
+
// pick: ⟩ | alt: ] ⟧
|
|
362
|
+
"format.bracketRight": "⟩",
|
|
363
363
|
// Markdown-specific
|
|
364
364
|
// pick: │ | alt: ┃ ║
|
|
365
365
|
"md.quoteBorder": "│",
|
|
@@ -574,15 +574,15 @@ const NERD_SYMBOLS: SymbolMap = {
|
|
|
574
574
|
"icon.extensionInstruction": "\uf02d",
|
|
575
575
|
// Thinking Levels - emoji labels
|
|
576
576
|
// pick: 🤨 min | alt: min min
|
|
577
|
-
"thinking.minimal": "
|
|
577
|
+
"thinking.minimal": "\u{F0E7} min",
|
|
578
578
|
// pick: 🤔 low | alt: low low
|
|
579
|
-
"thinking.low": "
|
|
579
|
+
"thinking.low": "\u{F10C} low",
|
|
580
580
|
// pick: 🤓 med | alt: med med
|
|
581
|
-
"thinking.medium": "
|
|
581
|
+
"thinking.medium": "\u{F192} med",
|
|
582
582
|
// pick: 🤯 high | alt: high high
|
|
583
|
-
"thinking.high": "
|
|
583
|
+
"thinking.high": "\u{F111} high",
|
|
584
584
|
// pick: 🧠 xhi | alt: xhi xhi
|
|
585
|
-
"thinking.xhigh": "
|
|
585
|
+
"thinking.xhigh": "\u{F06D} xhi",
|
|
586
586
|
// Checkboxes
|
|
587
587
|
// pick: | alt:
|
|
588
588
|
"checkbox.checked": "\uf14a",
|
|
@@ -595,10 +595,10 @@ const NERD_SYMBOLS: SymbolMap = {
|
|
|
595
595
|
"format.bullet": "\uf111",
|
|
596
596
|
// pick: – | alt: — ― -
|
|
597
597
|
"format.dash": "\u2013",
|
|
598
|
-
// pick:
|
|
599
|
-
"format.bracketLeft": "
|
|
600
|
-
// pick:
|
|
601
|
-
"format.bracketRight": "
|
|
598
|
+
// pick: ⟨ | alt: [ ⟦
|
|
599
|
+
"format.bracketLeft": "⟨",
|
|
600
|
+
// pick: ⟩ | alt: ] ⟧
|
|
601
|
+
"format.bracketRight": "⟩",
|
|
602
602
|
// Markdown-specific
|
|
603
603
|
// pick: │ | alt: ┃ ║
|
|
604
604
|
"md.quoteBorder": "\u2502",
|
|
@@ -608,41 +608,41 @@ const NERD_SYMBOLS: SymbolMap = {
|
|
|
608
608
|
"md.bullet": "\uf111",
|
|
609
609
|
// Language icons (nerd font devicons)
|
|
610
610
|
"lang.default": "",
|
|
611
|
-
"lang.typescript": "
|
|
612
|
-
"lang.javascript": "
|
|
613
|
-
"lang.python": "
|
|
614
|
-
"lang.rust": "
|
|
615
|
-
"lang.go": "
|
|
616
|
-
"lang.java": "
|
|
617
|
-
"lang.c": "
|
|
618
|
-
"lang.cpp": "
|
|
619
|
-
"lang.csharp": "
|
|
620
|
-
"lang.ruby": "
|
|
621
|
-
"lang.php": "
|
|
622
|
-
"lang.swift": "
|
|
623
|
-
"lang.kotlin": "
|
|
624
|
-
"lang.shell": "
|
|
625
|
-
"lang.html": "
|
|
626
|
-
"lang.css": "
|
|
627
|
-
"lang.json": "
|
|
628
|
-
"lang.yaml": "
|
|
629
|
-
"lang.markdown": "
|
|
630
|
-
"lang.sql": "
|
|
631
|
-
"lang.docker": "
|
|
632
|
-
"lang.lua": "
|
|
633
|
-
"lang.text": "
|
|
634
|
-
"lang.env": "
|
|
635
|
-
"lang.toml": "
|
|
636
|
-
"lang.xml": "
|
|
637
|
-
"lang.ini": "
|
|
638
|
-
"lang.conf": "
|
|
639
|
-
"lang.log": "
|
|
640
|
-
"lang.csv": "
|
|
641
|
-
"lang.tsv": "
|
|
642
|
-
"lang.image": "
|
|
643
|
-
"lang.pdf": "
|
|
644
|
-
"lang.archive": "
|
|
645
|
-
"lang.binary": "
|
|
611
|
+
"lang.typescript": "\u{E628}",
|
|
612
|
+
"lang.javascript": "\u{E60C}",
|
|
613
|
+
"lang.python": "\u{E606}",
|
|
614
|
+
"lang.rust": "\u{E7A8}",
|
|
615
|
+
"lang.go": "\u{E627}",
|
|
616
|
+
"lang.java": "\u{E738}",
|
|
617
|
+
"lang.c": "\u{E61E}",
|
|
618
|
+
"lang.cpp": "\u{E61D}",
|
|
619
|
+
"lang.csharp": "\u{E7BC}",
|
|
620
|
+
"lang.ruby": "\u{E791}",
|
|
621
|
+
"lang.php": "\u{E608}",
|
|
622
|
+
"lang.swift": "\u{E755}",
|
|
623
|
+
"lang.kotlin": "\u{E634}",
|
|
624
|
+
"lang.shell": "\u{E795}",
|
|
625
|
+
"lang.html": "\u{E736}",
|
|
626
|
+
"lang.css": "\u{E749}",
|
|
627
|
+
"lang.json": "\u{E60B}",
|
|
628
|
+
"lang.yaml": "\u{E615}",
|
|
629
|
+
"lang.markdown": "\u{E609}",
|
|
630
|
+
"lang.sql": "\u{E706}",
|
|
631
|
+
"lang.docker": "\u{E7B0}",
|
|
632
|
+
"lang.lua": "\u{E620}",
|
|
633
|
+
"lang.text": "\u{E612}",
|
|
634
|
+
"lang.env": "\u{E615}",
|
|
635
|
+
"lang.toml": "\u{E615}",
|
|
636
|
+
"lang.xml": "\u{F05C0}",
|
|
637
|
+
"lang.ini": "\u{E615}",
|
|
638
|
+
"lang.conf": "\u{E615}",
|
|
639
|
+
"lang.log": "\u{F0331}",
|
|
640
|
+
"lang.csv": "\u{F021B}",
|
|
641
|
+
"lang.tsv": "\u{F021B}",
|
|
642
|
+
"lang.image": "\u{F021F}",
|
|
643
|
+
"lang.pdf": "\u{F0226}",
|
|
644
|
+
"lang.archive": "\u{F187}",
|
|
645
|
+
"lang.binary": "\u{F019A}",
|
|
646
646
|
};
|
|
647
647
|
|
|
648
648
|
const ASCII_SYMBOLS: SymbolMap = {
|