@oh-my-pi/pi-coding-agent 13.14.0 → 13.15.2
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 +140 -0
- package/package.json +10 -8
- package/src/autoresearch/command-initialize.md +34 -0
- package/src/autoresearch/command-resume.md +17 -0
- package/src/autoresearch/contract.ts +332 -0
- package/src/autoresearch/dashboard.ts +447 -0
- package/src/autoresearch/git.ts +243 -0
- package/src/autoresearch/helpers.ts +458 -0
- package/src/autoresearch/index.ts +693 -0
- package/src/autoresearch/prompt.md +227 -0
- package/src/autoresearch/resume-message.md +16 -0
- package/src/autoresearch/state.ts +386 -0
- package/src/autoresearch/tools/init-experiment.ts +310 -0
- package/src/autoresearch/tools/log-experiment.ts +833 -0
- package/src/autoresearch/tools/run-experiment.ts +640 -0
- package/src/autoresearch/types.ts +218 -0
- package/src/cli/args.ts +8 -2
- package/src/cli/initial-message.ts +58 -0
- package/src/config/keybindings.ts +417 -212
- package/src/config/model-registry.ts +1 -0
- package/src/config/model-resolver.ts +57 -9
- package/src/config/settings-schema.ts +38 -10
- package/src/config/settings.ts +1 -4
- package/src/exec/bash-executor.ts +7 -5
- package/src/export/html/template.css +43 -13
- package/src/export/html/template.generated.ts +1 -1
- package/src/export/html/template.html +1 -0
- package/src/export/html/template.js +107 -0
- package/src/extensibility/extensions/types.ts +31 -8
- package/src/internal-urls/docs-index.generated.ts +1 -1
- package/src/lsp/index.ts +1 -1
- package/src/main.ts +44 -44
- package/src/mcp/oauth-discovery.ts +1 -1
- package/src/modes/acp/acp-agent.ts +957 -0
- package/src/modes/acp/acp-event-mapper.ts +531 -0
- package/src/modes/acp/acp-mode.ts +13 -0
- package/src/modes/acp/index.ts +2 -0
- package/src/modes/components/agent-dashboard.ts +5 -4
- package/src/modes/components/bash-execution.ts +40 -11
- package/src/modes/components/custom-editor.ts +47 -47
- package/src/modes/components/extensions/extension-dashboard.ts +2 -1
- package/src/modes/components/history-search.ts +2 -1
- package/src/modes/components/hook-editor.ts +2 -1
- package/src/modes/components/hook-input.ts +8 -7
- package/src/modes/components/hook-selector.ts +15 -10
- package/src/modes/components/keybinding-hints.ts +9 -9
- package/src/modes/components/login-dialog.ts +3 -3
- package/src/modes/components/mcp-add-wizard.ts +2 -1
- package/src/modes/components/model-selector.ts +14 -3
- package/src/modes/components/oauth-selector.ts +2 -1
- package/src/modes/components/python-execution.ts +2 -3
- package/src/modes/components/session-selector.ts +2 -1
- package/src/modes/components/settings-selector.ts +2 -1
- package/src/modes/components/status-line-segment-editor.ts +2 -1
- package/src/modes/components/tool-execution.ts +4 -5
- package/src/modes/components/tree-selector.ts +3 -2
- package/src/modes/components/user-message-selector.ts +3 -8
- package/src/modes/components/user-message.ts +16 -0
- package/src/modes/controllers/command-controller.ts +0 -2
- package/src/modes/controllers/extension-ui-controller.ts +89 -4
- package/src/modes/controllers/input-controller.ts +29 -23
- package/src/modes/controllers/mcp-command-controller.ts +1 -1
- package/src/modes/index.ts +1 -0
- package/src/modes/interactive-mode.ts +17 -5
- package/src/modes/print-mode.ts +1 -1
- package/src/modes/prompt-action-autocomplete.ts +7 -7
- package/src/modes/rpc/rpc-mode.ts +7 -2
- package/src/modes/rpc/rpc-types.ts +1 -0
- package/src/modes/theme/theme.ts +53 -44
- package/src/modes/types.ts +9 -2
- package/src/modes/utils/hotkeys-markdown.ts +19 -19
- package/src/modes/utils/keybinding-matchers.ts +21 -0
- package/src/modes/utils/ui-helpers.ts +1 -1
- package/src/patch/hashline.ts +139 -127
- package/src/patch/index.ts +77 -59
- package/src/patch/shared.ts +19 -11
- package/src/prompts/tools/hashline.md +43 -116
- package/src/sdk.ts +34 -17
- package/src/session/agent-session.ts +123 -30
- package/src/session/session-manager.ts +32 -31
- package/src/session/streaming-output.ts +87 -37
- package/src/tools/ask.ts +56 -30
- package/src/tools/bash-interactive.ts +2 -6
- package/src/tools/bash-interceptor.ts +1 -39
- package/src/tools/bash-skill-urls.ts +1 -1
- package/src/tools/browser.ts +1 -1
- package/src/tools/gemini-image.ts +1 -1
- package/src/tools/python.ts +2 -2
- package/src/tools/resolve.ts +1 -1
- package/src/utils/child-process.ts +88 -0
|
@@ -1,41 +1,41 @@
|
|
|
1
1
|
import { Editor, type KeyId, matchesKey, parseKittySequence } from "@oh-my-pi/pi-tui";
|
|
2
|
-
import type {
|
|
2
|
+
import type { AppKeybinding } from "../../config/keybindings";
|
|
3
3
|
|
|
4
4
|
type ConfigurableEditorAction = Extract<
|
|
5
|
-
|
|
6
|
-
| "interrupt"
|
|
7
|
-
| "clear"
|
|
8
|
-
| "exit"
|
|
9
|
-
| "suspend"
|
|
10
|
-
| "
|
|
11
|
-
| "
|
|
12
|
-
| "
|
|
13
|
-
| "
|
|
14
|
-
| "
|
|
15
|
-
| "
|
|
16
|
-
| "
|
|
17
|
-
| "
|
|
18
|
-
| "dequeue"
|
|
19
|
-
| "pasteImage"
|
|
20
|
-
| "copyPrompt"
|
|
5
|
+
AppKeybinding,
|
|
6
|
+
| "app.interrupt"
|
|
7
|
+
| "app.clear"
|
|
8
|
+
| "app.exit"
|
|
9
|
+
| "app.suspend"
|
|
10
|
+
| "app.thinking.cycle"
|
|
11
|
+
| "app.model.cycleForward"
|
|
12
|
+
| "app.model.cycleBackward"
|
|
13
|
+
| "app.model.select"
|
|
14
|
+
| "app.tools.expand"
|
|
15
|
+
| "app.thinking.toggle"
|
|
16
|
+
| "app.editor.external"
|
|
17
|
+
| "app.history.search"
|
|
18
|
+
| "app.message.dequeue"
|
|
19
|
+
| "app.clipboard.pasteImage"
|
|
20
|
+
| "app.clipboard.copyPrompt"
|
|
21
21
|
>;
|
|
22
22
|
|
|
23
23
|
const DEFAULT_ACTION_KEYS: Record<ConfigurableEditorAction, KeyId[]> = {
|
|
24
|
-
interrupt: ["escape"],
|
|
25
|
-
clear: ["ctrl+c"],
|
|
26
|
-
exit: ["ctrl+d"],
|
|
27
|
-
suspend: ["ctrl+z"],
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
dequeue: ["alt+up"],
|
|
37
|
-
pasteImage: ["ctrl+v"],
|
|
38
|
-
copyPrompt: ["alt+shift+c"],
|
|
24
|
+
"app.interrupt": ["escape"],
|
|
25
|
+
"app.clear": ["ctrl+c"],
|
|
26
|
+
"app.exit": ["ctrl+d"],
|
|
27
|
+
"app.suspend": ["ctrl+z"],
|
|
28
|
+
"app.thinking.cycle": ["shift+tab"],
|
|
29
|
+
"app.model.cycleForward": ["ctrl+p"],
|
|
30
|
+
"app.model.cycleBackward": ["shift+ctrl+p"],
|
|
31
|
+
"app.model.select": ["ctrl+l"],
|
|
32
|
+
"app.tools.expand": ["ctrl+o"],
|
|
33
|
+
"app.thinking.toggle": ["ctrl+t"],
|
|
34
|
+
"app.editor.external": ["ctrl+g"],
|
|
35
|
+
"app.history.search": ["ctrl+r"],
|
|
36
|
+
"app.message.dequeue": ["alt+up"],
|
|
37
|
+
"app.clipboard.pasteImage": ["ctrl+v"],
|
|
38
|
+
"app.clipboard.copyPrompt": ["alt+shift+c"],
|
|
39
39
|
};
|
|
40
40
|
|
|
41
41
|
/**
|
|
@@ -115,13 +115,13 @@ export class CustomEditor extends Editor {
|
|
|
115
115
|
}
|
|
116
116
|
|
|
117
117
|
// Intercept configured image paste (async - fires and handles result)
|
|
118
|
-
if (this.#matchesAction(data, "pasteImage") && this.onPasteImage) {
|
|
118
|
+
if (this.#matchesAction(data, "app.clipboard.pasteImage") && this.onPasteImage) {
|
|
119
119
|
void this.onPasteImage();
|
|
120
120
|
return;
|
|
121
121
|
}
|
|
122
122
|
|
|
123
123
|
// Intercept configured external editor shortcut
|
|
124
|
-
if (this.#matchesAction(data, "
|
|
124
|
+
if (this.#matchesAction(data, "app.editor.external") && this.onExternalEditor) {
|
|
125
125
|
this.onExternalEditor();
|
|
126
126
|
return;
|
|
127
127
|
}
|
|
@@ -133,56 +133,56 @@ export class CustomEditor extends Editor {
|
|
|
133
133
|
}
|
|
134
134
|
|
|
135
135
|
// Intercept configured suspend shortcut
|
|
136
|
-
if (this.#matchesAction(data, "suspend") && this.onSuspend) {
|
|
136
|
+
if (this.#matchesAction(data, "app.suspend") && this.onSuspend) {
|
|
137
137
|
this.onSuspend();
|
|
138
138
|
return;
|
|
139
139
|
}
|
|
140
140
|
|
|
141
141
|
// Intercept configured thinking block visibility toggle
|
|
142
|
-
if (this.#matchesAction(data, "
|
|
142
|
+
if (this.#matchesAction(data, "app.thinking.toggle") && this.onToggleThinking) {
|
|
143
143
|
this.onToggleThinking();
|
|
144
144
|
return;
|
|
145
145
|
}
|
|
146
146
|
|
|
147
147
|
// Intercept configured model selector shortcut
|
|
148
|
-
if (this.#matchesAction(data, "
|
|
148
|
+
if (this.#matchesAction(data, "app.model.select") && this.onSelectModel) {
|
|
149
149
|
this.onSelectModel();
|
|
150
150
|
return;
|
|
151
151
|
}
|
|
152
152
|
|
|
153
153
|
// Intercept configured history search shortcut
|
|
154
|
-
if (this.#matchesAction(data, "
|
|
154
|
+
if (this.#matchesAction(data, "app.history.search") && this.onHistorySearch) {
|
|
155
155
|
this.onHistorySearch();
|
|
156
156
|
return;
|
|
157
157
|
}
|
|
158
158
|
|
|
159
159
|
// Intercept configured tool output expansion shortcut
|
|
160
|
-
if (this.#matchesAction(data, "
|
|
160
|
+
if (this.#matchesAction(data, "app.tools.expand") && this.onExpandTools) {
|
|
161
161
|
this.onExpandTools();
|
|
162
162
|
return;
|
|
163
163
|
}
|
|
164
164
|
|
|
165
165
|
// Intercept configured backward model cycling (check before forward cycling)
|
|
166
|
-
if (this.#matchesAction(data, "
|
|
166
|
+
if (this.#matchesAction(data, "app.model.cycleBackward") && this.onCycleModelBackward) {
|
|
167
167
|
this.onCycleModelBackward();
|
|
168
168
|
return;
|
|
169
169
|
}
|
|
170
170
|
|
|
171
171
|
// Intercept configured forward model cycling
|
|
172
|
-
if (this.#matchesAction(data, "
|
|
172
|
+
if (this.#matchesAction(data, "app.model.cycleForward") && this.onCycleModelForward) {
|
|
173
173
|
this.onCycleModelForward();
|
|
174
174
|
return;
|
|
175
175
|
}
|
|
176
176
|
|
|
177
177
|
// Intercept configured thinking level cycling
|
|
178
|
-
if (this.#matchesAction(data, "
|
|
178
|
+
if (this.#matchesAction(data, "app.thinking.cycle") && this.onCycleThinkingLevel) {
|
|
179
179
|
this.onCycleThinkingLevel();
|
|
180
180
|
return;
|
|
181
181
|
}
|
|
182
182
|
|
|
183
183
|
// Intercept configured interrupt shortcut.
|
|
184
184
|
// Default behavior keeps autocomplete dismissal, but parent can prioritize global interrupt handling.
|
|
185
|
-
if (this.#matchesAction(data, "interrupt") && this.onEscape) {
|
|
185
|
+
if (this.#matchesAction(data, "app.interrupt") && this.onEscape) {
|
|
186
186
|
if (!this.isShowingAutocomplete() || this.shouldBypassAutocompleteOnEscape?.()) {
|
|
187
187
|
this.onEscape();
|
|
188
188
|
return;
|
|
@@ -190,13 +190,13 @@ export class CustomEditor extends Editor {
|
|
|
190
190
|
}
|
|
191
191
|
|
|
192
192
|
// Intercept configured clear shortcut
|
|
193
|
-
if (this.#matchesAction(data, "clear") && this.onClear) {
|
|
193
|
+
if (this.#matchesAction(data, "app.clear") && this.onClear) {
|
|
194
194
|
this.onClear();
|
|
195
195
|
return;
|
|
196
196
|
}
|
|
197
197
|
|
|
198
198
|
// Intercept configured exit shortcut (only when editor is empty)
|
|
199
|
-
if (this.#matchesAction(data, "exit")) {
|
|
199
|
+
if (this.#matchesAction(data, "app.exit")) {
|
|
200
200
|
if (this.getText().length === 0 && this.onExit) {
|
|
201
201
|
this.onExit();
|
|
202
202
|
}
|
|
@@ -205,13 +205,13 @@ export class CustomEditor extends Editor {
|
|
|
205
205
|
}
|
|
206
206
|
|
|
207
207
|
// Intercept configured dequeue shortcut (restore queued message to editor)
|
|
208
|
-
if (this.#matchesAction(data, "dequeue") && this.onDequeue) {
|
|
208
|
+
if (this.#matchesAction(data, "app.message.dequeue") && this.onDequeue) {
|
|
209
209
|
this.onDequeue();
|
|
210
210
|
return;
|
|
211
211
|
}
|
|
212
212
|
|
|
213
213
|
// Intercept configured copy-prompt shortcut
|
|
214
|
-
if (this.#matchesAction(data, "copyPrompt") && this.onCopyPrompt) {
|
|
214
|
+
if (this.#matchesAction(data, "app.clipboard.copyPrompt") && this.onCopyPrompt) {
|
|
215
215
|
this.onCopyPrompt();
|
|
216
216
|
return;
|
|
217
217
|
}
|
|
@@ -24,6 +24,7 @@ import {
|
|
|
24
24
|
import { Settings } from "../../../config/settings";
|
|
25
25
|
import { DynamicBorder } from "../../../modes/components/dynamic-border";
|
|
26
26
|
import { theme } from "../../../modes/theme/theme";
|
|
27
|
+
import { matchesAppInterrupt } from "../../../modes/utils/keybinding-matchers";
|
|
27
28
|
import { ExtensionList } from "./extension-list";
|
|
28
29
|
import { InspectorPanel } from "./inspector-panel";
|
|
29
30
|
import { applyFilter, createInitialState, filterByProvider, refreshState, toggleProvider } from "./state-manager";
|
|
@@ -251,7 +252,7 @@ export class ExtensionDashboard extends Container {
|
|
|
251
252
|
}
|
|
252
253
|
|
|
253
254
|
// Escape - clear search first, then close
|
|
254
|
-
if (
|
|
255
|
+
if (matchesAppInterrupt(data)) {
|
|
255
256
|
if (this.#state.searchQuery.length > 0) {
|
|
256
257
|
this.#state.searchQuery = "";
|
|
257
258
|
this.#state.searchFiltered = this.#state.tabFiltered;
|
|
@@ -11,6 +11,7 @@ import {
|
|
|
11
11
|
visibleWidth,
|
|
12
12
|
} from "@oh-my-pi/pi-tui";
|
|
13
13
|
import { theme } from "../../modes/theme/theme";
|
|
14
|
+
import { matchesAppInterrupt } from "../../modes/utils/keybinding-matchers";
|
|
14
15
|
import type { HistoryEntry, HistoryStorage } from "../../session/history-storage";
|
|
15
16
|
import { DynamicBorder } from "./dynamic-border";
|
|
16
17
|
|
|
@@ -137,7 +138,7 @@ export class HistorySearchComponent extends Container {
|
|
|
137
138
|
return;
|
|
138
139
|
}
|
|
139
140
|
|
|
140
|
-
if (
|
|
141
|
+
if (matchesAppInterrupt(keyData)) {
|
|
141
142
|
this.#onCancel();
|
|
142
143
|
return;
|
|
143
144
|
}
|
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
*/
|
|
5
5
|
import { Container, Editor, matchesKey, Spacer, Text, type TUI } from "@oh-my-pi/pi-tui";
|
|
6
6
|
import { getEditorTheme, theme } from "../../modes/theme/theme";
|
|
7
|
+
import { matchesAppInterrupt } from "../../modes/utils/keybinding-matchers";
|
|
7
8
|
import { getEditorCommand, openInEditor } from "../../utils/external-editor";
|
|
8
9
|
import { DynamicBorder } from "./dynamic-border";
|
|
9
10
|
|
|
@@ -67,7 +68,7 @@ export class HookEditorComponent extends Container {
|
|
|
67
68
|
}
|
|
68
69
|
|
|
69
70
|
// Escape to cancel
|
|
70
|
-
if (
|
|
71
|
+
if (matchesAppInterrupt(keyData)) {
|
|
71
72
|
this.#onCancelCallback();
|
|
72
73
|
return;
|
|
73
74
|
}
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Simple text input component for hooks.
|
|
3
3
|
*/
|
|
4
|
-
import { Container, Input, matchesKey, Spacer, Text, type TUI } from "@oh-my-pi/pi-tui";
|
|
5
|
-
import { theme } from "../../modes/theme/theme";
|
|
4
|
+
import { Container, Input, Markdown, matchesKey, Spacer, Text, type TUI } from "@oh-my-pi/pi-tui";
|
|
5
|
+
import { getMarkdownTheme, theme } from "../../modes/theme/theme";
|
|
6
|
+
import { matchesAppInterrupt } from "../../modes/utils/keybinding-matchers";
|
|
6
7
|
import { CountdownTimer } from "./countdown-timer";
|
|
7
8
|
import { DynamicBorder } from "./dynamic-border";
|
|
8
9
|
|
|
@@ -16,7 +17,7 @@ export class HookInputComponent extends Container {
|
|
|
16
17
|
#input: Input;
|
|
17
18
|
#onSubmitCallback: (value: string) => void;
|
|
18
19
|
#onCancelCallback: () => void;
|
|
19
|
-
#
|
|
20
|
+
#titleComponent: Markdown;
|
|
20
21
|
#baseTitle: string;
|
|
21
22
|
#countdown: CountdownTimer | undefined;
|
|
22
23
|
|
|
@@ -36,15 +37,15 @@ export class HookInputComponent extends Container {
|
|
|
36
37
|
this.addChild(new DynamicBorder());
|
|
37
38
|
this.addChild(new Spacer(1));
|
|
38
39
|
|
|
39
|
-
this.#
|
|
40
|
-
this.addChild(this.#
|
|
40
|
+
this.#titleComponent = new Markdown(title, 1, 0, getMarkdownTheme(), { color: t => theme.fg("accent", t) });
|
|
41
|
+
this.addChild(this.#titleComponent);
|
|
41
42
|
this.addChild(new Spacer(1));
|
|
42
43
|
|
|
43
44
|
if (opts?.timeout && opts.timeout > 0 && opts.tui) {
|
|
44
45
|
this.#countdown = new CountdownTimer(
|
|
45
46
|
opts.timeout,
|
|
46
47
|
opts.tui,
|
|
47
|
-
s => this.#
|
|
48
|
+
s => this.#titleComponent.setText(`${this.#baseTitle} (${s}s)`),
|
|
48
49
|
() => {
|
|
49
50
|
opts.onTimeout?.();
|
|
50
51
|
this.#onCancelCallback();
|
|
@@ -65,7 +66,7 @@ export class HookInputComponent extends Container {
|
|
|
65
66
|
this.#countdown?.reset();
|
|
66
67
|
if (matchesKey(keyData, "enter") || matchesKey(keyData, "return") || keyData === "\n") {
|
|
67
68
|
this.#onSubmitCallback(this.#input.getValue());
|
|
68
|
-
} else if (
|
|
69
|
+
} else if (matchesAppInterrupt(keyData)) {
|
|
69
70
|
this.#onCancelCallback();
|
|
70
71
|
} else {
|
|
71
72
|
this.#input.handleInput(keyData);
|
|
@@ -4,8 +4,10 @@
|
|
|
4
4
|
*/
|
|
5
5
|
import {
|
|
6
6
|
Container,
|
|
7
|
+
Markdown,
|
|
7
8
|
matchesKey,
|
|
8
9
|
padding,
|
|
10
|
+
renderInlineMarkdown,
|
|
9
11
|
replaceTabs,
|
|
10
12
|
Spacer,
|
|
11
13
|
Text,
|
|
@@ -13,7 +15,8 @@ import {
|
|
|
13
15
|
truncateToWidth,
|
|
14
16
|
visibleWidth,
|
|
15
17
|
} from "@oh-my-pi/pi-tui";
|
|
16
|
-
import { theme } from "../../modes/theme/theme";
|
|
18
|
+
import { getMarkdownTheme, theme } from "../../modes/theme/theme";
|
|
19
|
+
import { matchesSelectCancel } from "../../modes/utils/keybinding-matchers";
|
|
17
20
|
import { CountdownTimer } from "./countdown-timer";
|
|
18
21
|
import { DynamicBorder } from "./dynamic-border";
|
|
19
22
|
|
|
@@ -59,7 +62,7 @@ export class HookSelectorComponent extends Container {
|
|
|
59
62
|
#outlinedList: OutlinedList | undefined;
|
|
60
63
|
#onSelectCallback: (option: string) => void;
|
|
61
64
|
#onCancelCallback: () => void;
|
|
62
|
-
#
|
|
65
|
+
#titleComponent: Markdown;
|
|
63
66
|
#baseTitle: string;
|
|
64
67
|
#countdown: CountdownTimer | undefined;
|
|
65
68
|
#onLeftCallback: (() => void) | undefined;
|
|
@@ -85,15 +88,15 @@ export class HookSelectorComponent extends Container {
|
|
|
85
88
|
this.addChild(new DynamicBorder());
|
|
86
89
|
this.addChild(new Spacer(1));
|
|
87
90
|
|
|
88
|
-
this.#
|
|
89
|
-
this.addChild(this.#
|
|
91
|
+
this.#titleComponent = new Markdown(title, 1, 0, getMarkdownTheme(), { color: t => theme.fg("accent", t) });
|
|
92
|
+
this.addChild(this.#titleComponent);
|
|
90
93
|
this.addChild(new Spacer(1));
|
|
91
94
|
|
|
92
95
|
if (opts?.timeout && opts.timeout > 0 && opts.tui) {
|
|
93
96
|
this.#countdown = new CountdownTimer(
|
|
94
97
|
opts.timeout,
|
|
95
98
|
opts.tui,
|
|
96
|
-
s => this.#
|
|
99
|
+
s => this.#titleComponent.setText(`${this.#baseTitle} (${s}s)`),
|
|
97
100
|
() => {
|
|
98
101
|
opts?.onTimeout?.();
|
|
99
102
|
// Auto-select current option on timeout (typically the first/recommended option)
|
|
@@ -131,12 +134,14 @@ export class HookSelectorComponent extends Container {
|
|
|
131
134
|
);
|
|
132
135
|
const endIndex = Math.min(startIndex + this.#maxVisible, this.#options.length);
|
|
133
136
|
|
|
137
|
+
const mdTheme = getMarkdownTheme();
|
|
134
138
|
for (let i = startIndex; i < endIndex; i++) {
|
|
135
139
|
const isSelected = i === this.#selectedIndex;
|
|
136
|
-
const
|
|
137
|
-
?
|
|
138
|
-
:
|
|
139
|
-
|
|
140
|
+
const label = isSelected
|
|
141
|
+
? renderInlineMarkdown(this.#options[i], mdTheme, t => theme.fg("accent", t))
|
|
142
|
+
: renderInlineMarkdown(this.#options[i], mdTheme, t => theme.fg("text", t));
|
|
143
|
+
const prefix = isSelected ? theme.fg("accent", `${theme.nav.cursor} `) : " ";
|
|
144
|
+
lines.push(prefix + label);
|
|
140
145
|
}
|
|
141
146
|
|
|
142
147
|
if (startIndex > 0 || endIndex < this.#options.length) {
|
|
@@ -169,7 +174,7 @@ export class HookSelectorComponent extends Container {
|
|
|
169
174
|
this.#onLeftCallback?.();
|
|
170
175
|
} else if (matchesKey(keyData, "right")) {
|
|
171
176
|
this.#onRightCallback?.();
|
|
172
|
-
} else if (
|
|
177
|
+
} else if (matchesSelectCancel(keyData)) {
|
|
173
178
|
this.#onCancelCallback();
|
|
174
179
|
}
|
|
175
180
|
}
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Utilities for formatting keybinding hints in the UI.
|
|
3
3
|
*/
|
|
4
|
-
import { type
|
|
5
|
-
import type {
|
|
4
|
+
import { getKeybindings, type Keybinding, type KeyId } from "@oh-my-pi/pi-tui";
|
|
5
|
+
import type { AppKeybinding, KeybindingsManager } from "../../config/keybindings";
|
|
6
6
|
import { theme } from "../../modes/theme/theme";
|
|
7
7
|
|
|
8
8
|
/**
|
|
@@ -17,14 +17,14 @@ function formatKeys(keys: KeyId[]): string {
|
|
|
17
17
|
/**
|
|
18
18
|
* Get display string for an editor action.
|
|
19
19
|
*/
|
|
20
|
-
export function editorKey(action:
|
|
21
|
-
return formatKeys(
|
|
20
|
+
export function editorKey(action: Keybinding): string {
|
|
21
|
+
return formatKeys(getKeybindings().getKeys(action));
|
|
22
22
|
}
|
|
23
23
|
|
|
24
24
|
/**
|
|
25
25
|
* Get display string for an app action.
|
|
26
26
|
*/
|
|
27
|
-
export function appKey(keybindings: KeybindingsManager, action:
|
|
27
|
+
export function appKey(keybindings: KeybindingsManager, action: AppKeybinding): string {
|
|
28
28
|
return formatKeys(keybindings.getKeys(action));
|
|
29
29
|
}
|
|
30
30
|
|
|
@@ -32,11 +32,11 @@ export function appKey(keybindings: KeybindingsManager, action: AppAction): stri
|
|
|
32
32
|
* Format a keybinding hint with consistent styling: dim key, muted description.
|
|
33
33
|
* Looks up the key from editor keybindings automatically.
|
|
34
34
|
*
|
|
35
|
-
* @param action -
|
|
35
|
+
* @param action - Keybinding action name (e.g., "tui.select.confirm", "app.tools.expand")
|
|
36
36
|
* @param description - Description text (e.g., "to expand", "cancel")
|
|
37
37
|
* @returns Formatted string with dim key and muted description
|
|
38
38
|
*/
|
|
39
|
-
export function keyHint(action:
|
|
39
|
+
export function keyHint(action: Keybinding, description: string): string {
|
|
40
40
|
return theme.fg("dim", editorKey(action)) + theme.fg("muted", ` ${description}`);
|
|
41
41
|
}
|
|
42
42
|
|
|
@@ -45,11 +45,11 @@ export function keyHint(action: EditorAction, description: string): string {
|
|
|
45
45
|
* Requires the KeybindingsManager instance.
|
|
46
46
|
*
|
|
47
47
|
* @param keybindings - KeybindingsManager instance
|
|
48
|
-
* @param action - App
|
|
48
|
+
* @param action - App keybinding name (e.g., "app.interrupt", "app.editor.external")
|
|
49
49
|
* @param description - Description text
|
|
50
50
|
* @returns Formatted string with dim key and muted description
|
|
51
51
|
*/
|
|
52
|
-
export function appKeyHint(keybindings: KeybindingsManager, action:
|
|
52
|
+
export function appKeyHint(keybindings: KeybindingsManager, action: AppKeybinding, description: string): string {
|
|
53
53
|
return theme.fg("dim", appKey(keybindings, action)) + theme.fg("muted", ` ${description}`);
|
|
54
54
|
}
|
|
55
55
|
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { getOAuthProviders } from "@oh-my-pi/pi-ai";
|
|
2
|
-
import { Container,
|
|
2
|
+
import { Container, getKeybindings, Input, Spacer, Text, type TUI } from "@oh-my-pi/pi-tui";
|
|
3
3
|
import { theme } from "../../modes/theme/theme";
|
|
4
4
|
import { openPath } from "../../utils/open";
|
|
5
5
|
import { DynamicBorder } from "./dynamic-border";
|
|
@@ -151,9 +151,9 @@ export class LoginDialogComponent extends Container {
|
|
|
151
151
|
}
|
|
152
152
|
|
|
153
153
|
handleInput(data: string): void {
|
|
154
|
-
const kb =
|
|
154
|
+
const kb = getKeybindings();
|
|
155
155
|
|
|
156
|
-
if (kb.matches(data, "
|
|
156
|
+
if (kb.matches(data, "tui.select.cancel")) {
|
|
157
157
|
this.#cancel();
|
|
158
158
|
return;
|
|
159
159
|
}
|
|
@@ -19,6 +19,7 @@ import { analyzeAuthError, discoverOAuthEndpoints } from "../../mcp/oauth-discov
|
|
|
19
19
|
import type { MCPHttpServerConfig, MCPServerConfig, MCPSseServerConfig, MCPStdioServerConfig } from "../../mcp/types";
|
|
20
20
|
import { shortenPath } from "../../tools/render-utils";
|
|
21
21
|
import { theme } from "../theme/theme";
|
|
22
|
+
import { matchesAppInterrupt } from "../utils/keybinding-matchers";
|
|
22
23
|
import { DynamicBorder } from "./dynamic-border";
|
|
23
24
|
|
|
24
25
|
type TransportType = "stdio" | "http" | "sse";
|
|
@@ -452,7 +453,7 @@ export class MCPAddWizard extends Container {
|
|
|
452
453
|
}
|
|
453
454
|
|
|
454
455
|
// Handle Escape (always handled by wizard)
|
|
455
|
-
if (
|
|
456
|
+
if (matchesAppInterrupt(keyData)) {
|
|
456
457
|
if (this.#currentStep === "name") {
|
|
457
458
|
// Cancel wizard
|
|
458
459
|
this.#onCancelCallback();
|
|
@@ -1,6 +1,17 @@
|
|
|
1
1
|
import { ThinkingLevel } from "@oh-my-pi/pi-agent-core";
|
|
2
2
|
import { getSupportedEfforts, type Model, modelsAreEqual } from "@oh-my-pi/pi-ai";
|
|
3
|
-
import {
|
|
3
|
+
import {
|
|
4
|
+
Container,
|
|
5
|
+
getKeybindings,
|
|
6
|
+
Input,
|
|
7
|
+
matchesKey,
|
|
8
|
+
Spacer,
|
|
9
|
+
type Tab,
|
|
10
|
+
TabBar,
|
|
11
|
+
Text,
|
|
12
|
+
type TUI,
|
|
13
|
+
visibleWidth,
|
|
14
|
+
} from "@oh-my-pi/pi-tui";
|
|
4
15
|
import { MODEL_ROLE_IDS, MODEL_ROLES, type ModelRegistry, type ModelRole } from "../../config/model-registry";
|
|
5
16
|
import { resolveModelRoleValue } from "../../config/model-resolver";
|
|
6
17
|
import type { Settings } from "../../config/settings";
|
|
@@ -647,7 +658,7 @@ export class ModelSelectorComponent extends Container {
|
|
|
647
658
|
}
|
|
648
659
|
|
|
649
660
|
// Escape or Ctrl+C - close selector
|
|
650
|
-
if (
|
|
661
|
+
if (getKeybindings().matches(keyData, "tui.select.cancel")) {
|
|
651
662
|
this.#onCancelCallback();
|
|
652
663
|
return;
|
|
653
664
|
}
|
|
@@ -698,7 +709,7 @@ export class ModelSelectorComponent extends Container {
|
|
|
698
709
|
return;
|
|
699
710
|
}
|
|
700
711
|
|
|
701
|
-
if (
|
|
712
|
+
if (getKeybindings().matches(keyData, "tui.select.cancel")) {
|
|
702
713
|
if (this.#menuStep === "thinking" && this.#menuSelectedRole !== null) {
|
|
703
714
|
this.#menuStep = "role";
|
|
704
715
|
const roleIndex = MENU_ROLE_ACTIONS.findIndex(action => action.role === this.#menuSelectedRole);
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { getOAuthProviders, type OAuthProviderInfo } from "@oh-my-pi/pi-ai";
|
|
2
2
|
import { Container, matchesKey, Spacer, TruncatedText } from "@oh-my-pi/pi-tui";
|
|
3
3
|
import { theme } from "../../modes/theme/theme";
|
|
4
|
+
import { matchesSelectCancel } from "../../modes/utils/keybinding-matchers";
|
|
4
5
|
import type { AuthStorage } from "../../session/auth-storage";
|
|
5
6
|
import { DynamicBorder } from "./dynamic-border";
|
|
6
7
|
/**
|
|
@@ -202,7 +203,7 @@ export class OAuthSelectorComponent extends Container {
|
|
|
202
203
|
}
|
|
203
204
|
}
|
|
204
205
|
// Escape or Ctrl+C
|
|
205
|
-
else if (
|
|
206
|
+
else if (matchesSelectCancel(keyData)) {
|
|
206
207
|
this.stopValidation();
|
|
207
208
|
this.#onCancelCallback();
|
|
208
209
|
}
|
|
@@ -72,9 +72,8 @@ export class PythonExecutionComponent extends Container {
|
|
|
72
72
|
}
|
|
73
73
|
|
|
74
74
|
appendOutput(chunk: string): void {
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
const newLines = clean.split("\n").map(line => this.#clampDisplayLine(line));
|
|
75
|
+
// Chunk is pre-sanitized by OutputSink.push() — no need to sanitize again.
|
|
76
|
+
const newLines = chunk.split("\n").map(line => this.#clampDisplayLine(line));
|
|
78
77
|
if (this.#outputLines.length > 0 && newLines.length > 0) {
|
|
79
78
|
this.#outputLines[this.#outputLines.length - 1] = this.#clampDisplayLine(
|
|
80
79
|
`${this.#outputLines[this.#outputLines.length - 1]}${newLines[0]}`,
|
|
@@ -11,6 +11,7 @@ import {
|
|
|
11
11
|
visibleWidth,
|
|
12
12
|
} from "@oh-my-pi/pi-tui";
|
|
13
13
|
import { theme } from "../../modes/theme/theme";
|
|
14
|
+
import { matchesAppInterrupt } from "../../modes/utils/keybinding-matchers";
|
|
14
15
|
import type { SessionInfo } from "../../session/session-manager";
|
|
15
16
|
import { fuzzyFilter } from "../../utils/fuzzy";
|
|
16
17
|
import { DynamicBorder } from "./dynamic-border";
|
|
@@ -219,7 +220,7 @@ class SessionList implements Component {
|
|
|
219
220
|
return;
|
|
220
221
|
}
|
|
221
222
|
// Escape - cancel
|
|
222
|
-
if (
|
|
223
|
+
if (matchesAppInterrupt(keyData)) {
|
|
223
224
|
if (this.onCancel) {
|
|
224
225
|
this.onCancel();
|
|
225
226
|
}
|
|
@@ -21,6 +21,7 @@ import type {
|
|
|
21
21
|
} from "../../config/settings-schema";
|
|
22
22
|
import { SETTING_TABS, TAB_METADATA } from "../../config/settings-schema";
|
|
23
23
|
import { getCurrentThemeName, getSelectListTheme, getSettingsListTheme, theme } from "../../modes/theme/theme";
|
|
24
|
+
import { matchesAppInterrupt } from "../../modes/utils/keybinding-matchers";
|
|
24
25
|
import { getTabBarTheme } from "../shared";
|
|
25
26
|
import { DynamicBorder } from "./dynamic-border";
|
|
26
27
|
import { PluginSettingsComponent } from "./plugin-settings";
|
|
@@ -521,7 +522,7 @@ export class SettingsSelectorComponent extends Container {
|
|
|
521
522
|
}
|
|
522
523
|
|
|
523
524
|
// Escape at top level cancels
|
|
524
|
-
if ((
|
|
525
|
+
if (matchesAppInterrupt(data) && !this.#currentSubmenu) {
|
|
525
526
|
this.callbacks.onCancel();
|
|
526
527
|
return;
|
|
527
528
|
}
|
|
@@ -11,6 +11,7 @@
|
|
|
11
11
|
import { Container, matchesKey, padding } from "@oh-my-pi/pi-tui";
|
|
12
12
|
import type { StatusLineSegmentId } from "../../config/settings-schema";
|
|
13
13
|
import { theme } from "../../modes/theme/theme";
|
|
14
|
+
import { matchesAppInterrupt } from "../../modes/utils/keybinding-matchers";
|
|
14
15
|
import { ALL_SEGMENT_IDS } from "./status-line/segments";
|
|
15
16
|
|
|
16
17
|
// Segment display names and short descriptions
|
|
@@ -239,7 +240,7 @@ export class StatusLineSegmentEditorComponent extends Container {
|
|
|
239
240
|
const left = this.#getSegmentsForColumn("left").map(s => s.id);
|
|
240
241
|
const right = this.#getSegmentsForColumn("right").map(s => s.id);
|
|
241
242
|
this.callbacks.onSave(left, right);
|
|
242
|
-
} else if (
|
|
243
|
+
} else if (matchesAppInterrupt(data)) {
|
|
243
244
|
this.callbacks.onCancel();
|
|
244
245
|
}
|
|
245
246
|
}
|
|
@@ -105,17 +105,16 @@ export class ToolExecutionComponent extends Container {
|
|
|
105
105
|
// Cached converted images for Kitty protocol (which requires PNG), keyed by index
|
|
106
106
|
#convertedImages: Map<number, { data: string; mimeType: string }> = new Map();
|
|
107
107
|
// Spinner animation for partial task results
|
|
108
|
-
#spinnerFrame
|
|
108
|
+
#spinnerFrame?: number;
|
|
109
109
|
#spinnerInterval?: NodeJS.Timeout;
|
|
110
110
|
// Track if args are still being streamed (for edit/write spinner)
|
|
111
111
|
#argsComplete = false;
|
|
112
112
|
#renderState: {
|
|
113
|
-
spinnerFrame
|
|
113
|
+
spinnerFrame?: number;
|
|
114
114
|
expanded: boolean;
|
|
115
115
|
isPartial: boolean;
|
|
116
116
|
renderContext?: Record<string, unknown>;
|
|
117
117
|
} = {
|
|
118
|
-
spinnerFrame: 0,
|
|
119
118
|
expanded: false,
|
|
120
119
|
isPartial: true,
|
|
121
120
|
};
|
|
@@ -328,10 +327,9 @@ export class ToolExecutionComponent extends Container {
|
|
|
328
327
|
this.#spinnerInterval = setInterval(() => {
|
|
329
328
|
const frameCount = theme.spinnerFrames.length;
|
|
330
329
|
if (frameCount === 0) return;
|
|
331
|
-
this.#spinnerFrame = (this.#spinnerFrame + 1) % frameCount;
|
|
330
|
+
this.#spinnerFrame = ((this.#spinnerFrame ?? -1) + 1) % frameCount;
|
|
332
331
|
this.#renderState.spinnerFrame = this.#spinnerFrame;
|
|
333
332
|
this.#ui.requestRender();
|
|
334
|
-
// NO updateDisplay() — existing component closures read from renderState
|
|
335
333
|
}, 80);
|
|
336
334
|
} else if (!needsSpinner && this.#spinnerInterval) {
|
|
337
335
|
clearInterval(this.#spinnerInterval);
|
|
@@ -346,6 +344,7 @@ export class ToolExecutionComponent extends Container {
|
|
|
346
344
|
if (this.#spinnerInterval) {
|
|
347
345
|
clearInterval(this.#spinnerInterval);
|
|
348
346
|
this.#spinnerInterval = undefined;
|
|
347
|
+
this.#spinnerFrame = undefined;
|
|
349
348
|
}
|
|
350
349
|
}
|
|
351
350
|
|
|
@@ -12,6 +12,7 @@ import {
|
|
|
12
12
|
} from "@oh-my-pi/pi-tui";
|
|
13
13
|
import type { TreeFilterMode } from "../../config/settings-schema";
|
|
14
14
|
import { theme } from "../../modes/theme/theme";
|
|
15
|
+
import { matchesAppInterrupt } from "../../modes/utils/keybinding-matchers";
|
|
15
16
|
import type { SessionTreeNode } from "../../session/session-manager";
|
|
16
17
|
import { shortenPath } from "../../tools/render-utils";
|
|
17
18
|
import { DynamicBorder } from "./dynamic-border";
|
|
@@ -702,7 +703,7 @@ class TreeList implements Component {
|
|
|
702
703
|
if (selected && this.onSelect) {
|
|
703
704
|
this.onSelect(selected.node.entry.id);
|
|
704
705
|
}
|
|
705
|
-
} else if (
|
|
706
|
+
} else if (matchesAppInterrupt(keyData)) {
|
|
706
707
|
if (this.#searchQuery) {
|
|
707
708
|
this.#searchQuery = "";
|
|
708
709
|
this.#applyFilter();
|
|
@@ -807,7 +808,7 @@ class LabelInput implements Component {
|
|
|
807
808
|
if (matchesKey(keyData, "enter") || matchesKey(keyData, "return") || keyData === "\n") {
|
|
808
809
|
const value = this.#input.getValue().trim();
|
|
809
810
|
this.onSubmit?.(this.entryId, value || undefined);
|
|
810
|
-
} else if (
|
|
811
|
+
} else if (matchesAppInterrupt(keyData)) {
|
|
811
812
|
this.onCancel?.();
|
|
812
813
|
} else {
|
|
813
814
|
this.#input.handleInput(keyData);
|