@oh-my-pi/pi-coding-agent 15.4.3 → 15.5.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 +81 -5
- package/dist/types/cli/args.d.ts +2 -0
- package/dist/types/cli/auth-broker-cli.d.ts +1 -1
- package/dist/types/commands/launch.d.ts +8 -0
- package/dist/types/config/settings-schema.d.ts +42 -1
- package/dist/types/edit/index.d.ts +2 -0
- package/dist/types/extensibility/custom-tools/types.d.ts +8 -2
- package/dist/types/extensibility/hooks/types.d.ts +4 -0
- package/dist/types/hashline/executor.d.ts +6 -3
- package/dist/types/lsp/index.d.ts +9 -1
- package/dist/types/mcp/client.d.ts +2 -1
- package/dist/types/mcp/oauth-discovery.d.ts +4 -3
- package/dist/types/mcp/timeout.d.ts +9 -0
- package/dist/types/mcp/types.d.ts +1 -1
- package/dist/types/sdk.d.ts +2 -0
- package/dist/types/session/streaming-output.d.ts +1 -1
- package/dist/types/task/index.d.ts +2 -0
- package/dist/types/task/types.d.ts +4 -0
- package/dist/types/tools/approval.d.ts +46 -0
- package/dist/types/tools/ask.d.ts +1 -0
- package/dist/types/tools/ast-edit.d.ts +2 -0
- package/dist/types/tools/ast-grep.d.ts +1 -0
- package/dist/types/tools/bash.d.ts +11 -1
- package/dist/types/tools/browser.d.ts +2 -0
- package/dist/types/tools/calculator.d.ts +1 -0
- package/dist/types/tools/checkpoint.d.ts +2 -0
- package/dist/types/tools/debug.d.ts +9 -1
- package/dist/types/tools/eval.d.ts +2 -0
- package/dist/types/tools/find.d.ts +10 -0
- package/dist/types/tools/gh.d.ts +2 -1
- package/dist/types/tools/hindsight-recall.d.ts +1 -0
- package/dist/types/tools/hindsight-reflect.d.ts +1 -0
- package/dist/types/tools/hindsight-retain.d.ts +1 -0
- package/dist/types/tools/inspect-image.d.ts +1 -0
- package/dist/types/tools/irc.d.ts +1 -0
- package/dist/types/tools/job.d.ts +1 -0
- package/dist/types/tools/read.d.ts +1 -0
- package/dist/types/tools/recipe/index.d.ts +1 -0
- package/dist/types/tools/render-mermaid.d.ts +1 -0
- package/dist/types/tools/resolve.d.ts +1 -0
- package/dist/types/tools/search-tool-bm25.d.ts +1 -0
- package/dist/types/tools/search.d.ts +1 -0
- package/dist/types/tools/ssh.d.ts +2 -0
- package/dist/types/tools/todo-write.d.ts +1 -0
- package/dist/types/tools/write.d.ts +2 -0
- package/dist/types/tools/yield.d.ts +1 -0
- package/dist/types/web/search/index.d.ts +1 -0
- package/package.json +7 -7
- package/src/cli/args.ts +14 -0
- package/src/cli/auth-broker-cli.ts +171 -22
- package/src/commands/auth-broker.ts +3 -0
- package/src/commands/launch.ts +16 -0
- package/src/config/mcp-schema.json +2 -2
- package/src/config/model-registry.ts +19 -4
- package/src/config/prompt-templates.ts +0 -125
- package/src/config/settings-schema.ts +59 -1
- package/src/config/settings.ts +2 -1
- package/src/dap/session.ts +35 -2
- package/src/discovery/builtin.ts +2 -2
- package/src/discovery/mcp-json.ts +1 -1
- package/src/edit/index.ts +26 -0
- package/src/edit/modes/patch.ts +1 -1
- package/src/edit/streaming.ts +12 -2
- package/src/exec/bash-executor.ts +6 -2
- package/src/extensibility/custom-commands/bundled/review/index.ts +18 -14
- package/src/extensibility/custom-tools/types.ts +16 -2
- package/src/extensibility/extensions/wrapper.ts +36 -1
- package/src/extensibility/hooks/types.ts +8 -1
- package/src/hashline/apply.ts +47 -2
- package/src/hashline/executor.ts +46 -24
- package/src/internal-urls/docs-index.generated.ts +8 -7
- package/src/lsp/edits.ts +82 -29
- package/src/lsp/index.ts +38 -1
- package/src/lsp/utils.ts +1 -1
- package/src/main.ts +6 -0
- package/src/mcp/client.ts +8 -6
- package/src/mcp/oauth-discovery.ts +120 -32
- package/src/mcp/oauth-flow.ts +34 -6
- package/src/mcp/timeout.ts +59 -0
- package/src/mcp/transports/http.ts +42 -44
- package/src/mcp/transports/stdio.ts +8 -5
- package/src/mcp/types.ts +1 -1
- package/src/modes/components/hook-editor.ts +11 -3
- package/src/modes/components/mcp-add-wizard.ts +6 -2
- package/src/modes/components/model-selector.ts +33 -11
- package/src/modes/controllers/command-controller.ts +6 -4
- package/src/modes/controllers/mcp-command-controller.ts +8 -4
- package/src/prompts/review-custom-request.md +22 -0
- package/src/prompts/review-headless-request.md +16 -0
- package/src/prompts/review-request.md +2 -3
- package/src/prompts/system/project-prompt.md +4 -0
- package/src/prompts/tools/debug.md +1 -0
- package/src/prompts/tools/find.md +4 -2
- package/src/prompts/tools/hashline.md +43 -93
- package/src/sdk.ts +47 -73
- package/src/session/agent-session.ts +93 -27
- package/src/session/streaming-output.ts +1 -1
- package/src/slash-commands/helpers/usage-report.ts +3 -1
- package/src/task/executor.ts +11 -0
- package/src/task/index.ts +19 -0
- package/src/task/render.ts +12 -2
- package/src/task/types.ts +4 -0
- package/src/tools/approval.ts +185 -0
- package/src/tools/ask.ts +1 -0
- package/src/tools/ast-edit.ts +25 -1
- package/src/tools/ast-grep.ts +1 -0
- package/src/tools/bash.ts +69 -1
- package/src/tools/browser/tab-supervisor.ts +1 -1
- package/src/tools/browser.ts +15 -0
- package/src/tools/calculator.ts +1 -0
- package/src/tools/checkpoint.ts +2 -0
- package/src/tools/debug.ts +38 -0
- package/src/tools/eval.ts +15 -0
- package/src/tools/find.ts +17 -8
- package/src/tools/gh.ts +21 -1
- package/src/tools/hindsight-recall.ts +1 -0
- package/src/tools/hindsight-reflect.ts +1 -0
- package/src/tools/hindsight-retain.ts +1 -0
- package/src/tools/image-gen.ts +1 -0
- package/src/tools/inspect-image.ts +1 -0
- package/src/tools/irc.ts +1 -0
- package/src/tools/job.ts +1 -0
- package/src/tools/path-utils.ts +14 -1
- package/src/tools/read.ts +1 -0
- package/src/tools/recipe/index.ts +1 -0
- package/src/tools/render-mermaid.ts +1 -0
- package/src/tools/report-tool-issue.ts +1 -0
- package/src/tools/resolve.ts +1 -0
- package/src/tools/review.ts +1 -0
- package/src/tools/search-tool-bm25.ts +1 -0
- package/src/tools/search.ts +1 -0
- package/src/tools/ssh.ts +8 -0
- package/src/tools/todo-write.ts +1 -0
- package/src/tools/write.ts +12 -1
- package/src/tools/yield.ts +1 -0
- package/src/web/search/index.ts +2 -0
package/src/task/executor.ts
CHANGED
|
@@ -532,6 +532,11 @@ function createSubagentSettings(baseSettings: Settings): Settings {
|
|
|
532
532
|
...snapshot,
|
|
533
533
|
"async.enabled": false,
|
|
534
534
|
"bash.autoBackground.enabled": false,
|
|
535
|
+
// Subagents run headless — there is no UI to confirm prompts against, so
|
|
536
|
+
// the parent task approval is the authorization boundary. Use yolo mode
|
|
537
|
+
// to preserve unattended subagent execution while still honoring any
|
|
538
|
+
// tool-level safety override that can be handled before execution.
|
|
539
|
+
"tools.approvalMode": "yolo",
|
|
535
540
|
});
|
|
536
541
|
}
|
|
537
542
|
|
|
@@ -1148,6 +1153,11 @@ export async function runSubprocess(options: ExecutorOptions): Promise<SingleRes
|
|
|
1148
1153
|
if (model?.contextWindow && model.contextWindow > 0) {
|
|
1149
1154
|
progress.contextWindow = model.contextWindow;
|
|
1150
1155
|
}
|
|
1156
|
+
if (model) {
|
|
1157
|
+
progress.resolvedModel = explicitThinkingLevel
|
|
1158
|
+
? `${model.provider}/${model.id}:${resolvedThinkingLevel}`
|
|
1159
|
+
: `${model.provider}/${model.id}`;
|
|
1160
|
+
}
|
|
1151
1161
|
const effectiveThinkingLevel = explicitThinkingLevel
|
|
1152
1162
|
? resolvedThinkingLevel
|
|
1153
1163
|
: (thinkingLevel ?? resolvedThinkingLevel);
|
|
@@ -1606,6 +1616,7 @@ export async function runSubprocess(options: ExecutorOptions): Promise<SingleRes
|
|
|
1606
1616
|
contextTokens: progress.contextTokens,
|
|
1607
1617
|
contextWindow: progress.contextWindow,
|
|
1608
1618
|
modelOverride,
|
|
1619
|
+
resolvedModel: progress.resolvedModel,
|
|
1609
1620
|
error: exitCode !== 0 && stderr ? stderr : undefined,
|
|
1610
1621
|
aborted: wasAborted,
|
|
1611
1622
|
abortReason: finalAbortReason,
|
package/src/task/index.ts
CHANGED
|
@@ -27,6 +27,7 @@ import planModeSubagentPrompt from "../prompts/system/plan-mode-subagent.md" wit
|
|
|
27
27
|
import subagentUserPromptTemplate from "../prompts/system/subagent-user-prompt.md" with { type: "text" };
|
|
28
28
|
import taskDescriptionTemplate from "../prompts/tools/task.md" with { type: "text" };
|
|
29
29
|
import taskSummaryTemplate from "../prompts/tools/task-summary.md" with { type: "text" };
|
|
30
|
+
import { truncateForPrompt } from "../tools/approval";
|
|
30
31
|
import { formatBytes, formatDuration } from "../tools/render-utils";
|
|
31
32
|
import {
|
|
32
33
|
type AgentDefinition,
|
|
@@ -214,6 +215,24 @@ function validateTaskModeParams(simpleMode: TaskSimpleMode, params: TaskParams):
|
|
|
214
215
|
*/
|
|
215
216
|
export class TaskTool implements AgentTool<TaskToolSchemaInstance, TaskToolDetails, Theme> {
|
|
216
217
|
readonly name = "task";
|
|
218
|
+
readonly approval = "exec" as const;
|
|
219
|
+
readonly formatApprovalDetails = (args: unknown): string[] => {
|
|
220
|
+
const params = args as Partial<TaskParams>;
|
|
221
|
+
const lines: string[] = [];
|
|
222
|
+
if (typeof params.agent === "string") {
|
|
223
|
+
lines.push(`Agent: ${truncateForPrompt(params.agent)}`);
|
|
224
|
+
}
|
|
225
|
+
const tasks = Array.isArray(params.tasks) ? params.tasks : [];
|
|
226
|
+
const firstTask = tasks[0];
|
|
227
|
+
if (firstTask) {
|
|
228
|
+
lines.push(`Task: ${truncateForPrompt(firstTask.id)}`);
|
|
229
|
+
lines.push(`Assignment:\n${truncateForPrompt(firstTask.assignment)}`);
|
|
230
|
+
if (tasks.length > 1) {
|
|
231
|
+
lines.push(`+${tasks.length - 1} more task${tasks.length === 2 ? "" : "s"}`);
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
return lines;
|
|
235
|
+
};
|
|
217
236
|
readonly label = "Task";
|
|
218
237
|
readonly summary = "Spawn a subagent to complete a parallel task";
|
|
219
238
|
readonly strict = true;
|
package/src/task/render.ts
CHANGED
|
@@ -8,6 +8,7 @@ import path from "node:path";
|
|
|
8
8
|
import type { Component } from "@oh-my-pi/pi-tui";
|
|
9
9
|
import { Container, Text } from "@oh-my-pi/pi-tui";
|
|
10
10
|
import { formatNumber } from "@oh-my-pi/pi-utils";
|
|
11
|
+
import { settings } from "../config/settings";
|
|
11
12
|
import type { RenderResultOptions } from "../extensibility/custom-tools/types";
|
|
12
13
|
import type { Theme } from "../modes/theme/theme";
|
|
13
14
|
import {
|
|
@@ -59,6 +60,8 @@ function appendAgentStats(
|
|
|
59
60
|
contextTokens?: number;
|
|
60
61
|
contextWindow?: number;
|
|
61
62
|
cost: number;
|
|
63
|
+
resolvedModel?: string;
|
|
64
|
+
showResolvedModelBadge?: boolean;
|
|
62
65
|
},
|
|
63
66
|
theme: Theme,
|
|
64
67
|
): string {
|
|
@@ -83,6 +86,9 @@ function appendAgentStats(
|
|
|
83
86
|
if (opts.cost > 0) {
|
|
84
87
|
line += `${theme.sep.dot}${theme.fg("statusLineCost", `$${opts.cost.toFixed(2)}`)}`;
|
|
85
88
|
}
|
|
89
|
+
if (opts.resolvedModel && opts.showResolvedModelBadge) {
|
|
90
|
+
line += `${theme.sep.dot}${theme.fg("dim", truncateToWidth(replaceTabs(opts.resolvedModel), 30))}`;
|
|
91
|
+
}
|
|
86
92
|
return line;
|
|
87
93
|
}
|
|
88
94
|
|
|
@@ -564,14 +570,15 @@ function renderAgentProgress(
|
|
|
564
570
|
statusLine += ` ${formatBadge(statusLabel, iconColor, theme)}`;
|
|
565
571
|
}
|
|
566
572
|
|
|
573
|
+
const showBadge = settings.get("task.showResolvedModelBadge");
|
|
567
574
|
if (progress.status === "running") {
|
|
568
575
|
if (!description) {
|
|
569
576
|
const taskPreview = truncateToWidth(progress.assignment ?? progress.task, 40);
|
|
570
577
|
statusLine += ` ${theme.fg("muted", taskPreview)}`;
|
|
571
578
|
}
|
|
572
|
-
statusLine = appendAgentStats(statusLine, progress, theme);
|
|
579
|
+
statusLine = appendAgentStats(statusLine, { ...progress, showResolvedModelBadge: showBadge }, theme);
|
|
573
580
|
} else if (progress.status === "completed") {
|
|
574
|
-
statusLine = appendAgentStats(statusLine, progress, theme);
|
|
581
|
+
statusLine = appendAgentStats(statusLine, { ...progress, showResolvedModelBadge: showBadge }, theme);
|
|
575
582
|
}
|
|
576
583
|
|
|
577
584
|
lines.push(statusLine);
|
|
@@ -838,6 +845,7 @@ function renderAgentResult(result: SingleResult, isLast: boolean, expanded: bool
|
|
|
838
845
|
iconColor,
|
|
839
846
|
theme,
|
|
840
847
|
)}`;
|
|
848
|
+
const showBadge = settings.get("task.showResolvedModelBadge");
|
|
841
849
|
statusLine = appendAgentStats(
|
|
842
850
|
statusLine,
|
|
843
851
|
{
|
|
@@ -845,6 +853,8 @@ function renderAgentResult(result: SingleResult, isLast: boolean, expanded: bool
|
|
|
845
853
|
contextTokens: result.contextTokens,
|
|
846
854
|
contextWindow: result.contextWindow,
|
|
847
855
|
cost: result.usage?.cost.total ?? 0,
|
|
856
|
+
resolvedModel: result.resolvedModel,
|
|
857
|
+
showResolvedModelBadge: showBadge,
|
|
848
858
|
},
|
|
849
859
|
theme,
|
|
850
860
|
);
|
package/src/task/types.ts
CHANGED
|
@@ -210,6 +210,8 @@ export interface AgentProgress {
|
|
|
210
210
|
cost: number;
|
|
211
211
|
durationMs: number;
|
|
212
212
|
modelOverride?: string | string[];
|
|
213
|
+
/** Resolved model display string in the form `<provider>/<id>`, optionally suffixed with `:<thinkingLevel>` when the level was set explicitly. Undefined when the model could not be resolved. */
|
|
214
|
+
resolvedModel?: string;
|
|
213
215
|
/** Data extracted by registered subprocess tool handlers (keyed by tool name) */
|
|
214
216
|
extractedToolData?: Record<string, unknown[]>;
|
|
215
217
|
/**
|
|
@@ -268,6 +270,8 @@ export interface SingleResult {
|
|
|
268
270
|
/** Model's context window in tokens, when known. */
|
|
269
271
|
contextWindow?: number;
|
|
270
272
|
modelOverride?: string | string[];
|
|
273
|
+
/** Resolved model display string in the form `<provider>/<id>`, optionally suffixed with `:<thinkingLevel>` when the level was set explicitly. Omitted from tool-result JSON when undefined to keep wire payloads small. */
|
|
274
|
+
resolvedModel?: string;
|
|
271
275
|
error?: string;
|
|
272
276
|
aborted?: boolean;
|
|
273
277
|
abortReason?: string;
|
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tool approval resolution.
|
|
3
|
+
*
|
|
4
|
+
* Approval policy is declared by each tool. This module only knows how to:
|
|
5
|
+
* - normalize user `tools.approval.<tool>: allow | deny | prompt` overrides,
|
|
6
|
+
* - compare a tool capability tier against the active approval mode,
|
|
7
|
+
* - format the generic approval prompt body.
|
|
8
|
+
*/
|
|
9
|
+
import type { AgentTool, ToolApprovalDecision, ToolTier } from "@oh-my-pi/pi-agent-core";
|
|
10
|
+
|
|
11
|
+
export type { ToolApproval, ToolApprovalDecision, ToolTier } from "@oh-my-pi/pi-agent-core";
|
|
12
|
+
|
|
13
|
+
export type ApprovalPolicy = "allow" | "deny" | "prompt";
|
|
14
|
+
export type ApprovalMode = "always-ask" | "write" | "yolo";
|
|
15
|
+
|
|
16
|
+
type ApprovalSubject = Pick<AgentTool, "name" | "approval" | "formatApprovalDetails">;
|
|
17
|
+
|
|
18
|
+
export interface ResolvedApproval {
|
|
19
|
+
policy: ApprovalPolicy;
|
|
20
|
+
tier: ToolTier;
|
|
21
|
+
reason?: string;
|
|
22
|
+
override: boolean;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
const POLICY_VALUES: ReadonlySet<ApprovalPolicy> = new Set(["allow", "deny", "prompt"]);
|
|
26
|
+
const TIER_VALUES: ReadonlySet<ToolTier> = new Set(["read", "write", "exec"]);
|
|
27
|
+
|
|
28
|
+
const TIER_RANK: Record<ToolTier, number> = {
|
|
29
|
+
read: 0,
|
|
30
|
+
write: 1,
|
|
31
|
+
exec: 2,
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
const APPROVAL_MODE_MAX_TIER: Record<ApprovalMode, ToolTier> = {
|
|
35
|
+
"always-ask": "read",
|
|
36
|
+
write: "write",
|
|
37
|
+
yolo: "exec",
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
const DEFAULT_PROMPT_TRUNCATE_CHARS = 2000;
|
|
41
|
+
|
|
42
|
+
/** Best-effort conversion of an arbitrary user-supplied value to a policy. */
|
|
43
|
+
function normalizePolicy(value: unknown): ApprovalPolicy | undefined {
|
|
44
|
+
if (typeof value !== "string") return undefined;
|
|
45
|
+
const lowered = value.trim().toLowerCase();
|
|
46
|
+
return POLICY_VALUES.has(lowered as ApprovalPolicy) ? (lowered as ApprovalPolicy) : undefined;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function isToolTier(value: unknown): value is ToolTier {
|
|
50
|
+
return typeof value === "string" && TIER_VALUES.has(value as ToolTier);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function normalizeDecision(value: unknown): Omit<ResolvedApproval, "policy"> {
|
|
54
|
+
if (isToolTier(value)) {
|
|
55
|
+
return { tier: value, override: false };
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
if (value && typeof value === "object" && !Array.isArray(value)) {
|
|
59
|
+
const record = value as Record<string, unknown>;
|
|
60
|
+
const tier = isToolTier(record.tier) ? record.tier : "exec";
|
|
61
|
+
const reason = typeof record.reason === "string" && record.reason.length > 0 ? record.reason : undefined;
|
|
62
|
+
return {
|
|
63
|
+
tier,
|
|
64
|
+
override: record.override === true,
|
|
65
|
+
...(reason ? { reason } : {}),
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
return { tier: "exec", override: false };
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
function getToolDecision(tool: ApprovalSubject, args: unknown): Omit<ResolvedApproval, "policy"> {
|
|
73
|
+
const approval = tool.approval;
|
|
74
|
+
const decision: ToolApprovalDecision | undefined = typeof approval === "function" ? approval(args) : approval;
|
|
75
|
+
return normalizeDecision(decision);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
function modeApprovesTier(mode: ApprovalMode, tier: ToolTier): boolean {
|
|
79
|
+
return TIER_RANK[tier] <= TIER_RANK[APPROVAL_MODE_MAX_TIER[mode]];
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Resolve approval policy for a tool call.
|
|
84
|
+
*
|
|
85
|
+
* Resolution order:
|
|
86
|
+
* 1. Tool `approval(args)` decision, defaulting to tier "exec" when omitted.
|
|
87
|
+
* 2. User per-tool override, if set and valid.
|
|
88
|
+
* 3. Active mode tier comparison.
|
|
89
|
+
*
|
|
90
|
+
* Tool decisions with `override: true` force a prompt in every mode unless the
|
|
91
|
+
* user explicitly denies the tool; deny remains the strongest policy.
|
|
92
|
+
*/
|
|
93
|
+
export function resolveApproval(
|
|
94
|
+
tool: ApprovalSubject,
|
|
95
|
+
args: unknown,
|
|
96
|
+
mode: ApprovalMode,
|
|
97
|
+
userConfig: Record<string, unknown> = {},
|
|
98
|
+
): ResolvedApproval {
|
|
99
|
+
const decision = getToolDecision(tool, args);
|
|
100
|
+
const userPolicy = Object.hasOwn(userConfig, tool.name) ? normalizePolicy(userConfig[tool.name]) : undefined;
|
|
101
|
+
|
|
102
|
+
if (decision.override) {
|
|
103
|
+
if (userPolicy === "deny") {
|
|
104
|
+
return { policy: "deny", tier: decision.tier, override: true };
|
|
105
|
+
}
|
|
106
|
+
return {
|
|
107
|
+
policy: "prompt",
|
|
108
|
+
tier: decision.tier,
|
|
109
|
+
override: true,
|
|
110
|
+
...(decision.reason ? { reason: decision.reason } : {}),
|
|
111
|
+
};
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
if (userPolicy) {
|
|
115
|
+
return { policy: userPolicy, tier: decision.tier, override: false };
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
if (modeApprovesTier(mode, decision.tier)) {
|
|
119
|
+
return { policy: "allow", tier: decision.tier, override: false };
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
return {
|
|
123
|
+
policy: "prompt",
|
|
124
|
+
tier: decision.tier,
|
|
125
|
+
override: false,
|
|
126
|
+
...(decision.reason ? { reason: decision.reason } : {}),
|
|
127
|
+
};
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/**
|
|
131
|
+
* Check if a tool call requires user approval.
|
|
132
|
+
*
|
|
133
|
+
* @throws Error if policy is 'deny'
|
|
134
|
+
* @returns Object with required flag and optional reason for the prompt
|
|
135
|
+
*/
|
|
136
|
+
export function requiresApproval(
|
|
137
|
+
tool: ApprovalSubject,
|
|
138
|
+
args: unknown,
|
|
139
|
+
mode: ApprovalMode,
|
|
140
|
+
userConfig: Record<string, unknown> = {},
|
|
141
|
+
): { required: boolean; reason?: string } {
|
|
142
|
+
const { policy, reason } = resolveApproval(tool, args, mode, userConfig);
|
|
143
|
+
|
|
144
|
+
if (policy === "deny") {
|
|
145
|
+
throw new Error(
|
|
146
|
+
`Tool "${tool.name}" is blocked by user policy.\n` +
|
|
147
|
+
`To allow: remove "tools.approval.${tool.name}: deny" from config.`,
|
|
148
|
+
);
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
if (policy === "prompt") return { required: true, reason };
|
|
152
|
+
return { required: false };
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
export function truncateForPrompt(value: string, maxChars = DEFAULT_PROMPT_TRUNCATE_CHARS): string {
|
|
156
|
+
if (value.length <= maxChars) return value;
|
|
157
|
+
const omitted = value.length - maxChars;
|
|
158
|
+
return `${value.slice(0, maxChars)}… (${omitted} chars truncated)`;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
/**
|
|
162
|
+
* Format the approval prompt body shown to the user.
|
|
163
|
+
*/
|
|
164
|
+
export function formatApprovalPrompt(tool: ApprovalSubject, args: unknown, reason?: string): string {
|
|
165
|
+
const lines = [`Allow tool: ${tool.name}`];
|
|
166
|
+
|
|
167
|
+
if (tool.name.startsWith("mcp__") && tool.approval === undefined) {
|
|
168
|
+
lines.push("Origin: MCP server tool");
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
if (reason) {
|
|
172
|
+
lines.push(`Reason: ${reason}`);
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
const details = tool.formatApprovalDetails?.(args);
|
|
176
|
+
if (typeof details === "string") {
|
|
177
|
+
if (details.length > 0) lines.push(details);
|
|
178
|
+
} else if (Array.isArray(details)) {
|
|
179
|
+
for (const detail of details) {
|
|
180
|
+
if (detail.length > 0) lines.push(detail);
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
return lines.join("\n");
|
|
185
|
+
}
|
package/src/tools/ask.ts
CHANGED
|
@@ -378,6 +378,7 @@ type AskParams = AskToolInput;
|
|
|
378
378
|
*/
|
|
379
379
|
export class AskTool implements AgentTool<typeof askSchema, AskToolDetails> {
|
|
380
380
|
readonly name = "ask";
|
|
381
|
+
readonly approval = "read" as const;
|
|
381
382
|
readonly label = "Ask";
|
|
382
383
|
readonly summary = "Ask the user a clarifying question";
|
|
383
384
|
readonly description: string;
|
package/src/tools/ast-edit.ts
CHANGED
|
@@ -12,10 +12,11 @@ import astEditDescription from "../prompts/tools/ast-edit.md" with { type: "text
|
|
|
12
12
|
import { Ellipsis, fileHyperlink, renderStatusLine, renderTreeList, truncateToWidth } from "../tui";
|
|
13
13
|
import { resolveFileDisplayMode } from "../utils/file-display-mode";
|
|
14
14
|
import type { ToolSession } from ".";
|
|
15
|
+
import { truncateForPrompt } from "./approval";
|
|
15
16
|
import { createFileRecorder, formatResultPath } from "./file-recorder";
|
|
16
17
|
import { formatGroupedFiles } from "./grouped-file-output";
|
|
17
18
|
import type { OutputMeta } from "./output-meta";
|
|
18
|
-
import { resolveToolSearchScope } from "./path-utils";
|
|
19
|
+
import { isInternalUrlPath, resolveToolSearchScope } from "./path-utils";
|
|
19
20
|
import {
|
|
20
21
|
appendParseErrorsBulletList,
|
|
21
22
|
capParseErrors,
|
|
@@ -162,6 +163,29 @@ export interface AstEditToolDetails {
|
|
|
162
163
|
|
|
163
164
|
export class AstEditTool implements AgentTool<typeof astEditSchema, AstEditToolDetails> {
|
|
164
165
|
readonly name = "ast_edit";
|
|
166
|
+
readonly approval = (args: unknown) => {
|
|
167
|
+
const paths = Array.isArray((args as Partial<z.infer<typeof astEditSchema>>).paths)
|
|
168
|
+
? ((args as Partial<z.infer<typeof astEditSchema>>).paths as string[])
|
|
169
|
+
: [];
|
|
170
|
+
return paths.length > 0 && paths.every(path => isInternalUrlPath(path)) ? "read" : "write";
|
|
171
|
+
};
|
|
172
|
+
readonly formatApprovalDetails = (args: unknown): string[] => {
|
|
173
|
+
const params = args as Partial<z.infer<typeof astEditSchema>>;
|
|
174
|
+
const lines: string[] = [];
|
|
175
|
+
const ops = Array.isArray(params.ops) ? params.ops : [];
|
|
176
|
+
const firstOp = ops[0];
|
|
177
|
+
if (firstOp) {
|
|
178
|
+
lines.push(`Pattern: ${truncateForPrompt(firstOp.pat)}`);
|
|
179
|
+
lines.push(`Replacement: ${truncateForPrompt(firstOp.out)}`);
|
|
180
|
+
if (ops.length > 1) {
|
|
181
|
+
lines.push(`+${ops.length - 1} more op${ops.length === 2 ? "" : "s"}`);
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
if (Array.isArray(params.paths) && params.paths.length > 0) {
|
|
185
|
+
lines.push(`Paths: ${truncateForPrompt(params.paths.join(", "))}`);
|
|
186
|
+
}
|
|
187
|
+
return lines;
|
|
188
|
+
};
|
|
165
189
|
readonly label = "AST Edit";
|
|
166
190
|
readonly summary = "Perform AST-aware code edits (structural refactoring)";
|
|
167
191
|
readonly description: string;
|
package/src/tools/ast-grep.ts
CHANGED
|
@@ -122,6 +122,7 @@ export interface AstGrepToolDetails {
|
|
|
122
122
|
|
|
123
123
|
export class AstGrepTool implements AgentTool<typeof astGrepSchema, AstGrepToolDetails> {
|
|
124
124
|
readonly name = "ast_grep";
|
|
125
|
+
readonly approval = "read" as const;
|
|
125
126
|
readonly label = "AST Grep";
|
|
126
127
|
readonly summary = "Search code with AST patterns (structural grep)";
|
|
127
128
|
readonly description: string;
|
package/src/tools/bash.ts
CHANGED
|
@@ -1,5 +1,11 @@
|
|
|
1
1
|
import * as fs from "node:fs";
|
|
2
|
-
import type {
|
|
2
|
+
import type {
|
|
3
|
+
AgentTool,
|
|
4
|
+
AgentToolContext,
|
|
5
|
+
AgentToolResult,
|
|
6
|
+
AgentToolUpdateCallback,
|
|
7
|
+
ToolApprovalDecision,
|
|
8
|
+
} from "@oh-my-pi/pi-agent-core";
|
|
3
9
|
import type { Component } from "@oh-my-pi/pi-tui";
|
|
4
10
|
import { ImageProtocol, TERMINAL, Text } from "@oh-my-pi/pi-tui";
|
|
5
11
|
import { getProjectDir, isEnoent, logger, prompt } from "@oh-my-pi/pi-utils";
|
|
@@ -17,6 +23,7 @@ import { renderStatusLine } from "../tui";
|
|
|
17
23
|
import { CachedOutputBlock } from "../tui/output-block";
|
|
18
24
|
import { getSixelLineMask } from "../utils/sixel";
|
|
19
25
|
import type { ToolSession } from ".";
|
|
26
|
+
import { truncateForPrompt } from "./approval";
|
|
20
27
|
import { applyBashFixups } from "./bash-command-fixup";
|
|
21
28
|
import { type BashInteractiveResult, runInteractiveBashPty } from "./bash-interactive";
|
|
22
29
|
import { checkBashInterception } from "./bash-interceptor";
|
|
@@ -34,6 +41,54 @@ export const BASH_DEFAULT_PREVIEW_LINES = 10;
|
|
|
34
41
|
const BASH_ENV_NAME_PATTERN = /^[A-Za-z_][A-Za-z0-9_]*$/;
|
|
35
42
|
const DEFAULT_AUTO_BACKGROUND_THRESHOLD_MS = 60_000;
|
|
36
43
|
|
|
44
|
+
/**
|
|
45
|
+
* Bash patterns that force an approval prompt even in yolo mode.
|
|
46
|
+
*
|
|
47
|
+
* Kept intentionally tight — the cost of a false positive is one extra prompt;
|
|
48
|
+
* the cost of a false negative is data loss or a compromised host. New patterns
|
|
49
|
+
* should target shapes that are virtually never legitimate in automation.
|
|
50
|
+
*/
|
|
51
|
+
export const CRITICAL_BASH_PATTERNS = [
|
|
52
|
+
// Recursive destruction.
|
|
53
|
+
/\brm\s+-[a-z]*[rRfF][a-z]*\s+\//i, // rm -rf /, rm -fr /, rm -r /, rm -f /…
|
|
54
|
+
/\bsudo\s+rm\b/i, // any `sudo rm`.
|
|
55
|
+
/\bchmod\s+-R\s+[0-7]+\s+\//i, // `chmod -R 777 /`.
|
|
56
|
+
/\bchmod\s+-R\s+[ugoa+\-=rwxXst,]+\s+\//, // `chmod -R u+x /`, `chmod -R u+rwx,o+w /etc` (symbolic mode, root target).
|
|
57
|
+
/\bchown\s+-R\s+\S+\s+\//i, // `chown -R user /`.
|
|
58
|
+
|
|
59
|
+
// Fork bomb (a few common spacings).
|
|
60
|
+
/:\(\)\s*\{\s*:\s*\|\s*:/i,
|
|
61
|
+
|
|
62
|
+
// Disk / filesystem destruction.
|
|
63
|
+
/>\s*\/dev\/sd[a-z]/i, // write to disk device.
|
|
64
|
+
/\bmkfs(\.|\b)/i, // format filesystem.
|
|
65
|
+
/\bdd\s+if=.+of=\/dev\//i, // dd to a device.
|
|
66
|
+
/\bshred\s+\/dev\//i,
|
|
67
|
+
/\bcryptsetup\b/i,
|
|
68
|
+
|
|
69
|
+
// System-config destruction.
|
|
70
|
+
/>\s*\/etc\/(?:passwd|shadow|sudoers)\b/i,
|
|
71
|
+
/\btee\s+(?:-a\s+)?\/etc\/(?:passwd|shadow|sudoers)\b/i, // `tee /etc/passwd`, `tee -a /etc/sudoers`.
|
|
72
|
+
|
|
73
|
+
// Remote-fetch-then-execute (curl/wget piped to a shell or process-subbed).
|
|
74
|
+
/\b(?:curl|wget|fetch)\b[^|]*\|\s*(?:bash|sh|zsh|fish)\b/i,
|
|
75
|
+
// Process-sub variants — `bash <(curl …)`, `source <(curl …)`, `. <(curl …)`. `.` and `source` are
|
|
76
|
+
// anchored to a command boundary so `find . -name` and similar don't false-positive.
|
|
77
|
+
/(?:^|[\s;&|(])(?:bash|sh|zsh|source|\.)\s+<\(\s*(?:curl|wget|fetch)\b/i,
|
|
78
|
+
// `eval "$(curl …)"` / `eval $(curl …)` / `eval \`curl …\``.
|
|
79
|
+
/\beval\s+["'`]?\$\(\s*(?:curl|wget|fetch)\b|\beval\s+`\s*(?:curl|wget|fetch)\b/i,
|
|
80
|
+
|
|
81
|
+
// Process/host control.
|
|
82
|
+
/\bkill\s+-9\s+1\b/, // kill PID 1.
|
|
83
|
+
// Process/host control — must sit at command position so `npm run reboot-tests`
|
|
84
|
+
// or `echo 'shutdown the queue'` don't false-positive.
|
|
85
|
+
/(?:^|[\s;&|(])(?:shutdown|poweroff|reboot|halt)(?:\s|$|[;|&])/i,
|
|
86
|
+
/(?:^|[\s;&|(])init\s+0\b/i,
|
|
87
|
+
|
|
88
|
+
// Network-shell exfil.
|
|
89
|
+
/\bnc\b[^|;]*\s-[a-zA-Z]*[ec][a-zA-Z]*\s/i, // `nc -e` / `nc -c`.
|
|
90
|
+
] as const;
|
|
91
|
+
|
|
37
92
|
async function saveBashOriginalArtifact(session: ToolSession, originalText: string): Promise<string | undefined> {
|
|
38
93
|
try {
|
|
39
94
|
const alloc = await session.allocateOutputArtifact?.("bash-original");
|
|
@@ -224,6 +279,19 @@ function formatTimeoutClampNotice(requestedTimeoutSec: number, effectiveTimeoutS
|
|
|
224
279
|
*/
|
|
225
280
|
export class BashTool implements AgentTool<BashToolSchema, BashToolDetails> {
|
|
226
281
|
readonly name = "bash";
|
|
282
|
+
readonly approval = (args: unknown): ToolApprovalDecision => {
|
|
283
|
+
const rawCommand = (args as Partial<BashToolInput>).command;
|
|
284
|
+
const command = typeof rawCommand === "string" ? rawCommand : "";
|
|
285
|
+
if (command !== "" && CRITICAL_BASH_PATTERNS.some(pattern => pattern.test(command))) {
|
|
286
|
+
return { tier: "exec", override: true, reason: "Critical pattern detected" };
|
|
287
|
+
}
|
|
288
|
+
return "exec";
|
|
289
|
+
};
|
|
290
|
+
readonly formatApprovalDetails = (args: unknown): string[] => {
|
|
291
|
+
const rawCommand = (args as Partial<BashToolInput>).command;
|
|
292
|
+
const command = typeof rawCommand === "string" ? rawCommand : "(missing)";
|
|
293
|
+
return [`Command: ${truncateForPrompt(command)}`];
|
|
294
|
+
};
|
|
227
295
|
readonly label = "Bash";
|
|
228
296
|
readonly loadMode = "essential";
|
|
229
297
|
readonly description: string;
|
|
@@ -105,7 +105,7 @@ export async function acquireTab(
|
|
|
105
105
|
await runInTabWithSnapshot(
|
|
106
106
|
name,
|
|
107
107
|
{
|
|
108
|
-
code: `await tab.goto(${JSON.stringify(opts.url)}, { waitUntil: ${JSON.stringify(opts.waitUntil ?? "
|
|
108
|
+
code: `await tab.goto(${JSON.stringify(opts.url)}, { waitUntil: ${JSON.stringify(opts.waitUntil ?? "load")} });`,
|
|
109
109
|
timeoutMs: opts.timeoutMs,
|
|
110
110
|
signal: opts.signal,
|
|
111
111
|
},
|
package/src/tools/browser.ts
CHANGED
|
@@ -3,6 +3,7 @@ import { prompt, untilAborted } from "@oh-my-pi/pi-utils";
|
|
|
3
3
|
import * as z from "zod/v4";
|
|
4
4
|
import browserDescription from "../prompts/tools/browser.md" with { type: "text" };
|
|
5
5
|
import type { ToolSession } from "../sdk";
|
|
6
|
+
import { truncateForPrompt } from "./approval";
|
|
6
7
|
import { acquireBrowser, type BrowserHandle, type BrowserKind, type BrowserKindTag } from "./browser/registry";
|
|
7
8
|
import type { Observation, ScreenshotResult } from "./browser/tab-protocol";
|
|
8
9
|
import { acquireTab, dropHeadlessTabs, getTab, releaseAllTabs, releaseTab, runInTab } from "./browser/tab-supervisor";
|
|
@@ -87,6 +88,20 @@ function resolveBrowserKind(params: BrowserParams, session: ToolSession): Browse
|
|
|
87
88
|
*/
|
|
88
89
|
export class BrowserTool implements AgentTool<typeof browserSchema, BrowserToolDetails> {
|
|
89
90
|
readonly name = "browser";
|
|
91
|
+
readonly approval = "exec" as const;
|
|
92
|
+
readonly formatApprovalDetails = (args: unknown): string[] => {
|
|
93
|
+
const params = args as Partial<BrowserParams>;
|
|
94
|
+
const lines = [`Action: ${typeof params.action === "string" ? params.action : "(missing)"}`];
|
|
95
|
+
const tabName = typeof params.name === "string" ? params.name : DEFAULT_TAB_NAME;
|
|
96
|
+
lines.push(`Tab: ${truncateForPrompt(tabName)}`);
|
|
97
|
+
if (typeof params.url === "string" && params.url.length > 0) {
|
|
98
|
+
lines.push(`URL: ${truncateForPrompt(params.url)}`);
|
|
99
|
+
}
|
|
100
|
+
if (typeof params.code === "string" && params.code.length > 0) {
|
|
101
|
+
lines.push(`Code:\n${truncateForPrompt(params.code)}`);
|
|
102
|
+
}
|
|
103
|
+
return lines;
|
|
104
|
+
};
|
|
90
105
|
readonly label = "Browser";
|
|
91
106
|
readonly loadMode = "discoverable";
|
|
92
107
|
readonly summary = "Control a headless browser to navigate and interact with web pages";
|
package/src/tools/calculator.ts
CHANGED
|
@@ -396,6 +396,7 @@ type CalculatorParams = z.infer<typeof calculatorSchema>;
|
|
|
396
396
|
*/
|
|
397
397
|
export class CalculatorTool implements AgentTool<typeof calculatorSchema, CalculatorToolDetails> {
|
|
398
398
|
readonly name = "calc";
|
|
399
|
+
readonly approval = "read" as const;
|
|
399
400
|
readonly label = "Calc";
|
|
400
401
|
readonly summary = "Evaluate a mathematical expression";
|
|
401
402
|
readonly loadMode = "discoverable";
|
package/src/tools/checkpoint.ts
CHANGED
|
@@ -48,6 +48,7 @@ function isTopLevelSession(session: ToolSession): boolean {
|
|
|
48
48
|
|
|
49
49
|
export class CheckpointTool implements AgentTool<typeof checkpointSchema, CheckpointToolDetails> {
|
|
50
50
|
readonly name = "checkpoint";
|
|
51
|
+
readonly approval = "read" as const;
|
|
51
52
|
readonly label = "Checkpoint";
|
|
52
53
|
readonly summary = "Create a git-based checkpoint to save and restore session state";
|
|
53
54
|
readonly description: string;
|
|
@@ -93,6 +94,7 @@ export class CheckpointTool implements AgentTool<typeof checkpointSchema, Checkp
|
|
|
93
94
|
|
|
94
95
|
export class RewindTool implements AgentTool<typeof rewindSchema, RewindToolDetails> {
|
|
95
96
|
readonly name = "rewind";
|
|
97
|
+
readonly approval = "read" as const;
|
|
96
98
|
readonly label = "Rewind";
|
|
97
99
|
readonly summary = "Rewind to a previously created checkpoint";
|
|
98
100
|
readonly description: string;
|
package/src/tools/debug.ts
CHANGED
|
@@ -5,6 +5,7 @@ import type {
|
|
|
5
5
|
AgentToolResult,
|
|
6
6
|
AgentToolUpdateCallback,
|
|
7
7
|
RenderResultOptions,
|
|
8
|
+
ToolApprovalDecision,
|
|
8
9
|
} from "@oh-my-pi/pi-agent-core";
|
|
9
10
|
import { type Component, Text } from "@oh-my-pi/pi-tui";
|
|
10
11
|
import { isEnoent, prompt } from "@oh-my-pi/pi-utils";
|
|
@@ -37,6 +38,7 @@ import debugDescription from "../prompts/tools/debug.md" with { type: "text" };
|
|
|
37
38
|
import { renderStatusLine } from "../tui";
|
|
38
39
|
import { CachedOutputBlock } from "../tui/output-block";
|
|
39
40
|
import type { ToolSession } from ".";
|
|
41
|
+
import { truncateForPrompt } from "./approval";
|
|
40
42
|
import type { OutputMeta } from "./output-meta";
|
|
41
43
|
import { formatPathRelativeToCwd, resolveToCwd } from "./path-utils";
|
|
42
44
|
import {
|
|
@@ -51,6 +53,23 @@ import { ToolError } from "./tool-errors";
|
|
|
51
53
|
import { toolResult } from "./tool-result";
|
|
52
54
|
import { clampTimeout } from "./tool-timeouts";
|
|
53
55
|
|
|
56
|
+
/**
|
|
57
|
+
* DAP debug actions that only read program state (no mutation, no execution).
|
|
58
|
+
* Execution-side actions (`launch`, `attach`, `continue`, `step_*`, `pause`,
|
|
59
|
+
* `evaluate`, breakpoint mutations, memory writes) are exec-tier.
|
|
60
|
+
*/
|
|
61
|
+
export const DEBUG_READONLY_ACTIONS: ReadonlySet<string> = new Set([
|
|
62
|
+
"output",
|
|
63
|
+
"threads",
|
|
64
|
+
"stack_trace",
|
|
65
|
+
"scopes",
|
|
66
|
+
"variables",
|
|
67
|
+
"disassemble",
|
|
68
|
+
"read_memory",
|
|
69
|
+
"loaded_sources",
|
|
70
|
+
"modules",
|
|
71
|
+
"sessions",
|
|
72
|
+
]);
|
|
54
73
|
const debugSchema = z.object({
|
|
55
74
|
action: z.enum([
|
|
56
75
|
"launch",
|
|
@@ -609,6 +628,19 @@ export const debugToolRenderer = {
|
|
|
609
628
|
|
|
610
629
|
export class DebugTool implements AgentTool<typeof debugSchema, DebugToolDetails> {
|
|
611
630
|
readonly name = "debug";
|
|
631
|
+
readonly approval = (args: unknown): ToolApprovalDecision => {
|
|
632
|
+
const rawAction = (args as Partial<DebugParams>).action;
|
|
633
|
+
const action = typeof rawAction === "string" ? rawAction.toLowerCase() : "";
|
|
634
|
+
return DEBUG_READONLY_ACTIONS.has(action) ? "read" : "exec";
|
|
635
|
+
};
|
|
636
|
+
readonly formatApprovalDetails = (args: unknown): string[] => {
|
|
637
|
+
const params = args as Partial<DebugParams>;
|
|
638
|
+
const lines = [`Action: ${typeof params.action === "string" ? params.action : "(missing)"}`];
|
|
639
|
+
if (typeof params.program === "string" && params.program.length > 0) {
|
|
640
|
+
lines.push(`Program: ${truncateForPrompt(params.program)}`);
|
|
641
|
+
}
|
|
642
|
+
return lines;
|
|
643
|
+
};
|
|
612
644
|
readonly label = "Debug";
|
|
613
645
|
readonly summary = "Debug a running process with DAP (debugger adapter protocol)";
|
|
614
646
|
readonly description: string;
|
|
@@ -647,6 +679,9 @@ export class DebugTool implements AgentTool<typeof debugSchema, DebugToolDetails
|
|
|
647
679
|
await validateLaunchProgram(program, commandCwd);
|
|
648
680
|
const adapter = selectLaunchAdapter(program, commandCwd, params.adapter);
|
|
649
681
|
if (!adapter) {
|
|
682
|
+
if (params.adapter === "debugpy") {
|
|
683
|
+
throw new ToolError("adapter 'debugpy' is not available: python not found in PATH");
|
|
684
|
+
}
|
|
650
685
|
throw new ToolError(
|
|
651
686
|
`No debugger adapter available. Installed adapters: ${getConfiguredAdapters(commandCwd)}`,
|
|
652
687
|
);
|
|
@@ -667,6 +702,9 @@ export class DebugTool implements AgentTool<typeof debugSchema, DebugToolDetails
|
|
|
667
702
|
const commandCwd = params.cwd ? resolveToCwd(params.cwd, this.session.cwd) : this.session.cwd;
|
|
668
703
|
const adapter = selectAttachAdapter(commandCwd, params.adapter, params.port);
|
|
669
704
|
if (!adapter) {
|
|
705
|
+
if (params.adapter === "debugpy") {
|
|
706
|
+
throw new ToolError("adapter 'debugpy' is not available: python not found in PATH");
|
|
707
|
+
}
|
|
670
708
|
throw new ToolError(
|
|
671
709
|
`No debugger adapter available. Installed adapters: ${getConfiguredAdapters(commandCwd)}`,
|
|
672
710
|
);
|
package/src/tools/eval.ts
CHANGED
|
@@ -15,6 +15,7 @@ import evalDescription from "../prompts/tools/eval.md" with { type: "text" };
|
|
|
15
15
|
import { DEFAULT_MAX_BYTES, OutputSink, type OutputSummary, TailBuffer } from "../session/streaming-output";
|
|
16
16
|
import { getTreeBranch, getTreeContinuePrefix, renderCodeCell } from "../tui";
|
|
17
17
|
import { resolveEvalBackends, type ToolSession } from ".";
|
|
18
|
+
import { truncateForPrompt } from "./approval";
|
|
18
19
|
import {
|
|
19
20
|
formatStyledTruncationWarning,
|
|
20
21
|
resolveOutputMaxColumns,
|
|
@@ -202,6 +203,20 @@ async function resolveBackend(session: ToolSession, language: EvalLanguage): Pro
|
|
|
202
203
|
|
|
203
204
|
export class EvalTool implements AgentTool<typeof evalSchema> {
|
|
204
205
|
readonly name = "eval";
|
|
206
|
+
readonly approval = "exec" as const;
|
|
207
|
+
readonly formatApprovalDetails = (args: unknown): string[] => {
|
|
208
|
+
const params = args as Partial<EvalToolParams>;
|
|
209
|
+
const cells = Array.isArray(params.cells) ? params.cells : [];
|
|
210
|
+
const firstCell = cells[0] as Partial<EvalCellInput> | undefined;
|
|
211
|
+
if (!firstCell) return [];
|
|
212
|
+
const language = typeof firstCell.language === "string" ? firstCell.language : "(missing)";
|
|
213
|
+
const code = typeof firstCell.code === "string" ? firstCell.code : "";
|
|
214
|
+
const lines = [`Language: ${language}`, `Code:\n${truncateForPrompt(code)}`];
|
|
215
|
+
if (cells.length > 1) {
|
|
216
|
+
lines.push(`+${cells.length - 1} more cell${cells.length === 2 ? "" : "s"}`);
|
|
217
|
+
}
|
|
218
|
+
return lines;
|
|
219
|
+
};
|
|
205
220
|
readonly summary = "Execute Python or JavaScript code in an in-process eval backend";
|
|
206
221
|
readonly loadMode = "discoverable";
|
|
207
222
|
readonly label = "Eval";
|