@oh-my-pi/pi-coding-agent 15.4.3 → 15.5.0
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 +75 -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/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/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/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 +1 -0
- 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
|
@@ -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";
|
package/src/tools/find.ts
CHANGED
|
@@ -59,11 +59,15 @@ const MAX_GLOB_TIMEOUT_MS = 60_000;
|
|
|
59
59
|
* Commas inside brace expansion (`{a,b}`) are legitimate glob syntax and
|
|
60
60
|
* must pass through.
|
|
61
61
|
*/
|
|
62
|
-
function validateFindPathInputs(paths: readonly string[]): void {
|
|
62
|
+
export function validateFindPathInputs(paths: readonly string[]): void {
|
|
63
63
|
for (const entry of paths) {
|
|
64
64
|
let braceDepth = 0;
|
|
65
65
|
for (let i = 0; i < entry.length; i++) {
|
|
66
66
|
const ch = entry.charCodeAt(i);
|
|
67
|
+
if (ch === 0x5c /* \ */ && i + 1 < entry.length) {
|
|
68
|
+
i++;
|
|
69
|
+
continue;
|
|
70
|
+
}
|
|
67
71
|
if (ch === 0x7b /* { */) braceDepth++;
|
|
68
72
|
else if (ch === 0x7d /* } */) {
|
|
69
73
|
if (braceDepth > 0) braceDepth--;
|
|
@@ -115,6 +119,7 @@ export interface FindToolOptions {
|
|
|
115
119
|
|
|
116
120
|
export class FindTool implements AgentTool<typeof findSchema, FindToolDetails> {
|
|
117
121
|
readonly name = "find";
|
|
122
|
+
readonly approval = "read" as const;
|
|
118
123
|
readonly summary = "Find files and directories matching a glob pattern";
|
|
119
124
|
readonly loadMode = "discoverable";
|
|
120
125
|
readonly label = "Find";
|
|
@@ -304,6 +309,7 @@ export class FindTool implements AgentTool<typeof findSchema, FindToolDetails> {
|
|
|
304
309
|
|
|
305
310
|
let matches: natives.GlobMatch[];
|
|
306
311
|
const onUpdateMatches: string[] = [];
|
|
312
|
+
const onUpdateMtimes: number[] = [];
|
|
307
313
|
const updateIntervalMs = 200;
|
|
308
314
|
let lastUpdate = 0;
|
|
309
315
|
const emitUpdate = () => {
|
|
@@ -323,9 +329,10 @@ export class FindTool implements AgentTool<typeof findSchema, FindToolDetails> {
|
|
|
323
329
|
});
|
|
324
330
|
};
|
|
325
331
|
const onMatch = (err: Error | null, match: natives.GlobMatch | null) => {
|
|
326
|
-
if (err ||
|
|
332
|
+
if (err || combinedSignal.aborted || !match?.path) return;
|
|
327
333
|
const relativePath = formatMatchPath(match.path, match.fileType);
|
|
328
334
|
onUpdateMatches.push(relativePath);
|
|
335
|
+
onUpdateMtimes.push(match.mtime ?? 0);
|
|
329
336
|
emitUpdate();
|
|
330
337
|
};
|
|
331
338
|
|
|
@@ -335,7 +342,6 @@ export class FindTool implements AgentTool<typeof findSchema, FindToolDetails> {
|
|
|
335
342
|
{
|
|
336
343
|
pattern: globPattern,
|
|
337
344
|
path: searchPath,
|
|
338
|
-
fileType: natives.FileType.File,
|
|
339
345
|
hidden: includeHidden,
|
|
340
346
|
maxResults: effectiveLimit,
|
|
341
347
|
sortByMtime: true,
|
|
@@ -371,15 +377,18 @@ export class FindTool implements AgentTool<typeof findSchema, FindToolDetails> {
|
|
|
371
377
|
// instead of throwing — empty results after a multi-second wait force the
|
|
372
378
|
// caller to retry blind, which is the worst possible outcome.
|
|
373
379
|
const seen = new Set<string>();
|
|
374
|
-
const partial: string
|
|
375
|
-
for (
|
|
380
|
+
const partial: Array<{ p: string; m: number }> = [];
|
|
381
|
+
for (let i = 0; i < onUpdateMatches.length; i++) {
|
|
382
|
+
const entry = onUpdateMatches[i];
|
|
376
383
|
if (seen.has(entry)) continue;
|
|
377
384
|
seen.add(entry);
|
|
378
|
-
partial.push(entry);
|
|
385
|
+
partial.push({ p: entry, m: onUpdateMtimes[i] ?? 0 });
|
|
379
386
|
}
|
|
387
|
+
partial.sort((a, b) => b.m - a.m);
|
|
388
|
+
const sortedPaths = partial.map(e => e.p);
|
|
380
389
|
const seconds = timeoutMs % 1000 === 0 ? `${timeoutMs / 1000}` : (timeoutMs / 1000).toFixed(1);
|
|
381
|
-
const notice = `find timed out after ${seconds}s; returning ${
|
|
382
|
-
return buildResult(
|
|
390
|
+
const notice = `find timed out after ${seconds}s; returning ${sortedPaths.length} partial matches — increase timeout or narrow pattern`;
|
|
391
|
+
return buildResult(sortedPaths, { notice, forceTruncated: true });
|
|
383
392
|
}
|
|
384
393
|
|
|
385
394
|
const relativized: string[] = [];
|
package/src/tools/gh.ts
CHANGED
|
@@ -2,7 +2,13 @@ import * as fs from "node:fs/promises";
|
|
|
2
2
|
import * as os from "node:os";
|
|
3
3
|
import * as path from "node:path";
|
|
4
4
|
import { scheduler } from "node:timers/promises";
|
|
5
|
-
import type {
|
|
5
|
+
import type {
|
|
6
|
+
AgentTool,
|
|
7
|
+
AgentToolContext,
|
|
8
|
+
AgentToolResult,
|
|
9
|
+
AgentToolUpdateCallback,
|
|
10
|
+
ToolApprovalDecision,
|
|
11
|
+
} from "@oh-my-pi/pi-agent-core";
|
|
6
12
|
|
|
7
13
|
import { getWorktreeDir, hashPath, isEnoent, prompt, untilAborted } from "@oh-my-pi/pi-utils";
|
|
8
14
|
import * as z from "zod/v4";
|
|
@@ -196,6 +202,15 @@ const RUN_URL_PATTERN = /^https:\/\/github\.com\/([^/]+\/[^/]+)\/actions\/runs\/
|
|
|
196
202
|
const RUN_SUCCESS_CONCLUSIONS = new Set(["success", "neutral", "skipped"]);
|
|
197
203
|
const RUN_FAILURE_CONCLUSIONS = new Set(["failure", "timed_out", "cancelled", "action_required", "startup_failure"]);
|
|
198
204
|
const JOB_FAILURE_CONCLUSIONS = new Set(["failure", "timed_out", "cancelled", "action_required"]);
|
|
205
|
+
const GITHUB_READONLY_OPS: ReadonlySet<string> = new Set([
|
|
206
|
+
"repo_view",
|
|
207
|
+
"search_issues",
|
|
208
|
+
"search_prs",
|
|
209
|
+
"search_code",
|
|
210
|
+
"search_commits",
|
|
211
|
+
"search_repos",
|
|
212
|
+
"run_watch",
|
|
213
|
+
]);
|
|
199
214
|
|
|
200
215
|
const githubSchema = z
|
|
201
216
|
.object({
|
|
@@ -2344,6 +2359,11 @@ function buildTextResult(
|
|
|
2344
2359
|
|
|
2345
2360
|
export class GithubTool implements AgentTool<typeof githubSchema, GhToolDetails> {
|
|
2346
2361
|
readonly name = "github";
|
|
2362
|
+
readonly approval = (args: unknown): ToolApprovalDecision => {
|
|
2363
|
+
const rawOp = (args as Partial<GithubInput>).op;
|
|
2364
|
+
const op = typeof rawOp === "string" ? rawOp : "";
|
|
2365
|
+
return GITHUB_READONLY_OPS.has(op) ? "read" : "exec";
|
|
2366
|
+
};
|
|
2347
2367
|
readonly summary = "Interact with GitHub issues, pull requests, and repositories";
|
|
2348
2368
|
readonly loadMode = "discoverable";
|
|
2349
2369
|
readonly label = "GitHub";
|
|
@@ -13,6 +13,7 @@ export type HindsightRecallParams = z.infer<typeof hindsightRecallSchema>;
|
|
|
13
13
|
|
|
14
14
|
export class HindsightRecallTool implements AgentTool<typeof hindsightRecallSchema> {
|
|
15
15
|
readonly name = "recall";
|
|
16
|
+
readonly approval = "read" as const;
|
|
16
17
|
readonly label = "Recall";
|
|
17
18
|
readonly description = recallDescription;
|
|
18
19
|
readonly parameters = hindsightRecallSchema;
|
|
@@ -14,6 +14,7 @@ export type HindsightReflectParams = z.infer<typeof hindsightReflectSchema>;
|
|
|
14
14
|
|
|
15
15
|
export class HindsightReflectTool implements AgentTool<typeof hindsightReflectSchema> {
|
|
16
16
|
readonly name = "reflect";
|
|
17
|
+
readonly approval = "read" as const;
|
|
17
18
|
readonly label = "Reflect";
|
|
18
19
|
readonly description = reflectDescription;
|
|
19
20
|
readonly parameters = hindsightReflectSchema;
|
|
@@ -18,6 +18,7 @@ const hindsightRetainSchema = z.object({
|
|
|
18
18
|
export type HindsightRetainParams = z.infer<typeof hindsightRetainSchema>;
|
|
19
19
|
export class HindsightRetainTool implements AgentTool<typeof hindsightRetainSchema> {
|
|
20
20
|
readonly name = "retain";
|
|
21
|
+
readonly approval = "read" as const;
|
|
21
22
|
readonly label = "Retain";
|
|
22
23
|
readonly description = retainDescription;
|
|
23
24
|
readonly parameters = hindsightRetainSchema;
|
package/src/tools/image-gen.ts
CHANGED
|
@@ -901,6 +901,7 @@ export const imageGenTool: CustomTool<typeof imageGenSchema, ImageGenToolDetails
|
|
|
901
901
|
name: "generate_image",
|
|
902
902
|
label: "GenerateImage",
|
|
903
903
|
strict: false,
|
|
904
|
+
approval: "write",
|
|
904
905
|
description: prompt.render(imageGenDescription),
|
|
905
906
|
parameters: imageGenSchema,
|
|
906
907
|
async execute(_toolCallId, params, _onUpdate, ctx, signal) {
|