pi-cursor-agent 0.1.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/LICENSE +19 -0
- package/README.md +45 -0
- package/package.json +50 -0
- package/src/__generated__/agent/v1/agent_pb.ts +4642 -0
- package/src/__generated__/agent/v1/agent_service_connect.ts +71 -0
- package/src/__generated__/agent/v1/apply_agent_diff_tool_pb.ts +317 -0
- package/src/__generated__/agent/v1/ask_question_tool_pb.ts +588 -0
- package/src/__generated__/agent/v1/background_shell_exec_pb.ts +245 -0
- package/src/__generated__/agent/v1/computer_use_tool_pb.ts +959 -0
- package/src/__generated__/agent/v1/control_service_connect.ts +144 -0
- package/src/__generated__/agent/v1/control_service_pb.ts +1308 -0
- package/src/__generated__/agent/v1/create_plan_tool_pb.ts +366 -0
- package/src/__generated__/agent/v1/cursor_packages_pb.ts +278 -0
- package/src/__generated__/agent/v1/cursor_rules_pb.ts +301 -0
- package/src/__generated__/agent/v1/delete_exec_pb.ts +443 -0
- package/src/__generated__/agent/v1/delete_tool_pb.ts +52 -0
- package/src/__generated__/agent/v1/diagnostics_exec_pb.ts +399 -0
- package/src/__generated__/agent/v1/edit_tool_pb.ts +497 -0
- package/src/__generated__/agent/v1/exa_fetch_tool_pb.ts +472 -0
- package/src/__generated__/agent/v1/exa_search_tool_pb.ts +484 -0
- package/src/__generated__/agent/v1/exec_pb.ts +1271 -0
- package/src/__generated__/agent/v1/exec_service_connect.ts +14 -0
- package/src/__generated__/agent/v1/fetch_tool_pb.ts +242 -0
- package/src/__generated__/agent/v1/generate_image_tool_pb.ts +230 -0
- package/src/__generated__/agent/v1/glob_tool_pb.ts +248 -0
- package/src/__generated__/agent/v1/grep_exec_pb.ts +690 -0
- package/src/__generated__/agent/v1/grep_tool_pb.ts +52 -0
- package/src/__generated__/agent/v1/kv_pb.ts +281 -0
- package/src/__generated__/agent/v1/ls_exec_pb.ts +295 -0
- package/src/__generated__/agent/v1/ls_tool_pb.ts +52 -0
- package/src/__generated__/agent/v1/mcp_pb.ts +302 -0
- package/src/__generated__/agent/v1/mcp_resource_tool_pb.ts +688 -0
- package/src/__generated__/agent/v1/mcp_tool_pb.ts +630 -0
- package/src/__generated__/agent/v1/private_worker_bridge_external_connect.ts +26 -0
- package/src/__generated__/agent/v1/read_exec_pb.ts +412 -0
- package/src/__generated__/agent/v1/read_lints_tool_pb.ts +384 -0
- package/src/__generated__/agent/v1/read_tool_pb.ts +342 -0
- package/src/__generated__/agent/v1/record_screen_tool_pb.ts +376 -0
- package/src/__generated__/agent/v1/reflect_tool_pb.ts +236 -0
- package/src/__generated__/agent/v1/repo_pb.ts +154 -0
- package/src/__generated__/agent/v1/report_bugfix_results_tool_pb.ts +305 -0
- package/src/__generated__/agent/v1/request_context_exec_pb.ts +528 -0
- package/src/__generated__/agent/v1/sandbox_pb.ts +125 -0
- package/src/__generated__/agent/v1/selected_context_pb.ts +2272 -0
- package/src/__generated__/agent/v1/semsearch_tool_pb.ts +230 -0
- package/src/__generated__/agent/v1/setup_vm_environment_tool_pb.ts +168 -0
- package/src/__generated__/agent/v1/shell_exec_pb.ts +1195 -0
- package/src/__generated__/agent/v1/shell_tool_pb.ts +176 -0
- package/src/__generated__/agent/v1/start_grind_execution_tool_pb.ts +212 -0
- package/src/__generated__/agent/v1/start_grind_planning_tool_pb.ts +212 -0
- package/src/__generated__/agent/v1/subagents_pb.ts +1106 -0
- package/src/__generated__/agent/v1/switch_mode_tool_pb.ts +429 -0
- package/src/__generated__/agent/v1/todo_tool_pb.ts +551 -0
- package/src/__generated__/agent/v1/utils_pb.ts +348 -0
- package/src/__generated__/agent/v1/web_fetch_tool_pb.ts +429 -0
- package/src/__generated__/agent/v1/web_search_tool_pb.ts +466 -0
- package/src/__generated__/agent/v1/write_exec_pb.ts +379 -0
- package/src/__generated__/agent/v1/write_shell_stdin_tool_pb.ts +224 -0
- package/src/__generated__/aiserver/v1/aiserver_service_connect.ts +40 -0
- package/src/api/agent-service.ts +55 -0
- package/src/api/ai-service.ts +42 -0
- package/src/api/auth.ts +74 -0
- package/src/index.ts +101 -0
- package/src/lib/agent-store/disk.ts +139 -0
- package/src/lib/agent-store/index.ts +72 -0
- package/src/lib/agent-store/json-blob-store.ts +47 -0
- package/src/lib/auth.ts +135 -0
- package/src/lib/backoff.ts +32 -0
- package/src/lib/env.ts +3 -0
- package/src/lib/heartbeat.ts +21 -0
- package/src/pi/agent-store.ts +102 -0
- package/src/pi/env.ts +11 -0
- package/src/pi/executors/delete.ts +129 -0
- package/src/pi/executors/grep.ts +238 -0
- package/src/pi/executors/hook.ts +64 -0
- package/src/pi/executors/ls.ts +107 -0
- package/src/pi/executors/read.ts +73 -0
- package/src/pi/executors/request-context.ts +120 -0
- package/src/pi/executors/shell-stream.ts +136 -0
- package/src/pi/executors/shell.ts +157 -0
- package/src/pi/executors/stubs.ts +173 -0
- package/src/pi/executors/write.ts +189 -0
- package/src/pi/local-resource-provider/index.ts +10 -0
- package/src/pi/local-resource-provider/provider.ts +98 -0
- package/src/pi/local-resource-provider/types.ts +110 -0
- package/src/pi/model-mapping.ts +115 -0
- package/src/pi/model-override.ts +110 -0
- package/src/pi/model.ts +61 -0
- package/src/pi/request-builder.ts +279 -0
- package/src/pi/utils/tool-result.ts +35 -0
- package/src/stream.ts +386 -0
- package/src/tool-host.ts +44 -0
- package/src/vendor/agent-client/checkpoint-controller.ts +34 -0
- package/src/vendor/agent-client/connect.ts +348 -0
- package/src/vendor/agent-client/exec-controller.ts +102 -0
- package/src/vendor/agent-client/index.ts +25 -0
- package/src/vendor/agent-client/interaction-controller.ts +96 -0
- package/src/vendor/agent-client/split-stream.ts +143 -0
- package/src/vendor/agent-core/index.ts +9 -0
- package/src/vendor/agent-core/interaction-conversion.ts +558 -0
- package/src/vendor/agent-exec/controlled.ts +104 -0
- package/src/vendor/agent-exec/index.ts +45 -0
- package/src/vendor/agent-exec/registry-resource-accessor.ts +39 -0
- package/src/vendor/agent-exec/resources.ts +121 -0
- package/src/vendor/agent-exec/serialization.ts +22 -0
- package/src/vendor/agent-exec/simple-controlled-exec-manager.ts +161 -0
- package/src/vendor/agent-kv/agent-store.ts +115 -0
- package/src/vendor/agent-kv/blob-store.ts +36 -0
- package/src/vendor/agent-kv/controlled.ts +117 -0
- package/src/vendor/agent-kv/index.ts +15 -0
- package/src/vendor/agent-kv/serde.ts +44 -0
- package/src/vendor/local-exec/common.ts +19 -0
- package/src/vendor/local-exec/git-executor.ts +37 -0
- package/src/vendor/local-exec/git-helpers.ts +79 -0
- package/src/vendor/local-exec/index.ts +8 -0
- package/src/vendor/utils/index.ts +5 -0
- package/src/vendor/utils/map-writable.ts +34 -0
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import { createReadTool } from "@mariozechner/pi-coding-agent";
|
|
2
|
+
import type { ToolResultMessage } from "@mariozechner/pi-ai";
|
|
3
|
+
import type { Executor } from "../../vendor/agent-exec";
|
|
4
|
+
import type {
|
|
5
|
+
ReadArgs,
|
|
6
|
+
ReadResult,
|
|
7
|
+
} from "../../__generated__/agent/v1/read_exec_pb";
|
|
8
|
+
import {
|
|
9
|
+
ReadError,
|
|
10
|
+
ReadRejected,
|
|
11
|
+
ReadResult as ReadResultClass,
|
|
12
|
+
ReadSuccess,
|
|
13
|
+
} from "../../__generated__/agent/v1/read_exec_pb";
|
|
14
|
+
import { toolResultToText, toolResultWasTruncated } from "../utils/tool-result";
|
|
15
|
+
import { type PiToolContext, decodeToolCallId, executePiTool } from "../local-resource-provider/types";
|
|
16
|
+
|
|
17
|
+
function buildReadResultFromToolResult(
|
|
18
|
+
path: string,
|
|
19
|
+
result: ToolResultMessage,
|
|
20
|
+
): ReadResult {
|
|
21
|
+
const text = toolResultToText(result);
|
|
22
|
+
if (result.isError) {
|
|
23
|
+
return new ReadResultClass({
|
|
24
|
+
result: { case: "error", value: new ReadError({ path, error: text || "Read failed" }) },
|
|
25
|
+
});
|
|
26
|
+
}
|
|
27
|
+
const totalLines = text ? text.split("\n").length : 0;
|
|
28
|
+
return new ReadResultClass({
|
|
29
|
+
result: {
|
|
30
|
+
case: "success",
|
|
31
|
+
value: new ReadSuccess({
|
|
32
|
+
path,
|
|
33
|
+
totalLines,
|
|
34
|
+
fileSize: BigInt(Buffer.byteLength(text, "utf-8")),
|
|
35
|
+
truncated: toolResultWasTruncated(result),
|
|
36
|
+
output: { case: "content", value: text },
|
|
37
|
+
}),
|
|
38
|
+
},
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function buildReadRejectedResult(path: string, reason: string): ReadResult {
|
|
43
|
+
return new ReadResultClass({
|
|
44
|
+
result: { case: "rejected", value: new ReadRejected({ path, reason }) },
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export class LocalReadExecutor implements Executor<ReadArgs, ReadResult> {
|
|
49
|
+
private readonly readTool;
|
|
50
|
+
private readonly ctx: PiToolContext;
|
|
51
|
+
|
|
52
|
+
constructor(ctx: PiToolContext) {
|
|
53
|
+
this.ctx = ctx;
|
|
54
|
+
this.readTool = createReadTool(ctx.cwd);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
async execute(_ctx: unknown, args: ReadArgs): Promise<ReadResult> {
|
|
58
|
+
const toolCallId = decodeToolCallId(args.toolCallId);
|
|
59
|
+
|
|
60
|
+
if (!this.ctx.getActiveTools().has("read")) {
|
|
61
|
+
return buildReadRejectedResult(args.path, "Tool not available");
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
const toolResult = await executePiTool(
|
|
65
|
+
this.ctx,
|
|
66
|
+
this.readTool,
|
|
67
|
+
"read",
|
|
68
|
+
toolCallId,
|
|
69
|
+
{ path: args.path },
|
|
70
|
+
);
|
|
71
|
+
return buildReadResultFromToolResult(args.path, toolResult);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
import * as os from "node:os";
|
|
2
|
+
import type { Executor } from "../../vendor/agent-exec";
|
|
3
|
+
import {
|
|
4
|
+
LocalGitExecutor,
|
|
5
|
+
getGitRepoPath,
|
|
6
|
+
getGitStatus,
|
|
7
|
+
getGitBranch,
|
|
8
|
+
getGitRemoteUrl,
|
|
9
|
+
} from "../../vendor/local-exec";
|
|
10
|
+
import type { McpToolDefinition } from "../../__generated__/agent/v1/mcp_pb";
|
|
11
|
+
import { GitRepoInfo } from "../../__generated__/agent/v1/repo_pb";
|
|
12
|
+
import type { RequestContextArgs } from "../../__generated__/agent/v1/request_context_exec_pb";
|
|
13
|
+
import {
|
|
14
|
+
RequestContext,
|
|
15
|
+
RequestContextEnv,
|
|
16
|
+
RequestContextError,
|
|
17
|
+
RequestContextResult,
|
|
18
|
+
RequestContextSuccess,
|
|
19
|
+
} from "../../__generated__/agent/v1/request_context_exec_pb";
|
|
20
|
+
|
|
21
|
+
export class LocalRequestContextExecutor
|
|
22
|
+
implements Executor<RequestContextArgs, RequestContextResult>
|
|
23
|
+
{
|
|
24
|
+
private readonly tools: McpToolDefinition[];
|
|
25
|
+
private readonly workspacePaths: string[];
|
|
26
|
+
private readonly gitExecutor: LocalGitExecutor;
|
|
27
|
+
|
|
28
|
+
constructor(tools: McpToolDefinition[], workspacePaths: string[]) {
|
|
29
|
+
this.tools = tools;
|
|
30
|
+
this.workspacePaths = workspacePaths;
|
|
31
|
+
this.gitExecutor = new LocalGitExecutor();
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
async execute(
|
|
35
|
+
_ctx: unknown,
|
|
36
|
+
_args: RequestContextArgs,
|
|
37
|
+
): Promise<RequestContextResult> {
|
|
38
|
+
try {
|
|
39
|
+
const [gitRepos, env] = await Promise.all([
|
|
40
|
+
this.collectGitRepos(),
|
|
41
|
+
this.computeEnv(),
|
|
42
|
+
]);
|
|
43
|
+
|
|
44
|
+
const requestContext = new RequestContext({
|
|
45
|
+
rules: [],
|
|
46
|
+
env,
|
|
47
|
+
repositoryInfo: [],
|
|
48
|
+
tools: this.tools,
|
|
49
|
+
gitRepos,
|
|
50
|
+
projectLayouts: [],
|
|
51
|
+
mcpInstructions: [],
|
|
52
|
+
fileContents: {},
|
|
53
|
+
customSubagents: [],
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
return new RequestContextResult({
|
|
57
|
+
result: {
|
|
58
|
+
case: "success",
|
|
59
|
+
value: new RequestContextSuccess({ requestContext }),
|
|
60
|
+
},
|
|
61
|
+
});
|
|
62
|
+
} catch (error) {
|
|
63
|
+
return new RequestContextResult({
|
|
64
|
+
result: {
|
|
65
|
+
case: "error",
|
|
66
|
+
value: new RequestContextError({
|
|
67
|
+
error: error instanceof Error ? error.message : String(error),
|
|
68
|
+
}),
|
|
69
|
+
},
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
private async collectGitRepos(): Promise<GitRepoInfo[]> {
|
|
75
|
+
const seen = new Set<string>();
|
|
76
|
+
const repos: GitRepoInfo[] = [];
|
|
77
|
+
|
|
78
|
+
for (const workspacePath of this.workspacePaths) {
|
|
79
|
+
const repoPath = await getGitRepoPath(this.gitExecutor, workspacePath);
|
|
80
|
+
if (!repoPath || seen.has(repoPath)) continue;
|
|
81
|
+
seen.add(repoPath);
|
|
82
|
+
|
|
83
|
+
const [status, branchName, remoteUrl] = await Promise.all([
|
|
84
|
+
getGitStatus(this.gitExecutor, repoPath),
|
|
85
|
+
getGitBranch(this.gitExecutor, repoPath),
|
|
86
|
+
getGitRemoteUrl(this.gitExecutor, repoPath),
|
|
87
|
+
]);
|
|
88
|
+
|
|
89
|
+
const info: ConstructorParameters<typeof GitRepoInfo>[0] = {
|
|
90
|
+
path: repoPath,
|
|
91
|
+
status: status ?? "",
|
|
92
|
+
branchName: branchName ?? "",
|
|
93
|
+
};
|
|
94
|
+
if (remoteUrl) info.remoteUrl = remoteUrl;
|
|
95
|
+
repos.push(new GitRepoInfo(info));
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
return repos;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
private async computeEnv(): Promise<RequestContextEnv> {
|
|
102
|
+
const osVersion = `${os.platform()} ${os.release()}`;
|
|
103
|
+
const shell = process.env["SHELL"] || "";
|
|
104
|
+
|
|
105
|
+
let timeZone: string | undefined;
|
|
106
|
+
try {
|
|
107
|
+
timeZone = new Intl.DateTimeFormat().resolvedOptions().timeZone;
|
|
108
|
+
} catch {
|
|
109
|
+
timeZone = undefined;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
return new RequestContextEnv({
|
|
113
|
+
osVersion,
|
|
114
|
+
workspacePaths: this.workspacePaths,
|
|
115
|
+
shell,
|
|
116
|
+
sandboxEnabled: false,
|
|
117
|
+
timeZone: timeZone ?? "",
|
|
118
|
+
});
|
|
119
|
+
}
|
|
120
|
+
}
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
import type { TextContent } from "@mariozechner/pi-ai";
|
|
2
|
+
import type { StreamExecutor } from "../../vendor/agent-exec";
|
|
3
|
+
import type {
|
|
4
|
+
ShellArgs,
|
|
5
|
+
ShellStream,
|
|
6
|
+
} from "../../__generated__/agent/v1/shell_exec_pb";
|
|
7
|
+
import {
|
|
8
|
+
ShellStream as ShellStreamClass,
|
|
9
|
+
ShellRejected,
|
|
10
|
+
ShellStreamExit,
|
|
11
|
+
ShellStreamStart,
|
|
12
|
+
ShellStreamStderr,
|
|
13
|
+
ShellStreamStdout,
|
|
14
|
+
} from "../../__generated__/agent/v1/shell_exec_pb";
|
|
15
|
+
import { type PiToolContext, decodeToolCallId, executePiTool } from "../local-resource-provider/types";
|
|
16
|
+
import { type LocalShellExecutor, confirmIfDangerous } from "./shell";
|
|
17
|
+
|
|
18
|
+
export class LocalShellStreamExecutor implements StreamExecutor<
|
|
19
|
+
ShellArgs,
|
|
20
|
+
ShellStream
|
|
21
|
+
> {
|
|
22
|
+
private readonly ctx: PiToolContext;
|
|
23
|
+
private readonly shellExecutor: LocalShellExecutor;
|
|
24
|
+
|
|
25
|
+
constructor(ctx: PiToolContext, shellExecutor: LocalShellExecutor) {
|
|
26
|
+
this.ctx = ctx;
|
|
27
|
+
this.shellExecutor = shellExecutor;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
execute(_ctx: unknown, args: ShellArgs): AsyncIterable<ShellStream> {
|
|
31
|
+
return this.run(args);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
private async *run(args: ShellArgs): AsyncIterable<ShellStream> {
|
|
35
|
+
const toolCallId = decodeToolCallId(args.toolCallId);
|
|
36
|
+
const cwd = args.workingDirectory || this.ctx.cwd;
|
|
37
|
+
|
|
38
|
+
if (!this.ctx.getActiveTools().has("bash")) {
|
|
39
|
+
yield new ShellStreamClass({
|
|
40
|
+
event: {
|
|
41
|
+
case: "rejected",
|
|
42
|
+
value: new ShellRejected({
|
|
43
|
+
command: args.command,
|
|
44
|
+
workingDirectory: args.workingDirectory,
|
|
45
|
+
reason: "Tool not available",
|
|
46
|
+
isReadonly: false,
|
|
47
|
+
}),
|
|
48
|
+
},
|
|
49
|
+
});
|
|
50
|
+
yield new ShellStreamClass({
|
|
51
|
+
event: {
|
|
52
|
+
case: "exit",
|
|
53
|
+
value: new ShellStreamExit({ code: 1, cwd, aborted: false }),
|
|
54
|
+
},
|
|
55
|
+
});
|
|
56
|
+
return;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
const approved = await confirmIfDangerous(this.ctx.getCtx, args.command);
|
|
60
|
+
if (!approved) {
|
|
61
|
+
yield new ShellStreamClass({
|
|
62
|
+
event: {
|
|
63
|
+
case: "rejected",
|
|
64
|
+
value: new ShellRejected({
|
|
65
|
+
command: args.command,
|
|
66
|
+
workingDirectory: args.workingDirectory,
|
|
67
|
+
reason: "Command rejected",
|
|
68
|
+
isReadonly: false,
|
|
69
|
+
}),
|
|
70
|
+
},
|
|
71
|
+
});
|
|
72
|
+
yield new ShellStreamClass({
|
|
73
|
+
event: {
|
|
74
|
+
case: "exit",
|
|
75
|
+
value: new ShellStreamExit({ code: 1, cwd, aborted: false }),
|
|
76
|
+
},
|
|
77
|
+
});
|
|
78
|
+
return;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
yield new ShellStreamClass({
|
|
82
|
+
event: { case: "start", value: new ShellStreamStart({}) },
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
const timeoutSeconds =
|
|
86
|
+
args.timeout && args.timeout > 0 ? args.timeout : undefined;
|
|
87
|
+
const bashTool = this.shellExecutor.getBashTool(
|
|
88
|
+
args.workingDirectory || undefined,
|
|
89
|
+
);
|
|
90
|
+
|
|
91
|
+
const toolResult = await executePiTool(
|
|
92
|
+
this.ctx,
|
|
93
|
+
bashTool,
|
|
94
|
+
"bash",
|
|
95
|
+
toolCallId,
|
|
96
|
+
{ command: args.command, timeout: timeoutSeconds },
|
|
97
|
+
);
|
|
98
|
+
|
|
99
|
+
const text = toolResult.content
|
|
100
|
+
.filter((c): c is TextContent => c.type === "text")
|
|
101
|
+
.map((c) => c.text)
|
|
102
|
+
.join("\n");
|
|
103
|
+
|
|
104
|
+
if (toolResult.isError) {
|
|
105
|
+
yield new ShellStreamClass({
|
|
106
|
+
event: {
|
|
107
|
+
case: "stderr",
|
|
108
|
+
value: new ShellStreamStderr({ data: text || "Shell failed" }),
|
|
109
|
+
},
|
|
110
|
+
});
|
|
111
|
+
yield new ShellStreamClass({
|
|
112
|
+
event: {
|
|
113
|
+
case: "exit",
|
|
114
|
+
value: new ShellStreamExit({ code: 1, cwd, aborted: false }),
|
|
115
|
+
},
|
|
116
|
+
});
|
|
117
|
+
return;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
if (text) {
|
|
121
|
+
yield new ShellStreamClass({
|
|
122
|
+
event: {
|
|
123
|
+
case: "stdout",
|
|
124
|
+
value: new ShellStreamStdout({ data: text }),
|
|
125
|
+
},
|
|
126
|
+
});
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
yield new ShellStreamClass({
|
|
130
|
+
event: {
|
|
131
|
+
case: "exit",
|
|
132
|
+
value: new ShellStreamExit({ code: 0, cwd, aborted: false }),
|
|
133
|
+
},
|
|
134
|
+
});
|
|
135
|
+
}
|
|
136
|
+
}
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
import { createBashTool } from "@mariozechner/pi-coding-agent";
|
|
2
|
+
import type { AgentTool } from "@mariozechner/pi-agent-core";
|
|
3
|
+
import type { ExtensionContext } from "@mariozechner/pi-coding-agent";
|
|
4
|
+
import type { ToolResultMessage } from "@mariozechner/pi-ai";
|
|
5
|
+
import type { Executor } from "../../vendor/agent-exec";
|
|
6
|
+
import { resolvePath } from "../../vendor/local-exec";
|
|
7
|
+
import type {
|
|
8
|
+
ShellArgs,
|
|
9
|
+
ShellResult,
|
|
10
|
+
} from "../../__generated__/agent/v1/shell_exec_pb";
|
|
11
|
+
import {
|
|
12
|
+
ShellFailure,
|
|
13
|
+
ShellRejected,
|
|
14
|
+
ShellResult as ShellResultClass,
|
|
15
|
+
ShellSuccess,
|
|
16
|
+
} from "../../__generated__/agent/v1/shell_exec_pb";
|
|
17
|
+
import { toolResultToText } from "../utils/tool-result";
|
|
18
|
+
import { type PiToolContext, decodeToolCallId, executePiTool } from "../local-resource-provider/types";
|
|
19
|
+
|
|
20
|
+
function buildShellResultFromToolResult(
|
|
21
|
+
args: { command: string; workingDirectory: string },
|
|
22
|
+
result: ToolResultMessage,
|
|
23
|
+
): ShellResult {
|
|
24
|
+
const output = toolResultToText(result);
|
|
25
|
+
if (result.isError) {
|
|
26
|
+
return new ShellResultClass({
|
|
27
|
+
result: {
|
|
28
|
+
case: "failure",
|
|
29
|
+
value: new ShellFailure({
|
|
30
|
+
command: args.command,
|
|
31
|
+
workingDirectory: args.workingDirectory,
|
|
32
|
+
exitCode: 1,
|
|
33
|
+
signal: "",
|
|
34
|
+
stdout: "",
|
|
35
|
+
stderr: output || "Shell failed",
|
|
36
|
+
executionTime: 0,
|
|
37
|
+
aborted: false,
|
|
38
|
+
}),
|
|
39
|
+
},
|
|
40
|
+
});
|
|
41
|
+
}
|
|
42
|
+
return new ShellResultClass({
|
|
43
|
+
result: {
|
|
44
|
+
case: "success",
|
|
45
|
+
value: new ShellSuccess({
|
|
46
|
+
command: args.command,
|
|
47
|
+
workingDirectory: args.workingDirectory,
|
|
48
|
+
exitCode: 0,
|
|
49
|
+
signal: "",
|
|
50
|
+
stdout: output,
|
|
51
|
+
stderr: "",
|
|
52
|
+
executionTime: 0,
|
|
53
|
+
}),
|
|
54
|
+
},
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function buildShellRejectedResult(
|
|
59
|
+
command: string,
|
|
60
|
+
workingDirectory: string,
|
|
61
|
+
reason: string,
|
|
62
|
+
): ShellResult {
|
|
63
|
+
return new ShellResultClass({
|
|
64
|
+
result: {
|
|
65
|
+
case: "rejected",
|
|
66
|
+
value: new ShellRejected({
|
|
67
|
+
command,
|
|
68
|
+
workingDirectory,
|
|
69
|
+
reason,
|
|
70
|
+
isReadonly: false,
|
|
71
|
+
}),
|
|
72
|
+
},
|
|
73
|
+
});
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
function isDangerousShellCommand(command: string): boolean {
|
|
77
|
+
const c = command.toLowerCase();
|
|
78
|
+
if (/(^|\s)sudo\b/.test(c)) return true;
|
|
79
|
+
if (/\brm\b.*\s-rf\b/.test(c)) return true;
|
|
80
|
+
if (/\bmkfs\b|\bdd\b|\bshutdown\b|\breboot\b/.test(c)) return true;
|
|
81
|
+
if (/\bcurl\b.*\|\s*(sh|bash)\b/.test(c)) return true;
|
|
82
|
+
if (/\bwget\b.*\|\s*(sh|bash)\b/.test(c)) return true;
|
|
83
|
+
return false;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
export async function confirmIfDangerous(
|
|
87
|
+
getCtx: () => ExtensionContext | null,
|
|
88
|
+
command: string,
|
|
89
|
+
): Promise<boolean> {
|
|
90
|
+
if (!isDangerousShellCommand(command)) return true;
|
|
91
|
+
const ctx = getCtx();
|
|
92
|
+
if (!ctx?.hasUI) return false;
|
|
93
|
+
return ctx.ui.confirm("Cursor command approval", command);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
export class LocalShellExecutor implements Executor<ShellArgs, ShellResult> {
|
|
97
|
+
private readonly ctx: PiToolContext;
|
|
98
|
+
private readonly bashByCwd = new Map<string, AgentTool<any>>();
|
|
99
|
+
|
|
100
|
+
constructor(ctx: PiToolContext) {
|
|
101
|
+
this.ctx = ctx;
|
|
102
|
+
this.bashByCwd.set(ctx.cwd, createBashTool(ctx.cwd));
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
getBashTool(workingDirectory?: string): AgentTool<any> {
|
|
106
|
+
const resolved = resolvePath(
|
|
107
|
+
workingDirectory || this.ctx.cwd,
|
|
108
|
+
this.ctx.cwd,
|
|
109
|
+
);
|
|
110
|
+
const cached = this.bashByCwd.get(resolved);
|
|
111
|
+
if (cached) return cached;
|
|
112
|
+
const tool = createBashTool(resolved);
|
|
113
|
+
this.bashByCwd.set(resolved, tool);
|
|
114
|
+
return tool;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
async execute(_ctx: unknown, args: ShellArgs): Promise<ShellResult> {
|
|
118
|
+
const toolCallId = decodeToolCallId(args.toolCallId);
|
|
119
|
+
|
|
120
|
+
if (!this.ctx.getActiveTools().has("bash")) {
|
|
121
|
+
return buildShellRejectedResult(
|
|
122
|
+
args.command,
|
|
123
|
+
args.workingDirectory,
|
|
124
|
+
"Tool not available",
|
|
125
|
+
);
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
const approved = await confirmIfDangerous(this.ctx.getCtx, args.command);
|
|
129
|
+
if (!approved) {
|
|
130
|
+
return buildShellRejectedResult(
|
|
131
|
+
args.command,
|
|
132
|
+
args.workingDirectory,
|
|
133
|
+
"Command rejected",
|
|
134
|
+
);
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
const timeoutSeconds =
|
|
138
|
+
args.timeout && args.timeout > 0 ? args.timeout : undefined;
|
|
139
|
+
const bashTool = this.getBashTool(args.workingDirectory || undefined);
|
|
140
|
+
|
|
141
|
+
const toolResult = await executePiTool(
|
|
142
|
+
this.ctx,
|
|
143
|
+
bashTool,
|
|
144
|
+
"bash",
|
|
145
|
+
toolCallId,
|
|
146
|
+
{ command: args.command, timeout: timeoutSeconds },
|
|
147
|
+
);
|
|
148
|
+
|
|
149
|
+
return buildShellResultFromToolResult(
|
|
150
|
+
{
|
|
151
|
+
command: args.command,
|
|
152
|
+
workingDirectory: args.workingDirectory || this.ctx.cwd,
|
|
153
|
+
},
|
|
154
|
+
toolResult,
|
|
155
|
+
);
|
|
156
|
+
}
|
|
157
|
+
}
|
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
import type { Executor } from "../../vendor/agent-exec";
|
|
2
|
+
|
|
3
|
+
import type { BackgroundShellSpawnArgs } from "../../__generated__/agent/v1/background_shell_exec_pb";
|
|
4
|
+
import { BackgroundShellSpawnResult } from "../../__generated__/agent/v1/background_shell_exec_pb";
|
|
5
|
+
import type { WriteShellStdinArgs } from "../../__generated__/agent/v1/write_shell_stdin_tool_pb";
|
|
6
|
+
import {
|
|
7
|
+
WriteShellStdinError,
|
|
8
|
+
WriteShellStdinResult,
|
|
9
|
+
} from "../../__generated__/agent/v1/write_shell_stdin_tool_pb";
|
|
10
|
+
import type { FetchArgs } from "../../__generated__/agent/v1/fetch_tool_pb";
|
|
11
|
+
import {
|
|
12
|
+
FetchError,
|
|
13
|
+
FetchResult,
|
|
14
|
+
} from "../../__generated__/agent/v1/fetch_tool_pb";
|
|
15
|
+
import type { DiagnosticsArgs } from "../../__generated__/agent/v1/diagnostics_exec_pb";
|
|
16
|
+
import {
|
|
17
|
+
DiagnosticsResult,
|
|
18
|
+
DiagnosticsSuccess,
|
|
19
|
+
} from "../../__generated__/agent/v1/diagnostics_exec_pb";
|
|
20
|
+
import type { McpArgs } from "../../__generated__/agent/v1/mcp_tool_pb";
|
|
21
|
+
import { McpError, McpResult } from "../../__generated__/agent/v1/mcp_tool_pb";
|
|
22
|
+
import type {
|
|
23
|
+
ListMcpResourcesExecArgs,
|
|
24
|
+
ReadMcpResourceExecArgs,
|
|
25
|
+
} from "../../__generated__/agent/v1/mcp_resource_tool_pb";
|
|
26
|
+
import {
|
|
27
|
+
ListMcpResourcesExecResult,
|
|
28
|
+
ListMcpResourcesRejected,
|
|
29
|
+
ReadMcpResourceExecResult,
|
|
30
|
+
ReadMcpResourceRejected,
|
|
31
|
+
} from "../../__generated__/agent/v1/mcp_resource_tool_pb";
|
|
32
|
+
import type { RecordScreenArgs } from "../../__generated__/agent/v1/record_screen_tool_pb";
|
|
33
|
+
import {
|
|
34
|
+
RecordScreenFailure,
|
|
35
|
+
RecordScreenResult,
|
|
36
|
+
} from "../../__generated__/agent/v1/record_screen_tool_pb";
|
|
37
|
+
import type { ComputerUseArgs } from "../../__generated__/agent/v1/computer_use_tool_pb";
|
|
38
|
+
import {
|
|
39
|
+
ComputerUseError,
|
|
40
|
+
ComputerUseResult,
|
|
41
|
+
} from "../../__generated__/agent/v1/computer_use_tool_pb";
|
|
42
|
+
import { ShellRejected } from "../../__generated__/agent/v1/shell_exec_pb";
|
|
43
|
+
|
|
44
|
+
export class StubBackgroundShellExecutor implements Executor<
|
|
45
|
+
BackgroundShellSpawnArgs,
|
|
46
|
+
BackgroundShellSpawnResult
|
|
47
|
+
> {
|
|
48
|
+
async execute(_ctx: unknown, args: BackgroundShellSpawnArgs) {
|
|
49
|
+
return new BackgroundShellSpawnResult({
|
|
50
|
+
result: {
|
|
51
|
+
case: "rejected",
|
|
52
|
+
value: new ShellRejected({
|
|
53
|
+
command: args.command,
|
|
54
|
+
workingDirectory: args.workingDirectory,
|
|
55
|
+
reason: "Not implemented",
|
|
56
|
+
isReadonly: false,
|
|
57
|
+
}),
|
|
58
|
+
},
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export class StubWriteShellStdinExecutor implements Executor<
|
|
64
|
+
WriteShellStdinArgs,
|
|
65
|
+
WriteShellStdinResult
|
|
66
|
+
> {
|
|
67
|
+
async execute() {
|
|
68
|
+
return new WriteShellStdinResult({
|
|
69
|
+
result: {
|
|
70
|
+
case: "error",
|
|
71
|
+
value: new WriteShellStdinError({ error: "Not implemented" }),
|
|
72
|
+
},
|
|
73
|
+
});
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
export class StubFetchExecutor implements Executor<FetchArgs, FetchResult> {
|
|
78
|
+
async execute(_ctx: unknown, args: FetchArgs) {
|
|
79
|
+
return new FetchResult({
|
|
80
|
+
result: {
|
|
81
|
+
case: "error",
|
|
82
|
+
value: new FetchError({ url: args.url, error: "Not implemented" }),
|
|
83
|
+
},
|
|
84
|
+
});
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
export class StubDiagnosticsExecutor implements Executor<
|
|
89
|
+
DiagnosticsArgs,
|
|
90
|
+
DiagnosticsResult
|
|
91
|
+
> {
|
|
92
|
+
async execute() {
|
|
93
|
+
return new DiagnosticsResult({
|
|
94
|
+
result: {
|
|
95
|
+
case: "success",
|
|
96
|
+
value: new DiagnosticsSuccess({
|
|
97
|
+
diagnostics: [],
|
|
98
|
+
totalDiagnostics: 0,
|
|
99
|
+
}),
|
|
100
|
+
},
|
|
101
|
+
});
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
export class StubMcpExecutor implements Executor<McpArgs, McpResult> {
|
|
106
|
+
async execute() {
|
|
107
|
+
return new McpResult({
|
|
108
|
+
result: {
|
|
109
|
+
case: "error",
|
|
110
|
+
value: new McpError({ error: "MCP not supported" }),
|
|
111
|
+
},
|
|
112
|
+
});
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
export class StubListMcpResourcesExecutor implements Executor<
|
|
117
|
+
ListMcpResourcesExecArgs,
|
|
118
|
+
ListMcpResourcesExecResult
|
|
119
|
+
> {
|
|
120
|
+
async execute() {
|
|
121
|
+
return new ListMcpResourcesExecResult({
|
|
122
|
+
result: {
|
|
123
|
+
case: "rejected",
|
|
124
|
+
value: new ListMcpResourcesRejected({ reason: "MCP not supported" }),
|
|
125
|
+
},
|
|
126
|
+
});
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
export class StubReadMcpResourceExecutor implements Executor<
|
|
131
|
+
ReadMcpResourceExecArgs,
|
|
132
|
+
ReadMcpResourceExecResult
|
|
133
|
+
> {
|
|
134
|
+
async execute(_ctx: unknown, args: ReadMcpResourceExecArgs) {
|
|
135
|
+
return new ReadMcpResourceExecResult({
|
|
136
|
+
result: {
|
|
137
|
+
case: "rejected",
|
|
138
|
+
value: new ReadMcpResourceRejected({
|
|
139
|
+
uri: args.uri,
|
|
140
|
+
reason: "MCP not supported",
|
|
141
|
+
}),
|
|
142
|
+
},
|
|
143
|
+
});
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
export class StubRecordScreenExecutor implements Executor<
|
|
148
|
+
RecordScreenArgs,
|
|
149
|
+
RecordScreenResult
|
|
150
|
+
> {
|
|
151
|
+
async execute() {
|
|
152
|
+
return new RecordScreenResult({
|
|
153
|
+
result: {
|
|
154
|
+
case: "failure",
|
|
155
|
+
value: new RecordScreenFailure({ error: "Not implemented" }),
|
|
156
|
+
},
|
|
157
|
+
});
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
export class StubComputerUseExecutor implements Executor<
|
|
162
|
+
ComputerUseArgs,
|
|
163
|
+
ComputerUseResult
|
|
164
|
+
> {
|
|
165
|
+
async execute() {
|
|
166
|
+
return new ComputerUseResult({
|
|
167
|
+
result: {
|
|
168
|
+
case: "error",
|
|
169
|
+
value: new ComputerUseError({ error: "Not implemented" }),
|
|
170
|
+
},
|
|
171
|
+
});
|
|
172
|
+
}
|
|
173
|
+
}
|