@oh-my-pi/pi-coding-agent 3.37.1 → 4.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +103 -0
- package/README.md +44 -3
- package/docs/extensions.md +29 -4
- package/docs/sdk.md +3 -3
- package/package.json +5 -5
- package/src/cli/args.ts +8 -0
- package/src/config.ts +5 -15
- package/src/core/agent-session.ts +217 -51
- package/src/core/auth-storage.ts +456 -47
- package/src/core/bash-executor.ts +79 -14
- package/src/core/custom-commands/types.ts +1 -1
- package/src/core/custom-tools/types.ts +1 -1
- package/src/core/export-html/index.ts +33 -1
- package/src/core/export-html/template.css +99 -0
- package/src/core/export-html/template.generated.ts +1 -1
- package/src/core/export-html/template.js +133 -8
- package/src/core/extensions/index.ts +22 -4
- package/src/core/extensions/loader.ts +152 -214
- package/src/core/extensions/runner.ts +139 -79
- package/src/core/extensions/types.ts +143 -19
- package/src/core/extensions/wrapper.ts +5 -8
- package/src/core/hooks/types.ts +1 -1
- package/src/core/index.ts +2 -1
- package/src/core/keybindings.ts +4 -1
- package/src/core/model-registry.ts +4 -4
- package/src/core/model-resolver.ts +35 -26
- package/src/core/sdk.ts +96 -76
- package/src/core/settings-manager.ts +45 -14
- package/src/core/system-prompt.ts +5 -15
- package/src/core/tools/bash.ts +115 -54
- package/src/core/tools/find.ts +86 -7
- package/src/core/tools/grep.ts +27 -6
- package/src/core/tools/index.ts +15 -6
- package/src/core/tools/ls.ts +49 -18
- package/src/core/tools/render-utils.ts +2 -1
- package/src/core/tools/task/worker.ts +35 -12
- package/src/core/tools/web-search/auth.ts +37 -32
- package/src/core/tools/web-search/providers/anthropic.ts +35 -22
- package/src/index.ts +101 -9
- package/src/main.ts +60 -20
- package/src/migrations.ts +47 -2
- package/src/modes/index.ts +2 -2
- package/src/modes/interactive/components/assistant-message.ts +25 -7
- package/src/modes/interactive/components/bash-execution.ts +5 -0
- package/src/modes/interactive/components/branch-summary-message.ts +5 -0
- package/src/modes/interactive/components/compaction-summary-message.ts +5 -0
- package/src/modes/interactive/components/countdown-timer.ts +38 -0
- package/src/modes/interactive/components/custom-editor.ts +8 -0
- package/src/modes/interactive/components/custom-message.ts +5 -0
- package/src/modes/interactive/components/footer.ts +2 -5
- package/src/modes/interactive/components/hook-input.ts +29 -20
- package/src/modes/interactive/components/hook-selector.ts +52 -38
- package/src/modes/interactive/components/index.ts +39 -0
- package/src/modes/interactive/components/login-dialog.ts +160 -0
- package/src/modes/interactive/components/model-selector.ts +10 -2
- package/src/modes/interactive/components/session-selector.ts +5 -1
- package/src/modes/interactive/components/settings-defs.ts +9 -0
- package/src/modes/interactive/components/status-line/segments.ts +3 -3
- package/src/modes/interactive/components/tool-execution.ts +9 -16
- package/src/modes/interactive/components/tree-selector.ts +1 -6
- package/src/modes/interactive/interactive-mode.ts +466 -215
- package/src/modes/interactive/theme/theme.ts +50 -2
- package/src/modes/print-mode.ts +78 -31
- package/src/modes/rpc/rpc-mode.ts +186 -78
- package/src/modes/rpc/rpc-types.ts +10 -3
- package/src/prompts/system-prompt.md +36 -28
- package/src/utils/clipboard.ts +90 -50
- package/src/utils/image-convert.ts +1 -1
- package/src/utils/image-resize.ts +1 -1
- package/src/utils/tools-manager.ts +2 -2
|
@@ -3,89 +3,103 @@
|
|
|
3
3
|
* Displays a list of string options with keyboard navigation.
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
-
import {
|
|
6
|
+
import {
|
|
7
|
+
Container,
|
|
8
|
+
isArrowDown,
|
|
9
|
+
isArrowUp,
|
|
10
|
+
isCtrlC,
|
|
11
|
+
isEnter,
|
|
12
|
+
isEscape,
|
|
13
|
+
Spacer,
|
|
14
|
+
Text,
|
|
15
|
+
type TUI,
|
|
16
|
+
} from "@oh-my-pi/pi-tui";
|
|
7
17
|
import { theme } from "../theme/theme";
|
|
18
|
+
import { CountdownTimer } from "./countdown-timer";
|
|
8
19
|
import { DynamicBorder } from "./dynamic-border";
|
|
9
20
|
|
|
21
|
+
export interface HookSelectorOptions {
|
|
22
|
+
tui?: TUI;
|
|
23
|
+
timeout?: number;
|
|
24
|
+
}
|
|
25
|
+
|
|
10
26
|
export class HookSelectorComponent extends Container {
|
|
11
27
|
private options: string[];
|
|
12
28
|
private selectedIndex = 0;
|
|
13
29
|
private listContainer: Container;
|
|
14
30
|
private onSelectCallback: (option: string) => void;
|
|
15
31
|
private onCancelCallback: () => void;
|
|
16
|
-
|
|
17
|
-
|
|
32
|
+
private titleText: Text;
|
|
33
|
+
private baseTitle: string;
|
|
34
|
+
private countdown: CountdownTimer | undefined;
|
|
35
|
+
|
|
36
|
+
constructor(
|
|
37
|
+
title: string,
|
|
38
|
+
options: string[],
|
|
39
|
+
onSelect: (option: string) => void,
|
|
40
|
+
onCancel: () => void,
|
|
41
|
+
opts?: HookSelectorOptions,
|
|
42
|
+
) {
|
|
18
43
|
super();
|
|
19
44
|
|
|
20
45
|
this.options = options;
|
|
21
46
|
this.onSelectCallback = onSelect;
|
|
22
47
|
this.onCancelCallback = onCancel;
|
|
48
|
+
this.baseTitle = title;
|
|
23
49
|
|
|
24
|
-
// Add top border
|
|
25
50
|
this.addChild(new DynamicBorder());
|
|
26
51
|
this.addChild(new Spacer(1));
|
|
27
52
|
|
|
28
|
-
|
|
29
|
-
this.addChild(
|
|
53
|
+
this.titleText = new Text(theme.fg("accent", title), 1, 0);
|
|
54
|
+
this.addChild(this.titleText);
|
|
30
55
|
this.addChild(new Spacer(1));
|
|
31
56
|
|
|
32
|
-
|
|
57
|
+
if (opts?.timeout && opts.timeout > 0 && opts.tui) {
|
|
58
|
+
this.countdown = new CountdownTimer(
|
|
59
|
+
opts.timeout,
|
|
60
|
+
opts.tui,
|
|
61
|
+
(s) => this.titleText.setText(theme.fg("accent", `${this.baseTitle} (${s}s)`)),
|
|
62
|
+
() => this.onCancelCallback(),
|
|
63
|
+
);
|
|
64
|
+
}
|
|
65
|
+
|
|
33
66
|
this.listContainer = new Container();
|
|
34
67
|
this.addChild(this.listContainer);
|
|
35
|
-
|
|
36
68
|
this.addChild(new Spacer(1));
|
|
37
|
-
|
|
38
|
-
// Add hint
|
|
39
|
-
this.addChild(new Text(theme.fg("dim", "↑↓ navigate enter select esc cancel"), 1, 0));
|
|
40
|
-
|
|
69
|
+
this.addChild(new Text(theme.fg("dim", "up/down navigate enter select esc cancel"), 1, 0));
|
|
41
70
|
this.addChild(new Spacer(1));
|
|
42
|
-
|
|
43
|
-
// Add bottom border
|
|
44
71
|
this.addChild(new DynamicBorder());
|
|
45
72
|
|
|
46
|
-
// Initial render
|
|
47
73
|
this.updateList();
|
|
48
74
|
}
|
|
49
75
|
|
|
50
76
|
private updateList(): void {
|
|
51
77
|
this.listContainer.clear();
|
|
52
|
-
|
|
53
78
|
for (let i = 0; i < this.options.length; i++) {
|
|
54
|
-
const option = this.options[i];
|
|
55
79
|
const isSelected = i === this.selectedIndex;
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
text = theme.fg("accent", `${theme.nav.cursor} `) + theme.fg("accent", option);
|
|
60
|
-
} else {
|
|
61
|
-
text = ` ${theme.fg("text", option)}`;
|
|
62
|
-
}
|
|
63
|
-
|
|
80
|
+
const text = isSelected
|
|
81
|
+
? theme.fg("accent", `${theme.nav.cursor} `) + theme.fg("accent", this.options[i])
|
|
82
|
+
: ` ${theme.fg("text", this.options[i])}`;
|
|
64
83
|
this.listContainer.addChild(new Text(text, 1, 0));
|
|
65
84
|
}
|
|
66
85
|
}
|
|
67
86
|
|
|
68
87
|
handleInput(keyData: string): void {
|
|
69
|
-
// Up arrow or k
|
|
70
88
|
if (isArrowUp(keyData) || keyData === "k") {
|
|
71
89
|
this.selectedIndex = Math.max(0, this.selectedIndex - 1);
|
|
72
90
|
this.updateList();
|
|
73
|
-
}
|
|
74
|
-
// Down arrow or j
|
|
75
|
-
else if (isArrowDown(keyData) || keyData === "j") {
|
|
91
|
+
} else if (isArrowDown(keyData) || keyData === "j") {
|
|
76
92
|
this.selectedIndex = Math.min(this.options.length - 1, this.selectedIndex + 1);
|
|
77
93
|
this.updateList();
|
|
78
|
-
}
|
|
79
|
-
// Enter
|
|
80
|
-
else if (isEnter(keyData) || keyData === "\n") {
|
|
94
|
+
} else if (isEnter(keyData) || keyData === "\n") {
|
|
81
95
|
const selected = this.options[this.selectedIndex];
|
|
82
|
-
if (selected)
|
|
83
|
-
|
|
84
|
-
}
|
|
85
|
-
}
|
|
86
|
-
// Escape or Ctrl+C
|
|
87
|
-
else if (isEscape(keyData) || isCtrlC(keyData)) {
|
|
96
|
+
if (selected) this.onSelectCallback(selected);
|
|
97
|
+
} else if (isEscape(keyData) || isCtrlC(keyData)) {
|
|
88
98
|
this.onCancelCallback();
|
|
89
99
|
}
|
|
90
100
|
}
|
|
101
|
+
|
|
102
|
+
dispose(): void {
|
|
103
|
+
this.countdown?.dispose();
|
|
104
|
+
}
|
|
91
105
|
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
// UI Components barrel export
|
|
2
|
+
export { ArminComponent } from "./armin";
|
|
3
|
+
export { AssistantMessageComponent } from "./assistant-message";
|
|
4
|
+
export { BashExecutionComponent } from "./bash-execution";
|
|
5
|
+
export { BorderedLoader } from "./bordered-loader";
|
|
6
|
+
export { BranchSummaryMessageComponent } from "./branch-summary-message";
|
|
7
|
+
export { CompactionSummaryMessageComponent } from "./compaction-summary-message";
|
|
8
|
+
export { CountdownTimer } from "./countdown-timer";
|
|
9
|
+
export { CustomEditor } from "./custom-editor";
|
|
10
|
+
export { CustomMessageComponent } from "./custom-message";
|
|
11
|
+
export { type RenderDiffOptions, renderDiff } from "./diff";
|
|
12
|
+
export { DynamicBorder } from "./dynamic-border";
|
|
13
|
+
export { FooterComponent } from "./footer";
|
|
14
|
+
export { HookEditorComponent } from "./hook-editor";
|
|
15
|
+
export { HookInputComponent, type HookInputOptions } from "./hook-input";
|
|
16
|
+
export { HookMessageComponent } from "./hook-message";
|
|
17
|
+
export { HookSelectorComponent } from "./hook-selector";
|
|
18
|
+
export { LoginDialogComponent } from "./login-dialog";
|
|
19
|
+
export { ModelSelectorComponent } from "./model-selector";
|
|
20
|
+
export { OAuthSelectorComponent } from "./oauth-selector";
|
|
21
|
+
export { QueueModeSelectorComponent } from "./queue-mode-selector";
|
|
22
|
+
export { SessionSelectorComponent } from "./session-selector";
|
|
23
|
+
export {
|
|
24
|
+
type SettingChangeHandler,
|
|
25
|
+
type SettingsCallbacks,
|
|
26
|
+
type SettingsRuntimeContext,
|
|
27
|
+
SettingsSelectorComponent,
|
|
28
|
+
} from "./settings-selector";
|
|
29
|
+
export { ShowImagesSelectorComponent } from "./show-images-selector";
|
|
30
|
+
export { StatusLineComponent } from "./status-line";
|
|
31
|
+
export { ThemeSelectorComponent } from "./theme-selector";
|
|
32
|
+
export { ThinkingSelectorComponent } from "./thinking-selector";
|
|
33
|
+
export { ToolExecutionComponent, type ToolExecutionOptions } from "./tool-execution";
|
|
34
|
+
export { TreeSelectorComponent } from "./tree-selector";
|
|
35
|
+
export { TtsrNotificationComponent } from "./ttsr-notification";
|
|
36
|
+
export { UserMessageComponent } from "./user-message";
|
|
37
|
+
export { UserMessageSelectorComponent } from "./user-message-selector";
|
|
38
|
+
export { truncateToVisualLines, type VisualTruncateResult } from "./visual-truncate";
|
|
39
|
+
export { type LspServerInfo, type RecentSession, WelcomeComponent } from "./welcome";
|
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
import { getOAuthProviders } from "@oh-my-pi/pi-ai";
|
|
2
|
+
import { Container, getEditorKeybindings, Input, Spacer, Text, type TUI } from "@oh-my-pi/pi-tui";
|
|
3
|
+
import { theme } from "../theme/theme";
|
|
4
|
+
import { DynamicBorder } from "./dynamic-border";
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Login dialog component - replaces editor during OAuth login flow
|
|
8
|
+
*/
|
|
9
|
+
export class LoginDialogComponent extends Container {
|
|
10
|
+
private contentContainer: Container;
|
|
11
|
+
private input: Input;
|
|
12
|
+
private tui: TUI;
|
|
13
|
+
private abortController = new AbortController();
|
|
14
|
+
private inputResolver?: (value: string) => void;
|
|
15
|
+
private inputRejecter?: (error: Error) => void;
|
|
16
|
+
|
|
17
|
+
constructor(
|
|
18
|
+
tui: TUI,
|
|
19
|
+
providerId: string,
|
|
20
|
+
private onComplete: (success: boolean, message?: string) => void,
|
|
21
|
+
) {
|
|
22
|
+
super();
|
|
23
|
+
this.tui = tui;
|
|
24
|
+
|
|
25
|
+
const providerInfo = getOAuthProviders().find((p) => p.id === providerId);
|
|
26
|
+
const providerName = providerInfo?.name || providerId;
|
|
27
|
+
|
|
28
|
+
// Top border
|
|
29
|
+
this.addChild(new DynamicBorder());
|
|
30
|
+
|
|
31
|
+
// Title
|
|
32
|
+
this.addChild(new Text(theme.fg("warning", `Login to ${providerName}`), 1, 0));
|
|
33
|
+
|
|
34
|
+
// Dynamic content area
|
|
35
|
+
this.contentContainer = new Container();
|
|
36
|
+
this.addChild(this.contentContainer);
|
|
37
|
+
|
|
38
|
+
// Input (always present, used when needed)
|
|
39
|
+
this.input = new Input();
|
|
40
|
+
this.input.onSubmit = () => {
|
|
41
|
+
if (this.inputResolver) {
|
|
42
|
+
this.inputResolver(this.input.getValue());
|
|
43
|
+
this.inputResolver = undefined;
|
|
44
|
+
this.inputRejecter = undefined;
|
|
45
|
+
}
|
|
46
|
+
};
|
|
47
|
+
this.input.onEscape = () => {
|
|
48
|
+
this.cancel();
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
// Bottom border
|
|
52
|
+
this.addChild(new DynamicBorder());
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
get signal(): AbortSignal {
|
|
56
|
+
return this.abortController.signal;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
private cancel(): void {
|
|
60
|
+
this.abortController.abort();
|
|
61
|
+
if (this.inputRejecter) {
|
|
62
|
+
this.inputRejecter(new Error("Login cancelled"));
|
|
63
|
+
this.inputResolver = undefined;
|
|
64
|
+
this.inputRejecter = undefined;
|
|
65
|
+
}
|
|
66
|
+
this.onComplete(false, "Login cancelled");
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Called by onAuth callback - show URL and optional instructions
|
|
71
|
+
*/
|
|
72
|
+
showAuth(url: string, instructions?: string): void {
|
|
73
|
+
this.contentContainer.clear();
|
|
74
|
+
this.contentContainer.addChild(new Spacer(1));
|
|
75
|
+
this.contentContainer.addChild(new Text(theme.fg("accent", url), 1, 0));
|
|
76
|
+
|
|
77
|
+
const clickHint = process.platform === "darwin" ? "Cmd+click to open" : "Ctrl+click to open";
|
|
78
|
+
const hyperlink = `\x1b]8;;${url}\x07${clickHint}\x1b]8;;\x07`;
|
|
79
|
+
this.contentContainer.addChild(new Text(theme.fg("dim", hyperlink), 1, 0));
|
|
80
|
+
|
|
81
|
+
if (instructions) {
|
|
82
|
+
this.contentContainer.addChild(new Spacer(1));
|
|
83
|
+
this.contentContainer.addChild(new Text(theme.fg("warning", instructions), 1, 0));
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// Try to open browser using Bun.spawn
|
|
87
|
+
const openCmd = process.platform === "darwin" ? "open" : process.platform === "win32" ? "start" : "xdg-open";
|
|
88
|
+
Bun.spawn([openCmd, url], { stdout: "ignore", stderr: "ignore" });
|
|
89
|
+
|
|
90
|
+
this.tui.requestRender();
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* Show input for manual code/URL entry (for callback server providers)
|
|
95
|
+
*/
|
|
96
|
+
showManualInput(prompt: string): Promise<string> {
|
|
97
|
+
this.contentContainer.addChild(new Spacer(1));
|
|
98
|
+
this.contentContainer.addChild(new Text(theme.fg("dim", prompt), 1, 0));
|
|
99
|
+
this.contentContainer.addChild(this.input);
|
|
100
|
+
this.contentContainer.addChild(new Text(theme.fg("dim", "(Escape to cancel)"), 1, 0));
|
|
101
|
+
this.tui.requestRender();
|
|
102
|
+
|
|
103
|
+
return new Promise((resolve, reject) => {
|
|
104
|
+
this.inputResolver = resolve;
|
|
105
|
+
this.inputRejecter = reject;
|
|
106
|
+
});
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* Called by onPrompt callback - show prompt and wait for input
|
|
111
|
+
* Note: Does NOT clear content, appends to existing (preserves URL from showAuth)
|
|
112
|
+
*/
|
|
113
|
+
showPrompt(message: string, placeholder?: string): Promise<string> {
|
|
114
|
+
this.contentContainer.addChild(new Spacer(1));
|
|
115
|
+
this.contentContainer.addChild(new Text(theme.fg("text", message), 1, 0));
|
|
116
|
+
if (placeholder) {
|
|
117
|
+
this.contentContainer.addChild(new Text(theme.fg("dim", `e.g., ${placeholder}`), 1, 0));
|
|
118
|
+
}
|
|
119
|
+
this.contentContainer.addChild(this.input);
|
|
120
|
+
this.contentContainer.addChild(new Text(theme.fg("dim", "(Escape to cancel, Enter to submit)"), 1, 0));
|
|
121
|
+
|
|
122
|
+
this.input.setValue("");
|
|
123
|
+
this.tui.requestRender();
|
|
124
|
+
|
|
125
|
+
return new Promise((resolve, reject) => {
|
|
126
|
+
this.inputResolver = resolve;
|
|
127
|
+
this.inputRejecter = reject;
|
|
128
|
+
});
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* Show waiting message (for polling flows like GitHub Copilot)
|
|
133
|
+
*/
|
|
134
|
+
showWaiting(message: string): void {
|
|
135
|
+
this.contentContainer.addChild(new Spacer(1));
|
|
136
|
+
this.contentContainer.addChild(new Text(theme.fg("dim", message), 1, 0));
|
|
137
|
+
this.contentContainer.addChild(new Text(theme.fg("dim", "(Escape to cancel)"), 1, 0));
|
|
138
|
+
this.tui.requestRender();
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
/**
|
|
142
|
+
* Called by onProgress callback
|
|
143
|
+
*/
|
|
144
|
+
showProgress(message: string): void {
|
|
145
|
+
this.contentContainer.addChild(new Text(theme.fg("dim", message), 1, 0));
|
|
146
|
+
this.tui.requestRender();
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
handleInput(data: string): void {
|
|
150
|
+
const kb = getEditorKeybindings();
|
|
151
|
+
|
|
152
|
+
if (kb.matches(data, "selectCancel")) {
|
|
153
|
+
this.cancel();
|
|
154
|
+
return;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
// Pass to input
|
|
158
|
+
this.input.handleInput(data);
|
|
159
|
+
}
|
|
160
|
+
}
|
|
@@ -93,7 +93,7 @@ export class ModelSelectorComponent extends Container {
|
|
|
93
93
|
scopedModels: ReadonlyArray<ScopedModelItem>,
|
|
94
94
|
onSelect: (model: Model<any>, role: string) => void,
|
|
95
95
|
onCancel: () => void,
|
|
96
|
-
options?: { temporaryOnly?: boolean },
|
|
96
|
+
options?: { temporaryOnly?: boolean; initialSearchInput?: string },
|
|
97
97
|
) {
|
|
98
98
|
super();
|
|
99
99
|
|
|
@@ -105,6 +105,7 @@ export class ModelSelectorComponent extends Container {
|
|
|
105
105
|
this.onSelectCallback = onSelect;
|
|
106
106
|
this.onCancelCallback = onCancel;
|
|
107
107
|
this.temporaryOnly = options?.temporaryOnly ?? false;
|
|
108
|
+
const initialSearchInput = options?.initialSearchInput;
|
|
108
109
|
|
|
109
110
|
// Load current role assignments from settings
|
|
110
111
|
this._loadRoleModels();
|
|
@@ -129,6 +130,9 @@ export class ModelSelectorComponent extends Container {
|
|
|
129
130
|
|
|
130
131
|
// Create search input
|
|
131
132
|
this.searchInput = new Input();
|
|
133
|
+
if (initialSearchInput) {
|
|
134
|
+
this.searchInput.setValue(initialSearchInput);
|
|
135
|
+
}
|
|
132
136
|
this.searchInput.onSubmit = () => {
|
|
133
137
|
// Enter on search input opens menu if we have a selection
|
|
134
138
|
if (this.filteredModels[this.selectedIndex]) {
|
|
@@ -156,7 +160,11 @@ export class ModelSelectorComponent extends Container {
|
|
|
156
160
|
this.loadModels().then(() => {
|
|
157
161
|
this.buildProviderTabs();
|
|
158
162
|
this.updateTabBar();
|
|
159
|
-
|
|
163
|
+
if (initialSearchInput) {
|
|
164
|
+
this.filterModels(initialSearchInput);
|
|
165
|
+
} else {
|
|
166
|
+
this.updateList();
|
|
167
|
+
}
|
|
160
168
|
// Request re-render after models are loaded
|
|
161
169
|
this.tui.requestRender();
|
|
162
170
|
});
|
|
@@ -47,7 +47,11 @@ class SessionList implements Component {
|
|
|
47
47
|
}
|
|
48
48
|
|
|
49
49
|
private filterSessions(query: string): void {
|
|
50
|
-
this.filteredSessions = fuzzyFilter(
|
|
50
|
+
this.filteredSessions = fuzzyFilter(
|
|
51
|
+
this.allSessions,
|
|
52
|
+
query,
|
|
53
|
+
(session) => `${session.id} ${session.allMessagesText}`,
|
|
54
|
+
);
|
|
51
55
|
this.selectedIndex = Math.min(this.selectedIndex, Math.max(0, this.filteredSessions.length - 1));
|
|
52
56
|
}
|
|
53
57
|
|
|
@@ -135,6 +135,15 @@ export const SETTINGS_DEFS: SettingDef[] = [
|
|
|
135
135
|
get: (sm) => sm.getImageAutoResize(),
|
|
136
136
|
set: (sm, v) => sm.setImageAutoResize(v),
|
|
137
137
|
},
|
|
138
|
+
{
|
|
139
|
+
id: "blockImages",
|
|
140
|
+
tab: "config",
|
|
141
|
+
type: "boolean",
|
|
142
|
+
label: "Block images",
|
|
143
|
+
description: "Prevent images from being sent to LLM providers",
|
|
144
|
+
get: (sm) => sm.getBlockImages(),
|
|
145
|
+
set: (sm, v) => sm.setBlockImages(v),
|
|
146
|
+
},
|
|
138
147
|
{
|
|
139
148
|
id: "steeringMode",
|
|
140
149
|
tab: "config",
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { hostname as osHostname } from "node:os";
|
|
2
|
+
import { shortenPath } from "../../../../core/tools/render-utils";
|
|
2
3
|
import { theme } from "../../theme/theme";
|
|
3
4
|
import type { RenderedSegment, SegmentContext, StatusLineSegment, StatusLineSegmentId } from "./types";
|
|
4
5
|
|
|
@@ -76,10 +77,9 @@ const pathSegment: StatusLineSegment = {
|
|
|
76
77
|
const opts = ctx.options.path ?? {};
|
|
77
78
|
|
|
78
79
|
let pwd = process.cwd();
|
|
79
|
-
const home = process.env.HOME || process.env.USERPROFILE;
|
|
80
80
|
|
|
81
|
-
if (opts.abbreviate !== false
|
|
82
|
-
pwd =
|
|
81
|
+
if (opts.abbreviate !== false) {
|
|
82
|
+
pwd = shortenPath(pwd);
|
|
83
83
|
}
|
|
84
84
|
if (opts.stripWorkPrefix !== false && pwd.startsWith("/work/")) {
|
|
85
85
|
pwd = pwd.slice(6);
|
|
@@ -17,7 +17,6 @@ import { convertToPng } from "../../../utils/image-convert";
|
|
|
17
17
|
import { sanitizeBinaryOutput } from "../../../utils/shell";
|
|
18
18
|
import { theme } from "../theme/theme";
|
|
19
19
|
import { renderDiff } from "./diff";
|
|
20
|
-
import { truncateToVisualLines } from "./visual-truncate";
|
|
21
20
|
|
|
22
21
|
// Preview line limit for bash when not expanded
|
|
23
22
|
const BASH_PREVIEW_LINES = 5;
|
|
@@ -297,6 +296,11 @@ export class ToolExecutionComponent extends Container {
|
|
|
297
296
|
this.updateDisplay();
|
|
298
297
|
}
|
|
299
298
|
|
|
299
|
+
override invalidate(): void {
|
|
300
|
+
super.invalidate();
|
|
301
|
+
this.updateDisplay();
|
|
302
|
+
}
|
|
303
|
+
|
|
300
304
|
private updateDisplay(): void {
|
|
301
305
|
// Set background based on state
|
|
302
306
|
const bgFn = this.isPartial
|
|
@@ -477,22 +481,11 @@ export class ToolExecutionComponent extends Container {
|
|
|
477
481
|
const context: Record<string, unknown> = {};
|
|
478
482
|
|
|
479
483
|
if (this.toolName === "bash" && this.result) {
|
|
480
|
-
//
|
|
484
|
+
// Pass raw output and expanded state - renderer handles width-aware truncation
|
|
481
485
|
const output = this.getTextOutput().trim();
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
.map((line) => theme.fg("toolOutput", line))
|
|
486
|
-
.join("\n");
|
|
487
|
-
const { visualLines, skippedCount } = truncateToVisualLines(
|
|
488
|
-
`\n${styledOutput}`,
|
|
489
|
-
BASH_PREVIEW_LINES,
|
|
490
|
-
this.ui.terminal.columns - 2,
|
|
491
|
-
);
|
|
492
|
-
context.visualLines = visualLines;
|
|
493
|
-
context.skippedCount = skippedCount;
|
|
494
|
-
context.totalVisualLines = skippedCount + visualLines.length;
|
|
495
|
-
}
|
|
486
|
+
context.output = output;
|
|
487
|
+
context.expanded = this.expanded;
|
|
488
|
+
context.previewLines = BASH_PREVIEW_LINES;
|
|
496
489
|
} else if (this.toolName === "edit") {
|
|
497
490
|
// Edit needs diff preview and renderDiff function
|
|
498
491
|
context.editDiffPreview = this.editDiffPreview;
|
|
@@ -18,6 +18,7 @@ import {
|
|
|
18
18
|
truncateToWidth,
|
|
19
19
|
} from "@oh-my-pi/pi-tui";
|
|
20
20
|
import type { SessionTreeNode } from "../../../core/session-manager";
|
|
21
|
+
import { shortenPath } from "../../../core/tools/render-utils";
|
|
21
22
|
import { theme } from "../theme/theme";
|
|
22
23
|
import { DynamicBorder } from "./dynamic-border";
|
|
23
24
|
|
|
@@ -607,12 +608,6 @@ class TreeList implements Component {
|
|
|
607
608
|
}
|
|
608
609
|
|
|
609
610
|
private formatToolCall(name: string, args: Record<string, unknown>): string {
|
|
610
|
-
const shortenPath = (p: string): string => {
|
|
611
|
-
const home = process.env.HOME || process.env.USERPROFILE || "";
|
|
612
|
-
if (home && p.startsWith(home)) return `~${p.slice(home.length)}`;
|
|
613
|
-
return p;
|
|
614
|
-
};
|
|
615
|
-
|
|
616
611
|
switch (name) {
|
|
617
612
|
case "read": {
|
|
618
613
|
const path = shortenPath(String(args.path || args.file_path || ""));
|