@oh-my-pi/pi-coding-agent 13.15.3 → 13.16.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 +30 -16
- package/package.json +7 -7
- package/src/commit/agentic/tools/analyze-file.ts +1 -0
- package/src/config/model-registry.ts +215 -57
- package/src/config/settings-schema.ts +27 -0
- package/src/extensibility/custom-tools/types.ts +3 -0
- package/src/extensibility/extensions/runner.ts +7 -0
- package/src/extensibility/extensions/types.ts +10 -1
- package/src/extensibility/hooks/types.ts +1 -1
- package/src/internal-urls/docs-index.generated.ts +1 -1
- package/src/ipy/cancellation.ts +28 -0
- package/src/ipy/executor.ts +252 -77
- package/src/ipy/kernel.ts +181 -35
- package/src/ipy/modules.ts +39 -4
- package/src/modes/acp/acp-agent.ts +1 -0
- package/src/modes/components/hook-editor.ts +57 -8
- package/src/modes/components/model-selector.ts +48 -29
- package/src/modes/components/settings-defs.ts +10 -1
- package/src/modes/components/settings-selector.ts +92 -5
- package/src/modes/controllers/extension-ui-controller.ts +35 -4
- package/src/modes/controllers/input-controller.ts +4 -3
- package/src/modes/controllers/selector-controller.ts +2 -2
- package/src/modes/interactive-mode.ts +7 -2
- package/src/modes/print-mode.ts +1 -0
- package/src/modes/prompt-action-autocomplete.ts +5 -3
- package/src/modes/rpc/rpc-mode.ts +79 -30
- package/src/modes/rpc/rpc-types.ts +9 -1
- package/src/modes/theme/theme.ts +70 -0
- package/src/modes/types.ts +6 -1
- package/src/prompts/system/custom-system-prompt.md +5 -0
- package/src/prompts/system/system-prompt.md +6 -0
- package/src/prompts/tools/ask.md +1 -0
- package/src/prompts/tools/grep.md +1 -1
- package/src/prompts/tools/hashline.md +20 -5
- package/src/sdk.ts +26 -2
- package/src/session/agent-session.ts +18 -11
- package/src/system-prompt.ts +63 -2
- package/src/task/executor.ts +4 -0
- package/src/task/index.ts +2 -0
- package/src/tools/ask.ts +109 -61
- package/src/tools/ast-edit.ts +2 -16
- package/src/tools/ast-grep.ts +2 -17
- package/src/tools/browser.ts +35 -17
- package/src/tools/find.ts +1 -0
- package/src/tools/grep.ts +25 -34
- package/src/tools/index.ts +3 -0
- package/src/tools/path-utils.ts +7 -0
- package/src/tools/python.ts +3 -2
- package/src/tools/render-utils.ts +27 -0
- package/src/tui/tree-list.ts +51 -22
package/src/task/executor.ts
CHANGED
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
*/
|
|
6
6
|
import path from "node:path";
|
|
7
7
|
import type { AgentEvent, ThinkingLevel } from "@oh-my-pi/pi-agent-core";
|
|
8
|
+
import type { SearchDb } from "@oh-my-pi/pi-natives";
|
|
8
9
|
import { logger, untilAborted } from "@oh-my-pi/pi-utils";
|
|
9
10
|
import type { TSchema } from "@sinclair/typebox";
|
|
10
11
|
import Ajv, { type ValidateFunction } from "ajv";
|
|
@@ -147,6 +148,7 @@ export interface ExecutorOptions {
|
|
|
147
148
|
mcpManager?: MCPManager;
|
|
148
149
|
authStorage?: AuthStorage;
|
|
149
150
|
modelRegistry?: ModelRegistry;
|
|
151
|
+
searchDb?: SearchDb;
|
|
150
152
|
settings?: Settings;
|
|
151
153
|
}
|
|
152
154
|
|
|
@@ -950,6 +952,7 @@ export async function runSubprocess(options: ExecutorOptions): Promise<SingleRes
|
|
|
950
952
|
cwd: worktree ?? cwd,
|
|
951
953
|
authStorage,
|
|
952
954
|
modelRegistry,
|
|
955
|
+
searchDb: options.searchDb,
|
|
953
956
|
settings: subagentSettings,
|
|
954
957
|
model,
|
|
955
958
|
thinkingLevel: effectiveThinkingLevel,
|
|
@@ -1042,6 +1045,7 @@ export async function runSubprocess(options: ExecutorOptions): Promise<SingleRes
|
|
|
1042
1045
|
},
|
|
1043
1046
|
{
|
|
1044
1047
|
getModel: () => session.model,
|
|
1048
|
+
getSearchDb: () => session.searchDb,
|
|
1045
1049
|
isIdle: () => !session.isStreaming,
|
|
1046
1050
|
abort: () => session.abort(),
|
|
1047
1051
|
hasPendingMessages: () => session.queuedMessageCount > 0,
|
package/src/task/index.ts
CHANGED
|
@@ -775,6 +775,7 @@ export class TaskTool implements AgentTool<TaskSchema, TaskToolDetails, Theme> {
|
|
|
775
775
|
},
|
|
776
776
|
authStorage: this.session.authStorage,
|
|
777
777
|
modelRegistry: this.session.modelRegistry,
|
|
778
|
+
searchDb: this.session.searchDb,
|
|
778
779
|
settings: this.session.settings,
|
|
779
780
|
mcpManager: this.session.mcpManager,
|
|
780
781
|
contextFiles,
|
|
@@ -828,6 +829,7 @@ export class TaskTool implements AgentTool<TaskSchema, TaskToolDetails, Theme> {
|
|
|
828
829
|
},
|
|
829
830
|
authStorage: this.session.authStorage,
|
|
830
831
|
modelRegistry: this.session.modelRegistry,
|
|
832
|
+
searchDb: this.session.searchDb,
|
|
831
833
|
settings: this.session.settings,
|
|
832
834
|
mcpManager: this.session.mcpManager,
|
|
833
835
|
contextFiles,
|
package/src/tools/ask.ts
CHANGED
|
@@ -147,9 +147,11 @@ interface UIContext {
|
|
|
147
147
|
helpText?: string;
|
|
148
148
|
},
|
|
149
149
|
): Promise<string | undefined>;
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
150
|
+
editor(
|
|
151
|
+
title: string,
|
|
152
|
+
prefill?: string,
|
|
153
|
+
dialogOptions?: { signal?: AbortSignal },
|
|
154
|
+
editorOptions?: { promptStyle?: boolean },
|
|
153
155
|
): Promise<string | undefined>;
|
|
154
156
|
}
|
|
155
157
|
|
|
@@ -207,15 +209,11 @@ async function askSingleQuestion(
|
|
|
207
209
|
return { choice, timedOut: timeoutTriggered, navigation: navigationAction };
|
|
208
210
|
};
|
|
209
211
|
|
|
210
|
-
const
|
|
211
|
-
|
|
212
|
-
const
|
|
213
|
-
|
|
214
|
-
};
|
|
215
|
-
const input = signal
|
|
216
|
-
? await untilAborted(signal, () => ui.input("Enter your response:", { signal, timeout, onTimeout }))
|
|
217
|
-
: await ui.input("Enter your response:", { signal, timeout, onTimeout });
|
|
218
|
-
return { input, timedOut: inputTimedOut };
|
|
212
|
+
const promptForCustomInput = async (): Promise<{ input: string | undefined }> => {
|
|
213
|
+
const dialogOptions = signal ? { signal } : undefined;
|
|
214
|
+
const showCustomInput = () => ui.editor("Enter your response:", undefined, dialogOptions, { promptStyle: true });
|
|
215
|
+
const input = signal ? await untilAborted(signal, showCustomInput) : await showCustomInput();
|
|
216
|
+
return { input };
|
|
219
217
|
};
|
|
220
218
|
|
|
221
219
|
const promptWithProgress = navigation?.progressText ? `${question} (${navigation.progressText})` : question;
|
|
@@ -264,9 +262,11 @@ async function askSingleQuestion(
|
|
|
264
262
|
timedOut = true;
|
|
265
263
|
break;
|
|
266
264
|
}
|
|
267
|
-
const
|
|
268
|
-
if (
|
|
269
|
-
|
|
265
|
+
const customResult = await promptForCustomInput();
|
|
266
|
+
if (customResult.input === undefined) {
|
|
267
|
+
break;
|
|
268
|
+
}
|
|
269
|
+
customInput = customResult.input;
|
|
270
270
|
break;
|
|
271
271
|
}
|
|
272
272
|
|
|
@@ -306,7 +306,7 @@ async function askSingleQuestion(
|
|
|
306
306
|
if (previouslySelected) {
|
|
307
307
|
const selectedIndex = optionLabels.indexOf(previouslySelected);
|
|
308
308
|
if (selectedIndex >= 0) initialIndex = selectedIndex;
|
|
309
|
-
} else if (customInput) {
|
|
309
|
+
} else if (customInput !== undefined) {
|
|
310
310
|
initialIndex = displayLabels.length;
|
|
311
311
|
}
|
|
312
312
|
if (initialIndex !== undefined) {
|
|
@@ -330,11 +330,13 @@ async function askSingleQuestion(
|
|
|
330
330
|
}
|
|
331
331
|
} else if (choice === OTHER_OPTION) {
|
|
332
332
|
if (!selectTimedOut) {
|
|
333
|
-
const
|
|
334
|
-
if (
|
|
335
|
-
|
|
333
|
+
const customResult = await promptForCustomInput();
|
|
334
|
+
if (customResult.input !== undefined) {
|
|
335
|
+
customInput = customResult.input;
|
|
336
|
+
selectedOptions = [];
|
|
337
|
+
}
|
|
338
|
+
// If editor was dismissed (undefined), keep prior selectedOptions/customInput intact
|
|
336
339
|
}
|
|
337
|
-
selectedOptions = [];
|
|
338
340
|
} else {
|
|
339
341
|
selectedOptions = [stripRecommendedSuffix(choice)];
|
|
340
342
|
customInput = undefined;
|
|
@@ -344,7 +346,7 @@ async function askSingleQuestion(
|
|
|
344
346
|
}
|
|
345
347
|
}
|
|
346
348
|
|
|
347
|
-
if (timedOut && selectedOptions.length === 0 &&
|
|
349
|
+
if (timedOut && selectedOptions.length === 0 && customInput === undefined) {
|
|
348
350
|
selectedOptions = getAutoSelectionOnTimeout(optionLabels, recommended);
|
|
349
351
|
}
|
|
350
352
|
|
|
@@ -352,7 +354,7 @@ async function askSingleQuestion(
|
|
|
352
354
|
}
|
|
353
355
|
|
|
354
356
|
function formatQuestionResult(result: QuestionResult): string {
|
|
355
|
-
if (result.customInput) {
|
|
357
|
+
if (result.customInput !== undefined) {
|
|
356
358
|
return `${result.id}: "${result.customInput}"`;
|
|
357
359
|
}
|
|
358
360
|
if (result.selectedOptions.length > 0) {
|
|
@@ -415,7 +417,8 @@ export class AskTool implements AgentTool<typeof askSchema, AskToolDetails> {
|
|
|
415
417
|
const extensionUi = context.ui;
|
|
416
418
|
const ui: UIContext = {
|
|
417
419
|
select: (prompt, options, dialogOptions) => extensionUi.select(prompt, options, dialogOptions),
|
|
418
|
-
|
|
420
|
+
editor: (title, prefill, dialogOptions, editorOptions) =>
|
|
421
|
+
extensionUi.editor(title, prefill, dialogOptions, editorOptions),
|
|
419
422
|
};
|
|
420
423
|
|
|
421
424
|
// Determine timeout based on settings and plan mode
|
|
@@ -467,7 +470,7 @@ export class AskTool implements AgentTool<typeof askSchema, AskToolDetails> {
|
|
|
467
470
|
const [q] = params.questions;
|
|
468
471
|
const { optionLabels, selectedOptions, customInput, cancelled, timedOut } = await askQuestion(q);
|
|
469
472
|
|
|
470
|
-
if (!timedOut && (cancelled || (selectedOptions.length === 0 &&
|
|
473
|
+
if (!timedOut && (cancelled || (selectedOptions.length === 0 && customInput === undefined))) {
|
|
471
474
|
context.abort();
|
|
472
475
|
throw new ToolAbortError("Ask tool was cancelled by the user");
|
|
473
476
|
}
|
|
@@ -479,16 +482,23 @@ export class AskTool implements AgentTool<typeof askSchema, AskToolDetails> {
|
|
|
479
482
|
customInput,
|
|
480
483
|
};
|
|
481
484
|
|
|
482
|
-
|
|
483
|
-
if (
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
? `User selected: ${selectedOptions.join(", ")}`
|
|
488
|
-
: `User selected: ${selectedOptions[0]}`;
|
|
489
|
-
} else {
|
|
490
|
-
responseText = "User cancelled the selection";
|
|
485
|
+
const responseParts: string[] = [];
|
|
486
|
+
if (selectedOptions.length > 0) {
|
|
487
|
+
responseParts.push(
|
|
488
|
+
q.multi ? `User selected: ${selectedOptions.join(", ")}` : `User selected: ${selectedOptions[0]}`,
|
|
489
|
+
);
|
|
491
490
|
}
|
|
491
|
+
if (customInput !== undefined) {
|
|
492
|
+
responseParts.push(
|
|
493
|
+
customInput.includes("\n")
|
|
494
|
+
? `User provided custom input:\n${customInput
|
|
495
|
+
.split("\n")
|
|
496
|
+
.map(line => ` ${line}`)
|
|
497
|
+
.join("\n")}`
|
|
498
|
+
: `User provided custom input: ${customInput}`,
|
|
499
|
+
);
|
|
500
|
+
}
|
|
501
|
+
const responseText = responseParts.length > 0 ? responseParts.join("\n") : "User cancelled the selection";
|
|
492
502
|
|
|
493
503
|
return { content: [{ type: "text" as const, text: responseText }], details };
|
|
494
504
|
}
|
|
@@ -570,6 +580,25 @@ interface AskRenderArgs {
|
|
|
570
580
|
}>;
|
|
571
581
|
}
|
|
572
582
|
|
|
583
|
+
/** Render custom input as a single block with continuation lines (not one entry per line) */
|
|
584
|
+
function renderCustomInput(
|
|
585
|
+
uiTheme: Theme,
|
|
586
|
+
prefix: string,
|
|
587
|
+
customInput: string,
|
|
588
|
+
isLastEntry: boolean,
|
|
589
|
+
includeLeadingNewline = true,
|
|
590
|
+
): string {
|
|
591
|
+
const lines = customInput.split("\n");
|
|
592
|
+
const branch = isLastEntry ? uiTheme.tree.last : uiTheme.tree.branch;
|
|
593
|
+
const firstLine = lines[0] ?? "";
|
|
594
|
+
let text = `${includeLeadingNewline ? "\n" : ""}${prefix}${uiTheme.fg("dim", branch)} ${uiTheme.styledSymbol("status.success", "success")} ${uiTheme.fg("toolOutput", firstLine)}`;
|
|
595
|
+
const continuationIndent = isLastEntry ? " " : `${uiTheme.fg("dim", uiTheme.tree.vertical)} `;
|
|
596
|
+
for (let i = 1; i < lines.length; i++) {
|
|
597
|
+
text += `\n${prefix}${continuationIndent} ${uiTheme.fg("toolOutput", lines[i])}`;
|
|
598
|
+
}
|
|
599
|
+
return text;
|
|
600
|
+
}
|
|
601
|
+
|
|
573
602
|
export const askToolRenderer = {
|
|
574
603
|
renderCall(args: AskRenderArgs, _options: RenderResultOptions, uiTheme: Theme): Component {
|
|
575
604
|
const label = formatTitle("Ask", uiTheme);
|
|
@@ -658,7 +687,7 @@ export const askToolRenderer = {
|
|
|
658
687
|
// Multi-part results
|
|
659
688
|
if (details.results && details.results.length > 0) {
|
|
660
689
|
const hasAnySelection = details.results.some(
|
|
661
|
-
r => r.customInput || (r.selectedOptions && r.selectedOptions.length > 0),
|
|
690
|
+
r => r.customInput !== undefined || (r.selectedOptions && r.selectedOptions.length > 0),
|
|
662
691
|
);
|
|
663
692
|
const header = renderStatusLine(
|
|
664
693
|
{
|
|
@@ -676,7 +705,7 @@ export const askToolRenderer = {
|
|
|
676
705
|
const isLastQuestion = i === details.results.length - 1;
|
|
677
706
|
const branch = isLastQuestion ? uiTheme.tree.last : uiTheme.tree.branch;
|
|
678
707
|
const continuation = isLastQuestion ? " " : `${uiTheme.fg("dim", uiTheme.tree.vertical)} `;
|
|
679
|
-
const hasSelection = r.customInput || r.selectedOptions.length > 0;
|
|
708
|
+
const hasSelection = r.customInput !== undefined || r.selectedOptions.length > 0;
|
|
680
709
|
const statusIcon = hasSelection
|
|
681
710
|
? uiTheme.styledSymbol("status.success", "success")
|
|
682
711
|
: uiTheme.styledSymbol("status.warning", "warning");
|
|
@@ -686,23 +715,30 @@ export const askToolRenderer = {
|
|
|
686
715
|
);
|
|
687
716
|
container.addChild(new Markdown(r.question, 3, 0, mdTheme, accentStyle));
|
|
688
717
|
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
answerText = `${continuation}${uiTheme.fg("dim", uiTheme.tree.last)} ${uiTheme.styledSymbol("status.warning", "warning")} ${uiTheme.fg("warning", "Cancelled")}`;
|
|
718
|
+
const answerLines: string[] = [];
|
|
719
|
+
for (let j = 0; j < r.selectedOptions.length; j++) {
|
|
720
|
+
const isLast = j === r.selectedOptions.length - 1 && r.customInput === undefined;
|
|
721
|
+
const optBranch = isLast ? uiTheme.tree.last : uiTheme.tree.branch;
|
|
722
|
+
const selectedLabel = renderInlineMarkdown(r.selectedOptions[j], mdTheme, t =>
|
|
723
|
+
uiTheme.fg("toolOutput", t),
|
|
724
|
+
);
|
|
725
|
+
answerLines.push(
|
|
726
|
+
`${continuation}${uiTheme.fg("dim", optBranch)} ${uiTheme.fg("success", uiTheme.checkbox.checked)} ${selectedLabel}`,
|
|
727
|
+
);
|
|
728
|
+
}
|
|
729
|
+
if (answerLines.length > 0) {
|
|
730
|
+
container.addChild(new Text(answerLines.join("\n"), 0, 0));
|
|
703
731
|
}
|
|
704
|
-
if (
|
|
705
|
-
container.addChild(new Text(
|
|
732
|
+
if (r.customInput !== undefined) {
|
|
733
|
+
container.addChild(new Text(renderCustomInput(uiTheme, continuation, r.customInput, true, false), 0, 0));
|
|
734
|
+
} else if (r.selectedOptions.length === 0) {
|
|
735
|
+
container.addChild(
|
|
736
|
+
new Text(
|
|
737
|
+
`${continuation}${uiTheme.fg("dim", uiTheme.tree.last)} ${uiTheme.styledSymbol("status.warning", "warning")} ${uiTheme.fg("warning", "Cancelled")}`,
|
|
738
|
+
0,
|
|
739
|
+
0,
|
|
740
|
+
),
|
|
741
|
+
);
|
|
706
742
|
}
|
|
707
743
|
}
|
|
708
744
|
return container;
|
|
@@ -715,28 +751,40 @@ export const askToolRenderer = {
|
|
|
715
751
|
return new Text(fallback, 0, 0);
|
|
716
752
|
}
|
|
717
753
|
|
|
718
|
-
const hasSelection =
|
|
754
|
+
const hasSelection =
|
|
755
|
+
details.customInput !== undefined || (details.selectedOptions && details.selectedOptions.length > 0);
|
|
719
756
|
const header = renderStatusLine({ icon: hasSelection ? "success" : "warning", title: "Ask" }, uiTheme);
|
|
720
757
|
const container = new Container();
|
|
721
758
|
container.addChild(new Text(header, 0, 0));
|
|
722
759
|
container.addChild(new Markdown(details.question, 1, 0, mdTheme, accentStyle));
|
|
723
760
|
|
|
724
|
-
|
|
725
|
-
if (details.
|
|
726
|
-
answerText = ` ${uiTheme.fg("dim", uiTheme.tree.last)} ${uiTheme.styledSymbol("status.success", "success")} ${uiTheme.fg("toolOutput", details.customInput)}`;
|
|
727
|
-
} else if (details.selectedOptions && details.selectedOptions.length > 0) {
|
|
761
|
+
const answerLines: string[] = [];
|
|
762
|
+
if (details.selectedOptions && details.selectedOptions.length > 0) {
|
|
728
763
|
for (let i = 0; i < details.selectedOptions.length; i++) {
|
|
729
|
-
const isLast = i === details.selectedOptions.length - 1;
|
|
764
|
+
const isLast = i === details.selectedOptions.length - 1 && details.customInput === undefined;
|
|
730
765
|
const branch = isLast ? uiTheme.tree.last : uiTheme.tree.branch;
|
|
731
766
|
const selectedLabel = renderInlineMarkdown(details.selectedOptions[i], mdTheme, t =>
|
|
732
767
|
uiTheme.fg("toolOutput", t),
|
|
733
768
|
);
|
|
734
|
-
|
|
769
|
+
answerLines.push(
|
|
770
|
+
` ${uiTheme.fg("dim", branch)} ${uiTheme.fg("success", uiTheme.checkbox.checked)} ${selectedLabel}`,
|
|
771
|
+
);
|
|
735
772
|
}
|
|
736
|
-
} else {
|
|
737
|
-
answerText = ` ${uiTheme.fg("dim", uiTheme.tree.last)} ${uiTheme.styledSymbol("status.warning", "warning")} ${uiTheme.fg("warning", "Cancelled")}`;
|
|
738
773
|
}
|
|
739
|
-
|
|
774
|
+
if (answerLines.length > 0) {
|
|
775
|
+
container.addChild(new Text(answerLines.join("\n"), 0, 0));
|
|
776
|
+
}
|
|
777
|
+
if (details.customInput !== undefined) {
|
|
778
|
+
container.addChild(new Text(renderCustomInput(uiTheme, " ", details.customInput, true, false), 0, 0));
|
|
779
|
+
} else if (!details.selectedOptions || details.selectedOptions.length === 0) {
|
|
780
|
+
container.addChild(
|
|
781
|
+
new Text(
|
|
782
|
+
` ${uiTheme.fg("dim", uiTheme.tree.last)} ${uiTheme.styledSymbol("status.warning", "warning")} ${uiTheme.fg("warning", "Cancelled")}`,
|
|
783
|
+
0,
|
|
784
|
+
0,
|
|
785
|
+
),
|
|
786
|
+
);
|
|
787
|
+
}
|
|
740
788
|
|
|
741
789
|
return container;
|
|
742
790
|
},
|
package/src/tools/ast-edit.ts
CHANGED
|
@@ -435,18 +435,6 @@ export const astEditToolRenderer = {
|
|
|
435
435
|
group => !group[0]?.startsWith("Safety cap reached") && !group[0]?.startsWith("Parse issues:"),
|
|
436
436
|
);
|
|
437
437
|
|
|
438
|
-
const getCollapsedChangeLimit = (groups: string[][], maxLines: number): number => {
|
|
439
|
-
if (groups.length === 0) return 0;
|
|
440
|
-
let usedLines = 0;
|
|
441
|
-
let count = 0;
|
|
442
|
-
for (const group of groups) {
|
|
443
|
-
if (count > 0 && usedLines + group.length > maxLines) break;
|
|
444
|
-
usedLines += group.length;
|
|
445
|
-
count += 1;
|
|
446
|
-
if (usedLines >= maxLines) break;
|
|
447
|
-
}
|
|
448
|
-
return count;
|
|
449
|
-
};
|
|
450
438
|
const badge = { label: "proposed", color: "warning" as const };
|
|
451
439
|
const header = renderStatusLine(
|
|
452
440
|
{ icon: limitReached ? "warning" : "success", title: "AST Edit", description, badge, meta },
|
|
@@ -471,14 +459,12 @@ export const astEditToolRenderer = {
|
|
|
471
459
|
const { expanded } = options;
|
|
472
460
|
const key = new Hasher().bool(expanded).u32(width).digest();
|
|
473
461
|
if (cached?.key === key) return cached.lines;
|
|
474
|
-
const maxCollapsed = expanded
|
|
475
|
-
? changeGroups.length
|
|
476
|
-
: getCollapsedChangeLimit(changeGroups, COLLAPSED_CHANGE_LIMIT);
|
|
477
462
|
const changeLines = renderTreeList(
|
|
478
463
|
{
|
|
479
464
|
items: changeGroups,
|
|
480
465
|
expanded,
|
|
481
|
-
maxCollapsed,
|
|
466
|
+
maxCollapsed: changeGroups.length,
|
|
467
|
+
maxCollapsedLines: COLLAPSED_CHANGE_LIMIT,
|
|
482
468
|
itemType: "change",
|
|
483
469
|
renderItem: group =>
|
|
484
470
|
group.map(line => {
|
package/src/tools/ast-grep.ts
CHANGED
|
@@ -402,19 +402,6 @@ export const astGrepToolRenderer = {
|
|
|
402
402
|
group => !group[0]?.startsWith("Result limit reached") && !group[0]?.startsWith("Parse issues:"),
|
|
403
403
|
);
|
|
404
404
|
|
|
405
|
-
const getCollapsedMatchLimit = (groups: string[][], maxLines: number): number => {
|
|
406
|
-
if (groups.length === 0) return 0;
|
|
407
|
-
let usedLines = 0;
|
|
408
|
-
let count = 0;
|
|
409
|
-
for (const group of groups) {
|
|
410
|
-
if (count > 0 && usedLines + group.length > maxLines) break;
|
|
411
|
-
usedLines += group.length;
|
|
412
|
-
count += 1;
|
|
413
|
-
if (usedLines >= maxLines) break;
|
|
414
|
-
}
|
|
415
|
-
return count;
|
|
416
|
-
};
|
|
417
|
-
|
|
418
405
|
const extraLines: string[] = [];
|
|
419
406
|
if (limitReached) {
|
|
420
407
|
extraLines.push(uiTheme.fg("warning", "limit reached; narrow path pattern or increase limit"));
|
|
@@ -434,14 +421,12 @@ export const astGrepToolRenderer = {
|
|
|
434
421
|
const { expanded } = options;
|
|
435
422
|
const key = new Hasher().bool(expanded).u32(width).digest();
|
|
436
423
|
if (cached?.key === key) return cached.lines;
|
|
437
|
-
const maxCollapsed = expanded
|
|
438
|
-
? matchGroups.length
|
|
439
|
-
: getCollapsedMatchLimit(matchGroups, COLLAPSED_MATCH_LIMIT);
|
|
440
424
|
const matchLines = renderTreeList(
|
|
441
425
|
{
|
|
442
426
|
items: matchGroups,
|
|
443
427
|
expanded,
|
|
444
|
-
maxCollapsed,
|
|
428
|
+
maxCollapsed: matchGroups.length,
|
|
429
|
+
maxCollapsedLines: COLLAPSED_MATCH_LIMIT,
|
|
445
430
|
itemType: "match",
|
|
446
431
|
renderItem: group =>
|
|
447
432
|
group.map(line => {
|
package/src/tools/browser.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import * as fs from "node:fs/promises";
|
|
1
2
|
import * as os from "node:os";
|
|
2
3
|
import * as path from "node:path";
|
|
3
4
|
import { Readability } from "@mozilla/readability";
|
|
@@ -18,9 +19,10 @@ import type {
|
|
|
18
19
|
import { renderPromptTemplate } from "../config/prompt-templates";
|
|
19
20
|
import browserDescription from "../prompts/tools/browser.md" with { type: "text" };
|
|
20
21
|
import type { ToolSession } from "../sdk";
|
|
21
|
-
import {
|
|
22
|
+
import { resizeImage } from "../utils/image-resize";
|
|
22
23
|
import { htmlToBasicMarkdown } from "../web/scrapers/types";
|
|
23
24
|
import type { OutputMeta } from "./output-meta";
|
|
25
|
+
import { expandPath, resolveToCwd } from "./path-utils";
|
|
24
26
|
import stealthTamperingScript from "./puppeteer/00_stealth_tampering.txt" with { type: "text" };
|
|
25
27
|
import stealthActivityScript from "./puppeteer/01_stealth_activity.txt" with { type: "text" };
|
|
26
28
|
import stealthHairlineScript from "./puppeteer/02_stealth_hairline.txt" with { type: "text" };
|
|
@@ -35,6 +37,7 @@ import stealthPluginsScript from "./puppeteer/10_stealth_plugins.txt" with { typ
|
|
|
35
37
|
import stealthHardwareScript from "./puppeteer/11_stealth_hardware.txt" with { type: "text" };
|
|
36
38
|
import stealthCodecsScript from "./puppeteer/12_stealth_codecs.txt" with { type: "text" };
|
|
37
39
|
import stealthWorkerScript from "./puppeteer/13_stealth_worker.txt" with { type: "text" };
|
|
40
|
+
import { formatScreenshot } from "./render-utils";
|
|
38
41
|
import { ToolAbortError, ToolError, throwIfAborted } from "./tool-errors";
|
|
39
42
|
import { toolResult } from "./tool-result";
|
|
40
43
|
import { clampTimeout } from "./tool-timeouts";
|
|
@@ -1364,23 +1367,38 @@ export class BrowserTool implements AgentTool<typeof browserSchema, BrowserToolD
|
|
|
1364
1367
|
{ type: "image", data: buffer.toBase64(), mimeType: "image/png" },
|
|
1365
1368
|
{ maxBytes: 0.75 * 1024 * 1024 },
|
|
1366
1369
|
);
|
|
1367
|
-
|
|
1368
|
-
const
|
|
1369
|
-
|
|
1370
|
-
|
|
1371
|
-
|
|
1372
|
-
|
|
1373
|
-
|
|
1374
|
-
|
|
1375
|
-
|
|
1376
|
-
|
|
1377
|
-
|
|
1378
|
-
`
|
|
1379
|
-
|
|
1380
|
-
|
|
1381
|
-
lines.push(dimensionNote);
|
|
1370
|
+
// Resolve destination: user-defined path > screenshotDir (auto-named) > temp file.
|
|
1371
|
+
const screenshotDir = (() => {
|
|
1372
|
+
const v = this.session.settings.get("browser.screenshotDir") as string | undefined;
|
|
1373
|
+
return v ? expandPath(v) : undefined;
|
|
1374
|
+
})();
|
|
1375
|
+
const paramPath = params.path ? resolveToCwd(params.path as string, this.session.cwd) : undefined;
|
|
1376
|
+
let dest: string;
|
|
1377
|
+
if (paramPath) {
|
|
1378
|
+
dest = paramPath;
|
|
1379
|
+
} else if (screenshotDir) {
|
|
1380
|
+
const ts = new Date().toISOString().replace(/[:.]/g, "-").slice(0, -1);
|
|
1381
|
+
dest = path.join(screenshotDir, `screenshot-${ts}.png`);
|
|
1382
|
+
} else {
|
|
1383
|
+
dest = path.join(os.tmpdir(), `omp-sshots-${Snowflake.next()}.png`);
|
|
1382
1384
|
}
|
|
1383
|
-
|
|
1385
|
+
await fs.mkdir(path.dirname(dest), { recursive: true });
|
|
1386
|
+
// Full-res buffer when saving to a user-defined location; resized (API copy) for temp-only.
|
|
1387
|
+
const saveFullRes = !!(paramPath || screenshotDir);
|
|
1388
|
+
const savedBuffer = saveFullRes ? buffer : resized.buffer;
|
|
1389
|
+
const savedMimeType = saveFullRes ? "image/png" : resized.mimeType;
|
|
1390
|
+
await Bun.write(dest, savedBuffer);
|
|
1391
|
+
details.screenshotPath = dest;
|
|
1392
|
+
details.mimeType = savedMimeType;
|
|
1393
|
+
details.bytes = savedBuffer.length;
|
|
1394
|
+
|
|
1395
|
+
const lines = formatScreenshot({
|
|
1396
|
+
saveFullRes,
|
|
1397
|
+
savedMimeType,
|
|
1398
|
+
savedByteLength: savedBuffer.length,
|
|
1399
|
+
dest,
|
|
1400
|
+
resized,
|
|
1401
|
+
});
|
|
1384
1402
|
return toolResult(details)
|
|
1385
1403
|
.content([
|
|
1386
1404
|
{ type: "text", text: lines.join("\n") },
|
package/src/tools/find.ts
CHANGED
package/src/tools/grep.ts
CHANGED
|
@@ -169,23 +169,27 @@ export class GrepTool implements AgentTool<typeof grepSchema, GrepToolDetails> {
|
|
|
169
169
|
// Run grep
|
|
170
170
|
let result: GrepResult;
|
|
171
171
|
try {
|
|
172
|
-
result = await grep(
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
172
|
+
result = await grep(
|
|
173
|
+
{
|
|
174
|
+
pattern: normalizedPattern,
|
|
175
|
+
path: searchPath,
|
|
176
|
+
glob: globFilter,
|
|
177
|
+
type: type?.trim() || undefined,
|
|
178
|
+
ignoreCase,
|
|
179
|
+
multiline: effectiveMultiline,
|
|
180
|
+
hidden: true,
|
|
181
|
+
gitignore: useGitignore,
|
|
182
|
+
cache: false,
|
|
183
|
+
maxCount: internalLimit,
|
|
184
|
+
offset: normalizedOffset > 0 ? normalizedOffset : undefined,
|
|
185
|
+
contextBefore: normalizedContextBefore,
|
|
186
|
+
contextAfter: normalizedContextAfter,
|
|
187
|
+
maxColumns: DEFAULT_MAX_COLUMN,
|
|
188
|
+
mode: effectiveOutputMode,
|
|
189
|
+
},
|
|
190
|
+
undefined,
|
|
191
|
+
this.session.searchDb,
|
|
192
|
+
);
|
|
189
193
|
} catch (err) {
|
|
190
194
|
if (err instanceof Error && err.message.startsWith("regex parse error")) {
|
|
191
195
|
throw new ToolError(err.message);
|
|
@@ -457,6 +461,7 @@ export const grepToolRenderer = {
|
|
|
457
461
|
items: lines,
|
|
458
462
|
expanded,
|
|
459
463
|
maxCollapsed: COLLAPSED_TEXT_LIMIT,
|
|
464
|
+
maxCollapsedLines: COLLAPSED_TEXT_LIMIT,
|
|
460
465
|
itemType: "item",
|
|
461
466
|
renderItem: line => uiTheme.fg("toolOutput", line),
|
|
462
467
|
},
|
|
@@ -522,19 +527,6 @@ export const grepToolRenderer = {
|
|
|
522
527
|
}
|
|
523
528
|
}
|
|
524
529
|
|
|
525
|
-
const getCollapsedMatchLimit = (groups: string[][], maxLines: number): number => {
|
|
526
|
-
if (groups.length === 0) return 0;
|
|
527
|
-
let usedLines = 0;
|
|
528
|
-
let count = 0;
|
|
529
|
-
for (const group of groups) {
|
|
530
|
-
if (count > 0 && usedLines + group.length > maxLines) break;
|
|
531
|
-
usedLines += group.length;
|
|
532
|
-
count += 1;
|
|
533
|
-
if (usedLines >= maxLines) break;
|
|
534
|
-
}
|
|
535
|
-
return count;
|
|
536
|
-
};
|
|
537
|
-
|
|
538
530
|
const truncationReasons: string[] = [];
|
|
539
531
|
if (limits?.matchLimit) truncationReasons.push(`limit ${limits.matchLimit.reached} matches`);
|
|
540
532
|
if (limits?.resultLimit) truncationReasons.push(`limit ${limits.resultLimit.reached} results`);
|
|
@@ -551,14 +543,13 @@ export const grepToolRenderer = {
|
|
|
551
543
|
const { expanded } = options;
|
|
552
544
|
const key = new Hasher().bool(expanded).u32(width).digest();
|
|
553
545
|
if (cached?.key === key) return cached.lines;
|
|
554
|
-
const
|
|
555
|
-
? matchGroups.length
|
|
556
|
-
: getCollapsedMatchLimit(matchGroups, COLLAPSED_TEXT_LIMIT);
|
|
546
|
+
const collapsedMatchLineBudget = Math.max(COLLAPSED_TEXT_LIMIT - extraLines.length, 0);
|
|
557
547
|
const matchLines = renderTreeList(
|
|
558
548
|
{
|
|
559
549
|
items: matchGroups,
|
|
560
550
|
expanded,
|
|
561
|
-
maxCollapsed,
|
|
551
|
+
maxCollapsed: matchGroups.length,
|
|
552
|
+
maxCollapsedLines: collapsedMatchLineBudget,
|
|
562
553
|
itemType: "match",
|
|
563
554
|
renderItem: group =>
|
|
564
555
|
group.map(line => {
|
package/src/tools/index.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { AgentTool } from "@oh-my-pi/pi-agent-core";
|
|
2
|
+
import type { SearchDb } from "@oh-my-pi/pi-natives";
|
|
2
3
|
import { $env, logger } from "@oh-my-pi/pi-utils";
|
|
3
4
|
import type { AsyncJobManager } from "../async";
|
|
4
5
|
import type { PromptTemplate } from "../config/prompt-templates";
|
|
@@ -144,6 +145,8 @@ export interface ToolSession {
|
|
|
144
145
|
asyncJobManager?: AsyncJobManager;
|
|
145
146
|
/** Settings instance for passing to subagents */
|
|
146
147
|
settings: Settings;
|
|
148
|
+
/** Shared native search DB for grep/glob/fuzzyFind-backed workflows. */
|
|
149
|
+
searchDb?: SearchDb;
|
|
147
150
|
/** Plan mode state (if active) */
|
|
148
151
|
getPlanModeState?: () => PlanModeState | undefined;
|
|
149
152
|
/** Get compact conversation context for subagents (excludes tool results, system prompts) */
|
package/src/tools/path-utils.ts
CHANGED
|
@@ -102,9 +102,16 @@ export function expandPath(filePath: string): string {
|
|
|
102
102
|
/**
|
|
103
103
|
* Resolve a path relative to the given cwd.
|
|
104
104
|
* Handles ~ expansion and absolute paths.
|
|
105
|
+
*
|
|
106
|
+
* A bare root slash is treated as a workspace-root alias for tool inputs. Users
|
|
107
|
+
* often pass `/` to mean “search from here”, and letting tools escape to the
|
|
108
|
+
* filesystem root is almost never what they intended.
|
|
105
109
|
*/
|
|
106
110
|
export function resolveToCwd(filePath: string, cwd: string): string {
|
|
107
111
|
const expanded = expandPath(filePath);
|
|
112
|
+
if (/^\/+$/.test(expanded)) {
|
|
113
|
+
return cwd;
|
|
114
|
+
}
|
|
108
115
|
if (path.isAbsolute(expanded)) {
|
|
109
116
|
return expanded;
|
|
110
117
|
}
|
package/src/tools/python.ts
CHANGED
|
@@ -180,7 +180,8 @@ export class PythonTool implements AgentTool<typeof pythonSchema> {
|
|
|
180
180
|
// Clamp to reasonable range: 1s - 600s (10 min)
|
|
181
181
|
const timeoutSec = clampTimeout("python", rawTimeout);
|
|
182
182
|
const timeoutMs = timeoutSec * 1000;
|
|
183
|
-
const
|
|
183
|
+
const deadlineMs = Date.now() + timeoutMs;
|
|
184
|
+
const timeoutSignal = AbortSignal.timeout(Math.max(0, deadlineMs - Date.now()));
|
|
184
185
|
const combinedSignal = signal ? AbortSignal.any([signal, timeoutSignal]) : timeoutSignal;
|
|
185
186
|
let outputSink: OutputSink | undefined;
|
|
186
187
|
let outputSummary: OutputSummary | undefined;
|
|
@@ -267,7 +268,7 @@ export class PythonTool implements AgentTool<typeof pythonSchema> {
|
|
|
267
268
|
const sessionId = sessionFile ? `session:${sessionFile}:cwd:${commandCwd}` : `cwd:${commandCwd}`;
|
|
268
269
|
const baseExecutorOptions: Omit<PythonExecutorOptions, "reset"> = {
|
|
269
270
|
cwd: commandCwd,
|
|
270
|
-
|
|
271
|
+
deadlineMs,
|
|
271
272
|
signal: combinedSignal,
|
|
272
273
|
sessionId,
|
|
273
274
|
kernelMode: this.session.settings.get("python.kernelMode"),
|