@fleetagent/pi-coding-agent 0.0.9 → 0.0.10
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 +23 -0
- package/README.md +9 -0
- package/dist/cli/args.d.ts +3 -0
- package/dist/cli/args.d.ts.map +1 -1
- package/dist/cli/args.js +18 -0
- package/dist/cli/args.js.map +1 -1
- package/dist/core/agent-session.d.ts +12 -3
- package/dist/core/agent-session.d.ts.map +1 -1
- package/dist/core/agent-session.js +27 -6
- package/dist/core/agent-session.js.map +1 -1
- package/dist/core/bash-executor.d.ts +3 -3
- package/dist/core/bash-executor.d.ts.map +1 -1
- package/dist/core/bash-executor.js +3 -2
- package/dist/core/bash-executor.js.map +1 -1
- package/dist/core/extensions/types.d.ts +2 -2
- package/dist/core/extensions/types.d.ts.map +1 -1
- package/dist/core/extensions/types.js.map +1 -1
- package/dist/core/pi-agent.d.ts +2 -0
- package/dist/core/pi-agent.d.ts.map +1 -1
- package/dist/core/pi-agent.js +2 -0
- package/dist/core/pi-agent.js.map +1 -1
- package/dist/core/slash-commands.d.ts.map +1 -1
- package/dist/core/slash-commands.js +1 -0
- package/dist/core/slash-commands.js.map +1 -1
- package/dist/core/tools/bash.d.ts +6 -29
- package/dist/core/tools/bash.d.ts.map +1 -1
- package/dist/core/tools/bash.js +23 -97
- package/dist/core/tools/bash.js.map +1 -1
- package/dist/core/tools/edit-diff.d.ts +3 -2
- package/dist/core/tools/edit-diff.d.ts.map +1 -1
- package/dist/core/tools/edit-diff.js +6 -8
- package/dist/core/tools/edit-diff.js.map +1 -1
- package/dist/core/tools/edit.d.ts +3 -16
- package/dist/core/tools/edit.d.ts.map +1 -1
- package/dist/core/tools/edit.js +7 -13
- package/dist/core/tools/edit.js.map +1 -1
- package/dist/core/tools/find.d.ts +3 -17
- package/dist/core/tools/find.d.ts.map +1 -1
- package/dist/core/tools/find.js +11 -23
- package/dist/core/tools/find.js.map +1 -1
- package/dist/core/tools/grep.d.ts +3 -14
- package/dist/core/tools/grep.d.ts.map +1 -1
- package/dist/core/tools/grep.js +93 -41
- package/dist/core/tools/grep.js.map +1 -1
- package/dist/core/tools/index.d.ts +17 -15
- package/dist/core/tools/index.d.ts.map +1 -1
- package/dist/core/tools/index.js +53 -52
- package/dist/core/tools/index.js.map +1 -1
- package/dist/core/tools/ls.d.ts +3 -20
- package/dist/core/tools/ls.d.ts.map +1 -1
- package/dist/core/tools/ls.js +9 -20
- package/dist/core/tools/ls.js.map +1 -1
- package/dist/core/tools/operations.d.ts +145 -0
- package/dist/core/tools/operations.d.ts.map +1 -0
- package/dist/core/tools/operations.js +418 -0
- package/dist/core/tools/operations.js.map +1 -0
- package/dist/core/tools/read.d.ts +3 -16
- package/dist/core/tools/read.d.ts.map +1 -1
- package/dist/core/tools/read.js +6 -13
- package/dist/core/tools/read.js.map +1 -1
- package/dist/core/tools/write.d.ts +3 -14
- package/dist/core/tools/write.d.ts.map +1 -1
- package/dist/core/tools/write.js +6 -10
- package/dist/core/tools/write.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/main.d.ts.map +1 -1
- package/dist/main.js +33 -0
- package/dist/main.js.map +1 -1
- package/dist/modes/interactive/components/tool-execution.d.ts.map +1 -1
- package/dist/modes/interactive/components/tool-execution.js +2 -2
- package/dist/modes/interactive/components/tool-execution.js.map +1 -1
- package/dist/modes/interactive/interactive-mode.d.ts +3 -0
- package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
- package/dist/modes/interactive/interactive-mode.js +52 -0
- package/dist/modes/interactive/interactive-mode.js.map +1 -1
- package/dist/modes/rpc/rpc-client.d.ts +9 -0
- package/dist/modes/rpc/rpc-client.d.ts.map +1 -1
- package/dist/modes/rpc/rpc-client.js +14 -0
- package/dist/modes/rpc/rpc-client.js.map +1 -1
- package/dist/modes/rpc/rpc-mode.d.ts.map +1 -1
- package/dist/modes/rpc/rpc-mode.js +9 -0
- package/dist/modes/rpc/rpc-mode.js.map +1 -1
- package/dist/modes/rpc/rpc-types.d.ts +22 -0
- package/dist/modes/rpc/rpc-types.d.ts.map +1 -1
- package/dist/modes/rpc/rpc-types.js.map +1 -1
- package/docs/extensions.md +1 -2
- package/examples/extensions/README.md +0 -1
- package/examples/extensions/bash-spawn-hook.ts +2 -2
- package/examples/extensions/built-in-tool-renderer.ts +12 -5
- package/examples/extensions/custom-provider-anthropic/package.json +1 -1
- package/examples/extensions/custom-provider-gitlab-duo/package.json +1 -1
- package/examples/extensions/minimal-mode.ts +9 -7
- package/examples/extensions/sandbox/index.ts +55 -56
- package/examples/extensions/sandbox/package.json +1 -1
- package/examples/extensions/with-deps/package.json +1 -1
- package/npm-shrinkwrap.json +12 -12
- package/package.json +4 -4
- package/examples/extensions/ssh.ts +0 -220
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
import type { Stats } from "node:fs";
|
|
2
|
+
export type ToolAccessMode = "exists" | "read" | "write" | "readwrite";
|
|
3
|
+
export interface ToolFileStat {
|
|
4
|
+
isDirectory: () => boolean;
|
|
5
|
+
isFile: () => boolean;
|
|
6
|
+
}
|
|
7
|
+
export interface ToolExecOptions {
|
|
8
|
+
cwd?: string;
|
|
9
|
+
onData: (data: Buffer) => void;
|
|
10
|
+
signal?: AbortSignal;
|
|
11
|
+
timeout?: number;
|
|
12
|
+
env?: NodeJS.ProcessEnv;
|
|
13
|
+
}
|
|
14
|
+
export interface ToolGlobOptions {
|
|
15
|
+
ignore: string[];
|
|
16
|
+
limit: number;
|
|
17
|
+
}
|
|
18
|
+
export interface ToolGrepOptions {
|
|
19
|
+
pattern: string;
|
|
20
|
+
path: string;
|
|
21
|
+
glob?: string;
|
|
22
|
+
ignoreCase?: boolean;
|
|
23
|
+
literal?: boolean;
|
|
24
|
+
limit: number;
|
|
25
|
+
}
|
|
26
|
+
export interface ToolGrepMatch {
|
|
27
|
+
filePath: string;
|
|
28
|
+
lineNumber: number;
|
|
29
|
+
lineText?: string;
|
|
30
|
+
}
|
|
31
|
+
export interface ToolGrepResult {
|
|
32
|
+
isDirectory: boolean;
|
|
33
|
+
matches: ToolGrepMatch[];
|
|
34
|
+
}
|
|
35
|
+
export type ToolBackendInfo = {
|
|
36
|
+
type: "local";
|
|
37
|
+
cwd: string;
|
|
38
|
+
} | {
|
|
39
|
+
type: "ssh";
|
|
40
|
+
cwd: string;
|
|
41
|
+
remote: string;
|
|
42
|
+
configured: true;
|
|
43
|
+
} | {
|
|
44
|
+
type: "ssh";
|
|
45
|
+
cwd: string;
|
|
46
|
+
configured: false;
|
|
47
|
+
};
|
|
48
|
+
export interface ToolOperations {
|
|
49
|
+
cwd: string;
|
|
50
|
+
exec(command: string, options: ToolExecOptions): Promise<{
|
|
51
|
+
exitCode: number | null;
|
|
52
|
+
}>;
|
|
53
|
+
access(path: string, mode?: ToolAccessMode): Promise<void>;
|
|
54
|
+
readFile(path: string): Promise<Buffer>;
|
|
55
|
+
writeFile(path: string, content: string | Buffer): Promise<void>;
|
|
56
|
+
mkdir(path: string, options?: {
|
|
57
|
+
recursive?: boolean;
|
|
58
|
+
}): Promise<void>;
|
|
59
|
+
stat(path: string): Promise<ToolFileStat>;
|
|
60
|
+
readdir(path: string): Promise<string[]>;
|
|
61
|
+
glob?(pattern: string, cwd: string, options: ToolGlobOptions): Promise<string[]>;
|
|
62
|
+
grep?(options: ToolGrepOptions): Promise<ToolGrepResult>;
|
|
63
|
+
detectImageMimeType?(path: string): Promise<string | null | undefined>;
|
|
64
|
+
getBackendInfo?(): ToolBackendInfo;
|
|
65
|
+
dispose?(): Promise<void>;
|
|
66
|
+
}
|
|
67
|
+
export interface LocalToolOperationsOptions {
|
|
68
|
+
shellPath?: string;
|
|
69
|
+
}
|
|
70
|
+
export interface SshToolOperationsOptions {
|
|
71
|
+
remote: string;
|
|
72
|
+
cwd: string;
|
|
73
|
+
}
|
|
74
|
+
export interface DeferredSshToolOperationsConfigureOptions {
|
|
75
|
+
remote: string;
|
|
76
|
+
cwd?: string;
|
|
77
|
+
}
|
|
78
|
+
export interface ParsedSshTarget {
|
|
79
|
+
remote: string;
|
|
80
|
+
cwd?: string;
|
|
81
|
+
}
|
|
82
|
+
export declare class LocalToolOperations implements ToolOperations {
|
|
83
|
+
cwd: string;
|
|
84
|
+
private shellPath;
|
|
85
|
+
constructor(cwd: string, options?: LocalToolOperationsOptions);
|
|
86
|
+
exec(command: string, options: ToolExecOptions): Promise<{
|
|
87
|
+
exitCode: number | null;
|
|
88
|
+
}>;
|
|
89
|
+
access(path: string, mode?: ToolAccessMode): Promise<void>;
|
|
90
|
+
readFile(path: string): Promise<Buffer>;
|
|
91
|
+
writeFile(path: string, content: string | Buffer): Promise<void>;
|
|
92
|
+
mkdir(path: string, options?: {
|
|
93
|
+
recursive?: boolean;
|
|
94
|
+
}): Promise<void>;
|
|
95
|
+
stat(path: string): Promise<Stats>;
|
|
96
|
+
readdir(path: string): Promise<string[]>;
|
|
97
|
+
detectImageMimeType(path: string): Promise<string | null | undefined>;
|
|
98
|
+
getBackendInfo(): ToolBackendInfo;
|
|
99
|
+
}
|
|
100
|
+
export declare class SshToolOperations implements ToolOperations {
|
|
101
|
+
readonly remote: string;
|
|
102
|
+
cwd: string;
|
|
103
|
+
constructor(options: SshToolOperationsOptions);
|
|
104
|
+
static fromTarget(target: string): Promise<SshToolOperations>;
|
|
105
|
+
exec(command: string, options: ToolExecOptions): Promise<{
|
|
106
|
+
exitCode: number | null;
|
|
107
|
+
}>;
|
|
108
|
+
access(path: string, mode?: ToolAccessMode): Promise<void>;
|
|
109
|
+
readFile(path: string): Promise<Buffer>;
|
|
110
|
+
writeFile(path: string, content: string | Buffer): Promise<void>;
|
|
111
|
+
mkdir(path: string, options?: {
|
|
112
|
+
recursive?: boolean;
|
|
113
|
+
}): Promise<void>;
|
|
114
|
+
stat(path: string): Promise<ToolFileStat>;
|
|
115
|
+
readdir(path: string): Promise<string[]>;
|
|
116
|
+
glob(pattern: string, cwd: string, options: ToolGlobOptions): Promise<string[]>;
|
|
117
|
+
grep(options: ToolGrepOptions): Promise<ToolGrepResult>;
|
|
118
|
+
detectImageMimeType(path: string): Promise<string | null | undefined>;
|
|
119
|
+
getBackendInfo(): ToolBackendInfo;
|
|
120
|
+
}
|
|
121
|
+
export declare class DeferredSshToolOperations implements ToolOperations {
|
|
122
|
+
cwd: string;
|
|
123
|
+
private operations;
|
|
124
|
+
constructor(cwd: string);
|
|
125
|
+
configure(options: DeferredSshToolOperationsConfigureOptions): Promise<ToolBackendInfo>;
|
|
126
|
+
clear(): void;
|
|
127
|
+
private requireOperations;
|
|
128
|
+
exec(command: string, options: ToolExecOptions): Promise<{
|
|
129
|
+
exitCode: number | null;
|
|
130
|
+
}>;
|
|
131
|
+
access(path: string, mode?: ToolAccessMode): Promise<void>;
|
|
132
|
+
readFile(path: string): Promise<Buffer>;
|
|
133
|
+
writeFile(path: string, content: string | Buffer): Promise<void>;
|
|
134
|
+
mkdir(path: string, options?: {
|
|
135
|
+
recursive?: boolean;
|
|
136
|
+
}): Promise<void>;
|
|
137
|
+
stat(path: string): Promise<ToolFileStat>;
|
|
138
|
+
readdir(path: string): Promise<string[]>;
|
|
139
|
+
glob(pattern: string, cwd: string, options: ToolGlobOptions): Promise<string[]>;
|
|
140
|
+
grep(options: ToolGrepOptions): Promise<ToolGrepResult>;
|
|
141
|
+
detectImageMimeType(path: string): Promise<string | null | undefined>;
|
|
142
|
+
getBackendInfo(): ToolBackendInfo;
|
|
143
|
+
}
|
|
144
|
+
export declare function createSshToolOperations(target: string): Promise<SshToolOperations>;
|
|
145
|
+
//# sourceMappingURL=operations.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"operations.d.ts","sourceRoot":"","sources":["../../../src/core/tools/operations.ts"],"names":[],"mappings":"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","sourcesContent":["import { spawn } from \"node:child_process\";\nimport type { Stats } from \"node:fs\";\nimport { constants } from \"node:fs\";\nimport {\n\taccess as fsAccess,\n\tmkdir as fsMkdir,\n\treaddir as fsReaddir,\n\treadFile as fsReadFile,\n\tstat as fsStat,\n\twriteFile as fsWriteFile,\n} from \"node:fs/promises\";\nimport { waitForChildProcess } from \"../../utils/child-process.ts\";\nimport { detectSupportedImageMimeTypeFromFile } from \"../../utils/mime.ts\";\nimport {\n\tgetShellConfig,\n\tgetShellEnv,\n\tkillProcessTree,\n\ttrackDetachedChildPid,\n\tuntrackDetachedChildPid,\n} from \"../../utils/shell.ts\";\n\nexport type ToolAccessMode = \"exists\" | \"read\" | \"write\" | \"readwrite\";\n\nexport interface ToolFileStat {\n\tisDirectory: () => boolean;\n\tisFile: () => boolean;\n}\n\nexport interface ToolExecOptions {\n\tcwd?: string;\n\tonData: (data: Buffer) => void;\n\tsignal?: AbortSignal;\n\ttimeout?: number;\n\tenv?: NodeJS.ProcessEnv;\n}\n\nexport interface ToolGlobOptions {\n\tignore: string[];\n\tlimit: number;\n}\n\nexport interface ToolGrepOptions {\n\tpattern: string;\n\tpath: string;\n\tglob?: string;\n\tignoreCase?: boolean;\n\tliteral?: boolean;\n\tlimit: number;\n}\n\nexport interface ToolGrepMatch {\n\tfilePath: string;\n\tlineNumber: number;\n\tlineText?: string;\n}\n\nexport interface ToolGrepResult {\n\tisDirectory: boolean;\n\tmatches: ToolGrepMatch[];\n}\n\nexport type ToolBackendInfo =\n\t| { type: \"local\"; cwd: string }\n\t| { type: \"ssh\"; cwd: string; remote: string; configured: true }\n\t| { type: \"ssh\"; cwd: string; configured: false };\n\nexport interface ToolOperations {\n\tcwd: string;\n\texec(command: string, options: ToolExecOptions): Promise<{ exitCode: number | null }>;\n\taccess(path: string, mode?: ToolAccessMode): Promise<void>;\n\treadFile(path: string): Promise<Buffer>;\n\twriteFile(path: string, content: string | Buffer): Promise<void>;\n\tmkdir(path: string, options?: { recursive?: boolean }): Promise<void>;\n\tstat(path: string): Promise<ToolFileStat>;\n\treaddir(path: string): Promise<string[]>;\n\tglob?(pattern: string, cwd: string, options: ToolGlobOptions): Promise<string[]>;\n\tgrep?(options: ToolGrepOptions): Promise<ToolGrepResult>;\n\tdetectImageMimeType?(path: string): Promise<string | null | undefined>;\n\tgetBackendInfo?(): ToolBackendInfo;\n\tdispose?(): Promise<void>;\n}\n\nexport interface LocalToolOperationsOptions {\n\tshellPath?: string;\n}\n\nexport interface SshToolOperationsOptions {\n\tremote: string;\n\tcwd: string;\n}\n\nexport interface DeferredSshToolOperationsConfigureOptions {\n\tremote: string;\n\tcwd?: string;\n}\n\nexport interface ParsedSshTarget {\n\tremote: string;\n\tcwd?: string;\n}\n\nfunction accessModeToFsMode(mode: ToolAccessMode | undefined): number {\n\tswitch (mode) {\n\t\tcase \"read\":\n\t\t\treturn constants.R_OK;\n\t\tcase \"write\":\n\t\t\treturn constants.W_OK;\n\t\tcase \"readwrite\":\n\t\t\treturn constants.R_OK | constants.W_OK;\n\t\tcase \"exists\":\n\t\tcase undefined:\n\t\t\treturn constants.F_OK;\n\t}\n}\n\nfunction shellQuote(value: string): string {\n\treturn `'${value.replace(/'/g, `'\\\\''`)}'`;\n}\n\nfunction parseSshTarget(value: string): ParsedSshTarget {\n\tconst separatorIndex = value.indexOf(\":\");\n\tif (separatorIndex === -1) {\n\t\treturn { remote: value };\n\t}\n\tconst remote = value.slice(0, separatorIndex);\n\tconst cwd = value.slice(separatorIndex + 1);\n\treturn cwd ? { remote, cwd } : { remote };\n}\n\nfunction validateSshRemote(remote: string): void {\n\tif (!remote) {\n\t\tthrow new Error(\"--ssh requires a remote target like user@host or user@host:/path\");\n\t}\n\tif (remote.startsWith(\"-\")) {\n\t\tthrow new Error(\"--ssh remote target must not start with '-'\");\n\t}\n}\n\nfunction sshArgs(remote: string, command: string): string[] {\n\tvalidateSshRemote(remote);\n\treturn [\"--\", remote, command];\n}\n\nfunction buildFdArgs(pattern: string, searchPath: string, limit: number): string[] {\n\tconst args: string[] = [\"--glob\", \"--color=never\", \"--hidden\", \"--no-require-git\", \"--max-results\", String(limit)];\n\tlet effectivePattern = pattern;\n\tif (pattern.includes(\"/\")) {\n\t\targs.push(\"--full-path\");\n\t\tif (!pattern.startsWith(\"/\") && !pattern.startsWith(\"**/\") && pattern !== \"**\") {\n\t\t\teffectivePattern = `**/${pattern}`;\n\t\t}\n\t}\n\targs.push(\"--\", effectivePattern, searchPath);\n\treturn args;\n}\n\nfunction buildRgArgs(options: ToolGrepOptions): string[] {\n\tconst args: string[] = [\"--json\", \"--line-number\", \"--color=never\", \"--hidden\"];\n\tif (options.ignoreCase) args.push(\"--ignore-case\");\n\tif (options.literal) args.push(\"--fixed-strings\");\n\tif (options.glob) args.push(\"--glob\", options.glob);\n\targs.push(\"--\", options.pattern, options.path);\n\treturn args;\n}\n\nfunction commandWithArgs(command: string, args: string[]): string {\n\treturn [command, ...args.map(shellQuote)].join(\" \");\n}\n\nasync function runSshBuffer(\n\tremote: string,\n\tcommand: string,\n\toptions: { input?: Buffer | string; signal?: AbortSignal; timeout?: number } = {},\n): Promise<Buffer> {\n\treturn new Promise((resolve, reject) => {\n\t\tconst child = spawn(\"ssh\", sshArgs(remote, command), { stdio: [\"pipe\", \"pipe\", \"pipe\"] });\n\t\tconst stdout: Buffer[] = [];\n\t\tconst stderr: Buffer[] = [];\n\t\tlet timedOut = false;\n\t\tlet timeoutHandle: NodeJS.Timeout | undefined;\n\t\tif (options.timeout !== undefined && options.timeout > 0) {\n\t\t\ttimeoutHandle = setTimeout(() => {\n\t\t\t\ttimedOut = true;\n\t\t\t\tchild.kill();\n\t\t\t}, options.timeout * 1000);\n\t\t}\n\t\tchild.stdout.on(\"data\", (data: Buffer) => stdout.push(data));\n\t\tchild.stderr.on(\"data\", (data: Buffer) => stderr.push(data));\n\t\tchild.on(\"error\", reject);\n\t\tconst onAbort = () => child.kill();\n\t\toptions.signal?.addEventListener(\"abort\", onAbort, { once: true });\n\t\tif (options.input !== undefined) {\n\t\t\tchild.stdin.end(options.input);\n\t\t} else {\n\t\t\tchild.stdin.end();\n\t\t}\n\t\tchild.on(\"close\", (code) => {\n\t\t\tif (timeoutHandle) clearTimeout(timeoutHandle);\n\t\t\toptions.signal?.removeEventListener(\"abort\", onAbort);\n\t\t\tif (options.signal?.aborted) {\n\t\t\t\treject(new Error(\"aborted\"));\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tif (timedOut) {\n\t\t\t\treject(new Error(`timeout:${options.timeout}`));\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tif (code !== 0) {\n\t\t\t\treject(new Error(Buffer.concat(stderr).toString(\"utf-8\").trim() || `ssh exited with code ${code}`));\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tresolve(Buffer.concat(stdout));\n\t\t});\n\t});\n}\n\nexport class LocalToolOperations implements ToolOperations {\n\tcwd: string;\n\tprivate shellPath: string | undefined;\n\n\tconstructor(cwd: string, options: LocalToolOperationsOptions = {}) {\n\t\tthis.cwd = cwd;\n\t\tthis.shellPath = options.shellPath;\n\t}\n\n\tasync exec(command: string, options: ToolExecOptions): Promise<{ exitCode: number | null }> {\n\t\tconst cwd = options.cwd ?? this.cwd;\n\t\tconst { shell, args } = getShellConfig(this.shellPath);\n\t\ttry {\n\t\t\tawait fsAccess(cwd, constants.F_OK);\n\t\t} catch {\n\t\t\tthrow new Error(`Working directory does not exist: ${cwd}\\nCannot execute bash commands.`);\n\t\t}\n\t\tif (options.signal?.aborted) {\n\t\t\tthrow new Error(\"aborted\");\n\t\t}\n\t\treturn new Promise((resolve, reject) => {\n\t\t\tconst child = spawn(shell, [...args, command], {\n\t\t\t\tcwd,\n\t\t\t\tdetached: process.platform !== \"win32\",\n\t\t\t\tenv: options.env ?? getShellEnv(),\n\t\t\t\tstdio: [\"ignore\", \"pipe\", \"pipe\"],\n\t\t\t\twindowsHide: true,\n\t\t\t});\n\t\t\tif (child.pid) trackDetachedChildPid(child.pid);\n\t\t\tlet timedOut = false;\n\t\t\tlet timeoutHandle: NodeJS.Timeout | undefined;\n\t\t\tif (options.timeout !== undefined && options.timeout > 0) {\n\t\t\t\ttimeoutHandle = setTimeout(() => {\n\t\t\t\t\ttimedOut = true;\n\t\t\t\t\tif (child.pid) killProcessTree(child.pid);\n\t\t\t\t}, options.timeout * 1000);\n\t\t\t}\n\t\t\tchild.stdout?.on(\"data\", options.onData);\n\t\t\tchild.stderr?.on(\"data\", options.onData);\n\t\t\tconst onAbort = () => {\n\t\t\t\tif (child.pid) killProcessTree(child.pid);\n\t\t\t};\n\t\t\tif (options.signal) {\n\t\t\t\tif (options.signal.aborted) onAbort();\n\t\t\t\telse options.signal.addEventListener(\"abort\", onAbort, { once: true });\n\t\t\t}\n\t\t\twaitForChildProcess(child)\n\t\t\t\t.then((code) => {\n\t\t\t\t\tif (child.pid) untrackDetachedChildPid(child.pid);\n\t\t\t\t\tif (timeoutHandle) clearTimeout(timeoutHandle);\n\t\t\t\t\tif (options.signal) options.signal.removeEventListener(\"abort\", onAbort);\n\t\t\t\t\tif (options.signal?.aborted) {\n\t\t\t\t\t\treject(new Error(\"aborted\"));\n\t\t\t\t\t\treturn;\n\t\t\t\t\t}\n\t\t\t\t\tif (timedOut) {\n\t\t\t\t\t\treject(new Error(`timeout:${options.timeout}`));\n\t\t\t\t\t\treturn;\n\t\t\t\t\t}\n\t\t\t\t\tresolve({ exitCode: code });\n\t\t\t\t})\n\t\t\t\t.catch((error: unknown) => {\n\t\t\t\t\tif (child.pid) untrackDetachedChildPid(child.pid);\n\t\t\t\t\tif (timeoutHandle) clearTimeout(timeoutHandle);\n\t\t\t\t\tif (options.signal) options.signal.removeEventListener(\"abort\", onAbort);\n\t\t\t\t\treject(error);\n\t\t\t\t});\n\t\t});\n\t}\n\n\tasync access(path: string, mode?: ToolAccessMode): Promise<void> {\n\t\tawait fsAccess(path, accessModeToFsMode(mode));\n\t}\n\n\tasync readFile(path: string): Promise<Buffer> {\n\t\treturn fsReadFile(path);\n\t}\n\n\tasync writeFile(path: string, content: string | Buffer): Promise<void> {\n\t\tawait fsWriteFile(path, content, typeof content === \"string\" ? \"utf-8\" : undefined);\n\t}\n\n\tasync mkdir(path: string, options: { recursive?: boolean } = {}): Promise<void> {\n\t\tawait fsMkdir(path, { recursive: options.recursive ?? false });\n\t}\n\n\tasync stat(path: string): Promise<Stats> {\n\t\treturn fsStat(path);\n\t}\n\n\tasync readdir(path: string): Promise<string[]> {\n\t\treturn fsReaddir(path);\n\t}\n\n\tasync detectImageMimeType(path: string): Promise<string | null | undefined> {\n\t\treturn detectSupportedImageMimeTypeFromFile(path);\n\t}\n\n\tgetBackendInfo(): ToolBackendInfo {\n\t\treturn { type: \"local\", cwd: this.cwd };\n\t}\n}\n\nexport class SshToolOperations implements ToolOperations {\n\treadonly remote: string;\n\tcwd: string;\n\n\tconstructor(options: SshToolOperationsOptions) {\n\t\tthis.remote = options.remote;\n\t\tthis.cwd = options.cwd;\n\t}\n\n\tstatic async fromTarget(target: string): Promise<SshToolOperations> {\n\t\tconst parsed = parseSshTarget(target);\n\t\tvalidateSshRemote(parsed.remote);\n\t\tconst cwd = parsed.cwd ?? (await runSshBuffer(parsed.remote, \"pwd\")).toString(\"utf-8\").trim();\n\t\treturn new SshToolOperations({ remote: parsed.remote, cwd });\n\t}\n\n\tasync exec(command: string, options: ToolExecOptions): Promise<{ exitCode: number | null }> {\n\t\tconst cwd = options.cwd ?? this.cwd;\n\t\tconst remoteCommand = `cd ${shellQuote(cwd)} && bash -s`;\n\t\treturn new Promise((resolve, reject) => {\n\t\t\tconst child = spawn(\"ssh\", sshArgs(this.remote, remoteCommand), {\n\t\t\t\tstdio: [\"pipe\", \"pipe\", \"pipe\"],\n\t\t\t});\n\t\t\tlet timedOut = false;\n\t\t\tlet timeoutHandle: NodeJS.Timeout | undefined;\n\t\t\tif (options.timeout !== undefined && options.timeout > 0) {\n\t\t\t\ttimeoutHandle = setTimeout(() => {\n\t\t\t\t\ttimedOut = true;\n\t\t\t\t\tchild.kill();\n\t\t\t\t}, options.timeout * 1000);\n\t\t\t}\n\t\t\tchild.stdout?.on(\"data\", options.onData);\n\t\t\tchild.stderr?.on(\"data\", options.onData);\n\t\t\tchild.on(\"error\", reject);\n\t\t\tchild.stdin.end(command);\n\t\t\tconst onAbort = () => child.kill();\n\t\t\toptions.signal?.addEventListener(\"abort\", onAbort, { once: true });\n\t\t\tchild.on(\"close\", (code) => {\n\t\t\t\tif (timeoutHandle) clearTimeout(timeoutHandle);\n\t\t\t\toptions.signal?.removeEventListener(\"abort\", onAbort);\n\t\t\t\tif (options.signal?.aborted) {\n\t\t\t\t\treject(new Error(\"aborted\"));\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tif (timedOut) {\n\t\t\t\t\treject(new Error(`timeout:${options.timeout}`));\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tresolve({ exitCode: code });\n\t\t\t});\n\t\t});\n\t}\n\n\tasync access(path: string, mode?: ToolAccessMode): Promise<void> {\n\t\tconst remotePath = shellQuote(path);\n\t\tif (mode === \"readwrite\") {\n\t\t\tawait runSshBuffer(this.remote, `test -r ${remotePath} && test -w ${remotePath}`);\n\t\t\treturn;\n\t\t}\n\t\tconst flag = mode === \"read\" ? \"-r\" : mode === \"write\" ? \"-w\" : \"-e\";\n\t\tawait runSshBuffer(this.remote, `test ${flag} ${remotePath}`);\n\t}\n\n\tasync readFile(path: string): Promise<Buffer> {\n\t\treturn runSshBuffer(this.remote, `cat ${shellQuote(path)}`);\n\t}\n\n\tasync writeFile(path: string, content: string | Buffer): Promise<void> {\n\t\tawait runSshBuffer(this.remote, `base64 -d > ${shellQuote(path)}`, {\n\t\t\tinput: Buffer.from(content).toString(\"base64\"),\n\t\t});\n\t}\n\n\tasync mkdir(path: string, options: { recursive?: boolean } = {}): Promise<void> {\n\t\tconst flag = options.recursive ? \"-p \" : \"\";\n\t\tawait runSshBuffer(this.remote, `mkdir ${flag}${shellQuote(path)}`);\n\t}\n\n\tasync stat(path: string): Promise<ToolFileStat> {\n\t\tconst output = await runSshBuffer(\n\t\t\tthis.remote,\n\t\t\t`if test -d ${shellQuote(path)}; then echo d; elif test -f ${shellQuote(path)}; then echo f; else test -e ${shellQuote(path)} && echo o || exit 1; fi`,\n\t\t);\n\t\tconst kind = output.toString(\"utf-8\").trim();\n\t\treturn {\n\t\t\tisDirectory: () => kind === \"d\",\n\t\t\tisFile: () => kind === \"f\",\n\t\t};\n\t}\n\n\tasync readdir(path: string): Promise<string[]> {\n\t\tconst output = await runSshBuffer(\n\t\t\tthis.remote,\n\t\t\t`find ${shellQuote(path)} -maxdepth 1 -mindepth 1 -printf '%f\\\\n'`,\n\t\t);\n\t\treturn output.toString(\"utf-8\").split(\"\\n\").filter(Boolean);\n\t}\n\n\tasync glob(pattern: string, cwd: string, options: ToolGlobOptions): Promise<string[]> {\n\t\tconst command = commandWithArgs(\"fd\", buildFdArgs(pattern, cwd, options.limit));\n\t\tconst output = await runSshBuffer(this.remote, command);\n\t\treturn output.toString(\"utf-8\").split(\"\\n\").filter(Boolean);\n\t}\n\n\tasync grep(options: ToolGrepOptions): Promise<ToolGrepResult> {\n\t\tconst isDirectory = (await this.stat(options.path)).isDirectory();\n\t\tconst command = commandWithArgs(\"rg\", buildRgArgs(options));\n\t\tconst output = await runSshBuffer(this.remote, command).catch((error: unknown) => {\n\t\t\tif (error instanceof Error && error.message.includes(\"ssh exited with code 1\")) {\n\t\t\t\treturn Buffer.alloc(0);\n\t\t\t}\n\t\t\tthrow error;\n\t\t});\n\t\tconst matches: ToolGrepMatch[] = [];\n\t\tfor (const line of output.toString(\"utf-8\").split(\"\\n\")) {\n\t\t\tif (!line.trim() || matches.length >= options.limit) continue;\n\t\t\tlet event: unknown;\n\t\t\ttry {\n\t\t\t\tevent = JSON.parse(line);\n\t\t\t} catch {\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tif (!event || typeof event !== \"object\" || !(\"type\" in event) || event.type !== \"match\") continue;\n\t\t\tconst data = \"data\" in event && event.data && typeof event.data === \"object\" ? event.data : undefined;\n\t\t\tconst filePath =\n\t\t\t\tdata && \"path\" in data && data.path && typeof data.path === \"object\" && \"text\" in data.path\n\t\t\t\t\t? data.path.text\n\t\t\t\t\t: undefined;\n\t\t\tconst lineNumber = data && \"line_number\" in data ? data.line_number : undefined;\n\t\t\tconst lineText =\n\t\t\t\tdata && \"lines\" in data && data.lines && typeof data.lines === \"object\" && \"text\" in data.lines\n\t\t\t\t\t? data.lines.text\n\t\t\t\t\t: undefined;\n\t\t\tif (typeof filePath === \"string\" && typeof lineNumber === \"number\") {\n\t\t\t\tmatches.push({ filePath, lineNumber, lineText: typeof lineText === \"string\" ? lineText : undefined });\n\t\t\t}\n\t\t}\n\t\treturn { isDirectory, matches };\n\t}\n\n\tasync detectImageMimeType(path: string): Promise<string | null | undefined> {\n\t\ttry {\n\t\t\tconst output = await runSshBuffer(this.remote, `file --mime-type -b ${shellQuote(path)}`);\n\t\t\tconst mimeType = output.toString(\"utf-8\").trim();\n\t\t\treturn [\"image/jpeg\", \"image/png\", \"image/gif\", \"image/webp\"].includes(mimeType) ? mimeType : null;\n\t\t} catch {\n\t\t\treturn null;\n\t\t}\n\t}\n\n\tgetBackendInfo(): ToolBackendInfo {\n\t\treturn { type: \"ssh\", remote: this.remote, cwd: this.cwd, configured: true };\n\t}\n}\n\nexport class DeferredSshToolOperations implements ToolOperations {\n\tcwd: string;\n\tprivate operations: SshToolOperations | undefined;\n\n\tconstructor(cwd: string) {\n\t\tthis.cwd = cwd;\n\t}\n\n\tasync configure(options: DeferredSshToolOperationsConfigureOptions): Promise<ToolBackendInfo> {\n\t\tconst next = new SshToolOperations({ remote: options.remote, cwd: options.cwd ?? this.cwd });\n\t\tconst stat = await next.stat(next.cwd);\n\t\tif (!stat.isDirectory()) {\n\t\t\tthrow new Error(`SSH sandbox cwd is not a directory: ${next.cwd}`);\n\t\t}\n\t\tthis.cwd = next.cwd;\n\t\tthis.operations = next;\n\t\treturn this.getBackendInfo();\n\t}\n\n\tclear(): void {\n\t\tthis.operations = undefined;\n\t}\n\n\tprivate requireOperations(): SshToolOperations {\n\t\tif (!this.operations) {\n\t\t\tthrow new Error(\n\t\t\t\t\"SSH sandbox is not configured. Configure it over RPC or with /ssh-sandbox before using tools.\",\n\t\t\t);\n\t\t}\n\t\treturn this.operations;\n\t}\n\n\tasync exec(command: string, options: ToolExecOptions): Promise<{ exitCode: number | null }> {\n\t\treturn this.requireOperations().exec(command, options);\n\t}\n\n\tasync access(path: string, mode?: ToolAccessMode): Promise<void> {\n\t\tawait this.requireOperations().access(path, mode);\n\t}\n\n\tasync readFile(path: string): Promise<Buffer> {\n\t\treturn this.requireOperations().readFile(path);\n\t}\n\n\tasync writeFile(path: string, content: string | Buffer): Promise<void> {\n\t\tawait this.requireOperations().writeFile(path, content);\n\t}\n\n\tasync mkdir(path: string, options?: { recursive?: boolean }): Promise<void> {\n\t\tawait this.requireOperations().mkdir(path, options);\n\t}\n\n\tasync stat(path: string): Promise<ToolFileStat> {\n\t\treturn this.requireOperations().stat(path);\n\t}\n\n\tasync readdir(path: string): Promise<string[]> {\n\t\treturn this.requireOperations().readdir(path);\n\t}\n\n\tasync glob(pattern: string, cwd: string, options: ToolGlobOptions): Promise<string[]> {\n\t\treturn this.requireOperations().glob(pattern, cwd, options);\n\t}\n\n\tasync grep(options: ToolGrepOptions): Promise<ToolGrepResult> {\n\t\treturn this.requireOperations().grep(options);\n\t}\n\n\tasync detectImageMimeType(path: string): Promise<string | null | undefined> {\n\t\treturn this.requireOperations().detectImageMimeType(path);\n\t}\n\n\tgetBackendInfo(): ToolBackendInfo {\n\t\treturn this.operations?.getBackendInfo() ?? { type: \"ssh\", cwd: this.cwd, configured: false };\n\t}\n}\n\nexport function createSshToolOperations(target: string): Promise<SshToolOperations> {\n\treturn SshToolOperations.fromTarget(target);\n}\n"]}
|
|
@@ -0,0 +1,418 @@
|
|
|
1
|
+
import { spawn } from "node:child_process";
|
|
2
|
+
import { constants } from "node:fs";
|
|
3
|
+
import { access as fsAccess, mkdir as fsMkdir, readdir as fsReaddir, readFile as fsReadFile, stat as fsStat, writeFile as fsWriteFile, } from "node:fs/promises";
|
|
4
|
+
import { waitForChildProcess } from "../../utils/child-process.js";
|
|
5
|
+
import { detectSupportedImageMimeTypeFromFile } from "../../utils/mime.js";
|
|
6
|
+
import { getShellConfig, getShellEnv, killProcessTree, trackDetachedChildPid, untrackDetachedChildPid, } from "../../utils/shell.js";
|
|
7
|
+
function accessModeToFsMode(mode) {
|
|
8
|
+
switch (mode) {
|
|
9
|
+
case "read":
|
|
10
|
+
return constants.R_OK;
|
|
11
|
+
case "write":
|
|
12
|
+
return constants.W_OK;
|
|
13
|
+
case "readwrite":
|
|
14
|
+
return constants.R_OK | constants.W_OK;
|
|
15
|
+
case "exists":
|
|
16
|
+
case undefined:
|
|
17
|
+
return constants.F_OK;
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
function shellQuote(value) {
|
|
21
|
+
return `'${value.replace(/'/g, `'\\''`)}'`;
|
|
22
|
+
}
|
|
23
|
+
function parseSshTarget(value) {
|
|
24
|
+
const separatorIndex = value.indexOf(":");
|
|
25
|
+
if (separatorIndex === -1) {
|
|
26
|
+
return { remote: value };
|
|
27
|
+
}
|
|
28
|
+
const remote = value.slice(0, separatorIndex);
|
|
29
|
+
const cwd = value.slice(separatorIndex + 1);
|
|
30
|
+
return cwd ? { remote, cwd } : { remote };
|
|
31
|
+
}
|
|
32
|
+
function validateSshRemote(remote) {
|
|
33
|
+
if (!remote) {
|
|
34
|
+
throw new Error("--ssh requires a remote target like user@host or user@host:/path");
|
|
35
|
+
}
|
|
36
|
+
if (remote.startsWith("-")) {
|
|
37
|
+
throw new Error("--ssh remote target must not start with '-'");
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
function sshArgs(remote, command) {
|
|
41
|
+
validateSshRemote(remote);
|
|
42
|
+
return ["--", remote, command];
|
|
43
|
+
}
|
|
44
|
+
function buildFdArgs(pattern, searchPath, limit) {
|
|
45
|
+
const args = ["--glob", "--color=never", "--hidden", "--no-require-git", "--max-results", String(limit)];
|
|
46
|
+
let effectivePattern = pattern;
|
|
47
|
+
if (pattern.includes("/")) {
|
|
48
|
+
args.push("--full-path");
|
|
49
|
+
if (!pattern.startsWith("/") && !pattern.startsWith("**/") && pattern !== "**") {
|
|
50
|
+
effectivePattern = `**/${pattern}`;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
args.push("--", effectivePattern, searchPath);
|
|
54
|
+
return args;
|
|
55
|
+
}
|
|
56
|
+
function buildRgArgs(options) {
|
|
57
|
+
const args = ["--json", "--line-number", "--color=never", "--hidden"];
|
|
58
|
+
if (options.ignoreCase)
|
|
59
|
+
args.push("--ignore-case");
|
|
60
|
+
if (options.literal)
|
|
61
|
+
args.push("--fixed-strings");
|
|
62
|
+
if (options.glob)
|
|
63
|
+
args.push("--glob", options.glob);
|
|
64
|
+
args.push("--", options.pattern, options.path);
|
|
65
|
+
return args;
|
|
66
|
+
}
|
|
67
|
+
function commandWithArgs(command, args) {
|
|
68
|
+
return [command, ...args.map(shellQuote)].join(" ");
|
|
69
|
+
}
|
|
70
|
+
async function runSshBuffer(remote, command, options = {}) {
|
|
71
|
+
return new Promise((resolve, reject) => {
|
|
72
|
+
const child = spawn("ssh", sshArgs(remote, command), { stdio: ["pipe", "pipe", "pipe"] });
|
|
73
|
+
const stdout = [];
|
|
74
|
+
const stderr = [];
|
|
75
|
+
let timedOut = false;
|
|
76
|
+
let timeoutHandle;
|
|
77
|
+
if (options.timeout !== undefined && options.timeout > 0) {
|
|
78
|
+
timeoutHandle = setTimeout(() => {
|
|
79
|
+
timedOut = true;
|
|
80
|
+
child.kill();
|
|
81
|
+
}, options.timeout * 1000);
|
|
82
|
+
}
|
|
83
|
+
child.stdout.on("data", (data) => stdout.push(data));
|
|
84
|
+
child.stderr.on("data", (data) => stderr.push(data));
|
|
85
|
+
child.on("error", reject);
|
|
86
|
+
const onAbort = () => child.kill();
|
|
87
|
+
options.signal?.addEventListener("abort", onAbort, { once: true });
|
|
88
|
+
if (options.input !== undefined) {
|
|
89
|
+
child.stdin.end(options.input);
|
|
90
|
+
}
|
|
91
|
+
else {
|
|
92
|
+
child.stdin.end();
|
|
93
|
+
}
|
|
94
|
+
child.on("close", (code) => {
|
|
95
|
+
if (timeoutHandle)
|
|
96
|
+
clearTimeout(timeoutHandle);
|
|
97
|
+
options.signal?.removeEventListener("abort", onAbort);
|
|
98
|
+
if (options.signal?.aborted) {
|
|
99
|
+
reject(new Error("aborted"));
|
|
100
|
+
return;
|
|
101
|
+
}
|
|
102
|
+
if (timedOut) {
|
|
103
|
+
reject(new Error(`timeout:${options.timeout}`));
|
|
104
|
+
return;
|
|
105
|
+
}
|
|
106
|
+
if (code !== 0) {
|
|
107
|
+
reject(new Error(Buffer.concat(stderr).toString("utf-8").trim() || `ssh exited with code ${code}`));
|
|
108
|
+
return;
|
|
109
|
+
}
|
|
110
|
+
resolve(Buffer.concat(stdout));
|
|
111
|
+
});
|
|
112
|
+
});
|
|
113
|
+
}
|
|
114
|
+
export class LocalToolOperations {
|
|
115
|
+
cwd;
|
|
116
|
+
shellPath;
|
|
117
|
+
constructor(cwd, options = {}) {
|
|
118
|
+
this.cwd = cwd;
|
|
119
|
+
this.shellPath = options.shellPath;
|
|
120
|
+
}
|
|
121
|
+
async exec(command, options) {
|
|
122
|
+
const cwd = options.cwd ?? this.cwd;
|
|
123
|
+
const { shell, args } = getShellConfig(this.shellPath);
|
|
124
|
+
try {
|
|
125
|
+
await fsAccess(cwd, constants.F_OK);
|
|
126
|
+
}
|
|
127
|
+
catch {
|
|
128
|
+
throw new Error(`Working directory does not exist: ${cwd}\nCannot execute bash commands.`);
|
|
129
|
+
}
|
|
130
|
+
if (options.signal?.aborted) {
|
|
131
|
+
throw new Error("aborted");
|
|
132
|
+
}
|
|
133
|
+
return new Promise((resolve, reject) => {
|
|
134
|
+
const child = spawn(shell, [...args, command], {
|
|
135
|
+
cwd,
|
|
136
|
+
detached: process.platform !== "win32",
|
|
137
|
+
env: options.env ?? getShellEnv(),
|
|
138
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
139
|
+
windowsHide: true,
|
|
140
|
+
});
|
|
141
|
+
if (child.pid)
|
|
142
|
+
trackDetachedChildPid(child.pid);
|
|
143
|
+
let timedOut = false;
|
|
144
|
+
let timeoutHandle;
|
|
145
|
+
if (options.timeout !== undefined && options.timeout > 0) {
|
|
146
|
+
timeoutHandle = setTimeout(() => {
|
|
147
|
+
timedOut = true;
|
|
148
|
+
if (child.pid)
|
|
149
|
+
killProcessTree(child.pid);
|
|
150
|
+
}, options.timeout * 1000);
|
|
151
|
+
}
|
|
152
|
+
child.stdout?.on("data", options.onData);
|
|
153
|
+
child.stderr?.on("data", options.onData);
|
|
154
|
+
const onAbort = () => {
|
|
155
|
+
if (child.pid)
|
|
156
|
+
killProcessTree(child.pid);
|
|
157
|
+
};
|
|
158
|
+
if (options.signal) {
|
|
159
|
+
if (options.signal.aborted)
|
|
160
|
+
onAbort();
|
|
161
|
+
else
|
|
162
|
+
options.signal.addEventListener("abort", onAbort, { once: true });
|
|
163
|
+
}
|
|
164
|
+
waitForChildProcess(child)
|
|
165
|
+
.then((code) => {
|
|
166
|
+
if (child.pid)
|
|
167
|
+
untrackDetachedChildPid(child.pid);
|
|
168
|
+
if (timeoutHandle)
|
|
169
|
+
clearTimeout(timeoutHandle);
|
|
170
|
+
if (options.signal)
|
|
171
|
+
options.signal.removeEventListener("abort", onAbort);
|
|
172
|
+
if (options.signal?.aborted) {
|
|
173
|
+
reject(new Error("aborted"));
|
|
174
|
+
return;
|
|
175
|
+
}
|
|
176
|
+
if (timedOut) {
|
|
177
|
+
reject(new Error(`timeout:${options.timeout}`));
|
|
178
|
+
return;
|
|
179
|
+
}
|
|
180
|
+
resolve({ exitCode: code });
|
|
181
|
+
})
|
|
182
|
+
.catch((error) => {
|
|
183
|
+
if (child.pid)
|
|
184
|
+
untrackDetachedChildPid(child.pid);
|
|
185
|
+
if (timeoutHandle)
|
|
186
|
+
clearTimeout(timeoutHandle);
|
|
187
|
+
if (options.signal)
|
|
188
|
+
options.signal.removeEventListener("abort", onAbort);
|
|
189
|
+
reject(error);
|
|
190
|
+
});
|
|
191
|
+
});
|
|
192
|
+
}
|
|
193
|
+
async access(path, mode) {
|
|
194
|
+
await fsAccess(path, accessModeToFsMode(mode));
|
|
195
|
+
}
|
|
196
|
+
async readFile(path) {
|
|
197
|
+
return fsReadFile(path);
|
|
198
|
+
}
|
|
199
|
+
async writeFile(path, content) {
|
|
200
|
+
await fsWriteFile(path, content, typeof content === "string" ? "utf-8" : undefined);
|
|
201
|
+
}
|
|
202
|
+
async mkdir(path, options = {}) {
|
|
203
|
+
await fsMkdir(path, { recursive: options.recursive ?? false });
|
|
204
|
+
}
|
|
205
|
+
async stat(path) {
|
|
206
|
+
return fsStat(path);
|
|
207
|
+
}
|
|
208
|
+
async readdir(path) {
|
|
209
|
+
return fsReaddir(path);
|
|
210
|
+
}
|
|
211
|
+
async detectImageMimeType(path) {
|
|
212
|
+
return detectSupportedImageMimeTypeFromFile(path);
|
|
213
|
+
}
|
|
214
|
+
getBackendInfo() {
|
|
215
|
+
return { type: "local", cwd: this.cwd };
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
export class SshToolOperations {
|
|
219
|
+
remote;
|
|
220
|
+
cwd;
|
|
221
|
+
constructor(options) {
|
|
222
|
+
this.remote = options.remote;
|
|
223
|
+
this.cwd = options.cwd;
|
|
224
|
+
}
|
|
225
|
+
static async fromTarget(target) {
|
|
226
|
+
const parsed = parseSshTarget(target);
|
|
227
|
+
validateSshRemote(parsed.remote);
|
|
228
|
+
const cwd = parsed.cwd ?? (await runSshBuffer(parsed.remote, "pwd")).toString("utf-8").trim();
|
|
229
|
+
return new SshToolOperations({ remote: parsed.remote, cwd });
|
|
230
|
+
}
|
|
231
|
+
async exec(command, options) {
|
|
232
|
+
const cwd = options.cwd ?? this.cwd;
|
|
233
|
+
const remoteCommand = `cd ${shellQuote(cwd)} && bash -s`;
|
|
234
|
+
return new Promise((resolve, reject) => {
|
|
235
|
+
const child = spawn("ssh", sshArgs(this.remote, remoteCommand), {
|
|
236
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
237
|
+
});
|
|
238
|
+
let timedOut = false;
|
|
239
|
+
let timeoutHandle;
|
|
240
|
+
if (options.timeout !== undefined && options.timeout > 0) {
|
|
241
|
+
timeoutHandle = setTimeout(() => {
|
|
242
|
+
timedOut = true;
|
|
243
|
+
child.kill();
|
|
244
|
+
}, options.timeout * 1000);
|
|
245
|
+
}
|
|
246
|
+
child.stdout?.on("data", options.onData);
|
|
247
|
+
child.stderr?.on("data", options.onData);
|
|
248
|
+
child.on("error", reject);
|
|
249
|
+
child.stdin.end(command);
|
|
250
|
+
const onAbort = () => child.kill();
|
|
251
|
+
options.signal?.addEventListener("abort", onAbort, { once: true });
|
|
252
|
+
child.on("close", (code) => {
|
|
253
|
+
if (timeoutHandle)
|
|
254
|
+
clearTimeout(timeoutHandle);
|
|
255
|
+
options.signal?.removeEventListener("abort", onAbort);
|
|
256
|
+
if (options.signal?.aborted) {
|
|
257
|
+
reject(new Error("aborted"));
|
|
258
|
+
return;
|
|
259
|
+
}
|
|
260
|
+
if (timedOut) {
|
|
261
|
+
reject(new Error(`timeout:${options.timeout}`));
|
|
262
|
+
return;
|
|
263
|
+
}
|
|
264
|
+
resolve({ exitCode: code });
|
|
265
|
+
});
|
|
266
|
+
});
|
|
267
|
+
}
|
|
268
|
+
async access(path, mode) {
|
|
269
|
+
const remotePath = shellQuote(path);
|
|
270
|
+
if (mode === "readwrite") {
|
|
271
|
+
await runSshBuffer(this.remote, `test -r ${remotePath} && test -w ${remotePath}`);
|
|
272
|
+
return;
|
|
273
|
+
}
|
|
274
|
+
const flag = mode === "read" ? "-r" : mode === "write" ? "-w" : "-e";
|
|
275
|
+
await runSshBuffer(this.remote, `test ${flag} ${remotePath}`);
|
|
276
|
+
}
|
|
277
|
+
async readFile(path) {
|
|
278
|
+
return runSshBuffer(this.remote, `cat ${shellQuote(path)}`);
|
|
279
|
+
}
|
|
280
|
+
async writeFile(path, content) {
|
|
281
|
+
await runSshBuffer(this.remote, `base64 -d > ${shellQuote(path)}`, {
|
|
282
|
+
input: Buffer.from(content).toString("base64"),
|
|
283
|
+
});
|
|
284
|
+
}
|
|
285
|
+
async mkdir(path, options = {}) {
|
|
286
|
+
const flag = options.recursive ? "-p " : "";
|
|
287
|
+
await runSshBuffer(this.remote, `mkdir ${flag}${shellQuote(path)}`);
|
|
288
|
+
}
|
|
289
|
+
async stat(path) {
|
|
290
|
+
const output = await runSshBuffer(this.remote, `if test -d ${shellQuote(path)}; then echo d; elif test -f ${shellQuote(path)}; then echo f; else test -e ${shellQuote(path)} && echo o || exit 1; fi`);
|
|
291
|
+
const kind = output.toString("utf-8").trim();
|
|
292
|
+
return {
|
|
293
|
+
isDirectory: () => kind === "d",
|
|
294
|
+
isFile: () => kind === "f",
|
|
295
|
+
};
|
|
296
|
+
}
|
|
297
|
+
async readdir(path) {
|
|
298
|
+
const output = await runSshBuffer(this.remote, `find ${shellQuote(path)} -maxdepth 1 -mindepth 1 -printf '%f\\n'`);
|
|
299
|
+
return output.toString("utf-8").split("\n").filter(Boolean);
|
|
300
|
+
}
|
|
301
|
+
async glob(pattern, cwd, options) {
|
|
302
|
+
const command = commandWithArgs("fd", buildFdArgs(pattern, cwd, options.limit));
|
|
303
|
+
const output = await runSshBuffer(this.remote, command);
|
|
304
|
+
return output.toString("utf-8").split("\n").filter(Boolean);
|
|
305
|
+
}
|
|
306
|
+
async grep(options) {
|
|
307
|
+
const isDirectory = (await this.stat(options.path)).isDirectory();
|
|
308
|
+
const command = commandWithArgs("rg", buildRgArgs(options));
|
|
309
|
+
const output = await runSshBuffer(this.remote, command).catch((error) => {
|
|
310
|
+
if (error instanceof Error && error.message.includes("ssh exited with code 1")) {
|
|
311
|
+
return Buffer.alloc(0);
|
|
312
|
+
}
|
|
313
|
+
throw error;
|
|
314
|
+
});
|
|
315
|
+
const matches = [];
|
|
316
|
+
for (const line of output.toString("utf-8").split("\n")) {
|
|
317
|
+
if (!line.trim() || matches.length >= options.limit)
|
|
318
|
+
continue;
|
|
319
|
+
let event;
|
|
320
|
+
try {
|
|
321
|
+
event = JSON.parse(line);
|
|
322
|
+
}
|
|
323
|
+
catch {
|
|
324
|
+
continue;
|
|
325
|
+
}
|
|
326
|
+
if (!event || typeof event !== "object" || !("type" in event) || event.type !== "match")
|
|
327
|
+
continue;
|
|
328
|
+
const data = "data" in event && event.data && typeof event.data === "object" ? event.data : undefined;
|
|
329
|
+
const filePath = data && "path" in data && data.path && typeof data.path === "object" && "text" in data.path
|
|
330
|
+
? data.path.text
|
|
331
|
+
: undefined;
|
|
332
|
+
const lineNumber = data && "line_number" in data ? data.line_number : undefined;
|
|
333
|
+
const lineText = data && "lines" in data && data.lines && typeof data.lines === "object" && "text" in data.lines
|
|
334
|
+
? data.lines.text
|
|
335
|
+
: undefined;
|
|
336
|
+
if (typeof filePath === "string" && typeof lineNumber === "number") {
|
|
337
|
+
matches.push({ filePath, lineNumber, lineText: typeof lineText === "string" ? lineText : undefined });
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
return { isDirectory, matches };
|
|
341
|
+
}
|
|
342
|
+
async detectImageMimeType(path) {
|
|
343
|
+
try {
|
|
344
|
+
const output = await runSshBuffer(this.remote, `file --mime-type -b ${shellQuote(path)}`);
|
|
345
|
+
const mimeType = output.toString("utf-8").trim();
|
|
346
|
+
return ["image/jpeg", "image/png", "image/gif", "image/webp"].includes(mimeType) ? mimeType : null;
|
|
347
|
+
}
|
|
348
|
+
catch {
|
|
349
|
+
return null;
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
getBackendInfo() {
|
|
353
|
+
return { type: "ssh", remote: this.remote, cwd: this.cwd, configured: true };
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
export class DeferredSshToolOperations {
|
|
357
|
+
cwd;
|
|
358
|
+
operations;
|
|
359
|
+
constructor(cwd) {
|
|
360
|
+
this.cwd = cwd;
|
|
361
|
+
}
|
|
362
|
+
async configure(options) {
|
|
363
|
+
const next = new SshToolOperations({ remote: options.remote, cwd: options.cwd ?? this.cwd });
|
|
364
|
+
const stat = await next.stat(next.cwd);
|
|
365
|
+
if (!stat.isDirectory()) {
|
|
366
|
+
throw new Error(`SSH sandbox cwd is not a directory: ${next.cwd}`);
|
|
367
|
+
}
|
|
368
|
+
this.cwd = next.cwd;
|
|
369
|
+
this.operations = next;
|
|
370
|
+
return this.getBackendInfo();
|
|
371
|
+
}
|
|
372
|
+
clear() {
|
|
373
|
+
this.operations = undefined;
|
|
374
|
+
}
|
|
375
|
+
requireOperations() {
|
|
376
|
+
if (!this.operations) {
|
|
377
|
+
throw new Error("SSH sandbox is not configured. Configure it over RPC or with /ssh-sandbox before using tools.");
|
|
378
|
+
}
|
|
379
|
+
return this.operations;
|
|
380
|
+
}
|
|
381
|
+
async exec(command, options) {
|
|
382
|
+
return this.requireOperations().exec(command, options);
|
|
383
|
+
}
|
|
384
|
+
async access(path, mode) {
|
|
385
|
+
await this.requireOperations().access(path, mode);
|
|
386
|
+
}
|
|
387
|
+
async readFile(path) {
|
|
388
|
+
return this.requireOperations().readFile(path);
|
|
389
|
+
}
|
|
390
|
+
async writeFile(path, content) {
|
|
391
|
+
await this.requireOperations().writeFile(path, content);
|
|
392
|
+
}
|
|
393
|
+
async mkdir(path, options) {
|
|
394
|
+
await this.requireOperations().mkdir(path, options);
|
|
395
|
+
}
|
|
396
|
+
async stat(path) {
|
|
397
|
+
return this.requireOperations().stat(path);
|
|
398
|
+
}
|
|
399
|
+
async readdir(path) {
|
|
400
|
+
return this.requireOperations().readdir(path);
|
|
401
|
+
}
|
|
402
|
+
async glob(pattern, cwd, options) {
|
|
403
|
+
return this.requireOperations().glob(pattern, cwd, options);
|
|
404
|
+
}
|
|
405
|
+
async grep(options) {
|
|
406
|
+
return this.requireOperations().grep(options);
|
|
407
|
+
}
|
|
408
|
+
async detectImageMimeType(path) {
|
|
409
|
+
return this.requireOperations().detectImageMimeType(path);
|
|
410
|
+
}
|
|
411
|
+
getBackendInfo() {
|
|
412
|
+
return this.operations?.getBackendInfo() ?? { type: "ssh", cwd: this.cwd, configured: false };
|
|
413
|
+
}
|
|
414
|
+
}
|
|
415
|
+
export function createSshToolOperations(target) {
|
|
416
|
+
return SshToolOperations.fromTarget(target);
|
|
417
|
+
}
|
|
418
|
+
//# sourceMappingURL=operations.js.map
|