@oh-my-pi/pi-coding-agent 11.0.3 → 11.2.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 +199 -49
- package/README.md +1 -1
- package/docs/config-usage.md +3 -4
- package/docs/sdk.md +6 -5
- package/examples/sdk/09-api-keys-and-oauth.ts +2 -2
- package/examples/sdk/README.md +1 -1
- package/package.json +19 -11
- package/src/cli/args.ts +11 -94
- package/src/cli/config-cli.ts +1 -1
- package/src/cli/file-processor.ts +3 -3
- package/src/cli/oclif-help.ts +26 -0
- package/src/cli/web-search-cli.ts +148 -0
- package/src/cli.ts +8 -2
- package/src/commands/commit.ts +36 -0
- package/src/commands/config.ts +51 -0
- package/src/commands/grep.ts +41 -0
- package/src/commands/index/index.ts +136 -0
- package/src/commands/jupyter.ts +32 -0
- package/src/commands/plugin.ts +70 -0
- package/src/commands/setup.ts +39 -0
- package/src/commands/shell.ts +29 -0
- package/src/commands/stats.ts +29 -0
- package/src/commands/update.ts +21 -0
- package/src/commands/web-search.ts +50 -0
- package/src/commit/agentic/index.ts +3 -2
- package/src/commit/agentic/tools/analyze-file.ts +1 -3
- package/src/commit/git/errors.ts +4 -6
- package/src/commit/pipeline.ts +3 -2
- package/src/config/keybindings.ts +1 -3
- package/src/config/model-registry.ts +89 -162
- package/src/config/settings-schema.ts +10 -0
- package/src/config.ts +202 -132
- package/src/exa/mcp-client.ts +8 -41
- package/src/export/html/index.ts +1 -1
- package/src/extensibility/extensions/loader.ts +7 -10
- package/src/extensibility/extensions/runner.ts +5 -15
- package/src/extensibility/extensions/types.ts +1 -1
- package/src/extensibility/hooks/runner.ts +6 -9
- package/src/index.ts +0 -1
- package/src/ipy/kernel.ts +10 -22
- package/src/lsp/clients/biome-client.ts +4 -7
- package/src/lsp/clients/lsp-linter-client.ts +4 -6
- package/src/lsp/index.ts +5 -4
- package/src/lsp/utils.ts +18 -0
- package/src/main.ts +86 -181
- package/src/mcp/json-rpc.ts +2 -2
- package/src/mcp/transports/http.ts +12 -49
- package/src/modes/components/armin.ts +1 -3
- package/src/modes/components/assistant-message.ts +4 -4
- package/src/modes/components/bash-execution.ts +5 -3
- package/src/modes/components/branch-summary-message.ts +1 -3
- package/src/modes/components/compaction-summary-message.ts +1 -3
- package/src/modes/components/custom-message.ts +4 -5
- package/src/modes/components/extensions/extension-dashboard.ts +10 -16
- package/src/modes/components/extensions/extension-list.ts +5 -5
- package/src/modes/components/footer.ts +1 -4
- package/src/modes/components/hook-editor.ts +7 -32
- package/src/modes/components/hook-message.ts +4 -5
- package/src/modes/components/model-selector.ts +2 -2
- package/src/modes/components/plugin-settings.ts +16 -20
- package/src/modes/components/python-execution.ts +5 -5
- package/src/modes/components/session-selector.ts +6 -7
- package/src/modes/components/settings-defs.ts +49 -40
- package/src/modes/components/settings-selector.ts +8 -17
- package/src/modes/components/skill-message.ts +1 -3
- package/src/modes/components/status-line-segment-editor.ts +1 -3
- package/src/modes/components/status-line.ts +1 -3
- package/src/modes/components/todo-reminder.ts +5 -7
- package/src/modes/components/tree-selector.ts +10 -12
- package/src/modes/components/ttsr-notification.ts +1 -3
- package/src/modes/components/user-message-selector.ts +2 -4
- package/src/modes/components/welcome.ts +6 -18
- package/src/modes/controllers/event-controller.ts +1 -0
- package/src/modes/controllers/extension-ui-controller.ts +1 -1
- package/src/modes/controllers/input-controller.ts +7 -34
- package/src/modes/controllers/selector-controller.ts +3 -3
- package/src/modes/interactive-mode.ts +27 -1
- package/src/modes/rpc/rpc-client.ts +2 -5
- package/src/modes/rpc/rpc-mode.ts +2 -2
- package/src/modes/theme/theme.ts +2 -6
- package/src/modes/types.ts +1 -0
- package/src/modes/utils/ui-helpers.ts +6 -1
- package/src/patch/index.ts +1 -4
- package/src/prompts/agents/explore.md +1 -0
- package/src/prompts/agents/frontmatter.md +2 -1
- package/src/prompts/agents/init.md +1 -0
- package/src/prompts/agents/plan.md +1 -0
- package/src/prompts/agents/reviewer.md +1 -0
- package/src/prompts/system/subagent-submit-reminder.md +2 -0
- package/src/prompts/system/subagent-system-prompt.md +2 -0
- package/src/prompts/system/subagent-user-prompt.md +8 -0
- package/src/prompts/system/system-prompt.md +5 -3
- package/src/prompts/system/web-search.md +6 -4
- package/src/prompts/tools/task.md +216 -163
- package/src/sdk.ts +11 -110
- package/src/session/agent-session.ts +117 -83
- package/src/session/auth-storage.ts +10 -51
- package/src/session/messages.ts +17 -3
- package/src/session/session-manager.ts +30 -30
- package/src/session/streaming-output.ts +1 -1
- package/src/ssh/ssh-executor.ts +6 -3
- package/src/task/agents.ts +2 -0
- package/src/task/discovery.ts +1 -1
- package/src/task/executor.ts +5 -10
- package/src/task/index.ts +43 -23
- package/src/task/render.ts +67 -64
- package/src/task/template.ts +17 -34
- package/src/task/types.ts +49 -22
- package/src/tools/ask.ts +1 -3
- package/src/tools/bash.ts +1 -4
- package/src/tools/browser.ts +5 -7
- package/src/tools/exit-plan-mode.ts +1 -4
- package/src/tools/fetch.ts +1 -3
- package/src/tools/find.ts +4 -3
- package/src/tools/gemini-image.ts +24 -55
- package/src/tools/grep.ts +4 -4
- package/src/tools/index.ts +12 -14
- package/src/tools/notebook.ts +1 -5
- package/src/tools/python.ts +4 -3
- package/src/tools/read.ts +2 -4
- package/src/tools/render-utils.ts +23 -0
- package/src/tools/ssh.ts +8 -12
- package/src/tools/todo-write.ts +1 -4
- package/src/tools/tool-errors.ts +1 -4
- package/src/tools/write.ts +1 -3
- package/src/utils/external-editor.ts +59 -0
- package/src/utils/file-mentions.ts +39 -1
- package/src/utils/image-convert.ts +1 -1
- package/src/utils/image-resize.ts +4 -4
- package/src/web/search/auth.ts +3 -33
- package/src/web/search/index.ts +73 -139
- package/src/web/search/provider.ts +58 -0
- package/src/web/search/providers/anthropic.ts +53 -14
- package/src/web/search/providers/base.ts +22 -0
- package/src/web/search/providers/codex.ts +38 -16
- package/src/web/search/providers/exa.ts +30 -6
- package/src/web/search/providers/gemini.ts +56 -20
- package/src/web/search/providers/jina.ts +28 -5
- package/src/web/search/providers/perplexity.ts +103 -36
- package/src/web/search/render.ts +84 -74
- package/src/web/search/types.ts +285 -59
- package/src/migrations.ts +0 -175
- package/src/session/storage-migration.ts +0 -173
|
@@ -8,11 +8,9 @@ import type { CompactionSummaryMessage } from "../../session/messages";
|
|
|
8
8
|
*/
|
|
9
9
|
export class CompactionSummaryMessageComponent extends Box {
|
|
10
10
|
private expanded = false;
|
|
11
|
-
private message: CompactionSummaryMessage;
|
|
12
11
|
|
|
13
|
-
constructor(message: CompactionSummaryMessage) {
|
|
12
|
+
constructor(private readonly message: CompactionSummaryMessage) {
|
|
14
13
|
super(1, 1, t => theme.bg("customMessageBg", t));
|
|
15
|
-
this.message = message;
|
|
16
14
|
this.updateDisplay();
|
|
17
15
|
}
|
|
18
16
|
|
|
@@ -10,16 +10,15 @@ import type { CustomMessage } from "../../session/messages";
|
|
|
10
10
|
* Uses distinct styling to differentiate from user messages.
|
|
11
11
|
*/
|
|
12
12
|
export class CustomMessageComponent extends Container {
|
|
13
|
-
private message: CustomMessage<unknown>;
|
|
14
|
-
private customRenderer?: MessageRenderer;
|
|
15
13
|
private box: Box;
|
|
16
14
|
private customComponent?: Component;
|
|
17
15
|
private _expanded = false;
|
|
18
16
|
|
|
19
|
-
constructor(
|
|
17
|
+
constructor(
|
|
18
|
+
private readonly message: CustomMessage<unknown>,
|
|
19
|
+
private readonly customRenderer?: MessageRenderer,
|
|
20
|
+
) {
|
|
20
21
|
super();
|
|
21
|
-
this.message = message;
|
|
22
|
-
this.customRenderer = customRenderer;
|
|
23
22
|
|
|
24
23
|
this.addChild(new Spacer(1));
|
|
25
24
|
|
|
@@ -33,17 +33,15 @@ export class ExtensionDashboard extends Container {
|
|
|
33
33
|
private state!: DashboardState;
|
|
34
34
|
private mainList!: ExtensionList;
|
|
35
35
|
private inspector!: InspectorPanel;
|
|
36
|
-
private settingsInstance: Settings | null;
|
|
37
|
-
private cwd: string;
|
|
38
|
-
private terminalHeight: number;
|
|
39
36
|
|
|
40
37
|
public onClose?: () => void;
|
|
41
38
|
|
|
42
|
-
private constructor(
|
|
39
|
+
private constructor(
|
|
40
|
+
private readonly cwd: string,
|
|
41
|
+
private readonly settingsInstance: Settings | null,
|
|
42
|
+
private readonly terminalHeight: number,
|
|
43
|
+
) {
|
|
43
44
|
super();
|
|
44
|
-
this.cwd = cwd;
|
|
45
|
-
this.settingsInstance = settingsInstance;
|
|
46
|
-
this.terminalHeight = terminalHeight;
|
|
47
45
|
}
|
|
48
46
|
|
|
49
47
|
static async create(
|
|
@@ -292,15 +290,11 @@ export class ExtensionDashboard extends Container {
|
|
|
292
290
|
* Two-column body component for side-by-side rendering.
|
|
293
291
|
*/
|
|
294
292
|
class TwoColumnBody implements Component {
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
this.leftPane = left;
|
|
301
|
-
this.rightPane = right;
|
|
302
|
-
this.maxHeight = maxHeight;
|
|
303
|
-
}
|
|
293
|
+
constructor(
|
|
294
|
+
private readonly leftPane: ExtensionList,
|
|
295
|
+
private readonly rightPane: InspectorPanel,
|
|
296
|
+
private readonly maxHeight: number,
|
|
297
|
+
) {}
|
|
304
298
|
|
|
305
299
|
render(width: number): string[] {
|
|
306
300
|
const leftWidth = Math.floor(width * 0.5);
|
|
@@ -31,19 +31,19 @@ type ListItem =
|
|
|
31
31
|
| { type: "extension"; item: Extension };
|
|
32
32
|
|
|
33
33
|
export class ExtensionList implements Component {
|
|
34
|
-
private extensions: Extension[] = [];
|
|
35
34
|
private listItems: ListItem[] = [];
|
|
36
35
|
private selectedIndex = 0;
|
|
37
36
|
private scrollOffset = 0;
|
|
38
37
|
private searchQuery = "";
|
|
39
38
|
private focused = false;
|
|
40
|
-
private callbacks: ExtensionListCallbacks;
|
|
41
39
|
private masterSwitchProvider: string | null = null;
|
|
42
40
|
private maxVisible: number;
|
|
43
41
|
|
|
44
|
-
constructor(
|
|
45
|
-
|
|
46
|
-
|
|
42
|
+
constructor(
|
|
43
|
+
private extensions: Extension[],
|
|
44
|
+
private readonly callbacks: ExtensionListCallbacks = {},
|
|
45
|
+
maxVisible?: number,
|
|
46
|
+
) {
|
|
47
47
|
this.masterSwitchProvider = callbacks.masterSwitchProvider ?? null;
|
|
48
48
|
this.maxVisible = maxVisible ?? DEFAULT_MAX_VISIBLE;
|
|
49
49
|
this.rebuildList();
|
|
@@ -42,16 +42,13 @@ async function findGitHeadPath(): Promise<{ path: string; content: string } | nu
|
|
|
42
42
|
* Footer component that shows pwd, token stats, and context usage
|
|
43
43
|
*/
|
|
44
44
|
export class FooterComponent implements Component {
|
|
45
|
-
private session: AgentSession;
|
|
46
45
|
private cachedBranch: string | null | undefined = undefined; // undefined = not checked yet, null = not in git repo, string = branch name
|
|
47
46
|
private gitWatcher: fs.FSWatcher | null = null;
|
|
48
47
|
private onBranchChange: (() => void) | null = null;
|
|
49
48
|
private autoCompactEnabled: boolean = true;
|
|
50
49
|
private extensionStatuses: Map<string, string> = new Map();
|
|
51
50
|
|
|
52
|
-
constructor(session: AgentSession) {
|
|
53
|
-
this.session = session;
|
|
54
|
-
}
|
|
51
|
+
constructor(private readonly session: AgentSession) {}
|
|
55
52
|
|
|
56
53
|
setAutoCompactEnabled(enabled: boolean): void {
|
|
57
54
|
this.autoCompactEnabled = enabled;
|
|
@@ -2,12 +2,9 @@
|
|
|
2
2
|
* Multi-line editor component for hooks.
|
|
3
3
|
* Supports Ctrl+G for external editor.
|
|
4
4
|
*/
|
|
5
|
-
import * as fs from "node:fs/promises";
|
|
6
|
-
import * as os from "node:os";
|
|
7
|
-
import * as path from "node:path";
|
|
8
5
|
import { Container, Editor, matchesKey, Spacer, Text, type TUI } from "@oh-my-pi/pi-tui";
|
|
9
|
-
import { $env, Snowflake } from "@oh-my-pi/pi-utils";
|
|
10
6
|
import { getEditorTheme, theme } from "../../modes/theme/theme";
|
|
7
|
+
import { getEditorCommand, openInEditor } from "../../utils/external-editor";
|
|
11
8
|
import { DynamicBorder } from "./dynamic-border";
|
|
12
9
|
|
|
13
10
|
export class HookEditorComponent extends Container {
|
|
@@ -47,10 +44,7 @@ export class HookEditorComponent extends Container {
|
|
|
47
44
|
this.addChild(new Spacer(1));
|
|
48
45
|
|
|
49
46
|
// Add hint
|
|
50
|
-
const
|
|
51
|
-
const hint = hasExternalEditor
|
|
52
|
-
? "ctrl+enter submit esc cancel ctrl+g external editor"
|
|
53
|
-
: "ctrl+enter submit esc cancel";
|
|
47
|
+
const hint = "ctrl+enter submit esc cancel ctrl+g external editor";
|
|
54
48
|
this.addChild(new Text(theme.fg("dim", hint), 1, 0));
|
|
55
49
|
|
|
56
50
|
this.addChild(new Spacer(1));
|
|
@@ -83,36 +77,17 @@ export class HookEditorComponent extends Container {
|
|
|
83
77
|
}
|
|
84
78
|
|
|
85
79
|
private async openExternalEditor(): Promise<void> {
|
|
86
|
-
const editorCmd =
|
|
87
|
-
if (!editorCmd)
|
|
88
|
-
return;
|
|
89
|
-
}
|
|
80
|
+
const editorCmd = getEditorCommand();
|
|
81
|
+
if (!editorCmd) return;
|
|
90
82
|
|
|
91
83
|
const currentText = this.editor.getText();
|
|
92
|
-
const tmpFile = path.join(os.tmpdir(), `omp-hook-editor-${Snowflake.next()}.md`);
|
|
93
|
-
|
|
94
84
|
try {
|
|
95
|
-
await Bun.write(tmpFile, currentText);
|
|
96
85
|
this.tui.stop();
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
stdin: "inherit",
|
|
101
|
-
stdout: "inherit",
|
|
102
|
-
stderr: "inherit",
|
|
103
|
-
});
|
|
104
|
-
const exitCode = await child.exited;
|
|
105
|
-
|
|
106
|
-
if (exitCode === 0) {
|
|
107
|
-
const newContent = (await Bun.file(tmpFile).text()).replace(/\n$/, "");
|
|
108
|
-
this.editor.setText(newContent);
|
|
86
|
+
const result = await openInEditor(editorCmd, currentText);
|
|
87
|
+
if (result !== null) {
|
|
88
|
+
this.editor.setText(result);
|
|
109
89
|
}
|
|
110
90
|
} finally {
|
|
111
|
-
try {
|
|
112
|
-
await fs.rm(tmpFile, { force: true });
|
|
113
|
-
} catch {
|
|
114
|
-
// Ignore cleanup errors
|
|
115
|
-
}
|
|
116
91
|
this.tui.start();
|
|
117
92
|
this.tui.requestRender(true);
|
|
118
93
|
}
|
|
@@ -10,16 +10,15 @@ import type { HookMessage } from "../../session/messages";
|
|
|
10
10
|
* Uses distinct styling to differentiate from user messages.
|
|
11
11
|
*/
|
|
12
12
|
export class HookMessageComponent extends Container {
|
|
13
|
-
private message: HookMessage<unknown>;
|
|
14
|
-
private customRenderer?: HookMessageRenderer;
|
|
15
13
|
private box: Box;
|
|
16
14
|
private customComponent?: Component;
|
|
17
15
|
private _expanded = false;
|
|
18
16
|
|
|
19
|
-
constructor(
|
|
17
|
+
constructor(
|
|
18
|
+
private readonly message: HookMessage<unknown>,
|
|
19
|
+
private readonly customRenderer?: HookMessageRenderer,
|
|
20
|
+
) {
|
|
20
21
|
super();
|
|
21
|
-
this.message = message;
|
|
22
|
-
this.customRenderer = customRenderer;
|
|
23
22
|
|
|
24
23
|
this.addChild(new Spacer(1));
|
|
25
24
|
|
|
@@ -74,7 +74,7 @@ export class ModelSelectorComponent extends Container {
|
|
|
74
74
|
private modelRegistry: ModelRegistry;
|
|
75
75
|
private onSelectCallback: (model: Model, role: ModelRole | null) => void;
|
|
76
76
|
private onCancelCallback: () => void;
|
|
77
|
-
private errorMessage?:
|
|
77
|
+
private errorMessage?: unknown;
|
|
78
78
|
private tui: TUI;
|
|
79
79
|
private scopedModels: ReadonlyArray<ScopedModelItem>;
|
|
80
80
|
private temporaryOnly: boolean;
|
|
@@ -392,7 +392,7 @@ export class ModelSelectorComponent extends Container {
|
|
|
392
392
|
|
|
393
393
|
// Show error message or "no results" if empty
|
|
394
394
|
if (this.errorMessage) {
|
|
395
|
-
const errorLines = this.errorMessage.split("\n");
|
|
395
|
+
const errorLines = String(this.errorMessage).split("\n");
|
|
396
396
|
for (const line of errorLines) {
|
|
397
397
|
this.listContainer.addChild(new Text(theme.fg("error", line), 0, 0));
|
|
398
398
|
}
|
|
@@ -36,12 +36,13 @@ export interface PluginListCallbacks {
|
|
|
36
36
|
* Selecting a plugin opens its detail view.
|
|
37
37
|
*/
|
|
38
38
|
export class PluginListComponent extends Container {
|
|
39
|
-
private selectList: SelectList;
|
|
40
|
-
private plugins: InstalledPlugin[];
|
|
39
|
+
private readonly selectList: SelectList;
|
|
41
40
|
|
|
42
|
-
constructor(
|
|
41
|
+
constructor(
|
|
42
|
+
private readonly plugins: InstalledPlugin[],
|
|
43
|
+
callbacks: PluginListCallbacks,
|
|
44
|
+
) {
|
|
43
45
|
super();
|
|
44
|
-
this.plugins = plugins;
|
|
45
46
|
|
|
46
47
|
// Title
|
|
47
48
|
this.addChild(new DynamicBorder());
|
|
@@ -121,15 +122,13 @@ export interface PluginDetailCallbacks {
|
|
|
121
122
|
*/
|
|
122
123
|
export class PluginDetailComponent extends Container {
|
|
123
124
|
private settingsList!: SettingsList;
|
|
124
|
-
private plugin: InstalledPlugin;
|
|
125
|
-
private manager: PluginManager;
|
|
126
|
-
private callbacks: PluginDetailCallbacks;
|
|
127
125
|
|
|
128
|
-
constructor(
|
|
126
|
+
constructor(
|
|
127
|
+
private plugin: InstalledPlugin,
|
|
128
|
+
private readonly manager: PluginManager,
|
|
129
|
+
private readonly callbacks: PluginDetailCallbacks,
|
|
130
|
+
) {
|
|
129
131
|
super();
|
|
130
|
-
this.plugin = plugin;
|
|
131
|
-
this.manager = manager;
|
|
132
|
-
this.callbacks = callbacks;
|
|
133
132
|
|
|
134
133
|
void this.rebuild();
|
|
135
134
|
}
|
|
@@ -335,19 +334,15 @@ class ConfigEnumSubmenu extends Container {
|
|
|
335
334
|
*/
|
|
336
335
|
class ConfigInputSubmenu extends Container {
|
|
337
336
|
private input: Input;
|
|
338
|
-
private onSubmit: (value: string) => void;
|
|
339
|
-
private onCancel: () => void;
|
|
340
337
|
|
|
341
338
|
constructor(
|
|
342
339
|
key: string,
|
|
343
340
|
schema: PluginSettingSchema,
|
|
344
341
|
currentValue: string,
|
|
345
|
-
onSubmit: (value: string) => void,
|
|
346
|
-
onCancel: () => void,
|
|
342
|
+
private readonly onSubmit: (value: string) => void,
|
|
343
|
+
private readonly onCancel: () => void,
|
|
347
344
|
) {
|
|
348
345
|
super();
|
|
349
|
-
this.onSubmit = onSubmit;
|
|
350
|
-
this.onCancel = onCancel;
|
|
351
346
|
|
|
352
347
|
this.addChild(new Text(theme.bold(theme.fg("accent", key)), 0, 0));
|
|
353
348
|
if (schema.description) {
|
|
@@ -416,17 +411,18 @@ interface InputHandler {
|
|
|
416
411
|
*/
|
|
417
412
|
export class PluginSettingsComponent extends Container {
|
|
418
413
|
private manager: PluginManager;
|
|
419
|
-
private callbacks: PluginSettingsCallbacks;
|
|
420
414
|
private viewComponent: (Container & InputHandler) | null = null;
|
|
421
415
|
// biome-ignore lint/correctness/noUnusedPrivateClassMembers: state tracking for view management
|
|
422
416
|
private currentView: "list" | "detail" = "list";
|
|
423
417
|
// biome-ignore lint/correctness/noUnusedPrivateClassMembers: state tracking for view management
|
|
424
418
|
private currentPlugin: InstalledPlugin | null = null;
|
|
425
419
|
|
|
426
|
-
constructor(
|
|
420
|
+
constructor(
|
|
421
|
+
cwd: string,
|
|
422
|
+
private readonly callbacks: PluginSettingsCallbacks,
|
|
423
|
+
) {
|
|
427
424
|
super();
|
|
428
425
|
this.manager = new PluginManager(cwd);
|
|
429
|
-
this.callbacks = callbacks;
|
|
430
426
|
this.showPluginList();
|
|
431
427
|
}
|
|
432
428
|
|
|
@@ -12,7 +12,6 @@ import { truncateToVisualLines } from "./visual-truncate";
|
|
|
12
12
|
const PREVIEW_LINES = 20;
|
|
13
13
|
|
|
14
14
|
export class PythonExecutionComponent extends Container {
|
|
15
|
-
private code: string;
|
|
16
15
|
private outputLines: string[] = [];
|
|
17
16
|
private status: "running" | "complete" | "cancelled" | "error" = "running";
|
|
18
17
|
private exitCode: number | undefined = undefined;
|
|
@@ -20,7 +19,6 @@ export class PythonExecutionComponent extends Container {
|
|
|
20
19
|
private truncation?: TruncationMeta;
|
|
21
20
|
private expanded = false;
|
|
22
21
|
private contentContainer: Container;
|
|
23
|
-
private excludeFromContext: boolean;
|
|
24
22
|
|
|
25
23
|
private formatHeader(colorKey: "dim" | "pythonMode"): Text {
|
|
26
24
|
const prompt = theme.fg(colorKey, theme.bold(">>>"));
|
|
@@ -32,10 +30,12 @@ export class PythonExecutionComponent extends Container {
|
|
|
32
30
|
return new Text(headerLines.join("\n"), 1, 0);
|
|
33
31
|
}
|
|
34
32
|
|
|
35
|
-
constructor(
|
|
33
|
+
constructor(
|
|
34
|
+
private readonly code: string,
|
|
35
|
+
ui: TUI,
|
|
36
|
+
private readonly excludeFromContext = false,
|
|
37
|
+
) {
|
|
36
38
|
super();
|
|
37
|
-
this.code = code;
|
|
38
|
-
this.excludeFromContext = excludeFromContext;
|
|
39
39
|
|
|
40
40
|
const colorKey = this.excludeFromContext ? "dim" : "pythonMode";
|
|
41
41
|
const borderColor = (str: string) => theme.fg(colorKey, str);
|
|
@@ -18,20 +18,19 @@ import { DynamicBorder } from "./dynamic-border";
|
|
|
18
18
|
* Custom session list component with multi-line items and search
|
|
19
19
|
*/
|
|
20
20
|
class SessionList implements Component {
|
|
21
|
-
private allSessions: SessionInfo[] = [];
|
|
22
21
|
private filteredSessions: SessionInfo[] = [];
|
|
23
22
|
private selectedIndex: number = 0;
|
|
24
|
-
private searchInput: Input;
|
|
25
|
-
private showCwd = false;
|
|
23
|
+
private readonly searchInput: Input;
|
|
26
24
|
public onSelect?: (sessionPath: string) => void;
|
|
27
25
|
public onCancel?: () => void;
|
|
28
26
|
public onExit: () => void = () => {};
|
|
29
27
|
private maxVisible: number = 5; // Max sessions visible (each session is 3 lines: msg + metadata + blank)
|
|
30
28
|
|
|
31
|
-
constructor(
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
29
|
+
constructor(
|
|
30
|
+
private readonly allSessions: SessionInfo[],
|
|
31
|
+
private readonly showCwd = false,
|
|
32
|
+
) {
|
|
33
|
+
this.filteredSessions = allSessions;
|
|
35
34
|
this.searchInput = new Input();
|
|
36
35
|
|
|
37
36
|
// Handle Enter in search input - select current item
|
|
@@ -43,7 +43,7 @@ export interface EnumSettingDef extends BaseSettingDef {
|
|
|
43
43
|
|
|
44
44
|
export interface SubmenuSettingDef extends BaseSettingDef {
|
|
45
45
|
type: "submenu";
|
|
46
|
-
|
|
46
|
+
get options(): OptionList;
|
|
47
47
|
onPreview?: (value: string) => void;
|
|
48
48
|
onPreviewCancel?: (originalValue: string) => void;
|
|
49
49
|
}
|
|
@@ -62,11 +62,12 @@ const CONDITIONS: Record<string, () => boolean> = {
|
|
|
62
62
|
// Submenu Option Providers
|
|
63
63
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
64
64
|
|
|
65
|
-
type
|
|
65
|
+
type OptionList = ReadonlyArray<{ value: string; label: string; description?: string }>;
|
|
66
|
+
type OptionProvider = (() => OptionList) | OptionList;
|
|
66
67
|
|
|
67
68
|
const OPTION_PROVIDERS: Partial<Record<SettingPath, OptionProvider>> = {
|
|
68
69
|
// Retry max retries
|
|
69
|
-
"retry.maxRetries":
|
|
70
|
+
"retry.maxRetries": [
|
|
70
71
|
{ value: "1", label: "1 retry" },
|
|
71
72
|
{ value: "2", label: "2 retries" },
|
|
72
73
|
{ value: "3", label: "3 retries" },
|
|
@@ -74,7 +75,7 @@ const OPTION_PROVIDERS: Partial<Record<SettingPath, OptionProvider>> = {
|
|
|
74
75
|
{ value: "10", label: "10 retries" },
|
|
75
76
|
],
|
|
76
77
|
// Task max concurrency
|
|
77
|
-
"task.maxConcurrency":
|
|
78
|
+
"task.maxConcurrency": [
|
|
78
79
|
{ value: "0", label: "Unlimited" },
|
|
79
80
|
{ value: "1", label: "1 task" },
|
|
80
81
|
{ value: "2", label: "2 tasks" },
|
|
@@ -85,7 +86,7 @@ const OPTION_PROVIDERS: Partial<Record<SettingPath, OptionProvider>> = {
|
|
|
85
86
|
{ value: "64", label: "64 tasks" },
|
|
86
87
|
],
|
|
87
88
|
// Task max recursion depth
|
|
88
|
-
"task.maxRecursionDepth":
|
|
89
|
+
"task.maxRecursionDepth": [
|
|
89
90
|
{ value: "-1", label: "Unlimited" },
|
|
90
91
|
{ value: "0", label: "None" },
|
|
91
92
|
{ value: "1", label: "Single" },
|
|
@@ -93,21 +94,21 @@ const OPTION_PROVIDERS: Partial<Record<SettingPath, OptionProvider>> = {
|
|
|
93
94
|
{ value: "3", label: "Triple" },
|
|
94
95
|
],
|
|
95
96
|
// Todo max reminders
|
|
96
|
-
"todo.reminders.max":
|
|
97
|
+
"todo.reminders.max": [
|
|
97
98
|
{ value: "1", label: "1 reminder" },
|
|
98
99
|
{ value: "2", label: "2 reminders" },
|
|
99
100
|
{ value: "3", label: "3 reminders" },
|
|
100
101
|
{ value: "5", label: "5 reminders" },
|
|
101
102
|
],
|
|
102
103
|
// Grep context
|
|
103
|
-
"grep.contextBefore":
|
|
104
|
+
"grep.contextBefore": [
|
|
104
105
|
{ value: "0", label: "0 lines" },
|
|
105
106
|
{ value: "1", label: "1 line" },
|
|
106
107
|
{ value: "2", label: "2 lines" },
|
|
107
108
|
{ value: "3", label: "3 lines" },
|
|
108
109
|
{ value: "5", label: "5 lines" },
|
|
109
110
|
],
|
|
110
|
-
"grep.contextAfter":
|
|
111
|
+
"grep.contextAfter": [
|
|
111
112
|
{ value: "0", label: "0 lines" },
|
|
112
113
|
{ value: "1", label: "1 line" },
|
|
113
114
|
{ value: "2", label: "2 lines" },
|
|
@@ -116,7 +117,7 @@ const OPTION_PROVIDERS: Partial<Record<SettingPath, OptionProvider>> = {
|
|
|
116
117
|
{ value: "10", label: "10 lines" },
|
|
117
118
|
],
|
|
118
119
|
// Ask timeout
|
|
119
|
-
"ask.timeout":
|
|
120
|
+
"ask.timeout": [
|
|
120
121
|
{ value: "0", label: "Disabled" },
|
|
121
122
|
{ value: "15", label: "15 seconds" },
|
|
122
123
|
{ value: "30", label: "30 seconds" },
|
|
@@ -124,14 +125,14 @@ const OPTION_PROVIDERS: Partial<Record<SettingPath, OptionProvider>> = {
|
|
|
124
125
|
{ value: "120", label: "120 seconds" },
|
|
125
126
|
],
|
|
126
127
|
// Edit fuzzy threshold
|
|
127
|
-
"edit.fuzzyThreshold":
|
|
128
|
+
"edit.fuzzyThreshold": [
|
|
128
129
|
{ value: "0.85", label: "0.85", description: "Lenient" },
|
|
129
130
|
{ value: "0.90", label: "0.90", description: "Moderate" },
|
|
130
131
|
{ value: "0.95", label: "0.95", description: "Default" },
|
|
131
132
|
{ value: "0.98", label: "0.98", description: "Strict" },
|
|
132
133
|
],
|
|
133
134
|
// TTSR repeat gap
|
|
134
|
-
"ttsr.repeatGap":
|
|
135
|
+
"ttsr.repeatGap": [
|
|
135
136
|
{ value: "5", label: "5 messages" },
|
|
136
137
|
{ value: "10", label: "10 messages" },
|
|
137
138
|
{ value: "15", label: "15 messages" },
|
|
@@ -139,29 +140,38 @@ const OPTION_PROVIDERS: Partial<Record<SettingPath, OptionProvider>> = {
|
|
|
139
140
|
{ value: "30", label: "30 messages" },
|
|
140
141
|
],
|
|
141
142
|
// Provider options
|
|
142
|
-
"providers.webSearch":
|
|
143
|
+
"providers.webSearch": [
|
|
143
144
|
{ value: "auto", label: "Auto", description: "Priority: Exa > Perplexity > Anthropic" },
|
|
144
145
|
{ value: "exa", label: "Exa", description: "Requires EXA_API_KEY" },
|
|
145
146
|
{ value: "perplexity", label: "Perplexity", description: "Requires PERPLEXITY_API_KEY" },
|
|
146
147
|
{ value: "anthropic", label: "Anthropic", description: "Uses Anthropic web search" },
|
|
147
148
|
],
|
|
148
|
-
"providers.image":
|
|
149
|
+
"providers.image": [
|
|
149
150
|
{ value: "auto", label: "Auto", description: "Priority: OpenRouter > Gemini" },
|
|
150
151
|
{ value: "gemini", label: "Gemini", description: "Requires GEMINI_API_KEY" },
|
|
151
152
|
{ value: "openrouter", label: "OpenRouter", description: "Requires OPENROUTER_API_KEY" },
|
|
152
153
|
],
|
|
153
|
-
"providers.kimiApiFormat":
|
|
154
|
+
"providers.kimiApiFormat": [
|
|
154
155
|
{ value: "openai", label: "OpenAI", description: "api.kimi.com" },
|
|
155
156
|
{ value: "anthropic", label: "Anthropic", description: "api.moonshot.ai" },
|
|
156
157
|
],
|
|
158
|
+
// Default thinking level
|
|
159
|
+
defaultThinkingLevel: [
|
|
160
|
+
{ value: "off", label: "off", description: "No reasoning" },
|
|
161
|
+
{ value: "minimal", label: "minimal", description: "Very brief (~1k tokens)" },
|
|
162
|
+
{ value: "low", label: "low", description: "Light (~2k tokens)" },
|
|
163
|
+
{ value: "medium", label: "medium", description: "Moderate (~8k tokens)" },
|
|
164
|
+
{ value: "high", label: "high", description: "Deep (~16k tokens)" },
|
|
165
|
+
{ value: "xhigh", label: "xhigh", description: "Maximum (~32k tokens)" },
|
|
166
|
+
],
|
|
157
167
|
// Symbol preset
|
|
158
|
-
symbolPreset:
|
|
168
|
+
symbolPreset: [
|
|
159
169
|
{ value: "unicode", label: "Unicode", description: "Standard symbols (default)" },
|
|
160
170
|
{ value: "nerd", label: "Nerd Font", description: "Requires Nerd Font" },
|
|
161
171
|
{ value: "ascii", label: "ASCII", description: "Maximum compatibility" },
|
|
162
172
|
],
|
|
163
173
|
// Status line preset
|
|
164
|
-
"statusLine.preset":
|
|
174
|
+
"statusLine.preset": [
|
|
165
175
|
{ value: "default", label: "Default", description: "Model, path, git, context, tokens, cost" },
|
|
166
176
|
{ value: "minimal", label: "Minimal", description: "Path and git only" },
|
|
167
177
|
{ value: "compact", label: "Compact", description: "Model, git, cost, context" },
|
|
@@ -171,7 +181,7 @@ const OPTION_PROVIDERS: Partial<Record<SettingPath, OptionProvider>> = {
|
|
|
171
181
|
{ value: "custom", label: "Custom", description: "User-defined segments" },
|
|
172
182
|
],
|
|
173
183
|
// Status line separator
|
|
174
|
-
"statusLine.separator":
|
|
184
|
+
"statusLine.separator": [
|
|
175
185
|
{ value: "powerline", label: "Powerline", description: "Solid arrows (Nerd Font)" },
|
|
176
186
|
{ value: "powerline-thin", label: "Thin chevron", description: "Thin arrows (Nerd Font)" },
|
|
177
187
|
{ value: "slash", label: "Slash", description: "Forward slashes" },
|
|
@@ -182,14 +192,23 @@ const OPTION_PROVIDERS: Partial<Record<SettingPath, OptionProvider>> = {
|
|
|
182
192
|
],
|
|
183
193
|
};
|
|
184
194
|
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
}
|
|
195
|
+
function createSubmenuSettingDef(base: Omit<SettingDef, "type" | "options">, provider: OptionProvider): SettingDef {
|
|
196
|
+
if (typeof provider === "function") {
|
|
197
|
+
return {
|
|
198
|
+
...base,
|
|
199
|
+
type: "submenu",
|
|
200
|
+
get options() {
|
|
201
|
+
return provider();
|
|
202
|
+
},
|
|
203
|
+
};
|
|
204
|
+
} else {
|
|
205
|
+
return {
|
|
206
|
+
...base,
|
|
207
|
+
type: "submenu",
|
|
208
|
+
options: provider,
|
|
209
|
+
};
|
|
210
|
+
}
|
|
211
|
+
}
|
|
193
212
|
|
|
194
213
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
195
214
|
// Schema to UI Conversion
|
|
@@ -215,18 +234,8 @@ function pathToSettingDef(path: SettingPath): SettingDef | null {
|
|
|
215
234
|
// If marked as submenu, use submenu type
|
|
216
235
|
if (ui.submenu) {
|
|
217
236
|
const provider = OPTION_PROVIDERS[path];
|
|
218
|
-
return
|
|
219
|
-
|
|
220
|
-
type: "submenu",
|
|
221
|
-
getOptions:
|
|
222
|
-
provider ??
|
|
223
|
-
(() =>
|
|
224
|
-
values.map(v => ({
|
|
225
|
-
value: v,
|
|
226
|
-
label: v,
|
|
227
|
-
description: path === "defaultThinkingLevel" ? THINKING_DESCRIPTIONS[v] : undefined,
|
|
228
|
-
}))),
|
|
229
|
-
};
|
|
237
|
+
if (!provider) return null;
|
|
238
|
+
return createSubmenuSettingDef(base, provider);
|
|
230
239
|
}
|
|
231
240
|
|
|
232
241
|
return { ...base, type: "enum", values };
|
|
@@ -235,17 +244,17 @@ function pathToSettingDef(path: SettingPath): SettingDef | null {
|
|
|
235
244
|
if (schemaType === "number" && ui.submenu) {
|
|
236
245
|
const provider = OPTION_PROVIDERS[path];
|
|
237
246
|
if (provider) {
|
|
238
|
-
return
|
|
247
|
+
return createSubmenuSettingDef(base, provider);
|
|
239
248
|
}
|
|
240
249
|
}
|
|
241
250
|
|
|
242
251
|
if (schemaType === "string" && ui.submenu) {
|
|
243
252
|
const provider = OPTION_PROVIDERS[path];
|
|
244
253
|
if (provider) {
|
|
245
|
-
return
|
|
254
|
+
return createSubmenuSettingDef(base, provider);
|
|
246
255
|
}
|
|
247
256
|
// For theme etc, options will be injected at runtime
|
|
248
|
-
return
|
|
257
|
+
return createSubmenuSettingDef(base, []);
|
|
249
258
|
}
|
|
250
259
|
|
|
251
260
|
return null;
|
|
@@ -41,20 +41,18 @@ function getTabBarTheme(): TabBarTheme {
|
|
|
41
41
|
class SelectSubmenu extends Container {
|
|
42
42
|
private selectList: SelectList;
|
|
43
43
|
private previewText: Text | null = null;
|
|
44
|
-
private getPreview: (() => string) | undefined;
|
|
45
44
|
|
|
46
45
|
constructor(
|
|
47
46
|
title: string,
|
|
48
47
|
description: string,
|
|
49
|
-
options: SelectItem
|
|
48
|
+
options: ReadonlyArray<SelectItem>,
|
|
50
49
|
currentValue: string,
|
|
51
50
|
onSelect: (value: string) => void,
|
|
52
51
|
onCancel: () => void,
|
|
53
52
|
onSelectionChange?: (value: string) => void,
|
|
54
|
-
getPreview?: () => string,
|
|
53
|
+
private readonly getPreview?: () => string,
|
|
55
54
|
) {
|
|
56
55
|
super();
|
|
57
|
-
this.getPreview = getPreview;
|
|
58
56
|
|
|
59
57
|
// Title
|
|
60
58
|
this.addChild(new Text(theme.bold(theme.fg("accent", title)), 0, 0));
|
|
@@ -179,15 +177,12 @@ export class SettingsSelectorComponent extends Container {
|
|
|
179
177
|
private statusPreviewText: Text | null = null;
|
|
180
178
|
private currentTabId: SettingTab | "plugins" = "display";
|
|
181
179
|
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
180
|
+
constructor(
|
|
181
|
+
private readonly context: SettingsRuntimeContext,
|
|
182
|
+
private readonly callbacks: SettingsCallbacks,
|
|
183
|
+
) {
|
|
186
184
|
super();
|
|
187
185
|
|
|
188
|
-
this.context = context;
|
|
189
|
-
this.callbacks = callbacks;
|
|
190
|
-
|
|
191
186
|
// Add top border
|
|
192
187
|
this.addChild(new DynamicBorder());
|
|
193
188
|
|
|
@@ -285,10 +280,6 @@ export class SettingsSelectorComponent extends Container {
|
|
|
285
280
|
* Get the current value for a setting.
|
|
286
281
|
*/
|
|
287
282
|
private getCurrentValue(def: SettingDef): unknown {
|
|
288
|
-
// Special case: thinking level comes from runtime context
|
|
289
|
-
if (def.path === "defaultThinkingLevel") {
|
|
290
|
-
return this.context.thinkingLevel;
|
|
291
|
-
}
|
|
292
283
|
return settings.get(def.path);
|
|
293
284
|
}
|
|
294
285
|
|
|
@@ -300,12 +291,12 @@ export class SettingsSelectorComponent extends Container {
|
|
|
300
291
|
currentValue: string,
|
|
301
292
|
done: (value?: string) => void,
|
|
302
293
|
): Container {
|
|
303
|
-
let options = def.
|
|
294
|
+
let options = def.options;
|
|
304
295
|
|
|
305
296
|
// Special case: inject runtime options for thinking level
|
|
306
297
|
if (def.path === "defaultThinkingLevel") {
|
|
307
298
|
options = this.context.availableThinkingLevels.map(level => {
|
|
308
|
-
const baseOpt =
|
|
299
|
+
const baseOpt = options.find(o => o.value === level);
|
|
309
300
|
return baseOpt || { value: level, label: level };
|
|
310
301
|
});
|
|
311
302
|
} else if (def.path === "theme") {
|