@oh-my-pi/pi-coding-agent 14.5.7 → 14.5.9
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 +43 -0
- package/package.json +7 -7
- package/src/config/model-registry.ts +23 -1
- package/src/config/settings-schema.ts +23 -0
- package/src/edit/modes/atom.lark +7 -5
- package/src/edit/modes/atom.ts +462 -56
- package/src/edit/modes/hashline.ts +21 -1
- package/src/lsp/index.ts +2 -4
- package/src/lsp/render.ts +0 -3
- package/src/lsp/types.ts +1 -4
- package/src/lsp/utils.ts +18 -14
- package/src/modes/components/settings-defs.ts +10 -0
- package/src/modes/controllers/command-controller.ts +17 -0
- package/src/modes/controllers/event-controller.ts +14 -9
- package/src/modes/controllers/input-controller.ts +13 -1
- package/src/modes/interactive-mode.ts +44 -23
- package/src/modes/types.ts +5 -2
- package/src/modes/utils/context-usage.ts +294 -0
- package/src/prompts/tools/atom.md +99 -44
- package/src/prompts/tools/exit-plan-mode.md +5 -39
- package/src/prompts/tools/lsp.md +2 -3
- package/src/prompts/tools/recipe.md +16 -0
- package/src/prompts/tools/task.md +34 -147
- package/src/prompts/tools/todo-write.md +22 -64
- package/src/session/compaction/compaction.ts +35 -22
- package/src/session/session-dump-format.ts +1 -0
- package/src/slash-commands/builtin-registry.ts +12 -5
- package/src/tools/bash.ts +149 -115
- package/src/tools/debug.ts +57 -70
- package/src/tools/index.ts +11 -0
- package/src/tools/recipe/index.ts +80 -0
- package/src/tools/recipe/render.ts +19 -0
- package/src/tools/recipe/runner.ts +219 -0
- package/src/tools/recipe/runners/cargo.ts +131 -0
- package/src/tools/recipe/runners/index.ts +8 -0
- package/src/tools/recipe/runners/just.ts +73 -0
- package/src/tools/recipe/runners/make.ts +101 -0
- package/src/tools/recipe/runners/pkg.ts +165 -0
- package/src/tools/recipe/runners/task.ts +72 -0
- package/src/tools/renderers.ts +2 -0
package/src/tools/bash.ts
CHANGED
|
@@ -222,15 +222,6 @@ function extractPartialBashEnv(partialJson: string | undefined): Record<string,
|
|
|
222
222
|
return Object.keys(env).length > 0 ? env : undefined;
|
|
223
223
|
}
|
|
224
224
|
|
|
225
|
-
function getBashEnvForDisplay(args: BashRenderArgs): Record<string, string> | undefined {
|
|
226
|
-
// During streaming, partial-json parsing often does not surface env values until the object closes.
|
|
227
|
-
// Recover them from the raw JSON buffer so the pending bash preview can show `NAME="..." cmd` immediately,
|
|
228
|
-
// instead of rendering only the command and making the env assignment appear at the very end.
|
|
229
|
-
const partialEnv = extractPartialBashEnv(args.__partialJson);
|
|
230
|
-
if (partialEnv && args.env) return { ...partialEnv, ...args.env };
|
|
231
|
-
return args.env ?? partialEnv;
|
|
232
|
-
}
|
|
233
|
-
|
|
234
225
|
function formatTimeoutClampNotice(requestedTimeoutSec: number, effectiveTimeoutSec: number): string | undefined {
|
|
235
226
|
return requestedTimeoutSec !== effectiveTimeoutSec
|
|
236
227
|
? `Timeout clamped to ${effectiveTimeoutSec}s (requested ${requestedTimeoutSec}s; allowed range ${TOOL_TIMEOUTS.bash.min}-${TOOL_TIMEOUTS.bash.max}s).`
|
|
@@ -688,8 +679,7 @@ export class BashTool implements AgentTool<BashToolSchema, BashToolDetails> {
|
|
|
688
679
|
// =============================================================================
|
|
689
680
|
// TUI Renderer
|
|
690
681
|
// =============================================================================
|
|
691
|
-
|
|
692
|
-
interface BashRenderArgs {
|
|
682
|
+
export interface BashRenderArgs {
|
|
693
683
|
command?: string;
|
|
694
684
|
env?: Record<string, string>;
|
|
695
685
|
timeout?: number;
|
|
@@ -698,7 +688,7 @@ interface BashRenderArgs {
|
|
|
698
688
|
[key: string]: unknown;
|
|
699
689
|
}
|
|
700
690
|
|
|
701
|
-
interface BashRenderContext {
|
|
691
|
+
export interface BashRenderContext {
|
|
702
692
|
/** Raw output text */
|
|
703
693
|
output?: string;
|
|
704
694
|
/** Whether output came from artifact storage */
|
|
@@ -711,7 +701,29 @@ interface BashRenderContext {
|
|
|
711
701
|
timeout?: number;
|
|
712
702
|
}
|
|
713
703
|
|
|
714
|
-
|
|
704
|
+
export interface ShellRendererConfig<TArgs> {
|
|
705
|
+
resolveTitle: (args: TArgs | undefined, options: RenderResultOptions) => string;
|
|
706
|
+
resolveCommand?: (args: TArgs | undefined) => string | undefined;
|
|
707
|
+
resolveCwd?: (args: TArgs | undefined) => string | undefined;
|
|
708
|
+
resolveEnv?: (args: TArgs | undefined) => Record<string, string> | undefined;
|
|
709
|
+
}
|
|
710
|
+
|
|
711
|
+
function getPartialJson<TArgs>(args: TArgs | undefined): string | undefined {
|
|
712
|
+
if (!args || typeof args !== "object" || !("__partialJson" in args)) return undefined;
|
|
713
|
+
const value = (args as { __partialJson?: unknown }).__partialJson;
|
|
714
|
+
return typeof value === "string" ? value : undefined;
|
|
715
|
+
}
|
|
716
|
+
|
|
717
|
+
export function getBashEnvForDisplay(args: BashRenderArgs): Record<string, string> | undefined {
|
|
718
|
+
// During streaming, partial-json parsing often does not surface env values until the object closes.
|
|
719
|
+
// Recover them from the raw JSON buffer so the pending bash preview can show `NAME="..." cmd` immediately,
|
|
720
|
+
// instead of rendering only the command and making the env assignment appear at the very end.
|
|
721
|
+
const partialEnv = extractPartialBashEnv(args.__partialJson);
|
|
722
|
+
if (partialEnv && args.env) return { ...partialEnv, ...args.env };
|
|
723
|
+
return args.env ?? partialEnv;
|
|
724
|
+
}
|
|
725
|
+
|
|
726
|
+
export function formatBashCommand(args: BashRenderArgs): string {
|
|
715
727
|
const command = replaceTabs(args.command || "…");
|
|
716
728
|
const prompt = "$";
|
|
717
729
|
const cwd = getProjectDir();
|
|
@@ -720,113 +732,135 @@ function formatBashCommand(args: BashRenderArgs): string {
|
|
|
720
732
|
return displayWorkdir ? `${prompt} cd ${displayWorkdir} && ${renderedCommand}` : `${prompt} ${renderedCommand}`;
|
|
721
733
|
}
|
|
722
734
|
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
+
function toBashRenderArgs<TArgs>(args: TArgs | undefined, config: ShellRendererConfig<TArgs>): BashRenderArgs {
|
|
736
|
+
return {
|
|
737
|
+
command: config.resolveCommand?.(args),
|
|
738
|
+
cwd: config.resolveCwd?.(args),
|
|
739
|
+
env: config.resolveEnv?.(args),
|
|
740
|
+
__partialJson: getPartialJson(args),
|
|
741
|
+
};
|
|
742
|
+
}
|
|
743
|
+
|
|
744
|
+
export function createShellRenderer<TArgs>(config: ShellRendererConfig<TArgs>) {
|
|
745
|
+
return {
|
|
746
|
+
renderCall(args: TArgs, options: RenderResultOptions, uiTheme: Theme): Component {
|
|
747
|
+
const renderArgs = toBashRenderArgs(args, config);
|
|
748
|
+
const cmdText = formatBashCommand(renderArgs);
|
|
749
|
+
const title = config.resolveTitle(args, options);
|
|
750
|
+
const text = renderStatusLine({ icon: "pending", title, description: cmdText }, uiTheme);
|
|
751
|
+
return new Text(text, 0, 0);
|
|
735
752
|
},
|
|
736
|
-
options: RenderResultOptions & { renderContext?: BashRenderContext },
|
|
737
|
-
uiTheme: Theme,
|
|
738
|
-
args?: BashRenderArgs,
|
|
739
|
-
): Component {
|
|
740
|
-
const cmdText = args ? formatBashCommand(args) : undefined;
|
|
741
|
-
const isError = result.isError === true;
|
|
742
|
-
const icon = options.isPartial ? "pending" : isError ? "error" : "success";
|
|
743
|
-
const header = renderStatusLine({ icon, title: "Bash" }, uiTheme);
|
|
744
|
-
const details = result.details;
|
|
745
|
-
const outputBlock = new CachedOutputBlock();
|
|
746
753
|
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
754
|
+
renderResult(
|
|
755
|
+
result: {
|
|
756
|
+
content: Array<{ type: string; text?: string }>;
|
|
757
|
+
details?: BashToolDetails;
|
|
758
|
+
isError?: boolean;
|
|
759
|
+
},
|
|
760
|
+
options: RenderResultOptions & { renderContext?: BashRenderContext },
|
|
761
|
+
uiTheme: Theme,
|
|
762
|
+
args?: TArgs,
|
|
763
|
+
): Component {
|
|
764
|
+
const renderArgs = toBashRenderArgs(args, config);
|
|
765
|
+
const cmdText = args ? formatBashCommand(renderArgs) : undefined;
|
|
766
|
+
const isError = result.isError === true;
|
|
767
|
+
const icon = options.isPartial ? "pending" : isError ? "error" : "success";
|
|
768
|
+
const title = config.resolveTitle(args, options);
|
|
769
|
+
const header = renderStatusLine({ icon, title }, uiTheme);
|
|
770
|
+
const details = result.details;
|
|
771
|
+
const outputBlock = new CachedOutputBlock();
|
|
772
|
+
|
|
773
|
+
return {
|
|
774
|
+
render: (width: number): string[] => {
|
|
775
|
+
// REACTIVE: read mutable options at render time
|
|
776
|
+
const { renderContext } = options;
|
|
777
|
+
const expanded = renderContext?.expanded ?? options.expanded;
|
|
778
|
+
const previewLines = renderContext?.previewLines ?? BASH_DEFAULT_PREVIEW_LINES;
|
|
779
|
+
|
|
780
|
+
// Get output from context (preferred) or fall back to result content
|
|
781
|
+
const output = renderContext?.output ?? result.content?.find(c => c.type === "text")?.text ?? "";
|
|
782
|
+
const displayOutput = output.trimEnd();
|
|
783
|
+
const showingFullOutput = expanded && renderContext?.isFullOutput === true;
|
|
784
|
+
|
|
785
|
+
// Build truncation warning
|
|
786
|
+
const timeoutSeconds = details?.timeoutSeconds ?? renderContext?.timeout;
|
|
787
|
+
const requestedTimeoutSeconds = details?.requestedTimeoutSeconds;
|
|
788
|
+
const timeoutLabel =
|
|
789
|
+
typeof timeoutSeconds === "number"
|
|
790
|
+
? requestedTimeoutSeconds !== undefined && requestedTimeoutSeconds !== timeoutSeconds
|
|
791
|
+
? `Timeout: ${timeoutSeconds}s (requested ${requestedTimeoutSeconds}s clamped)`
|
|
792
|
+
: `Timeout: ${timeoutSeconds}s`
|
|
793
|
+
: undefined;
|
|
794
|
+
const timeoutLine =
|
|
795
|
+
timeoutLabel !== undefined
|
|
796
|
+
? uiTheme.fg("dim", `${uiTheme.format.bracketLeft}${timeoutLabel}${uiTheme.format.bracketRight}`)
|
|
797
|
+
: undefined;
|
|
798
|
+
let warningLine: string | undefined;
|
|
799
|
+
if (details?.meta?.truncation && !showingFullOutput) {
|
|
800
|
+
warningLine = formatStyledTruncationWarning(details.meta, uiTheme) ?? undefined;
|
|
801
|
+
}
|
|
776
802
|
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
outputLines.push(
|
|
786
|
-
...rawOutputLines.map((line, index) =>
|
|
787
|
-
sixelLineMask?.[index] ? line : uiTheme.fg("toolOutput", replaceTabs(line)),
|
|
788
|
-
),
|
|
789
|
-
);
|
|
790
|
-
} else if (expanded) {
|
|
791
|
-
outputLines.push(...rawOutputLines.map(line => uiTheme.fg("toolOutput", replaceTabs(line))));
|
|
792
|
-
} else {
|
|
793
|
-
const styledOutput = rawOutputLines
|
|
794
|
-
.map(line => uiTheme.fg("toolOutput", replaceTabs(line)))
|
|
795
|
-
.join("\n");
|
|
796
|
-
const textContent = styledOutput;
|
|
797
|
-
const result = truncateToVisualLines(textContent, previewLines, width);
|
|
798
|
-
if (result.skippedCount > 0) {
|
|
803
|
+
const outputLines: string[] = [];
|
|
804
|
+
const hasOutput = displayOutput.trim().length > 0;
|
|
805
|
+
const rawOutputLines = displayOutput.split("\n");
|
|
806
|
+
const sixelLineMask =
|
|
807
|
+
TERMINAL.imageProtocol === ImageProtocol.Sixel ? getSixelLineMask(rawOutputLines) : undefined;
|
|
808
|
+
const hasSixelOutput = sixelLineMask?.some(Boolean) ?? false;
|
|
809
|
+
if (hasOutput) {
|
|
810
|
+
if (hasSixelOutput) {
|
|
799
811
|
outputLines.push(
|
|
800
|
-
|
|
801
|
-
"
|
|
802
|
-
`… (${result.skippedCount} earlier lines, showing ${result.visualLines.length} of ${result.skippedCount + result.visualLines.length}) (ctrl+o to expand)`,
|
|
812
|
+
...rawOutputLines.map((line, index) =>
|
|
813
|
+
sixelLineMask?.[index] ? line : uiTheme.fg("toolOutput", replaceTabs(line)),
|
|
803
814
|
),
|
|
804
815
|
);
|
|
816
|
+
} else if (expanded) {
|
|
817
|
+
outputLines.push(...rawOutputLines.map(line => uiTheme.fg("toolOutput", replaceTabs(line))));
|
|
818
|
+
} else {
|
|
819
|
+
const styledOutput = rawOutputLines
|
|
820
|
+
.map(line => uiTheme.fg("toolOutput", replaceTabs(line)))
|
|
821
|
+
.join("\n");
|
|
822
|
+
const textContent = styledOutput;
|
|
823
|
+
const result = truncateToVisualLines(textContent, previewLines, width);
|
|
824
|
+
if (result.skippedCount > 0) {
|
|
825
|
+
outputLines.push(
|
|
826
|
+
uiTheme.fg(
|
|
827
|
+
"dim",
|
|
828
|
+
`… (${result.skippedCount} earlier lines, showing ${result.visualLines.length} of ${result.skippedCount + result.visualLines.length}) (ctrl+o to expand)`,
|
|
829
|
+
),
|
|
830
|
+
);
|
|
831
|
+
}
|
|
832
|
+
outputLines.push(...result.visualLines);
|
|
805
833
|
}
|
|
806
|
-
outputLines.push(...result.visualLines);
|
|
807
834
|
}
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
}
|
|
828
|
-
}
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
}
|
|
835
|
+
if (timeoutLine) outputLines.push(timeoutLine);
|
|
836
|
+
if (warningLine) outputLines.push(warningLine);
|
|
837
|
+
|
|
838
|
+
return outputBlock.render(
|
|
839
|
+
{
|
|
840
|
+
header,
|
|
841
|
+
state: options.isPartial ? "pending" : isError ? "error" : "success",
|
|
842
|
+
sections: [
|
|
843
|
+
{ lines: cmdText ? [uiTheme.fg("dim", cmdText)] : [] },
|
|
844
|
+
{ label: uiTheme.fg("toolTitle", "Output"), lines: outputLines },
|
|
845
|
+
],
|
|
846
|
+
width,
|
|
847
|
+
},
|
|
848
|
+
uiTheme,
|
|
849
|
+
);
|
|
850
|
+
},
|
|
851
|
+
invalidate: () => {
|
|
852
|
+
outputBlock.invalidate();
|
|
853
|
+
},
|
|
854
|
+
};
|
|
855
|
+
},
|
|
856
|
+
mergeCallAndResult: true,
|
|
857
|
+
inline: true,
|
|
858
|
+
};
|
|
859
|
+
}
|
|
860
|
+
|
|
861
|
+
export const bashToolRenderer = createShellRenderer<BashRenderArgs>({
|
|
862
|
+
resolveTitle: () => "Bash",
|
|
863
|
+
resolveCommand: args => args?.command,
|
|
864
|
+
resolveCwd: args => args?.cwd,
|
|
865
|
+
resolveEnv: args => args?.env,
|
|
866
|
+
});
|
package/src/tools/debug.ts
CHANGED
|
@@ -52,85 +52,72 @@ import { toolResult } from "./tool-result";
|
|
|
52
52
|
import { clampTimeout } from "./tool-timeouts";
|
|
53
53
|
|
|
54
54
|
const debugSchema = Type.Object({
|
|
55
|
-
action: StringEnum(
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
),
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
),
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
function: Type.Optional(Type.String({ description: "function name", examples: ["main", "handle_request"] })),
|
|
97
|
-
name: Type.Optional(Type.String({ description: "variable or data name", examples: ["counter", "buffer"] })),
|
|
98
|
-
condition: Type.Optional(Type.String({ description: "breakpoint condition", examples: ["i == 10", "x > 0"] })),
|
|
99
|
-
hit_condition: Type.Optional(Type.String({ description: "hit condition" })),
|
|
100
|
-
expression: Type.Optional(Type.String({ description: "expression to evaluate", examples: ["x + 1", "obj.field"] })),
|
|
55
|
+
action: StringEnum([
|
|
56
|
+
"launch",
|
|
57
|
+
"attach",
|
|
58
|
+
"set_breakpoint",
|
|
59
|
+
"remove_breakpoint",
|
|
60
|
+
"set_instruction_breakpoint",
|
|
61
|
+
"remove_instruction_breakpoint",
|
|
62
|
+
"data_breakpoint_info",
|
|
63
|
+
"set_data_breakpoint",
|
|
64
|
+
"remove_data_breakpoint",
|
|
65
|
+
"continue",
|
|
66
|
+
"step_over",
|
|
67
|
+
"step_in",
|
|
68
|
+
"step_out",
|
|
69
|
+
"pause",
|
|
70
|
+
"evaluate",
|
|
71
|
+
"stack_trace",
|
|
72
|
+
"threads",
|
|
73
|
+
"scopes",
|
|
74
|
+
"variables",
|
|
75
|
+
"disassemble",
|
|
76
|
+
"read_memory",
|
|
77
|
+
"write_memory",
|
|
78
|
+
"modules",
|
|
79
|
+
"loaded_sources",
|
|
80
|
+
"custom_request",
|
|
81
|
+
"output",
|
|
82
|
+
"terminate",
|
|
83
|
+
"sessions",
|
|
84
|
+
]),
|
|
85
|
+
program: Type.Optional(Type.String({ description: "program path" })),
|
|
86
|
+
args: Type.Optional(Type.Array(Type.String(), { description: "program arguments" })),
|
|
87
|
+
adapter: Type.Optional(Type.String({ description: "debugger adapter (gdb, lldb-dap, debugpy, dlv)" })),
|
|
88
|
+
cwd: Type.Optional(Type.String()),
|
|
89
|
+
file: Type.Optional(Type.String({ description: "source file" })),
|
|
90
|
+
line: Type.Optional(Type.Number({ description: "source line" })),
|
|
91
|
+
function: Type.Optional(Type.String({ description: "function name" })),
|
|
92
|
+
name: Type.Optional(Type.String({ description: "variable or data name" })),
|
|
93
|
+
condition: Type.Optional(Type.String({ description: "breakpoint condition" })),
|
|
94
|
+
hit_condition: Type.Optional(Type.String()),
|
|
95
|
+
expression: Type.Optional(Type.String({ description: "expression to evaluate" })),
|
|
101
96
|
context: Type.Optional(
|
|
102
|
-
Type.String({ description: "evaluate context
|
|
97
|
+
Type.String({ description: "evaluate context: watch | repl | hover | variables | clipboard" }),
|
|
103
98
|
),
|
|
104
|
-
frame_id: Type.Optional(Type.Number(
|
|
99
|
+
frame_id: Type.Optional(Type.Number()),
|
|
105
100
|
scope_id: Type.Optional(Type.Number({ description: "scope variables reference" })),
|
|
106
101
|
variable_ref: Type.Optional(Type.Number({ description: "variable reference" })),
|
|
107
|
-
pid: Type.Optional(Type.Number({ description: "process id for attach"
|
|
108
|
-
port: Type.Optional(Type.Number({ description: "remote attach port"
|
|
109
|
-
host: Type.Optional(Type.String({ description: "remote attach host"
|
|
102
|
+
pid: Type.Optional(Type.Number({ description: "process id for attach" })),
|
|
103
|
+
port: Type.Optional(Type.Number({ description: "remote attach port" })),
|
|
104
|
+
host: Type.Optional(Type.String({ description: "remote attach host" })),
|
|
110
105
|
levels: Type.Optional(Type.Number({ description: "max stack frames" })),
|
|
111
|
-
memory_reference: Type.Optional(
|
|
112
|
-
|
|
113
|
-
),
|
|
114
|
-
|
|
115
|
-
instruction_count: Type.Optional(Type.Number({ description: "instructions to disassemble" })),
|
|
116
|
-
instruction_offset: Type.Optional(Type.Number({ description: "instruction offset" })),
|
|
106
|
+
memory_reference: Type.Optional(Type.String({ description: "memory reference or address" })),
|
|
107
|
+
instruction_reference: Type.Optional(Type.String()),
|
|
108
|
+
instruction_count: Type.Optional(Type.Number()),
|
|
109
|
+
instruction_offset: Type.Optional(Type.Number()),
|
|
117
110
|
count: Type.Optional(Type.Number({ description: "bytes to read" })),
|
|
118
111
|
data: Type.Optional(Type.String({ description: "base64 memory payload" })),
|
|
119
112
|
data_id: Type.Optional(Type.String({ description: "data breakpoint id" })),
|
|
120
|
-
access_type: Type.Optional(
|
|
121
|
-
StringEnum(["read", "write", "readWrite"], { description: "data breakpoint access type" }),
|
|
122
|
-
),
|
|
113
|
+
access_type: Type.Optional(StringEnum(["read", "write", "readWrite"])),
|
|
123
114
|
command: Type.Optional(Type.String({ description: "custom dap request command" })),
|
|
124
|
-
arguments: Type.Optional(
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
),
|
|
129
|
-
|
|
130
|
-
resolve_symbols: Type.Optional(Type.Boolean({ description: "resolve symbols during disassembly" })),
|
|
131
|
-
allow_partial: Type.Optional(Type.Boolean({ description: "allow partial writes" })),
|
|
132
|
-
start_module: Type.Optional(Type.Number({ description: "modules start index" })),
|
|
133
|
-
module_count: Type.Optional(Type.Number({ description: "max modules to fetch" })),
|
|
115
|
+
arguments: Type.Optional(Type.Record(Type.String(), Type.Any(), { description: "custom request arguments" })),
|
|
116
|
+
offset: Type.Optional(Type.Number()),
|
|
117
|
+
resolve_symbols: Type.Optional(Type.Boolean()),
|
|
118
|
+
allow_partial: Type.Optional(Type.Boolean()),
|
|
119
|
+
start_module: Type.Optional(Type.Number()),
|
|
120
|
+
module_count: Type.Optional(Type.Number()),
|
|
134
121
|
timeout: Type.Optional(Type.Number({ description: "per-request timeout seconds" })),
|
|
135
122
|
});
|
|
136
123
|
|
package/src/tools/index.ts
CHANGED
|
@@ -37,6 +37,7 @@ import { NotebookTool } from "./notebook";
|
|
|
37
37
|
import { wrapToolWithMetaNotice } from "./output-meta";
|
|
38
38
|
import { PythonTool } from "./python";
|
|
39
39
|
import { ReadTool } from "./read";
|
|
40
|
+
import { RecipeTool } from "./recipe";
|
|
40
41
|
import { RenderMermaidTool } from "./render-mermaid";
|
|
41
42
|
import { createReportToolIssueTool, isAutoQaEnabled } from "./report-tool-issue";
|
|
42
43
|
import { ResolveTool } from "./resolve";
|
|
@@ -75,6 +76,7 @@ export * from "./job";
|
|
|
75
76
|
export * from "./notebook";
|
|
76
77
|
export * from "./python";
|
|
77
78
|
export * from "./read";
|
|
79
|
+
export * from "./recipe";
|
|
78
80
|
export * from "./render-mermaid";
|
|
79
81
|
export * from "./report-tool-issue";
|
|
80
82
|
export * from "./resolve";
|
|
@@ -224,6 +226,7 @@ export const BUILTIN_TOOLS: Record<string, ToolFactory> = {
|
|
|
224
226
|
rewind: RewindTool.createIf,
|
|
225
227
|
task: TaskTool.create,
|
|
226
228
|
job: JobTool.createIf,
|
|
229
|
+
recipe: RecipeTool.createIf,
|
|
227
230
|
irc: IrcTool.createIf,
|
|
228
231
|
todo_write: s => new TodoWriteTool(s),
|
|
229
232
|
web_search: s => new WebSearchTool(s),
|
|
@@ -370,6 +373,13 @@ export async function createTools(session: ToolSession, toolNames?: string[]): P
|
|
|
370
373
|
) {
|
|
371
374
|
requestedTools.push("ast_edit");
|
|
372
375
|
}
|
|
376
|
+
if (
|
|
377
|
+
requestedTools.includes("bash") &&
|
|
378
|
+
!requestedTools.includes("recipe") &&
|
|
379
|
+
session.settings.get("recipe.enabled")
|
|
380
|
+
) {
|
|
381
|
+
requestedTools.push("recipe");
|
|
382
|
+
}
|
|
373
383
|
}
|
|
374
384
|
const allTools: Record<string, ToolFactory> = { ...BUILTIN_TOOLS, ...HIDDEN_TOOLS };
|
|
375
385
|
const isToolAllowed = (name: string) => {
|
|
@@ -392,6 +402,7 @@ export async function createTools(session: ToolSession, toolNames?: string[]): P
|
|
|
392
402
|
if (name === "browser") return session.settings.get("browser.enabled");
|
|
393
403
|
if (name === "checkpoint" || name === "rewind") return session.settings.get("checkpoint.enabled");
|
|
394
404
|
if (name === "irc") return session.settings.get("irc.enabled");
|
|
405
|
+
if (name === "recipe") return session.settings.get("recipe.enabled");
|
|
395
406
|
if (name === "task") {
|
|
396
407
|
const maxDepth = session.settings.get("task.maxRecursionDepth") ?? 2;
|
|
397
408
|
const currentDepth = session.taskDepth ?? 0;
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import type { AgentTool, AgentToolContext, AgentToolResult, AgentToolUpdateCallback } from "@oh-my-pi/pi-agent-core";
|
|
2
|
+
import type { Component } from "@oh-my-pi/pi-tui";
|
|
3
|
+
import { prompt } from "@oh-my-pi/pi-utils";
|
|
4
|
+
import { type Static, Type } from "@sinclair/typebox";
|
|
5
|
+
import type { RenderResultOptions } from "../../extensibility/custom-tools/types";
|
|
6
|
+
import type { Theme } from "../../modes/theme/theme";
|
|
7
|
+
import recipeDescription from "../../prompts/tools/recipe.md" with { type: "text" };
|
|
8
|
+
import type { ToolSession } from "..";
|
|
9
|
+
import { type BashRenderContext, BashTool, type BashToolDetails } from "../bash";
|
|
10
|
+
import { createRecipeToolRenderer, type RecipeRenderArgs } from "./render";
|
|
11
|
+
import { buildPromptModel, type DetectedRunner, resolveCommand } from "./runner";
|
|
12
|
+
import { RUNNERS } from "./runners";
|
|
13
|
+
|
|
14
|
+
const recipeSchema = Type.Object({
|
|
15
|
+
op: Type.String({
|
|
16
|
+
description: 'task name and args, e.g. "test" or "build --release"',
|
|
17
|
+
examples: ["test", "build --release", "pkg:test --watch"],
|
|
18
|
+
}),
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
type RecipeParams = Static<typeof recipeSchema>;
|
|
22
|
+
|
|
23
|
+
type RecipeRenderResult = {
|
|
24
|
+
content: Array<{ type: string; text?: string }>;
|
|
25
|
+
details?: BashToolDetails;
|
|
26
|
+
isError?: boolean;
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
export class RecipeTool implements AgentTool<typeof recipeSchema, BashToolDetails, Theme> {
|
|
30
|
+
readonly name = "recipe";
|
|
31
|
+
readonly label = "Run";
|
|
32
|
+
readonly description: string;
|
|
33
|
+
readonly parameters = recipeSchema;
|
|
34
|
+
readonly strict = true;
|
|
35
|
+
readonly concurrency = "exclusive";
|
|
36
|
+
readonly mergeCallAndResult = true;
|
|
37
|
+
readonly inline = true;
|
|
38
|
+
readonly renderCall: (args: RecipeRenderArgs, options: RenderResultOptions, uiTheme: Theme) => Component;
|
|
39
|
+
readonly renderResult: (
|
|
40
|
+
result: RecipeRenderResult,
|
|
41
|
+
options: RenderResultOptions & { renderContext?: BashRenderContext },
|
|
42
|
+
uiTheme: Theme,
|
|
43
|
+
args?: RecipeRenderArgs,
|
|
44
|
+
) => Component;
|
|
45
|
+
|
|
46
|
+
readonly #bash: BashTool;
|
|
47
|
+
readonly #runners: DetectedRunner[];
|
|
48
|
+
|
|
49
|
+
constructor(session: ToolSession, runners: DetectedRunner[]) {
|
|
50
|
+
this.#runners = runners;
|
|
51
|
+
this.#bash = new BashTool(session);
|
|
52
|
+
this.description = prompt.render(recipeDescription, buildPromptModel(runners));
|
|
53
|
+
const renderer = createRecipeToolRenderer(runners);
|
|
54
|
+
this.renderCall = renderer.renderCall;
|
|
55
|
+
this.renderResult = renderer.renderResult;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
static async createIf(session: ToolSession): Promise<RecipeTool | null> {
|
|
59
|
+
if (!session.settings.get("recipe.enabled")) return null;
|
|
60
|
+
const detected = (await Promise.all(RUNNERS.map(runner => runner.detect(session.cwd)))).filter(
|
|
61
|
+
(runner): runner is DetectedRunner => runner !== null && runner.tasks.length > 0,
|
|
62
|
+
);
|
|
63
|
+
if (detected.length === 0) return null;
|
|
64
|
+
return new RecipeTool(session, detected);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
async execute(
|
|
68
|
+
toolCallId: string,
|
|
69
|
+
{ op }: RecipeParams,
|
|
70
|
+
signal?: AbortSignal,
|
|
71
|
+
onUpdate?: AgentToolUpdateCallback<BashToolDetails>,
|
|
72
|
+
ctx?: AgentToolContext,
|
|
73
|
+
): Promise<AgentToolResult<BashToolDetails>> {
|
|
74
|
+
const { command, cwd } = resolveCommand(op, this.#runners);
|
|
75
|
+
return await this.#bash.execute(toolCallId, { command, cwd }, signal, onUpdate, ctx);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
export * from "./runner";
|
|
80
|
+
export { tasksFromCargoMetadata } from "./runners/cargo";
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { createShellRenderer } from "../bash";
|
|
2
|
+
import type { DetectedRunner } from "./runner";
|
|
3
|
+
import { commandFromOp, cwdFromOp, titleFromOp } from "./runner";
|
|
4
|
+
|
|
5
|
+
export interface RecipeRenderArgs {
|
|
6
|
+
op?: string;
|
|
7
|
+
__partialJson?: string;
|
|
8
|
+
[key: string]: unknown;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export function createRecipeToolRenderer(runners: DetectedRunner[]) {
|
|
12
|
+
return createShellRenderer<RecipeRenderArgs>({
|
|
13
|
+
resolveTitle: args => titleFromOp(args?.op, runners),
|
|
14
|
+
resolveCommand: args => commandFromOp(args?.op, runners),
|
|
15
|
+
resolveCwd: args => cwdFromOp(args?.op, runners),
|
|
16
|
+
});
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export const recipeToolRenderer = createRecipeToolRenderer([]);
|