@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,6 +1,11 @@
|
|
|
1
1
|
import { Container, Markdown, Spacer } from "@oh-my-pi/pi-tui";
|
|
2
2
|
import { getMarkdownTheme, theme } from "../../modes/theme/theme";
|
|
3
3
|
|
|
4
|
+
// OSC 133 shell integration: marks prompt zones for terminal multiplexers
|
|
5
|
+
const OSC133_ZONE_START = "\x1b]133;A\x07";
|
|
6
|
+
const OSC133_ZONE_END = "\x1b]133;B\x07";
|
|
7
|
+
const OSC133_ZONE_FINAL = "\x1b]133;C\x07";
|
|
8
|
+
|
|
4
9
|
/**
|
|
5
10
|
* Component that renders a user message
|
|
6
11
|
*/
|
|
@@ -19,4 +24,15 @@ export class UserMessageComponent extends Container {
|
|
|
19
24
|
}),
|
|
20
25
|
);
|
|
21
26
|
}
|
|
27
|
+
|
|
28
|
+
override render(width: number): string[] {
|
|
29
|
+
const lines = super.render(width);
|
|
30
|
+
if (lines.length === 0) {
|
|
31
|
+
return lines;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
lines[0] = OSC133_ZONE_START + lines[0];
|
|
35
|
+
lines[lines.length - 1] = lines[lines.length - 1] + OSC133_ZONE_END + OSC133_ZONE_FINAL;
|
|
36
|
+
return lines;
|
|
37
|
+
}
|
|
22
38
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { Component, OverlayHandle, TUI } from "@oh-my-pi/pi-tui";
|
|
2
|
-
import { Spacer, Text } from "@oh-my-pi/pi-tui";
|
|
2
|
+
import { Container, Spacer, Text } from "@oh-my-pi/pi-tui";
|
|
3
3
|
import { logger } from "@oh-my-pi/pi-utils";
|
|
4
4
|
import { KeybindingsManager } from "../../config/keybindings";
|
|
5
5
|
import type {
|
|
@@ -9,6 +9,9 @@ import type {
|
|
|
9
9
|
ExtensionError,
|
|
10
10
|
ExtensionUIContext,
|
|
11
11
|
ExtensionUIDialogOptions,
|
|
12
|
+
ExtensionUiComponent,
|
|
13
|
+
ExtensionWidgetContent,
|
|
14
|
+
ExtensionWidgetOptions,
|
|
12
15
|
TerminalInputHandler,
|
|
13
16
|
} from "../../extensibility/extensions";
|
|
14
17
|
import { HookEditorComponent } from "../../modes/components/hook-editor";
|
|
@@ -18,8 +21,12 @@ import { getAvailableThemesWithPaths, getThemeByName, setTheme, type Theme, them
|
|
|
18
21
|
import type { InteractiveModeContext } from "../../modes/types";
|
|
19
22
|
import { setSessionTerminalTitle, setTerminalTitle } from "../../utils/title-generator";
|
|
20
23
|
|
|
24
|
+
const MAX_WIDGET_LINES = 10;
|
|
25
|
+
|
|
21
26
|
export class ExtensionUiController {
|
|
22
27
|
#extensionTerminalInputUnsubscribers = new Set<() => void>();
|
|
28
|
+
#hookWidgetsAbove = new Map<string, ExtensionUiComponent>();
|
|
29
|
+
#hookWidgetsBelow = new Map<string, ExtensionUiComponent>();
|
|
23
30
|
constructor(private ctx: InteractiveModeContext) {}
|
|
24
31
|
|
|
25
32
|
/**
|
|
@@ -35,7 +42,7 @@ export class ExtensionUiController {
|
|
|
35
42
|
onTerminalInput: handler => this.addExtensionTerminalInputListener(handler),
|
|
36
43
|
setStatus: (key, text) => this.setHookStatus(key, text),
|
|
37
44
|
setWorkingMessage: message => this.ctx.setWorkingMessage(message),
|
|
38
|
-
setWidget: (key, content) => this.setHookWidget(key, content),
|
|
45
|
+
setWidget: (key, content, options) => this.setHookWidget(key, content, options),
|
|
39
46
|
setTitle: title => setTerminalTitle(title),
|
|
40
47
|
custom: (factory, options) => this.showHookCustom(factory, options),
|
|
41
48
|
setEditorText: text => this.ctx.editor.setText(text),
|
|
@@ -150,6 +157,7 @@ export class ExtensionUiController {
|
|
|
150
157
|
|
|
151
158
|
// Create new session
|
|
152
159
|
this.clearExtensionTerminalInputListeners();
|
|
160
|
+
this.clearHookWidgets();
|
|
153
161
|
const success = await this.ctx.session.newSession({ parentSession: options?.parentSession });
|
|
154
162
|
if (!success) {
|
|
155
163
|
return { cancelled: true };
|
|
@@ -227,6 +235,7 @@ export class ExtensionUiController {
|
|
|
227
235
|
await this.ctx.executeCompaction(instructionsOrOptions, false);
|
|
228
236
|
},
|
|
229
237
|
switchSession: async sessionPath => {
|
|
238
|
+
this.clearHookWidgets();
|
|
230
239
|
const result = await this.ctx.session.switchSession(sessionPath);
|
|
231
240
|
if (!result) {
|
|
232
241
|
return { cancelled: true };
|
|
@@ -252,11 +261,73 @@ export class ExtensionUiController {
|
|
|
252
261
|
});
|
|
253
262
|
}
|
|
254
263
|
|
|
255
|
-
setHookWidget(key: string, content:
|
|
256
|
-
|
|
264
|
+
setHookWidget(key: string, content: ExtensionWidgetContent, options?: ExtensionWidgetOptions): void {
|
|
265
|
+
const placement = options?.placement ?? "aboveEditor";
|
|
266
|
+
this.#removeHookWidget(this.#hookWidgetsAbove, key);
|
|
267
|
+
this.#removeHookWidget(this.#hookWidgetsBelow, key);
|
|
268
|
+
|
|
269
|
+
if (content === undefined) {
|
|
270
|
+
this.#rebuildHookWidgets();
|
|
271
|
+
return;
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
const target = placement === "belowEditor" ? this.#hookWidgetsBelow : this.#hookWidgetsAbove;
|
|
275
|
+
target.set(key, this.#createHookWidget(content));
|
|
276
|
+
this.#rebuildHookWidgets();
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
#removeHookWidget(widgets: Map<string, ExtensionUiComponent>, key: string): void {
|
|
280
|
+
const existing = widgets.get(key);
|
|
281
|
+
existing?.dispose?.();
|
|
282
|
+
widgets.delete(key);
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
#createHookWidget(content: ExtensionWidgetContent): ExtensionUiComponent {
|
|
286
|
+
if (Array.isArray(content)) {
|
|
287
|
+
const container = new Container();
|
|
288
|
+
for (const line of content.slice(0, MAX_WIDGET_LINES)) {
|
|
289
|
+
container.addChild(new Text(line, 1, 0));
|
|
290
|
+
}
|
|
291
|
+
if (content.length > MAX_WIDGET_LINES) {
|
|
292
|
+
container.addChild(new Text(theme.fg("muted", "... (widget truncated)"), 1, 0));
|
|
293
|
+
}
|
|
294
|
+
return container;
|
|
295
|
+
}
|
|
296
|
+
if (content === undefined) {
|
|
297
|
+
throw new Error("Widget content missing");
|
|
298
|
+
}
|
|
299
|
+
return content(this.ctx.ui, theme);
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
#rebuildHookWidgets(): void {
|
|
303
|
+
this.#renderHookWidgetContainer(this.ctx.hookWidgetContainerAbove, this.#hookWidgetsAbove, true, true);
|
|
304
|
+
this.#renderHookWidgetContainer(this.ctx.hookWidgetContainerBelow, this.#hookWidgetsBelow, false, false);
|
|
257
305
|
this.ctx.ui.requestRender();
|
|
258
306
|
}
|
|
259
307
|
|
|
308
|
+
#renderHookWidgetContainer(
|
|
309
|
+
container: Container,
|
|
310
|
+
widgets: Map<string, ExtensionUiComponent>,
|
|
311
|
+
spacerWhenEmpty: boolean,
|
|
312
|
+
leadingSpacer: boolean,
|
|
313
|
+
): void {
|
|
314
|
+
container.clear();
|
|
315
|
+
|
|
316
|
+
if (widgets.size === 0) {
|
|
317
|
+
if (spacerWhenEmpty) {
|
|
318
|
+
container.addChild(new Spacer(1));
|
|
319
|
+
}
|
|
320
|
+
return;
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
if (leadingSpacer) {
|
|
324
|
+
container.addChild(new Spacer(1));
|
|
325
|
+
}
|
|
326
|
+
for (const widget of widgets.values()) {
|
|
327
|
+
container.addChild(widget);
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
|
|
260
331
|
initializeHookRunner(uiContext: ExtensionUIContext, _hasUI: boolean): void {
|
|
261
332
|
const extensionRunner = this.ctx.session.extensionRunner;
|
|
262
333
|
if (!extensionRunner) {
|
|
@@ -353,6 +424,7 @@ export class ExtensionUiController {
|
|
|
353
424
|
|
|
354
425
|
// Create new session
|
|
355
426
|
this.clearExtensionTerminalInputListeners();
|
|
427
|
+
this.clearHookWidgets();
|
|
356
428
|
const success = await this.ctx.session.newSession({ parentSession: options?.parentSession });
|
|
357
429
|
if (!success) {
|
|
358
430
|
return { cancelled: true };
|
|
@@ -432,6 +504,7 @@ export class ExtensionUiController {
|
|
|
432
504
|
if (this.ctx.isBackgrounded) {
|
|
433
505
|
return { cancelled: true };
|
|
434
506
|
}
|
|
507
|
+
this.clearHookWidgets();
|
|
435
508
|
const result = await this.ctx.session.switchSession(sessionPath);
|
|
436
509
|
if (!result) {
|
|
437
510
|
return { cancelled: true };
|
|
@@ -828,6 +901,18 @@ export class ExtensionUiController {
|
|
|
828
901
|
};
|
|
829
902
|
}
|
|
830
903
|
|
|
904
|
+
clearHookWidgets(): void {
|
|
905
|
+
for (const widget of this.#hookWidgetsAbove.values()) {
|
|
906
|
+
widget.dispose?.();
|
|
907
|
+
}
|
|
908
|
+
for (const widget of this.#hookWidgetsBelow.values()) {
|
|
909
|
+
widget.dispose?.();
|
|
910
|
+
}
|
|
911
|
+
this.#hookWidgetsAbove.clear();
|
|
912
|
+
this.#hookWidgetsBelow.clear();
|
|
913
|
+
this.#rebuildHookWidgets();
|
|
914
|
+
}
|
|
915
|
+
|
|
831
916
|
clearExtensionTerminalInputListeners(): void {
|
|
832
917
|
for (const unsubscribe of this.#extensionTerminalInputUnsubscribers) {
|
|
833
918
|
unsubscribe();
|
|
@@ -11,6 +11,7 @@ import type { AgentSessionEvent } from "../../session/agent-session";
|
|
|
11
11
|
import { SKILL_PROMPT_MESSAGE_TYPE, type SkillPromptDetails } from "../../session/messages";
|
|
12
12
|
import { executeBuiltinSlashCommand } from "../../slash-commands/builtin-registry";
|
|
13
13
|
import { getEditorCommand, openInEditor } from "../../utils/external-editor";
|
|
14
|
+
import { ensureSupportedImageInput } from "../../utils/image-input";
|
|
14
15
|
import { resizeImage } from "../../utils/image-resize";
|
|
15
16
|
import { generateSessionTitle, setSessionTerminalTitle } from "../../utils/title-generator";
|
|
16
17
|
|
|
@@ -26,7 +27,7 @@ export class InputController {
|
|
|
26
27
|
constructor(private ctx: InteractiveModeContext) {}
|
|
27
28
|
|
|
28
29
|
setupKeyHandlers(): void {
|
|
29
|
-
this.ctx.editor.setActionKeys("interrupt", this.ctx.keybindings.getKeys("interrupt"));
|
|
30
|
+
this.ctx.editor.setActionKeys("app.interrupt", this.ctx.keybindings.getKeys("app.interrupt"));
|
|
30
31
|
this.ctx.editor.shouldBypassAutocompleteOnEscape = () =>
|
|
31
32
|
Boolean(
|
|
32
33
|
this.ctx.loadingAnimation ||
|
|
@@ -83,68 +84,78 @@ export class InputController {
|
|
|
83
84
|
}
|
|
84
85
|
};
|
|
85
86
|
|
|
86
|
-
this.ctx.editor.setActionKeys("clear", this.ctx.keybindings.getKeys("clear"));
|
|
87
|
+
this.ctx.editor.setActionKeys("app.clear", this.ctx.keybindings.getKeys("app.clear"));
|
|
87
88
|
this.ctx.editor.onClear = () => this.handleCtrlC();
|
|
88
|
-
this.ctx.editor.setActionKeys("exit", this.ctx.keybindings.getKeys("exit"));
|
|
89
|
+
this.ctx.editor.setActionKeys("app.exit", this.ctx.keybindings.getKeys("app.exit"));
|
|
89
90
|
this.ctx.editor.onExit = () => this.handleCtrlD();
|
|
90
|
-
this.ctx.editor.setActionKeys("suspend", this.ctx.keybindings.getKeys("suspend"));
|
|
91
|
+
this.ctx.editor.setActionKeys("app.suspend", this.ctx.keybindings.getKeys("app.suspend"));
|
|
91
92
|
this.ctx.editor.onSuspend = () => this.handleCtrlZ();
|
|
92
|
-
this.ctx.editor.setActionKeys("
|
|
93
|
+
this.ctx.editor.setActionKeys("app.thinking.cycle", this.ctx.keybindings.getKeys("app.thinking.cycle"));
|
|
93
94
|
this.ctx.editor.onCycleThinkingLevel = () => this.cycleThinkingLevel();
|
|
94
|
-
this.ctx.editor.setActionKeys("
|
|
95
|
+
this.ctx.editor.setActionKeys("app.model.cycleForward", this.ctx.keybindings.getKeys("app.model.cycleForward"));
|
|
95
96
|
this.ctx.editor.onCycleModelForward = () => this.cycleRoleModel();
|
|
96
|
-
this.ctx.editor.setActionKeys("
|
|
97
|
+
this.ctx.editor.setActionKeys("app.model.cycleBackward", this.ctx.keybindings.getKeys("app.model.cycleBackward"));
|
|
97
98
|
this.ctx.editor.onCycleModelBackward = () => this.cycleRoleModel({ temporary: true });
|
|
98
|
-
this.ctx.editor.
|
|
99
|
+
this.ctx.editor.setActionKeys(
|
|
100
|
+
"app.model.selectTemporary",
|
|
101
|
+
this.ctx.keybindings.getKeys("app.model.selectTemporary"),
|
|
102
|
+
);
|
|
103
|
+
this.ctx.editor.onSelectModelTemporary = () => this.ctx.showModelSelector({ temporaryOnly: true });
|
|
99
104
|
|
|
100
105
|
// Global debug handler on TUI (works regardless of focus)
|
|
101
106
|
this.ctx.ui.onDebug = () => this.ctx.showDebugSelector();
|
|
102
|
-
this.ctx.editor.setActionKeys("
|
|
107
|
+
this.ctx.editor.setActionKeys("app.model.select", this.ctx.keybindings.getKeys("app.model.select"));
|
|
103
108
|
this.ctx.editor.onSelectModel = () => this.ctx.showModelSelector();
|
|
104
|
-
this.ctx.editor.setActionKeys("
|
|
109
|
+
this.ctx.editor.setActionKeys("app.history.search", this.ctx.keybindings.getKeys("app.history.search"));
|
|
105
110
|
this.ctx.editor.onHistorySearch = () => this.ctx.showHistorySearch();
|
|
106
|
-
this.ctx.editor.setActionKeys("
|
|
111
|
+
this.ctx.editor.setActionKeys("app.thinking.toggle", this.ctx.keybindings.getKeys("app.thinking.toggle"));
|
|
107
112
|
this.ctx.editor.onToggleThinking = () => this.ctx.toggleThinkingBlockVisibility();
|
|
108
|
-
this.ctx.editor.setActionKeys("
|
|
113
|
+
this.ctx.editor.setActionKeys("app.editor.external", this.ctx.keybindings.getKeys("app.editor.external"));
|
|
109
114
|
this.ctx.editor.onExternalEditor = () => void this.openExternalEditor();
|
|
110
115
|
this.ctx.editor.onShowHotkeys = () => this.ctx.handleHotkeysCommand();
|
|
111
|
-
this.ctx.editor.setActionKeys(
|
|
116
|
+
this.ctx.editor.setActionKeys(
|
|
117
|
+
"app.clipboard.pasteImage",
|
|
118
|
+
this.ctx.keybindings.getKeys("app.clipboard.pasteImage"),
|
|
119
|
+
);
|
|
112
120
|
this.ctx.editor.onPasteImage = () => this.handleImagePaste();
|
|
113
|
-
this.ctx.editor.setActionKeys(
|
|
121
|
+
this.ctx.editor.setActionKeys(
|
|
122
|
+
"app.clipboard.copyPrompt",
|
|
123
|
+
this.ctx.keybindings.getKeys("app.clipboard.copyPrompt"),
|
|
124
|
+
);
|
|
114
125
|
this.ctx.editor.onCopyPrompt = () => this.handleCopyPrompt();
|
|
115
|
-
this.ctx.editor.setActionKeys("
|
|
126
|
+
this.ctx.editor.setActionKeys("app.tools.expand", this.ctx.keybindings.getKeys("app.tools.expand"));
|
|
116
127
|
this.ctx.editor.onExpandTools = () => this.toggleToolOutputExpansion();
|
|
117
|
-
this.ctx.editor.setActionKeys("dequeue", this.ctx.keybindings.getKeys("dequeue"));
|
|
128
|
+
this.ctx.editor.setActionKeys("app.message.dequeue", this.ctx.keybindings.getKeys("app.message.dequeue"));
|
|
118
129
|
this.ctx.editor.onDequeue = () => this.handleDequeue();
|
|
119
130
|
|
|
120
131
|
this.ctx.editor.clearCustomKeyHandlers();
|
|
121
132
|
// Wire up extension shortcuts
|
|
122
133
|
this.registerExtensionShortcuts();
|
|
123
134
|
|
|
124
|
-
const planModeKeys = this.ctx.keybindings.getKeys("
|
|
135
|
+
const planModeKeys = this.ctx.keybindings.getKeys("app.plan.toggle");
|
|
125
136
|
for (const key of planModeKeys) {
|
|
126
137
|
this.ctx.editor.setCustomKeyHandler(key, () => void this.ctx.handlePlanModeCommand());
|
|
127
138
|
}
|
|
128
139
|
|
|
129
|
-
for (const key of this.ctx.keybindings.getKeys("
|
|
140
|
+
for (const key of this.ctx.keybindings.getKeys("app.session.new")) {
|
|
130
141
|
this.ctx.editor.setCustomKeyHandler(key, () => this.ctx.handleClearCommand());
|
|
131
142
|
}
|
|
132
|
-
for (const key of this.ctx.keybindings.getKeys("tree")) {
|
|
143
|
+
for (const key of this.ctx.keybindings.getKeys("app.session.tree")) {
|
|
133
144
|
this.ctx.editor.setCustomKeyHandler(key, () => this.ctx.showTreeSelector());
|
|
134
145
|
}
|
|
135
|
-
for (const key of this.ctx.keybindings.getKeys("fork")) {
|
|
146
|
+
for (const key of this.ctx.keybindings.getKeys("app.session.fork")) {
|
|
136
147
|
this.ctx.editor.setCustomKeyHandler(key, () => this.ctx.showUserMessageSelector());
|
|
137
148
|
}
|
|
138
|
-
for (const key of this.ctx.keybindings.getKeys("resume")) {
|
|
149
|
+
for (const key of this.ctx.keybindings.getKeys("app.session.resume")) {
|
|
139
150
|
this.ctx.editor.setCustomKeyHandler(key, () => this.ctx.showSessionSelector());
|
|
140
151
|
}
|
|
141
|
-
for (const key of this.ctx.keybindings.getKeys("followUp")) {
|
|
152
|
+
for (const key of this.ctx.keybindings.getKeys("app.message.followUp")) {
|
|
142
153
|
this.ctx.editor.setCustomKeyHandler(key, () => void this.handleFollowUp());
|
|
143
154
|
}
|
|
144
|
-
for (const key of this.ctx.keybindings.getKeys("
|
|
155
|
+
for (const key of this.ctx.keybindings.getKeys("app.stt.toggle")) {
|
|
145
156
|
this.ctx.editor.setCustomKeyHandler(key, () => void this.ctx.handleSTTToggle());
|
|
146
157
|
}
|
|
147
|
-
for (const key of this.ctx.keybindings.getKeys("copyLine")) {
|
|
158
|
+
for (const key of this.ctx.keybindings.getKeys("app.clipboard.copyLine")) {
|
|
148
159
|
this.ctx.editor.setCustomKeyHandler(key, () => this.handleCopyCurrentLine());
|
|
149
160
|
}
|
|
150
161
|
|
|
@@ -492,17 +503,25 @@ export class InputController {
|
|
|
492
503
|
const image = await readImageFromClipboard();
|
|
493
504
|
if (image) {
|
|
494
505
|
const base64Data = image.data.toBase64();
|
|
495
|
-
let imageData = {
|
|
506
|
+
let imageData = await ensureSupportedImageInput({
|
|
507
|
+
type: "image",
|
|
508
|
+
data: base64Data,
|
|
509
|
+
mimeType: image.mimeType,
|
|
510
|
+
});
|
|
511
|
+
if (!imageData) {
|
|
512
|
+
this.ctx.showStatus(`Unsupported clipboard image format: ${image.mimeType}`);
|
|
513
|
+
return false;
|
|
514
|
+
}
|
|
496
515
|
if (settings.get("images.autoResize")) {
|
|
497
516
|
try {
|
|
498
517
|
const resized = await resizeImage({
|
|
499
518
|
type: "image",
|
|
500
|
-
data:
|
|
501
|
-
mimeType:
|
|
519
|
+
data: imageData.data,
|
|
520
|
+
mimeType: imageData.mimeType,
|
|
502
521
|
});
|
|
503
|
-
imageData = { data: resized.data, mimeType: resized.mimeType };
|
|
522
|
+
imageData = { type: "image", data: resized.data, mimeType: resized.mimeType };
|
|
504
523
|
} catch {
|
|
505
|
-
|
|
524
|
+
// Keep the normalized image when resize fails.
|
|
506
525
|
}
|
|
507
526
|
}
|
|
508
527
|
|
|
@@ -660,7 +660,7 @@ export class MCPCommandController {
|
|
|
660
660
|
}
|
|
661
661
|
|
|
662
662
|
async #removeManagedOAuthCredential(credentialId: string | undefined): Promise<void> {
|
|
663
|
-
if (!credentialId
|
|
663
|
+
if (!credentialId?.startsWith("mcp_oauth_")) return;
|
|
664
664
|
await this.ctx.session.modelRegistry.authStorage.remove(credentialId);
|
|
665
665
|
}
|
|
666
666
|
|
package/src/modes/index.ts
CHANGED
|
@@ -4,6 +4,7 @@ import { postmortem } from "@oh-my-pi/pi-utils";
|
|
|
4
4
|
/**
|
|
5
5
|
* Run modes for the coding agent.
|
|
6
6
|
*/
|
|
7
|
+
export { runAcpMode } from "./acp";
|
|
7
8
|
export { InteractiveMode, type InteractiveModeOptions } from "./interactive-mode";
|
|
8
9
|
export { type PrintModeOptions, runPrintMode } from "./print-mode";
|
|
9
10
|
export { type ModelInfo, RpcClient, type RpcClientOptions, type RpcEventListener } from "./rpc/rpc-client";
|
|
@@ -12,7 +12,12 @@ import chalk from "chalk";
|
|
|
12
12
|
import { KeybindingsManager } from "../config/keybindings";
|
|
13
13
|
import { renderPromptTemplate } from "../config/prompt-templates";
|
|
14
14
|
import { type Settings, settings } from "../config/settings";
|
|
15
|
-
import type {
|
|
15
|
+
import type {
|
|
16
|
+
ExtensionUIContext,
|
|
17
|
+
ExtensionUIDialogOptions,
|
|
18
|
+
ExtensionWidgetContent,
|
|
19
|
+
ExtensionWidgetOptions,
|
|
20
|
+
} from "../extensibility/extensions";
|
|
16
21
|
import type { CompactOptions } from "../extensibility/extensions/types";
|
|
17
22
|
import { BUILTIN_SLASH_COMMANDS, loadSlashCommands } from "../extensibility/slash-commands";
|
|
18
23
|
import { resolveLocalUrlToPath } from "../internal-urls";
|
|
@@ -93,6 +98,8 @@ export class InteractiveMode implements InteractiveModeContext {
|
|
|
93
98
|
btwContainer: Container;
|
|
94
99
|
editor: CustomEditor;
|
|
95
100
|
editorContainer: Container;
|
|
101
|
+
hookWidgetContainerAbove: Container;
|
|
102
|
+
hookWidgetContainerBelow: Container;
|
|
96
103
|
statusLine: StatusLineComponent;
|
|
97
104
|
|
|
98
105
|
isInitialized = false;
|
|
@@ -216,6 +223,9 @@ export class InteractiveMode implements InteractiveModeContext {
|
|
|
216
223
|
} catch (error) {
|
|
217
224
|
logger.warn("History storage unavailable", { error: String(error) });
|
|
218
225
|
}
|
|
226
|
+
this.hookWidgetContainerAbove = new Container();
|
|
227
|
+
this.hookWidgetContainerAbove.addChild(new Spacer(1));
|
|
228
|
+
this.hookWidgetContainerBelow = new Container();
|
|
219
229
|
this.editorContainer = new Container();
|
|
220
230
|
this.editorContainer.addChild(this.editor);
|
|
221
231
|
this.statusLine = new StatusLineComponent(session);
|
|
@@ -263,7 +273,7 @@ export class InteractiveMode implements InteractiveModeContext {
|
|
|
263
273
|
async init(): Promise<void> {
|
|
264
274
|
if (this.isInitialized) return;
|
|
265
275
|
|
|
266
|
-
this.keybindings =
|
|
276
|
+
this.keybindings = logger.time("InteractiveMode.init:keybindings", () => KeybindingsManager.create());
|
|
267
277
|
|
|
268
278
|
// Register session manager flush for signal handlers (SIGINT, SIGTERM, SIGHUP)
|
|
269
279
|
this.#cleanupUnsubscribe = postmortem.register("session-manager-flush", () => this.sessionManager.flush());
|
|
@@ -329,8 +339,9 @@ export class InteractiveMode implements InteractiveModeContext {
|
|
|
329
339
|
this.ui.addChild(this.todoContainer);
|
|
330
340
|
this.ui.addChild(this.btwContainer);
|
|
331
341
|
this.ui.addChild(this.statusLine); // Only renders hook statuses (main status in editor border)
|
|
332
|
-
this.ui.addChild(
|
|
342
|
+
this.ui.addChild(this.hookWidgetContainerAbove);
|
|
333
343
|
this.ui.addChild(this.editorContainer);
|
|
344
|
+
this.ui.addChild(this.hookWidgetContainerBelow);
|
|
334
345
|
this.ui.setFocus(this.editor);
|
|
335
346
|
|
|
336
347
|
this.#inputController.setupKeyHandlers();
|
|
@@ -837,6 +848,7 @@ export class InteractiveMode implements InteractiveModeContext {
|
|
|
837
848
|
this.#sttController = undefined;
|
|
838
849
|
}
|
|
839
850
|
this.#extensionUiController.clearExtensionTerminalInputListeners();
|
|
851
|
+
this.#extensionUiController.clearHookWidgets();
|
|
840
852
|
this.statusLine.dispose();
|
|
841
853
|
if (this.#resizeHandler) {
|
|
842
854
|
process.stdout.removeListener("resize", this.#resizeHandler);
|
|
@@ -1359,8 +1371,8 @@ export class InteractiveMode implements InteractiveModeContext {
|
|
|
1359
1371
|
return this.#extensionUiController.emitCustomToolSessionEvent(reason, previousSessionFile);
|
|
1360
1372
|
}
|
|
1361
1373
|
|
|
1362
|
-
setHookWidget(key: string, content:
|
|
1363
|
-
this.#extensionUiController.setHookWidget(key, content);
|
|
1374
|
+
setHookWidget(key: string, content: ExtensionWidgetContent, options?: ExtensionWidgetOptions): void {
|
|
1375
|
+
this.#extensionUiController.setHookWidget(key, content, options);
|
|
1364
1376
|
}
|
|
1365
1377
|
|
|
1366
1378
|
setHookStatus(key: string, text: string | undefined): void {
|
package/src/modes/print-mode.ts
CHANGED
|
@@ -146,7 +146,7 @@ export async function runPrintMode(session: AgentSession, options: PrintModeOpti
|
|
|
146
146
|
});
|
|
147
147
|
|
|
148
148
|
// Send initial message with attachments
|
|
149
|
-
if (initialMessage) {
|
|
149
|
+
if (initialMessage !== undefined) {
|
|
150
150
|
await session.prompt(initialMessage, { images: initialImages });
|
|
151
151
|
}
|
|
152
152
|
|
|
@@ -2,7 +2,7 @@ import {
|
|
|
2
2
|
type AutocompleteItem,
|
|
3
3
|
type AutocompleteProvider,
|
|
4
4
|
CombinedAutocompleteProvider,
|
|
5
|
-
|
|
5
|
+
getKeybindings,
|
|
6
6
|
type SlashCommand,
|
|
7
7
|
} from "@oh-my-pi/pi-tui";
|
|
8
8
|
import { formatKeyHints, type KeybindingsManager } from "../config/keybindings";
|
|
@@ -174,26 +174,26 @@ export class PromptActionAutocompleteProvider implements AutocompleteProvider {
|
|
|
174
174
|
export function createPromptActionAutocompleteProvider(
|
|
175
175
|
options: PromptActionAutocompleteOptions,
|
|
176
176
|
): PromptActionAutocompleteProvider {
|
|
177
|
-
const editorKeybindings =
|
|
177
|
+
const editorKeybindings = getKeybindings();
|
|
178
178
|
const actions: PromptActionDefinition[] = [
|
|
179
179
|
{
|
|
180
180
|
id: "copy-line",
|
|
181
181
|
label: "Copy current line",
|
|
182
|
-
description: formatKeyHints(options.keybindings.getKeys("copyLine")),
|
|
182
|
+
description: formatKeyHints(options.keybindings.getKeys("app.clipboard.copyLine")),
|
|
183
183
|
keywords: ["copy", "line", "clipboard", "current"],
|
|
184
184
|
execute: options.copyCurrentLine,
|
|
185
185
|
},
|
|
186
186
|
{
|
|
187
187
|
id: "copy-prompt",
|
|
188
188
|
label: "Copy whole prompt",
|
|
189
|
-
description: formatKeyHints(options.keybindings.getKeys("copyPrompt")),
|
|
189
|
+
description: formatKeyHints(options.keybindings.getKeys("app.clipboard.copyPrompt")),
|
|
190
190
|
keywords: ["copy", "prompt", "clipboard", "message"],
|
|
191
191
|
execute: options.copyPrompt,
|
|
192
192
|
},
|
|
193
193
|
{
|
|
194
194
|
id: "undo",
|
|
195
195
|
label: "Undo",
|
|
196
|
-
description: formatKeyHints(editorKeybindings.getKeys("undo")),
|
|
196
|
+
description: formatKeyHints(editorKeybindings.getKeys("tui.editor.undo")),
|
|
197
197
|
keywords: ["undo", "revert", "edit", "history"],
|
|
198
198
|
execute: options.undo,
|
|
199
199
|
},
|
|
@@ -214,14 +214,14 @@ export function createPromptActionAutocompleteProvider(
|
|
|
214
214
|
{
|
|
215
215
|
id: "cursor-line-start",
|
|
216
216
|
label: "Move cursor to beginning of line",
|
|
217
|
-
description: formatKeyHints(editorKeybindings.getKeys("cursorLineStart")),
|
|
217
|
+
description: formatKeyHints(editorKeybindings.getKeys("tui.editor.cursorLineStart")),
|
|
218
218
|
keywords: ["move", "cursor", "line", "start", "beginning", "home"],
|
|
219
219
|
execute: options.moveCursorToLineStart,
|
|
220
220
|
},
|
|
221
221
|
{
|
|
222
222
|
id: "cursor-line-end",
|
|
223
223
|
label: "Move cursor to end of line",
|
|
224
|
-
description: formatKeyHints(editorKeybindings.getKeys("cursorLineEnd")),
|
|
224
|
+
description: formatKeyHints(editorKeybindings.getKeys("tui.editor.cursorLineEnd")),
|
|
225
225
|
keywords: ["move", "cursor", "line", "end"],
|
|
226
226
|
execute: options.moveCursorToLineEnd,
|
|
227
227
|
},
|
|
@@ -11,7 +11,11 @@
|
|
|
11
11
|
* - Extension UI: Extension UI requests are emitted, client responds with extension_ui_response
|
|
12
12
|
*/
|
|
13
13
|
import { readJsonl, Snowflake } from "@oh-my-pi/pi-utils";
|
|
14
|
-
import type {
|
|
14
|
+
import type {
|
|
15
|
+
ExtensionUIContext,
|
|
16
|
+
ExtensionUIDialogOptions,
|
|
17
|
+
ExtensionWidgetOptions,
|
|
18
|
+
} from "../../extensibility/extensions";
|
|
15
19
|
import { type Theme, theme } from "../../modes/theme/theme";
|
|
16
20
|
import type { AgentSession } from "../../session/agent-session";
|
|
17
21
|
import type {
|
|
@@ -198,7 +202,7 @@ export async function runRpcMode(session: AgentSession): Promise<never> {
|
|
|
198
202
|
// Not supported in RPC mode
|
|
199
203
|
}
|
|
200
204
|
|
|
201
|
-
setWidget(key: string, content: unknown): void {
|
|
205
|
+
setWidget(key: string, content: unknown, options?: ExtensionWidgetOptions): void {
|
|
202
206
|
// Only support string arrays in RPC mode - factory functions are ignored
|
|
203
207
|
if (content === undefined || Array.isArray(content)) {
|
|
204
208
|
this.output({
|
|
@@ -207,6 +211,7 @@ export async function runRpcMode(session: AgentSession): Promise<never> {
|
|
|
207
211
|
method: "setWidget",
|
|
208
212
|
widgetKey: key,
|
|
209
213
|
widgetLines: content as string[] | undefined,
|
|
214
|
+
widgetPlacement: options?.placement,
|
|
210
215
|
} as RpcExtensionUIRequest);
|
|
211
216
|
}
|
|
212
217
|
// Component factories are not supported in RPC mode - would need TUI access
|
|
@@ -215,6 +215,7 @@ export type RpcExtensionUIRequest =
|
|
|
215
215
|
method: "setWidget";
|
|
216
216
|
widgetKey: string;
|
|
217
217
|
widgetLines: string[] | undefined;
|
|
218
|
+
widgetPlacement?: "aboveEditor" | "belowEditor";
|
|
218
219
|
}
|
|
219
220
|
| { type: "extension_ui_request"; id: string; method: "setTitle"; title: string }
|
|
220
221
|
| { type: "extension_ui_request"; id: string; method: "set_editor_text"; text: string };
|