@oh-my-pi/pi-coding-agent 13.14.2 → 13.15.3
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 +150 -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 +423 -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/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/custom-editor.ts +53 -51
- 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/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/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/extension-ui-controller.ts +89 -4
- package/src/modes/controllers/input-controller.ts +48 -29
- 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 +20 -20
- 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 +436 -86
- package/src/session/messages.ts +23 -0
- package/src/session/session-manager.ts +97 -31
- package/src/tools/ask.ts +56 -30
- 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/resolve.ts +1 -1
- package/src/utils/child-process.ts +88 -0
- package/src/utils/image-input.ts +11 -1
- package/src/web/search/providers/codex.ts +10 -3
|
@@ -1,41 +1,43 @@
|
|
|
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
|
-
| "
|
|
19
|
-
| "
|
|
20
|
-
| "
|
|
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.model.selectTemporary"
|
|
15
|
+
| "app.tools.expand"
|
|
16
|
+
| "app.thinking.toggle"
|
|
17
|
+
| "app.editor.external"
|
|
18
|
+
| "app.history.search"
|
|
19
|
+
| "app.message.dequeue"
|
|
20
|
+
| "app.clipboard.pasteImage"
|
|
21
|
+
| "app.clipboard.copyPrompt"
|
|
21
22
|
>;
|
|
22
23
|
|
|
23
24
|
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
|
-
|
|
37
|
-
|
|
38
|
-
|
|
25
|
+
"app.interrupt": ["escape"],
|
|
26
|
+
"app.clear": ["ctrl+c"],
|
|
27
|
+
"app.exit": ["ctrl+d"],
|
|
28
|
+
"app.suspend": ["ctrl+z"],
|
|
29
|
+
"app.thinking.cycle": ["shift+tab"],
|
|
30
|
+
"app.model.cycleForward": ["ctrl+p"],
|
|
31
|
+
"app.model.cycleBackward": ["shift+ctrl+p"],
|
|
32
|
+
"app.model.select": ["ctrl+l"],
|
|
33
|
+
"app.model.selectTemporary": ["alt+p"],
|
|
34
|
+
"app.tools.expand": ["ctrl+o"],
|
|
35
|
+
"app.thinking.toggle": ["ctrl+t"],
|
|
36
|
+
"app.editor.external": ["ctrl+g"],
|
|
37
|
+
"app.history.search": ["ctrl+r"],
|
|
38
|
+
"app.message.dequeue": ["alt+up"],
|
|
39
|
+
"app.clipboard.pasteImage": ["ctrl+v"],
|
|
40
|
+
"app.clipboard.copyPrompt": ["alt+shift+c"],
|
|
39
41
|
};
|
|
40
42
|
|
|
41
43
|
/**
|
|
@@ -56,7 +58,7 @@ export class CustomEditor extends Editor {
|
|
|
56
58
|
onHistorySearch?: () => void;
|
|
57
59
|
onSuspend?: () => void;
|
|
58
60
|
onShowHotkeys?: () => void;
|
|
59
|
-
|
|
61
|
+
onSelectModelTemporary?: () => void;
|
|
60
62
|
/** Called when the configured copy-prompt shortcut is pressed. */
|
|
61
63
|
onCopyPrompt?: () => void;
|
|
62
64
|
/** Called when the configured image-paste shortcut is pressed. */
|
|
@@ -115,74 +117,74 @@ export class CustomEditor extends Editor {
|
|
|
115
117
|
}
|
|
116
118
|
|
|
117
119
|
// Intercept configured image paste (async - fires and handles result)
|
|
118
|
-
if (this.#matchesAction(data, "pasteImage") && this.onPasteImage) {
|
|
120
|
+
if (this.#matchesAction(data, "app.clipboard.pasteImage") && this.onPasteImage) {
|
|
119
121
|
void this.onPasteImage();
|
|
120
122
|
return;
|
|
121
123
|
}
|
|
122
124
|
|
|
123
125
|
// Intercept configured external editor shortcut
|
|
124
|
-
if (this.#matchesAction(data, "
|
|
126
|
+
if (this.#matchesAction(data, "app.editor.external") && this.onExternalEditor) {
|
|
125
127
|
this.onExternalEditor();
|
|
126
128
|
return;
|
|
127
129
|
}
|
|
128
130
|
|
|
129
|
-
// Intercept
|
|
130
|
-
if (
|
|
131
|
-
this.
|
|
131
|
+
// Intercept configured temporary model selector shortcut
|
|
132
|
+
if (this.#matchesAction(data, "app.model.selectTemporary") && this.onSelectModelTemporary) {
|
|
133
|
+
this.onSelectModelTemporary();
|
|
132
134
|
return;
|
|
133
135
|
}
|
|
134
136
|
|
|
135
137
|
// Intercept configured suspend shortcut
|
|
136
|
-
if (this.#matchesAction(data, "suspend") && this.onSuspend) {
|
|
138
|
+
if (this.#matchesAction(data, "app.suspend") && this.onSuspend) {
|
|
137
139
|
this.onSuspend();
|
|
138
140
|
return;
|
|
139
141
|
}
|
|
140
142
|
|
|
141
143
|
// Intercept configured thinking block visibility toggle
|
|
142
|
-
if (this.#matchesAction(data, "
|
|
144
|
+
if (this.#matchesAction(data, "app.thinking.toggle") && this.onToggleThinking) {
|
|
143
145
|
this.onToggleThinking();
|
|
144
146
|
return;
|
|
145
147
|
}
|
|
146
148
|
|
|
147
149
|
// Intercept configured model selector shortcut
|
|
148
|
-
if (this.#matchesAction(data, "
|
|
150
|
+
if (this.#matchesAction(data, "app.model.select") && this.onSelectModel) {
|
|
149
151
|
this.onSelectModel();
|
|
150
152
|
return;
|
|
151
153
|
}
|
|
152
154
|
|
|
153
155
|
// Intercept configured history search shortcut
|
|
154
|
-
if (this.#matchesAction(data, "
|
|
156
|
+
if (this.#matchesAction(data, "app.history.search") && this.onHistorySearch) {
|
|
155
157
|
this.onHistorySearch();
|
|
156
158
|
return;
|
|
157
159
|
}
|
|
158
160
|
|
|
159
161
|
// Intercept configured tool output expansion shortcut
|
|
160
|
-
if (this.#matchesAction(data, "
|
|
162
|
+
if (this.#matchesAction(data, "app.tools.expand") && this.onExpandTools) {
|
|
161
163
|
this.onExpandTools();
|
|
162
164
|
return;
|
|
163
165
|
}
|
|
164
166
|
|
|
165
167
|
// Intercept configured backward model cycling (check before forward cycling)
|
|
166
|
-
if (this.#matchesAction(data, "
|
|
168
|
+
if (this.#matchesAction(data, "app.model.cycleBackward") && this.onCycleModelBackward) {
|
|
167
169
|
this.onCycleModelBackward();
|
|
168
170
|
return;
|
|
169
171
|
}
|
|
170
172
|
|
|
171
173
|
// Intercept configured forward model cycling
|
|
172
|
-
if (this.#matchesAction(data, "
|
|
174
|
+
if (this.#matchesAction(data, "app.model.cycleForward") && this.onCycleModelForward) {
|
|
173
175
|
this.onCycleModelForward();
|
|
174
176
|
return;
|
|
175
177
|
}
|
|
176
178
|
|
|
177
179
|
// Intercept configured thinking level cycling
|
|
178
|
-
if (this.#matchesAction(data, "
|
|
180
|
+
if (this.#matchesAction(data, "app.thinking.cycle") && this.onCycleThinkingLevel) {
|
|
179
181
|
this.onCycleThinkingLevel();
|
|
180
182
|
return;
|
|
181
183
|
}
|
|
182
184
|
|
|
183
185
|
// Intercept configured interrupt shortcut.
|
|
184
186
|
// Default behavior keeps autocomplete dismissal, but parent can prioritize global interrupt handling.
|
|
185
|
-
if (this.#matchesAction(data, "interrupt") && this.onEscape) {
|
|
187
|
+
if (this.#matchesAction(data, "app.interrupt") && this.onEscape) {
|
|
186
188
|
if (!this.isShowingAutocomplete() || this.shouldBypassAutocompleteOnEscape?.()) {
|
|
187
189
|
this.onEscape();
|
|
188
190
|
return;
|
|
@@ -190,13 +192,13 @@ export class CustomEditor extends Editor {
|
|
|
190
192
|
}
|
|
191
193
|
|
|
192
194
|
// Intercept configured clear shortcut
|
|
193
|
-
if (this.#matchesAction(data, "clear") && this.onClear) {
|
|
195
|
+
if (this.#matchesAction(data, "app.clear") && this.onClear) {
|
|
194
196
|
this.onClear();
|
|
195
197
|
return;
|
|
196
198
|
}
|
|
197
199
|
|
|
198
200
|
// Intercept configured exit shortcut (only when editor is empty)
|
|
199
|
-
if (this.#matchesAction(data, "exit")) {
|
|
201
|
+
if (this.#matchesAction(data, "app.exit")) {
|
|
200
202
|
if (this.getText().length === 0 && this.onExit) {
|
|
201
203
|
this.onExit();
|
|
202
204
|
}
|
|
@@ -205,13 +207,13 @@ export class CustomEditor extends Editor {
|
|
|
205
207
|
}
|
|
206
208
|
|
|
207
209
|
// Intercept configured dequeue shortcut (restore queued message to editor)
|
|
208
|
-
if (this.#matchesAction(data, "dequeue") && this.onDequeue) {
|
|
210
|
+
if (this.#matchesAction(data, "app.message.dequeue") && this.onDequeue) {
|
|
209
211
|
this.onDequeue();
|
|
210
212
|
return;
|
|
211
213
|
}
|
|
212
214
|
|
|
213
215
|
// Intercept configured copy-prompt shortcut
|
|
214
|
-
if (this.#matchesAction(data, "copyPrompt") && this.onCopyPrompt) {
|
|
216
|
+
if (this.#matchesAction(data, "app.clipboard.copyPrompt") && this.onCopyPrompt) {
|
|
215
217
|
this.onCopyPrompt();
|
|
216
218
|
return;
|
|
217
219
|
}
|
|
@@ -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
|
}
|
|
@@ -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
|
}
|
|
@@ -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);
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { type Component, Container, matchesKey, Spacer, Text, truncateToWidth } from "@oh-my-pi/pi-tui";
|
|
2
2
|
import { theme } from "../../modes/theme/theme";
|
|
3
|
+
import { matchesSelectCancel } from "../../modes/utils/keybinding-matchers";
|
|
3
4
|
import { DynamicBorder } from "./dynamic-border";
|
|
4
5
|
|
|
5
6
|
interface UserMessageItem {
|
|
@@ -91,14 +92,8 @@ class UserMessageList implements Component {
|
|
|
91
92
|
this.onSelect(selected.id);
|
|
92
93
|
}
|
|
93
94
|
}
|
|
94
|
-
// Escape
|
|
95
|
-
else if (
|
|
96
|
-
if (this.onCancel) {
|
|
97
|
-
this.onCancel();
|
|
98
|
-
}
|
|
99
|
-
}
|
|
100
|
-
// Ctrl+C - cancel
|
|
101
|
-
else if (matchesKey(keyData, "ctrl+c")) {
|
|
95
|
+
// Escape / cancel
|
|
96
|
+
else if (matchesSelectCancel(keyData)) {
|
|
102
97
|
if (this.onCancel) {
|
|
103
98
|
this.onCancel();
|
|
104
99
|
}
|