@oh-my-pi/pi-coding-agent 14.1.2 → 14.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +47 -2
- package/package.json +8 -8
- package/scripts/build-binary.ts +61 -0
- package/src/autoresearch/helpers.ts +10 -0
- package/src/autoresearch/index.ts +1 -11
- package/src/autoresearch/tools/init-experiment.ts +1 -10
- package/src/autoresearch/tools/log-experiment.ts +1 -11
- package/src/autoresearch/tools/run-experiment.ts +1 -10
- package/src/bun-imports.d.ts +6 -0
- package/src/cli/plugin-cli.ts +23 -45
- package/src/commit/agentic/tools/propose-commit.ts +1 -14
- package/src/commit/agentic/tools/split-commit.ts +1 -15
- package/src/commit/utils.ts +15 -1
- package/src/config/model-registry.ts +3 -3
- package/src/config/prompt-templates.ts +4 -12
- package/src/config/settings-schema.ts +27 -2
- package/src/config/settings.ts +1 -1
- package/src/dap/session.ts +8 -2
- package/src/discovery/claude-plugins.ts +61 -6
- package/src/discovery/codex.ts +2 -15
- package/src/discovery/gemini.ts +2 -15
- package/src/discovery/helpers.ts +40 -1
- package/src/discovery/opencode.ts +2 -15
- package/src/edit/apply-patch/index.ts +87 -0
- package/src/edit/apply-patch/parser.ts +174 -0
- package/src/edit/diff.ts +3 -14
- package/src/edit/index.ts +67 -3
- package/src/edit/modes/apply-patch.lark +19 -0
- package/src/edit/modes/apply-patch.ts +63 -0
- package/src/edit/modes/chunk.ts +6 -2
- package/src/edit/modes/hashline.ts +3 -3
- package/src/edit/modes/replace.ts +2 -13
- package/src/edit/read-file.ts +18 -0
- package/src/edit/renderer.ts +61 -33
- package/src/extensibility/extensions/compact-handler.ts +40 -0
- package/src/extensibility/extensions/runner.ts +11 -29
- package/src/extensibility/utils.ts +7 -1
- package/src/internal-urls/docs-index.generated.ts +9 -2
- package/src/lsp/client.ts +14 -5
- package/src/lsp/index.ts +53 -10
- package/src/lsp/render.ts +14 -2
- package/src/lsp/types.ts +2 -0
- package/src/main.ts +1 -0
- package/src/mcp/manager.ts +29 -48
- package/src/memories/index.ts +7 -1
- package/src/modes/acp/acp-agent.ts +3 -16
- package/src/modes/components/model-selector.ts +15 -24
- package/src/modes/components/plugin-settings.ts +16 -5
- package/src/modes/components/read-tool-group.ts +92 -9
- package/src/modes/components/settings-defs.ts +18 -0
- package/src/modes/components/settings-selector.ts +2 -6
- package/src/modes/components/tool-execution.ts +61 -28
- package/src/modes/controllers/event-controller.ts +3 -1
- package/src/modes/controllers/extension-ui-controller.ts +99 -150
- package/src/modes/controllers/selector-controller.ts +3 -12
- package/src/modes/interactive-mode.ts +4 -2
- package/src/modes/print-mode.ts +4 -22
- package/src/modes/rpc/rpc-mode.ts +18 -38
- package/src/modes/shared.ts +10 -1
- package/src/modes/utils/ui-helpers.ts +6 -2
- package/src/plan-mode/approved-plan.ts +5 -4
- package/src/prompts/system/subagent-system-prompt.md +4 -4
- package/src/prompts/system/subagent-user-prompt.md +2 -2
- package/src/prompts/system/system-prompt.md +208 -243
- package/src/prompts/tools/apply-patch.md +67 -0
- package/src/prompts/tools/ast-edit.md +18 -23
- package/src/prompts/tools/ast-grep.md +25 -32
- package/src/prompts/tools/bash.md +11 -23
- package/src/prompts/tools/debug.md +8 -22
- package/src/prompts/tools/find.md +0 -4
- package/src/prompts/tools/grep.md +3 -5
- package/src/prompts/tools/hashline.md +16 -10
- package/src/prompts/tools/python.md +10 -14
- package/src/prompts/tools/read.md +17 -24
- package/src/prompts/tools/task.md +57 -21
- package/src/prompts/tools/todo-write.md +45 -67
- package/src/session/agent-session.ts +4 -4
- package/src/session/session-manager.ts +15 -7
- package/src/session/streaming-output.ts +24 -0
- package/src/slash-commands/builtin-registry.ts +3 -14
- package/src/task/executor.ts +13 -34
- package/src/task/index.ts +82 -18
- package/src/task/simple-mode.ts +27 -0
- package/src/task/template.ts +17 -3
- package/src/task/types.ts +77 -30
- package/src/tools/ask.ts +2 -4
- package/src/tools/ast-edit.ts +41 -17
- package/src/tools/ast-grep.ts +8 -27
- package/src/tools/bash-skill-urls.ts +9 -7
- package/src/tools/bash.ts +66 -24
- package/src/tools/browser.ts +1 -1
- package/src/tools/fetch.ts +1 -14
- package/src/tools/file-recorder.ts +35 -0
- package/src/tools/find.ts +25 -29
- package/src/tools/gh-format.ts +12 -0
- package/src/tools/gh-renderer.ts +1 -8
- package/src/tools/gh.ts +6 -13
- package/src/tools/grep.ts +103 -59
- package/src/tools/jtd-to-json-schema.ts +16 -0
- package/src/tools/match-line-format.ts +20 -0
- package/src/tools/path-utils.ts +61 -5
- package/src/tools/plan-mode-guard.ts +6 -5
- package/src/tools/python.ts +1 -1
- package/src/tools/read.ts +1 -1
- package/src/tools/render-utils.ts +38 -6
- package/src/tools/renderers.ts +1 -0
- package/src/tools/resolve.ts +12 -3
- package/src/tools/ssh.ts +3 -11
- package/src/tools/submit-result.ts +1 -13
- package/src/tools/todo-write.ts +137 -103
- package/src/tools/vim.ts +1 -1
- package/src/tools/write.ts +2 -23
- package/src/tui/code-cell.ts +12 -7
- package/src/utils/edit-mode.ts +3 -2
- package/src/utils/git.ts +1 -1
- package/src/vim/engine.ts +41 -58
- package/src/web/scrapers/crates-io.ts +1 -14
- package/src/web/scrapers/types.ts +13 -0
- package/src/web/search/providers/base.ts +13 -0
- package/src/web/search/providers/brave.ts +2 -5
- package/src/web/search/providers/codex.ts +20 -24
- package/src/web/search/providers/gemini.ts +39 -1
- package/src/web/search/providers/jina.ts +2 -5
- package/src/web/search/providers/kagi.ts +3 -8
- package/src/web/search/providers/kimi.ts +3 -7
- package/src/web/search/providers/parallel.ts +3 -8
- package/src/web/search/providers/synthetic.ts +3 -7
- package/src/web/search/providers/tavily.ts +15 -11
- package/src/web/search/providers/utils.ts +36 -0
- package/src/web/search/providers/zai.ts +3 -7
|
@@ -25,7 +25,7 @@ import { getCurrentThemeName, getSelectListTheme, getSettingsListTheme, theme }
|
|
|
25
25
|
import { matchesAppInterrupt } from "../../modes/utils/keybinding-matchers";
|
|
26
26
|
import { getTabBarTheme } from "../shared";
|
|
27
27
|
import { DynamicBorder } from "./dynamic-border";
|
|
28
|
-
import { PluginSettingsComponent } from "./plugin-settings";
|
|
28
|
+
import { handleInputOrEscape, PluginSettingsComponent } from "./plugin-settings";
|
|
29
29
|
import { getSettingsForTab, type SettingDef } from "./settings-defs";
|
|
30
30
|
import { getPreset } from "./status-line/presets";
|
|
31
31
|
|
|
@@ -70,11 +70,7 @@ class TextInputSubmenu extends Container {
|
|
|
70
70
|
}
|
|
71
71
|
|
|
72
72
|
handleInput(data: string): void {
|
|
73
|
-
|
|
74
|
-
this.onCancel();
|
|
75
|
-
return;
|
|
76
|
-
}
|
|
77
|
-
this.#input.handleInput(data);
|
|
73
|
+
handleInputOrEscape(data, this.#input, this.onCancel);
|
|
78
74
|
}
|
|
79
75
|
}
|
|
80
76
|
|
|
@@ -14,7 +14,14 @@ import {
|
|
|
14
14
|
type TUI,
|
|
15
15
|
} from "@oh-my-pi/pi-tui";
|
|
16
16
|
import { getProjectDir, logger } from "@oh-my-pi/pi-utils";
|
|
17
|
-
import {
|
|
17
|
+
import {
|
|
18
|
+
computeEditDiff,
|
|
19
|
+
computeHashlineDiff,
|
|
20
|
+
computePatchDiff,
|
|
21
|
+
type DiffError,
|
|
22
|
+
type DiffResult,
|
|
23
|
+
expandApplyPatchToEntries,
|
|
24
|
+
} from "../../edit";
|
|
18
25
|
import type { Theme } from "../../modes/theme/theme";
|
|
19
26
|
import { theme } from "../../modes/theme/theme";
|
|
20
27
|
import { BASH_DEFAULT_PREVIEW_LINES } from "../../tools/bash";
|
|
@@ -54,6 +61,10 @@ function cloneToolArgs<T>(args: T): T {
|
|
|
54
61
|
}
|
|
55
62
|
}
|
|
56
63
|
|
|
64
|
+
function isEditLikeToolName(toolName: string): boolean {
|
|
65
|
+
return toolName === "edit" || toolName === "apply_patch";
|
|
66
|
+
}
|
|
67
|
+
|
|
57
68
|
export interface ToolExecutionOptions {
|
|
58
69
|
showImages?: boolean; // default: true (only used if terminal supports images)
|
|
59
70
|
editFuzzyThreshold?: number;
|
|
@@ -166,7 +177,7 @@ export class ToolExecutionComponent extends Container {
|
|
|
166
177
|
|
|
167
178
|
/**
|
|
168
179
|
* Signal that args are complete (tool is about to execute).
|
|
169
|
-
* This triggers diff computation for edit
|
|
180
|
+
* This triggers diff computation for edit-like tools.
|
|
170
181
|
*/
|
|
171
182
|
setArgsComplete(_toolCallId?: string): void {
|
|
172
183
|
this.#argsComplete = true;
|
|
@@ -179,10 +190,39 @@ export class ToolExecutionComponent extends Container {
|
|
|
179
190
|
* This runs async and updates display when done.
|
|
180
191
|
*/
|
|
181
192
|
#maybeComputeEditDiff(): void {
|
|
182
|
-
if (this.#toolName
|
|
193
|
+
if (!isEditLikeToolName(this.#toolName)) return;
|
|
183
194
|
|
|
184
195
|
const edits = this.#args?.edits;
|
|
185
|
-
if (!Array.isArray(edits) || edits.length === 0)
|
|
196
|
+
if (!Array.isArray(edits) || edits.length === 0) {
|
|
197
|
+
if (this.#toolName !== "apply_patch" || typeof this.#args?.input !== "string") {
|
|
198
|
+
return;
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
const input = this.#args.input;
|
|
202
|
+
const argsKey = JSON.stringify({ input });
|
|
203
|
+
if (this.#editDiffArgsKey === argsKey) return;
|
|
204
|
+
this.#editDiffArgsKey = argsKey;
|
|
205
|
+
|
|
206
|
+
try {
|
|
207
|
+
const first = expandApplyPatchToEntries({ input })[0];
|
|
208
|
+
if (!first?.path) return;
|
|
209
|
+
computePatchDiff({ ...first, op: first.op ?? "update" }, this.#cwd, {
|
|
210
|
+
fuzzyThreshold: this.#editFuzzyThreshold,
|
|
211
|
+
allowFuzzy: this.#editAllowFuzzy,
|
|
212
|
+
}).then(result => {
|
|
213
|
+
if (this.#editDiffArgsKey === argsKey) {
|
|
214
|
+
this.#editDiffPreview = result;
|
|
215
|
+
this.#updateDisplay();
|
|
216
|
+
this.#ui.requestRender();
|
|
217
|
+
}
|
|
218
|
+
});
|
|
219
|
+
} catch (err) {
|
|
220
|
+
this.#editDiffPreview = { error: err instanceof Error ? err.message : String(err) };
|
|
221
|
+
this.#updateDisplay();
|
|
222
|
+
this.#ui.requestRender();
|
|
223
|
+
}
|
|
224
|
+
return;
|
|
225
|
+
}
|
|
186
226
|
|
|
187
227
|
const first = edits[0];
|
|
188
228
|
if (!first || typeof first !== "object") return;
|
|
@@ -197,13 +237,9 @@ export class ToolExecutionComponent extends Container {
|
|
|
197
237
|
if (this.#editDiffArgsKey === argsKey) return;
|
|
198
238
|
this.#editDiffArgsKey = argsKey;
|
|
199
239
|
|
|
200
|
-
computeEditDiff(path, oldText, newText, this.#cwd, true, all, this.#editFuzzyThreshold).then(result =>
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
this.#updateDisplay();
|
|
204
|
-
this.#ui.requestRender();
|
|
205
|
-
}
|
|
206
|
-
});
|
|
240
|
+
computeEditDiff(path, oldText, newText, this.#cwd, true, all, this.#editFuzzyThreshold).then(result =>
|
|
241
|
+
this.#applyEditDiffResult(argsKey, result),
|
|
242
|
+
);
|
|
207
243
|
} else if ("path" in first && ("diff" in first || ("op" in first && !("content" in first)))) {
|
|
208
244
|
// Patch mode (has diff or op without content — chunk edits always have content)
|
|
209
245
|
const { path, op, rename, diff } = first;
|
|
@@ -216,13 +252,7 @@ export class ToolExecutionComponent extends Container {
|
|
|
216
252
|
computePatchDiff({ path, op, rename, diff }, this.#cwd, {
|
|
217
253
|
fuzzyThreshold: this.#editFuzzyThreshold,
|
|
218
254
|
allowFuzzy: this.#editAllowFuzzy,
|
|
219
|
-
}).then(result =>
|
|
220
|
-
if (this.#editDiffArgsKey === argsKey) {
|
|
221
|
-
this.#editDiffPreview = result;
|
|
222
|
-
this.#updateDisplay();
|
|
223
|
-
this.#ui.requestRender();
|
|
224
|
-
}
|
|
225
|
-
});
|
|
255
|
+
}).then(result => this.#applyEditDiffResult(argsKey, result));
|
|
226
256
|
} else if ("loc" in first && "path" in first) {
|
|
227
257
|
// Hashline mode — group edits by path, preview first file
|
|
228
258
|
const path = first.path;
|
|
@@ -234,17 +264,20 @@ export class ToolExecutionComponent extends Container {
|
|
|
234
264
|
if (this.#editDiffArgsKey === argsKey) return;
|
|
235
265
|
this.#editDiffArgsKey = argsKey;
|
|
236
266
|
|
|
237
|
-
computeHashlineDiff({ path, edits: fileEdits, move }, this.#cwd).then(result =>
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
this.#updateDisplay();
|
|
241
|
-
this.#ui.requestRender();
|
|
242
|
-
}
|
|
243
|
-
});
|
|
267
|
+
computeHashlineDiff({ path, edits: fileEdits, move }, this.#cwd).then(result =>
|
|
268
|
+
this.#applyEditDiffResult(argsKey, result),
|
|
269
|
+
);
|
|
244
270
|
}
|
|
245
271
|
// Chunk mode edits don't have a pre-execution diff preview
|
|
246
272
|
}
|
|
247
273
|
|
|
274
|
+
#applyEditDiffResult(argsKey: string, result: DiffResult | DiffError): void {
|
|
275
|
+
if (this.#editDiffArgsKey !== argsKey) return;
|
|
276
|
+
this.#editDiffPreview = result;
|
|
277
|
+
this.#updateDisplay();
|
|
278
|
+
this.#ui.requestRender();
|
|
279
|
+
}
|
|
280
|
+
|
|
248
281
|
updateResult(
|
|
249
282
|
result: {
|
|
250
283
|
content: Array<{ type: string; text?: string; data?: string; mimeType?: string }>;
|
|
@@ -316,7 +349,7 @@ export class ToolExecutionComponent extends Container {
|
|
|
316
349
|
*/
|
|
317
350
|
#updateSpinnerAnimation(): void {
|
|
318
351
|
// Spinner for: task tool with partial result, or edit/write while args streaming
|
|
319
|
-
const isStreamingArgs = !this.#argsComplete && (this.#toolName
|
|
352
|
+
const isStreamingArgs = !this.#argsComplete && (isEditLikeToolName(this.#toolName) || this.#toolName === "write");
|
|
320
353
|
const isBackgroundAsyncTask =
|
|
321
354
|
this.#toolName === "task" &&
|
|
322
355
|
(this.#result?.details as { async?: { state?: string } } | undefined)?.async?.state === "running";
|
|
@@ -610,7 +643,7 @@ export class ToolExecutionComponent extends Container {
|
|
|
610
643
|
}
|
|
611
644
|
|
|
612
645
|
#getCallArgsForRender(): any {
|
|
613
|
-
if (this.#toolName
|
|
646
|
+
if (!isEditLikeToolName(this.#toolName)) {
|
|
614
647
|
return this.#args;
|
|
615
648
|
}
|
|
616
649
|
if (!this.#editDiffPreview || !("diff" in this.#editDiffPreview) || !this.#editDiffPreview.diff) {
|
|
@@ -646,7 +679,7 @@ export class ToolExecutionComponent extends Container {
|
|
|
646
679
|
context.expanded = this.#expanded;
|
|
647
680
|
context.previewLines = PYTHON_DEFAULT_PREVIEW_LINES;
|
|
648
681
|
context.timeout = normalizeTimeoutSeconds(this.#args?.timeout, 600);
|
|
649
|
-
} else if (this.#toolName
|
|
682
|
+
} else if (isEditLikeToolName(this.#toolName)) {
|
|
650
683
|
// Edit needs diff preview and renderDiff function
|
|
651
684
|
context.editDiffPreview = this.#editDiffPreview;
|
|
652
685
|
context.renderDiff = renderDiff;
|
|
@@ -66,7 +66,9 @@ export class EventController {
|
|
|
66
66
|
#getReadGroup(): ReadToolGroupComponent {
|
|
67
67
|
if (!this.#lastReadGroup) {
|
|
68
68
|
this.ctx.chatContainer.addChild(new Text("", 0, 0));
|
|
69
|
-
const group = new ReadToolGroupComponent(
|
|
69
|
+
const group = new ReadToolGroupComponent({
|
|
70
|
+
showContentPreview: this.ctx.settings.get("read.toolResultPreview"),
|
|
71
|
+
});
|
|
70
72
|
group.setExpanded(this.ctx.toolOutputExpanded);
|
|
71
73
|
this.ctx.chatContainer.addChild(group);
|
|
72
74
|
this.#lastReadGroup = group;
|
|
@@ -3,6 +3,7 @@ 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 {
|
|
6
|
+
CompactOptions,
|
|
6
7
|
ExtensionActions,
|
|
7
8
|
ExtensionCommandContextActions,
|
|
8
9
|
ExtensionContextActions,
|
|
@@ -12,6 +13,7 @@ import type {
|
|
|
12
13
|
ExtensionUiComponent,
|
|
13
14
|
ExtensionWidgetContent,
|
|
14
15
|
ExtensionWidgetOptions,
|
|
16
|
+
SendUserMessageHandler,
|
|
15
17
|
TerminalInputHandler,
|
|
16
18
|
} from "../../extensibility/extensions";
|
|
17
19
|
import { HookEditorComponent } from "../../modes/components/hook-editor";
|
|
@@ -82,26 +84,14 @@ export class ExtensionUiController {
|
|
|
82
84
|
const wasStreaming = this.ctx.session.isStreaming;
|
|
83
85
|
this.ctx.session
|
|
84
86
|
.sendCustomMessage(message, options)
|
|
85
|
-
.then(() =>
|
|
86
|
-
// For non-streaming cases with display=true, update UI
|
|
87
|
-
// (streaming cases update via message_end event)
|
|
88
|
-
if (!this.ctx.isBackgrounded && !wasStreaming && message.display) {
|
|
89
|
-
this.ctx.rebuildChatFromMessages();
|
|
90
|
-
}
|
|
91
|
-
})
|
|
87
|
+
.then(() => this.#applyCustomMessageDisplay(wasStreaming, message.display))
|
|
92
88
|
.catch((err: unknown) => {
|
|
93
89
|
this.ctx.showError(
|
|
94
90
|
`Extension sendMessage failed: ${err instanceof Error ? err.message : String(err)}`,
|
|
95
91
|
);
|
|
96
92
|
});
|
|
97
93
|
},
|
|
98
|
-
sendUserMessage:
|
|
99
|
-
this.ctx.session.sendUserMessage(content, options).catch((err: unknown) => {
|
|
100
|
-
this.ctx.showError(
|
|
101
|
-
`Extension sendUserMessage failed: ${err instanceof Error ? err.message : String(err)}`,
|
|
102
|
-
);
|
|
103
|
-
});
|
|
104
|
-
},
|
|
94
|
+
sendUserMessage: this.#sendExtensionUserMessage,
|
|
105
95
|
appendEntry: (customType, data) => {
|
|
106
96
|
this.ctx.sessionManager.appendCustomEntry(customType, data);
|
|
107
97
|
},
|
|
@@ -121,14 +111,7 @@ export class ExtensionUiController {
|
|
|
121
111
|
setThinkingLevel: level => this.ctx.session.setThinkingLevel(level),
|
|
122
112
|
getCommands: () => [],
|
|
123
113
|
getSessionName: () => this.ctx.sessionManager.getSessionName(),
|
|
124
|
-
setSessionName:
|
|
125
|
-
await this.ctx.sessionManager.setSessionName(name, "user");
|
|
126
|
-
setSessionTerminalTitle(
|
|
127
|
-
this.ctx.sessionManager.getSessionName(),
|
|
128
|
-
this.ctx.sessionManager.getCwd(),
|
|
129
|
-
this.ctx.sessionManager.titleSource,
|
|
130
|
-
);
|
|
131
|
-
},
|
|
114
|
+
setSessionName: name => this.#updateSessionName(name),
|
|
132
115
|
};
|
|
133
116
|
const contextActions: ExtensionContextActions = {
|
|
134
117
|
getModel: () => this.ctx.session.model,
|
|
@@ -139,12 +122,7 @@ export class ExtensionUiController {
|
|
|
139
122
|
// Signal shutdown request (will be handled by main loop)
|
|
140
123
|
},
|
|
141
124
|
getContextUsage: () => this.ctx.session.getContextUsage(),
|
|
142
|
-
compact:
|
|
143
|
-
const instructions = typeof instructionsOrOptions === "string" ? instructionsOrOptions : undefined;
|
|
144
|
-
const options =
|
|
145
|
-
instructionsOrOptions && typeof instructionsOrOptions === "object" ? instructionsOrOptions : undefined;
|
|
146
|
-
await this.ctx.session.compact(instructions, options);
|
|
147
|
-
},
|
|
125
|
+
compact: instructionsOrOptions => this.#compactSession(instructionsOrOptions),
|
|
148
126
|
getSystemPrompt: () => this.ctx.session.systemPrompt,
|
|
149
127
|
};
|
|
150
128
|
const commandActions: ExtensionCommandContextActions = {
|
|
@@ -238,16 +216,7 @@ export class ExtensionUiController {
|
|
|
238
216
|
|
|
239
217
|
return { cancelled: false };
|
|
240
218
|
},
|
|
241
|
-
compact: async instructionsOrOptions =>
|
|
242
|
-
const instructions = typeof instructionsOrOptions === "string" ? instructionsOrOptions : undefined;
|
|
243
|
-
const options =
|
|
244
|
-
instructionsOrOptions && typeof instructionsOrOptions === "object" ? instructionsOrOptions : undefined;
|
|
245
|
-
if (this.ctx.isBackgrounded) {
|
|
246
|
-
await this.ctx.session.compact(instructions, options);
|
|
247
|
-
return;
|
|
248
|
-
}
|
|
249
|
-
await this.ctx.executeCompaction(instructionsOrOptions, false);
|
|
250
|
-
},
|
|
219
|
+
compact: async instructionsOrOptions => this.#handleInteractiveCompact(instructionsOrOptions),
|
|
251
220
|
switchSession: async sessionPath => {
|
|
252
221
|
this.clearHookWidgets();
|
|
253
222
|
const result = await this.ctx.session.switchSession(sessionPath);
|
|
@@ -357,13 +326,7 @@ export class ExtensionUiController {
|
|
|
357
326
|
const wasStreaming = this.ctx.session.isStreaming;
|
|
358
327
|
this.ctx.session
|
|
359
328
|
.sendCustomMessage(message, options)
|
|
360
|
-
.then(() =>
|
|
361
|
-
// For non-streaming cases with display=true, update UI
|
|
362
|
-
// (streaming cases update via message_end event)
|
|
363
|
-
if (!this.ctx.isBackgrounded && !wasStreaming && message.display) {
|
|
364
|
-
this.ctx.rebuildChatFromMessages();
|
|
365
|
-
}
|
|
366
|
-
})
|
|
329
|
+
.then(() => this.#applyCustomMessageDisplay(wasStreaming, message.display))
|
|
367
330
|
.catch((err: unknown) => {
|
|
368
331
|
const errorText = `Extension sendMessage failed: ${err instanceof Error ? err.message : String(err)}`;
|
|
369
332
|
if (this.ctx.isBackgrounded) {
|
|
@@ -373,13 +336,7 @@ export class ExtensionUiController {
|
|
|
373
336
|
this.ctx.showError(errorText);
|
|
374
337
|
});
|
|
375
338
|
},
|
|
376
|
-
sendUserMessage:
|
|
377
|
-
this.ctx.session.sendUserMessage(content, options).catch((err: unknown) => {
|
|
378
|
-
this.ctx.showError(
|
|
379
|
-
`Extension sendUserMessage failed: ${err instanceof Error ? err.message : String(err)}`,
|
|
380
|
-
);
|
|
381
|
-
});
|
|
382
|
-
},
|
|
339
|
+
sendUserMessage: this.#sendExtensionUserMessage,
|
|
383
340
|
appendEntry: (customType, data) => {
|
|
384
341
|
this.ctx.sessionManager.appendCustomEntry(customType, data);
|
|
385
342
|
},
|
|
@@ -399,14 +356,7 @@ export class ExtensionUiController {
|
|
|
399
356
|
setThinkingLevel: (level, persist) => this.ctx.session.setThinkingLevel(level, persist),
|
|
400
357
|
getCommands: () => [],
|
|
401
358
|
getSessionName: () => this.ctx.sessionManager.getSessionName(),
|
|
402
|
-
setSessionName:
|
|
403
|
-
await this.ctx.sessionManager.setSessionName(name, "user");
|
|
404
|
-
setSessionTerminalTitle(
|
|
405
|
-
this.ctx.sessionManager.getSessionName(),
|
|
406
|
-
this.ctx.sessionManager.getCwd(),
|
|
407
|
-
this.ctx.sessionManager.titleSource,
|
|
408
|
-
);
|
|
409
|
-
},
|
|
359
|
+
setSessionName: name => this.#updateSessionName(name),
|
|
410
360
|
};
|
|
411
361
|
const contextActions: ExtensionContextActions = {
|
|
412
362
|
getModel: () => this.ctx.session.model,
|
|
@@ -417,12 +367,7 @@ export class ExtensionUiController {
|
|
|
417
367
|
// Signal shutdown request (will be handled by main loop)
|
|
418
368
|
},
|
|
419
369
|
getContextUsage: () => this.ctx.session.getContextUsage(),
|
|
420
|
-
compact:
|
|
421
|
-
const instructions = typeof instructionsOrOptions === "string" ? instructionsOrOptions : undefined;
|
|
422
|
-
const options =
|
|
423
|
-
instructionsOrOptions && typeof instructionsOrOptions === "object" ? instructionsOrOptions : undefined;
|
|
424
|
-
await this.ctx.session.compact(instructions, options);
|
|
425
|
-
},
|
|
370
|
+
compact: instructionsOrOptions => this.#compactSession(instructionsOrOptions),
|
|
426
371
|
getSystemPrompt: () => this.ctx.session.systemPrompt,
|
|
427
372
|
};
|
|
428
373
|
const commandActions: ExtensionCommandContextActions = {
|
|
@@ -517,16 +462,7 @@ export class ExtensionUiController {
|
|
|
517
462
|
|
|
518
463
|
return { cancelled: false };
|
|
519
464
|
},
|
|
520
|
-
compact: async instructionsOrOptions =>
|
|
521
|
-
const instructions = typeof instructionsOrOptions === "string" ? instructionsOrOptions : undefined;
|
|
522
|
-
const options =
|
|
523
|
-
instructionsOrOptions && typeof instructionsOrOptions === "object" ? instructionsOrOptions : undefined;
|
|
524
|
-
if (this.ctx.isBackgrounded) {
|
|
525
|
-
await this.ctx.session.compact(instructions, options);
|
|
526
|
-
return;
|
|
527
|
-
}
|
|
528
|
-
await this.ctx.executeCompaction(instructionsOrOptions, false);
|
|
529
|
-
},
|
|
465
|
+
compact: async instructionsOrOptions => this.#handleInteractiveCompact(instructionsOrOptions),
|
|
530
466
|
switchSession: async sessionPath => {
|
|
531
467
|
if (this.ctx.isBackgrounded) {
|
|
532
468
|
return { cancelled: true };
|
|
@@ -594,14 +530,7 @@ export class ExtensionUiController {
|
|
|
594
530
|
await registeredTool.definition.onSession(event, {
|
|
595
531
|
ui: uiContext,
|
|
596
532
|
getContextUsage: () => this.ctx.session.getContextUsage(),
|
|
597
|
-
compact:
|
|
598
|
-
const instructions = typeof instructionsOrOptions === "string" ? instructionsOrOptions : undefined;
|
|
599
|
-
const options =
|
|
600
|
-
instructionsOrOptions && typeof instructionsOrOptions === "object"
|
|
601
|
-
? instructionsOrOptions
|
|
602
|
-
: undefined;
|
|
603
|
-
await this.ctx.session.compact(instructions, options);
|
|
604
|
-
},
|
|
533
|
+
compact: instructionsOrOptions => this.#compactSession(instructionsOrOptions),
|
|
605
534
|
hasUI: !this.ctx.isBackgrounded,
|
|
606
535
|
cwd: this.ctx.sessionManager.getCwd(),
|
|
607
536
|
sessionManager: this.ctx.session.sessionManager,
|
|
@@ -657,21 +586,10 @@ export class ExtensionUiController {
|
|
|
657
586
|
options: string[],
|
|
658
587
|
dialogOptions?: ExtensionUIDialogOptions,
|
|
659
588
|
): Promise<string | undefined> {
|
|
660
|
-
const { promise,
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
if (!settled) {
|
|
665
|
-
settled = true;
|
|
666
|
-
resolve(undefined);
|
|
667
|
-
}
|
|
668
|
-
};
|
|
669
|
-
const finish = (value: string | undefined) => {
|
|
670
|
-
if (settled) return;
|
|
671
|
-
settled = true;
|
|
672
|
-
dialogOptions?.signal?.removeEventListener("abort", onAbort);
|
|
673
|
-
resolve(value);
|
|
674
|
-
};
|
|
589
|
+
const { promise, finish, attachAbort } = this.#createHookDialogState(
|
|
590
|
+
() => this.hideHookSelector(),
|
|
591
|
+
dialogOptions?.signal,
|
|
592
|
+
);
|
|
675
593
|
const maxVisible = Math.max(4, Math.min(15, this.ctx.ui.terminal.rows - 12));
|
|
676
594
|
this.ctx.hookSelector = new HookSelectorComponent(
|
|
677
595
|
title,
|
|
@@ -713,13 +631,7 @@ export class ExtensionUiController {
|
|
|
713
631
|
this.ctx.editorContainer.addChild(this.ctx.hookSelector);
|
|
714
632
|
this.ctx.ui.setFocus(this.ctx.hookSelector);
|
|
715
633
|
this.ctx.ui.requestRender();
|
|
716
|
-
|
|
717
|
-
if (dialogOptions.signal.aborted) {
|
|
718
|
-
onAbort();
|
|
719
|
-
} else {
|
|
720
|
-
dialogOptions.signal.addEventListener("abort", onAbort, { once: true });
|
|
721
|
-
}
|
|
722
|
-
}
|
|
634
|
+
attachAbort();
|
|
723
635
|
return promise;
|
|
724
636
|
}
|
|
725
637
|
/**
|
|
@@ -750,21 +662,10 @@ export class ExtensionUiController {
|
|
|
750
662
|
placeholder?: string,
|
|
751
663
|
dialogOptions?: ExtensionUIDialogOptions,
|
|
752
664
|
): Promise<string | undefined> {
|
|
753
|
-
const { promise,
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
if (!settled) {
|
|
758
|
-
settled = true;
|
|
759
|
-
resolve(undefined);
|
|
760
|
-
}
|
|
761
|
-
};
|
|
762
|
-
const finish = (value: string | undefined) => {
|
|
763
|
-
if (settled) return;
|
|
764
|
-
settled = true;
|
|
765
|
-
dialogOptions?.signal?.removeEventListener("abort", onAbort);
|
|
766
|
-
resolve(value);
|
|
767
|
-
};
|
|
665
|
+
const { promise, finish, attachAbort } = this.#createHookDialogState(
|
|
666
|
+
() => this.hideHookInput(),
|
|
667
|
+
dialogOptions?.signal,
|
|
668
|
+
);
|
|
768
669
|
this.ctx.hookInput = new HookInputComponent(
|
|
769
670
|
title,
|
|
770
671
|
placeholder,
|
|
@@ -786,13 +687,7 @@ export class ExtensionUiController {
|
|
|
786
687
|
this.ctx.editorContainer.addChild(this.ctx.hookInput);
|
|
787
688
|
this.ctx.ui.setFocus(this.ctx.hookInput);
|
|
788
689
|
this.ctx.ui.requestRender();
|
|
789
|
-
|
|
790
|
-
if (dialogOptions.signal.aborted) {
|
|
791
|
-
onAbort();
|
|
792
|
-
} else {
|
|
793
|
-
dialogOptions.signal.addEventListener("abort", onAbort, { once: true });
|
|
794
|
-
}
|
|
795
|
-
}
|
|
690
|
+
attachAbort();
|
|
796
691
|
return promise;
|
|
797
692
|
}
|
|
798
693
|
|
|
@@ -817,21 +712,10 @@ export class ExtensionUiController {
|
|
|
817
712
|
dialogOptions?: ExtensionUIDialogOptions,
|
|
818
713
|
editorOptions?: { promptStyle?: boolean },
|
|
819
714
|
): Promise<string | undefined> {
|
|
820
|
-
const { promise,
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
if (!settled) {
|
|
825
|
-
settled = true;
|
|
826
|
-
resolve(undefined);
|
|
827
|
-
}
|
|
828
|
-
};
|
|
829
|
-
const finish = (value: string | undefined) => {
|
|
830
|
-
if (settled) return;
|
|
831
|
-
settled = true;
|
|
832
|
-
dialogOptions?.signal?.removeEventListener("abort", onAbort);
|
|
833
|
-
resolve(value);
|
|
834
|
-
};
|
|
715
|
+
const { promise, finish, attachAbort } = this.#createHookDialogState(
|
|
716
|
+
() => this.hideHookEditor(),
|
|
717
|
+
dialogOptions?.signal,
|
|
718
|
+
);
|
|
835
719
|
this.ctx.hookEditor = new HookEditorComponent(
|
|
836
720
|
this.ctx.ui,
|
|
837
721
|
title,
|
|
@@ -851,13 +735,7 @@ export class ExtensionUiController {
|
|
|
851
735
|
this.ctx.editorContainer.addChild(this.ctx.hookEditor);
|
|
852
736
|
this.ctx.ui.setFocus(this.ctx.hookEditor);
|
|
853
737
|
this.ctx.ui.requestRender();
|
|
854
|
-
|
|
855
|
-
if (dialogOptions.signal.aborted) {
|
|
856
|
-
onAbort();
|
|
857
|
-
} else {
|
|
858
|
-
dialogOptions.signal.addEventListener("abort", onAbort, { once: true });
|
|
859
|
-
}
|
|
860
|
-
}
|
|
738
|
+
attachAbort();
|
|
861
739
|
return promise;
|
|
862
740
|
}
|
|
863
741
|
|
|
@@ -980,4 +858,75 @@ export class ExtensionUiController {
|
|
|
980
858
|
this.ctx.chatContainer.addChild(errorText);
|
|
981
859
|
this.ctx.ui.requestRender();
|
|
982
860
|
}
|
|
861
|
+
async #handleInteractiveCompact(instructionsOrOptions: string | CompactOptions | undefined): Promise<void> {
|
|
862
|
+
if (this.ctx.isBackgrounded) {
|
|
863
|
+
await this.#compactSession(instructionsOrOptions);
|
|
864
|
+
return;
|
|
865
|
+
}
|
|
866
|
+
await this.ctx.executeCompaction(instructionsOrOptions, false);
|
|
867
|
+
}
|
|
868
|
+
|
|
869
|
+
async #compactSession(instructionsOrOptions: string | CompactOptions | undefined): Promise<void> {
|
|
870
|
+
const instructions = typeof instructionsOrOptions === "string" ? instructionsOrOptions : undefined;
|
|
871
|
+
const options =
|
|
872
|
+
instructionsOrOptions && typeof instructionsOrOptions === "object" ? instructionsOrOptions : undefined;
|
|
873
|
+
await this.ctx.session.compact(instructions, options);
|
|
874
|
+
}
|
|
875
|
+
|
|
876
|
+
async #updateSessionName(name: string): Promise<void> {
|
|
877
|
+
await this.ctx.sessionManager.setSessionName(name, "user");
|
|
878
|
+
setSessionTerminalTitle(
|
|
879
|
+
this.ctx.sessionManager.getSessionName(),
|
|
880
|
+
this.ctx.sessionManager.getCwd(),
|
|
881
|
+
this.ctx.sessionManager.titleSource,
|
|
882
|
+
);
|
|
883
|
+
}
|
|
884
|
+
|
|
885
|
+
#sendExtensionUserMessage: SendUserMessageHandler = (content, options) => {
|
|
886
|
+
this.ctx.session.sendUserMessage(content, options).catch((err: unknown) => {
|
|
887
|
+
this.ctx.showError(`Extension sendUserMessage failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
888
|
+
});
|
|
889
|
+
};
|
|
890
|
+
|
|
891
|
+
#applyCustomMessageDisplay(wasStreaming: boolean, shouldDisplay: boolean | undefined): void {
|
|
892
|
+
// For non-streaming cases with display=true, update UI
|
|
893
|
+
// (streaming cases update via message_end event)
|
|
894
|
+
if (!this.ctx.isBackgrounded && !wasStreaming && shouldDisplay) {
|
|
895
|
+
this.ctx.rebuildChatFromMessages();
|
|
896
|
+
}
|
|
897
|
+
}
|
|
898
|
+
|
|
899
|
+
#createHookDialogState(
|
|
900
|
+
hide: () => void,
|
|
901
|
+
signal: AbortSignal | undefined,
|
|
902
|
+
): {
|
|
903
|
+
promise: Promise<string | undefined>;
|
|
904
|
+
finish: (value: string | undefined) => void;
|
|
905
|
+
attachAbort: () => void;
|
|
906
|
+
} {
|
|
907
|
+
const { promise, resolve } = Promise.withResolvers<string | undefined>();
|
|
908
|
+
let settled = false;
|
|
909
|
+
const onAbort = () => {
|
|
910
|
+
hide();
|
|
911
|
+
if (!settled) {
|
|
912
|
+
settled = true;
|
|
913
|
+
resolve(undefined);
|
|
914
|
+
}
|
|
915
|
+
};
|
|
916
|
+
const finish = (value: string | undefined) => {
|
|
917
|
+
if (settled) return;
|
|
918
|
+
settled = true;
|
|
919
|
+
signal?.removeEventListener("abort", onAbort);
|
|
920
|
+
resolve(value);
|
|
921
|
+
};
|
|
922
|
+
const attachAbort = () => {
|
|
923
|
+
if (!signal) return;
|
|
924
|
+
if (signal.aborted) {
|
|
925
|
+
onAbort();
|
|
926
|
+
} else {
|
|
927
|
+
signal.addEventListener("abort", onAbort, { once: true });
|
|
928
|
+
}
|
|
929
|
+
};
|
|
930
|
+
return { promise, finish, attachAbort };
|
|
931
|
+
}
|
|
983
932
|
}
|
|
@@ -1,17 +1,14 @@
|
|
|
1
|
-
import * as os from "node:os";
|
|
2
|
-
import * as path from "node:path";
|
|
3
1
|
import { ThinkingLevel } from "@oh-my-pi/pi-agent-core";
|
|
4
2
|
import { getOAuthProviders, type OAuthProvider } from "@oh-my-pi/pi-ai";
|
|
5
3
|
import type { Component } from "@oh-my-pi/pi-tui";
|
|
6
4
|
import { Input, Loader, Spacer, Text } from "@oh-my-pi/pi-tui";
|
|
7
|
-
import { getAgentDbPath,
|
|
8
|
-
import { invalidate as invalidateFsCache } from "../../capability/fs";
|
|
5
|
+
import { getAgentDbPath, getProjectDir } from "@oh-my-pi/pi-utils";
|
|
9
6
|
import { getRoleInfo } from "../../config/model-registry";
|
|
10
7
|
import { formatModelSelectorValue } from "../../config/model-resolver";
|
|
11
8
|
import { settings } from "../../config/settings";
|
|
12
9
|
import { DebugSelectorComponent } from "../../debug";
|
|
13
10
|
import { disableProvider, enableProvider } from "../../discovery";
|
|
14
|
-
import {
|
|
11
|
+
import { clearPluginRootsAndCaches, resolveActiveProjectRegistryPath } from "../../discovery/helpers";
|
|
15
12
|
import {
|
|
16
13
|
getInstalledPluginsRegistryPath,
|
|
17
14
|
getMarketplacesCacheDir,
|
|
@@ -443,13 +440,7 @@ export class SelectorController {
|
|
|
443
440
|
projectInstalledRegistryPath: (await resolveActiveProjectRegistryPath(getProjectDir())) ?? undefined,
|
|
444
441
|
marketplacesCacheDir: getMarketplacesCacheDir(),
|
|
445
442
|
pluginsCacheDir: getPluginsCacheDir(),
|
|
446
|
-
clearPluginRootsCache:
|
|
447
|
-
const home = os.homedir();
|
|
448
|
-
invalidateFsCache(path.join(home, ".claude", "plugins", "installed_plugins.json"));
|
|
449
|
-
invalidateFsCache(path.join(home, getConfigDirName(), "plugins", "installed_plugins.json"));
|
|
450
|
-
for (const p of extraPaths ?? []) invalidateFsCache(p);
|
|
451
|
-
clearClaudePluginRootsCache();
|
|
452
|
-
},
|
|
443
|
+
clearPluginRootsCache: clearPluginRootsAndCaches,
|
|
453
444
|
});
|
|
454
445
|
|
|
455
446
|
const [marketplaces, installed] = await Promise.all([mgr.listMarketplaces(), mgr.listInstalledPlugins()]);
|
|
@@ -37,6 +37,7 @@ import type { SessionContext, SessionManager } from "../session/session-manager"
|
|
|
37
37
|
import { getRecentSessions } from "../session/session-manager";
|
|
38
38
|
import { STTController, type SttState } from "../stt";
|
|
39
39
|
import type { ExitPlanModeDetails, LspStartupServerInfo } from "../tools";
|
|
40
|
+
import { normalizeLocalScheme } from "../tools/path-utils";
|
|
40
41
|
import type { EventBus } from "../utils/event-bus";
|
|
41
42
|
import { getEditorCommand, openInEditor } from "../utils/external-editor";
|
|
42
43
|
import { getSessionAccentAnsi, getSessionAccentHexForTitle } from "../utils/session-color";
|
|
@@ -637,8 +638,9 @@ export class InteractiveMode implements InteractiveModeContext {
|
|
|
637
638
|
}
|
|
638
639
|
|
|
639
640
|
#resolvePlanFilePath(planFilePath: string): string {
|
|
640
|
-
if (planFilePath.startsWith("local
|
|
641
|
-
|
|
641
|
+
if (planFilePath.startsWith("local:")) {
|
|
642
|
+
const normalized = normalizeLocalScheme(planFilePath);
|
|
643
|
+
return resolveLocalUrlToPath(normalized, {
|
|
642
644
|
getArtifactsDir: () => this.sessionManager.getArtifactsDir(),
|
|
643
645
|
getSessionId: () => this.sessionManager.getSessionId(),
|
|
644
646
|
});
|