@artenx/dshb 0.1.11 → 0.1.13

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.
@@ -11,6 +11,7 @@ interface DockerBackend {
11
11
  stderr: string;
12
12
  }>;
13
13
  readFile(path: string): Promise<Buffer>;
14
+ readFileRange(path: string, offset: number, length: number): Promise<Buffer>;
14
15
  writeFile(path: string, content: Buffer | string): Promise<void>;
15
16
  listDir(path: string): Promise<Array<{
16
17
  name: string;
@@ -16,6 +16,7 @@ declare class DockerClient implements DockerBackend {
16
16
  exec(argv: string[], cwd: string, env?: Record<string, string>, stdinData?: Buffer): Promise<ExecResult>;
17
17
  execShell(command: string, cwd: string): Promise<ExecResult>;
18
18
  readFile(path: string): Promise<Buffer>;
19
+ readFileRange(path: string, offset: number, length: number): Promise<Buffer>;
19
20
  writeFile(path: string, content: Buffer | string): Promise<void>;
20
21
  listDir(path: string): Promise<Array<{
21
22
  name: string;
@@ -191,8 +191,24 @@ var DockerClient = class {
191
191
  ], cwd);
192
192
  }
193
193
  async readFile(path) {
194
- const result = await this.exec(["cat", path], "/");
195
- return Buffer.from(result.stdout, "utf8");
194
+ const result = await this.exec([
195
+ "bash",
196
+ "-c",
197
+ `base64 ${shellQuote(path)}`
198
+ ], "/");
199
+ if (result.code !== 0) throw new Error(result.stderr || `failed to read file: ${path}`);
200
+ return Buffer.from(result.stdout, "base64");
201
+ }
202
+ async readFileRange(path, offset, length) {
203
+ if (length === 0) return Buffer.alloc(0);
204
+ const command = `set -o pipefail; dd if=${shellQuote(path)} iflag=skip_bytes,count_bytes skip=${offset} count=${length} status=none | base64`;
205
+ const result = await this.exec([
206
+ "bash",
207
+ "-c",
208
+ command
209
+ ], "/");
210
+ if (result.code !== 0) throw new Error(result.stderr || `failed to read file range: ${path}`);
211
+ return Buffer.from(result.stdout, "base64");
196
212
  }
197
213
  async writeFile(path, content) {
198
214
  const b64 = (Buffer.isBuffer(content) ? content : Buffer.from(content, "utf8")).toString("base64");
@@ -1 +1 @@
1
- {"version":3,"file":"docker-client.js","names":["httpRequest","httpsRequest"],"sources":["../../src/exec-docker/docker-client.ts"],"sourcesContent":["import type { IncomingMessage, ClientRequest } from 'node:http'\nimport { request as httpRequest } from 'node:http'\nimport { request as httpsRequest } from 'node:https'\nimport type { DockerBackend } from './docker-backend.js'\nimport { parseDaemonHost } from './daemon-runner.js'\n\nconst DAEMON = parseDaemonHost()\n\ninterface ExecResult {\n code: number\n stdout: string\n stderr: string\n}\n\ninterface Demuxed {\n stdout: Buffer[]\n stderr: Buffer[]\n}\n\nfunction requestRaw(\n opts: { method: string; path: string; headers: Record<string, string> },\n onResponse: (res: IncomingMessage) => void,\n): ClientRequest {\n const common = { ...opts }\n if (DAEMON.socketPath) return httpRequest({ ...common, socketPath: DAEMON.socketPath } as never, onResponse)\n const mod = DAEMON.tls ? httpsRequest : httpRequest\n return mod({ ...common, hostname: DAEMON.host, port: DAEMON.port } as never, onResponse)\n}\n\nfunction dockerRequest(method: string, path: string, body?: unknown): Promise<{ statusCode: number; headers: Record<string, string>; body: Buffer }> {\n const headers = body ? { 'content-type': 'application/json' } : {}\n if (DAEMON.socketPath) {\n return new Promise((resolve, reject) => {\n const req = httpRequest({ method, path, socketPath: DAEMON.socketPath as string, headers }, (res) => {\n const chunks: Buffer[] = []\n res.on('data', (c: Buffer) => chunks.push(c))\n res.on('end', () => {\n resolve({\n statusCode: res.statusCode ?? 0,\n headers: res.headers as Record<string, string>,\n body: Buffer.concat(chunks),\n })\n })\n res.on('error', reject)\n })\n req.on('error', reject)\n if (body) req.write(JSON.stringify(body))\n req.end()\n })\n }\n const mod = DAEMON.tls ? httpsRequest : httpRequest\n return new Promise((resolve, reject) => {\n const req = mod({ method, path, hostname: DAEMON.host, port: DAEMON.port, headers }, (res) => {\n const chunks: Buffer[] = []\n res.on('data', (c: Buffer) => chunks.push(c))\n res.on('end', () => {\n resolve({\n statusCode: res.statusCode ?? 0,\n headers: res.headers as Record<string, string>,\n body: Buffer.concat(chunks),\n })\n })\n res.on('error', reject)\n })\n req.on('error', reject)\n if (body) req.write(JSON.stringify(body))\n req.end()\n })\n}\n\nfunction demux(data: Buffer): Demuxed {\n const stdout: Buffer[] = []\n const stderr: Buffer[] = []\n let pos = 0\n while (pos + 8 <= data.length) {\n const streamType = data[pos]\n const length = data.readUInt32BE(pos + 4)\n pos += 8\n if (pos + length > data.length) break\n const payload = data.subarray(pos, pos + length)\n pos += length\n if (streamType === 1) stdout.push(payload)\n else if (streamType === 2) stderr.push(payload)\n }\n return { stdout, stderr }\n}\n\nfunction shellQuote(s: string): string {\n return `'${s.replace(/'/g, \"'\\\\''\")}'`\n}\n\nexport class DockerClient implements DockerBackend {\n private readonly containerId: string\n\n constructor(containerId: string) {\n this.containerId = containerId\n }\n\n async ping(): Promise<boolean> {\n try {\n const res = await dockerRequest('GET', '/_ping')\n return res.statusCode === 200\n } catch {\n return false\n }\n }\n\n async inspect(): Promise<{ running: boolean; name: string } | undefined> {\n const res = await dockerRequest('GET', `/containers/${this.containerId}/json`)\n if (res.statusCode !== 200) return undefined\n const data = JSON.parse(res.body.toString('utf8'))\n return { running: data.State?.Running ?? false, name: data.Name ?? this.containerId }\n }\n\n async exec(argv: string[], cwd: string, env: Record<string, string> = {}, stdinData?: Buffer): Promise<ExecResult> {\n const createRes = await dockerRequest('POST', `/containers/${this.containerId}/exec`, {\n AttachStdout: true,\n AttachStderr: true,\n AttachStdin: stdinData !== undefined,\n Tty: false,\n WorkingDir: cwd,\n Env: Object.entries(env).map(([k, v]) => `${k}=${v}`),\n Cmd: argv,\n })\n if (createRes.statusCode !== 201) {\n const msg = createRes.body.toString('utf8')\n return { code: 126, stdout: '', stderr: `docker exec create failed: ${msg}` }\n }\n const execId = JSON.parse(createRes.body.toString('utf8')).Id\n if (!execId) return { code: 126, stdout: '', stderr: 'docker exec: no Id returned' }\n\n return new Promise<ExecResult>((resolve, reject) => {\n const req = requestRaw(\n {\n method: 'POST',\n path: `/exec/${execId}/start`,\n headers: { 'content-type': 'application/json', connection: 'upgrade' },\n },\n (res) => {\n const chunks: Buffer[] = []\n res.on('data', (c: Buffer) => chunks.push(c))\n res.on('end', () => {\n const raw = Buffer.concat(chunks)\n const demuxed = demux(raw)\n const inspectReq = requestRaw(\n { method: 'GET', path: `/exec/${execId}/json`, headers: {} },\n (inspectRes) => {\n const ichunks: Buffer[] = []\n inspectRes.on('data', (c: Buffer) => ichunks.push(c))\n inspectRes.on('end', () => {\n let code = 0\n try {\n const info = JSON.parse(Buffer.concat(ichunks).toString('utf8'))\n code = info.ExitCode ?? 0\n } catch {}\n resolve({\n code,\n stdout: Buffer.concat(demuxed.stdout).toString('utf8'),\n stderr: Buffer.concat(demuxed.stderr).toString('utf8'),\n })\n })\n inspectRes.on('error', () => resolve({ code: 0, stdout: Buffer.concat(demuxed.stdout).toString('utf8'), stderr: Buffer.concat(demuxed.stderr).toString('utf8') }))\n },\n )\n inspectReq.on('error', () => resolve({ code: 0, stdout: Buffer.concat(demuxed.stdout).toString('utf8'), stderr: Buffer.concat(demuxed.stderr).toString('utf8') }))\n inspectReq.end()\n })\n res.on('error', reject)\n },\n )\n req.on('error', reject)\n req.write(JSON.stringify({ Detach: false, Tty: false }))\n if (stdinData) req.write(stdinData)\n req.end()\n })\n }\n\n async execShell(command: string, cwd: string): Promise<ExecResult> {\n return this.exec(['bash', '-c', command], cwd)\n }\n\n async readFile(path: string): Promise<Buffer> {\n const result = await this.exec(['cat', path], '/')\n return Buffer.from(result.stdout, 'utf8')\n }\n\n async writeFile(path: string, content: Buffer | string): Promise<void> {\n const data = Buffer.isBuffer(content) ? content : Buffer.from(content, 'utf8')\n const b64 = data.toString('base64')\n await this.execShell(`printf '%s' '${b64}' | base64 -d > ${shellQuote(path)}`, '/')\n }\n\n async listDir(path: string): Promise<Array<{ name: string; isDir: boolean; isFile: boolean; isSymlink: boolean; size: number; mtime: number }>> {\n const result = await this.execShell(`ls -1pa --time-style=+%s ${shellQuote(path)} 2>/dev/null`, '/')\n return result.stdout\n .split('\\n')\n .filter((line) => line.trim())\n .map((line) => {\n const name = line.replace(/[/@*]$/, '').trim()\n if (!name) return null\n const isDir = line.endsWith('/')\n const isSymlink = line.endsWith('@')\n return {\n name,\n isDir,\n isFile: !isDir && !isSymlink,\n isSymlink,\n size: 0,\n mtime: 0,\n }\n })\n .filter((e): e is NonNullable<typeof e> => e !== null)\n }\n\n async stat(path: string): Promise<{ size: number; mtime: number; isDirectory: boolean; isFile: boolean; isSymlink: boolean } | undefined> {\n return this.readStat(path, true)\n }\n\n async lstat(path: string): Promise<{ size: number; mtime: number; isDirectory: boolean; isFile: boolean; isSymlink: boolean } | undefined> {\n return this.readStat(path, false)\n }\n\n private async readStat(path: string, followSymlink: boolean): Promise<{ size: number; mtime: number; isDirectory: boolean; isFile: boolean; isSymlink: boolean } | undefined> {\n const follow = followSymlink ? '-L ' : ''\n const result = await this.execShell(`stat ${follow}-c '%s %Y %F' ${shellQuote(path)} 2>/dev/null`, '/')\n const line = result.stdout.trim()\n if (!line) return undefined\n const parts = line.split(/\\s+/)\n if (parts.length < 3) return undefined\n const size = Number(parts[0])\n const mtime = Number(parts[1]) * 1000\n const ftype = parts.slice(2).join(' ')\n return {\n size,\n mtime,\n isDirectory: ftype.includes('directory'),\n isFile: ftype.includes('regular file') || ftype.includes('regular empty file'),\n isSymlink: ftype.includes('symbolic link'),\n }\n }\n\n async mkdir(path: string): Promise<void> {\n await this.exec(['mkdir', '-p', path], '/')\n }\n\n async remove(path: string): Promise<void> {\n await this.exec(['rm', '-f', path], '/')\n }\n\n async rename(src: string, dest: string): Promise<void> {\n await this.exec(['mv', src, dest], '/')\n }\n\n async pty(argv: string[], cwd: string, cols: number, rows: number, env: Record<string, string> = {}, terminalType = 'xterm-256color'): Promise<{ stream: NodeJS.ReadWriteStream; resize: (c: number, r: number) => void; kill: () => void }> {\n const environment = Object.entries(env).map(([key, value]) => `${key}=${value}`)\n if (!environment.some((entry) => entry.startsWith('TERM='))) environment.push(`TERM=${terminalType}`)\n const createRes = await dockerRequest('POST', `/containers/${this.containerId}/exec`, {\n AttachStdout: true,\n AttachStderr: true,\n AttachStdin: true,\n Tty: true,\n WorkingDir: cwd,\n Env: environment,\n Cmd: argv,\n })\n if (createRes.statusCode !== 201) throw new Error('docker exec create failed')\n const execId = JSON.parse(createRes.body.toString('utf8')).Id\n\n return new Promise((resolve, reject) => {\n const req = requestRaw(\n {\n method: 'POST',\n path: `/exec/${execId}/start`,\n headers: { 'content-type': 'application/json', connection: 'upgrade' },\n },\n (res) => {\n resolve({\n stream: res as unknown as NodeJS.ReadWriteStream,\n resize: (c: number, r: number) => {\n const r2 = requestRaw({ method: 'POST', path: `/exec/${execId}/resize?h=${r}&w=${c}`, headers: {} }, () => {})\n r2.end()\n },\n kill: () => {\n res.destroy()\n },\n })\n },\n )\n req.on('error', reject)\n req.write(JSON.stringify({ Detach: false, Tty: true }))\n req.end()\n })\n }\n\n async ensureRg(): Promise<void> {\n const result = await this.execShell('command -v rg || echo __missing__', '/')\n if (result.stdout.includes('__missing__')) {\n await this.execShell('apk add --no-cache ripgrep 2>/dev/null || apt-get install -y ripgrep 2>/dev/null || true', '/')\n }\n }\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"docker-client.js","names":["httpRequest","httpsRequest"],"sources":["../../src/exec-docker/docker-client.ts"],"sourcesContent":["import type { IncomingMessage, ClientRequest } from 'node:http'\nimport { request as httpRequest } from 'node:http'\nimport { request as httpsRequest } from 'node:https'\nimport type { DockerBackend } from './docker-backend.js'\nimport { parseDaemonHost } from './daemon-runner.js'\n\nconst DAEMON = parseDaemonHost()\n\ninterface ExecResult {\n code: number\n stdout: string\n stderr: string\n}\n\ninterface Demuxed {\n stdout: Buffer[]\n stderr: Buffer[]\n}\n\nfunction requestRaw(\n opts: { method: string; path: string; headers: Record<string, string> },\n onResponse: (res: IncomingMessage) => void,\n): ClientRequest {\n const common = { ...opts }\n if (DAEMON.socketPath) return httpRequest({ ...common, socketPath: DAEMON.socketPath } as never, onResponse)\n const mod = DAEMON.tls ? httpsRequest : httpRequest\n return mod({ ...common, hostname: DAEMON.host, port: DAEMON.port } as never, onResponse)\n}\n\nfunction dockerRequest(method: string, path: string, body?: unknown): Promise<{ statusCode: number; headers: Record<string, string>; body: Buffer }> {\n const headers = body ? { 'content-type': 'application/json' } : {}\n if (DAEMON.socketPath) {\n return new Promise((resolve, reject) => {\n const req = httpRequest({ method, path, socketPath: DAEMON.socketPath as string, headers }, (res) => {\n const chunks: Buffer[] = []\n res.on('data', (c: Buffer) => chunks.push(c))\n res.on('end', () => {\n resolve({\n statusCode: res.statusCode ?? 0,\n headers: res.headers as Record<string, string>,\n body: Buffer.concat(chunks),\n })\n })\n res.on('error', reject)\n })\n req.on('error', reject)\n if (body) req.write(JSON.stringify(body))\n req.end()\n })\n }\n const mod = DAEMON.tls ? httpsRequest : httpRequest\n return new Promise((resolve, reject) => {\n const req = mod({ method, path, hostname: DAEMON.host, port: DAEMON.port, headers }, (res) => {\n const chunks: Buffer[] = []\n res.on('data', (c: Buffer) => chunks.push(c))\n res.on('end', () => {\n resolve({\n statusCode: res.statusCode ?? 0,\n headers: res.headers as Record<string, string>,\n body: Buffer.concat(chunks),\n })\n })\n res.on('error', reject)\n })\n req.on('error', reject)\n if (body) req.write(JSON.stringify(body))\n req.end()\n })\n}\n\nfunction demux(data: Buffer): Demuxed {\n const stdout: Buffer[] = []\n const stderr: Buffer[] = []\n let pos = 0\n while (pos + 8 <= data.length) {\n const streamType = data[pos]\n const length = data.readUInt32BE(pos + 4)\n pos += 8\n if (pos + length > data.length) break\n const payload = data.subarray(pos, pos + length)\n pos += length\n if (streamType === 1) stdout.push(payload)\n else if (streamType === 2) stderr.push(payload)\n }\n return { stdout, stderr }\n}\n\nfunction shellQuote(s: string): string {\n return `'${s.replace(/'/g, \"'\\\\''\")}'`\n}\n\nexport class DockerClient implements DockerBackend {\n private readonly containerId: string\n\n constructor(containerId: string) {\n this.containerId = containerId\n }\n\n async ping(): Promise<boolean> {\n try {\n const res = await dockerRequest('GET', '/_ping')\n return res.statusCode === 200\n } catch {\n return false\n }\n }\n\n async inspect(): Promise<{ running: boolean; name: string } | undefined> {\n const res = await dockerRequest('GET', `/containers/${this.containerId}/json`)\n if (res.statusCode !== 200) return undefined\n const data = JSON.parse(res.body.toString('utf8'))\n return { running: data.State?.Running ?? false, name: data.Name ?? this.containerId }\n }\n\n async exec(argv: string[], cwd: string, env: Record<string, string> = {}, stdinData?: Buffer): Promise<ExecResult> {\n const createRes = await dockerRequest('POST', `/containers/${this.containerId}/exec`, {\n AttachStdout: true,\n AttachStderr: true,\n AttachStdin: stdinData !== undefined,\n Tty: false,\n WorkingDir: cwd,\n Env: Object.entries(env).map(([k, v]) => `${k}=${v}`),\n Cmd: argv,\n })\n if (createRes.statusCode !== 201) {\n const msg = createRes.body.toString('utf8')\n return { code: 126, stdout: '', stderr: `docker exec create failed: ${msg}` }\n }\n const execId = JSON.parse(createRes.body.toString('utf8')).Id\n if (!execId) return { code: 126, stdout: '', stderr: 'docker exec: no Id returned' }\n\n return new Promise<ExecResult>((resolve, reject) => {\n const req = requestRaw(\n {\n method: 'POST',\n path: `/exec/${execId}/start`,\n headers: { 'content-type': 'application/json', connection: 'upgrade' },\n },\n (res) => {\n const chunks: Buffer[] = []\n res.on('data', (c: Buffer) => chunks.push(c))\n res.on('end', () => {\n const raw = Buffer.concat(chunks)\n const demuxed = demux(raw)\n const inspectReq = requestRaw(\n { method: 'GET', path: `/exec/${execId}/json`, headers: {} },\n (inspectRes) => {\n const ichunks: Buffer[] = []\n inspectRes.on('data', (c: Buffer) => ichunks.push(c))\n inspectRes.on('end', () => {\n let code = 0\n try {\n const info = JSON.parse(Buffer.concat(ichunks).toString('utf8'))\n code = info.ExitCode ?? 0\n } catch {}\n resolve({\n code,\n stdout: Buffer.concat(demuxed.stdout).toString('utf8'),\n stderr: Buffer.concat(demuxed.stderr).toString('utf8'),\n })\n })\n inspectRes.on('error', () => resolve({ code: 0, stdout: Buffer.concat(demuxed.stdout).toString('utf8'), stderr: Buffer.concat(demuxed.stderr).toString('utf8') }))\n },\n )\n inspectReq.on('error', () => resolve({ code: 0, stdout: Buffer.concat(demuxed.stdout).toString('utf8'), stderr: Buffer.concat(demuxed.stderr).toString('utf8') }))\n inspectReq.end()\n })\n res.on('error', reject)\n },\n )\n req.on('error', reject)\n req.write(JSON.stringify({ Detach: false, Tty: false }))\n if (stdinData) req.write(stdinData)\n req.end()\n })\n }\n\n async execShell(command: string, cwd: string): Promise<ExecResult> {\n return this.exec(['bash', '-c', command], cwd)\n }\n\n async readFile(path: string): Promise<Buffer> {\n const result = await this.exec(['bash', '-c', `base64 ${shellQuote(path)}`], '/')\n if (result.code !== 0) throw new Error(result.stderr || `failed to read file: ${path}`)\n return Buffer.from(result.stdout, 'base64')\n }\n\n async readFileRange(path: string, offset: number, length: number): Promise<Buffer> {\n if (length === 0) return Buffer.alloc(0)\n const command = `set -o pipefail; dd if=${shellQuote(path)} iflag=skip_bytes,count_bytes skip=${offset} count=${length} status=none | base64`\n const result = await this.exec(['bash', '-c', command], '/')\n if (result.code !== 0) throw new Error(result.stderr || `failed to read file range: ${path}`)\n return Buffer.from(result.stdout, 'base64')\n }\n\n async writeFile(path: string, content: Buffer | string): Promise<void> {\n const data = Buffer.isBuffer(content) ? content : Buffer.from(content, 'utf8')\n const b64 = data.toString('base64')\n await this.execShell(`printf '%s' '${b64}' | base64 -d > ${shellQuote(path)}`, '/')\n }\n\n async listDir(path: string): Promise<Array<{ name: string; isDir: boolean; isFile: boolean; isSymlink: boolean; size: number; mtime: number }>> {\n const result = await this.execShell(`ls -1pa --time-style=+%s ${shellQuote(path)} 2>/dev/null`, '/')\n return result.stdout\n .split('\\n')\n .filter((line) => line.trim())\n .map((line) => {\n const name = line.replace(/[/@*]$/, '').trim()\n if (!name) return null\n const isDir = line.endsWith('/')\n const isSymlink = line.endsWith('@')\n return {\n name,\n isDir,\n isFile: !isDir && !isSymlink,\n isSymlink,\n size: 0,\n mtime: 0,\n }\n })\n .filter((e): e is NonNullable<typeof e> => e !== null)\n }\n\n async stat(path: string): Promise<{ size: number; mtime: number; isDirectory: boolean; isFile: boolean; isSymlink: boolean } | undefined> {\n return this.readStat(path, true)\n }\n\n async lstat(path: string): Promise<{ size: number; mtime: number; isDirectory: boolean; isFile: boolean; isSymlink: boolean } | undefined> {\n return this.readStat(path, false)\n }\n\n private async readStat(path: string, followSymlink: boolean): Promise<{ size: number; mtime: number; isDirectory: boolean; isFile: boolean; isSymlink: boolean } | undefined> {\n const follow = followSymlink ? '-L ' : ''\n const result = await this.execShell(`stat ${follow}-c '%s %Y %F' ${shellQuote(path)} 2>/dev/null`, '/')\n const line = result.stdout.trim()\n if (!line) return undefined\n const parts = line.split(/\\s+/)\n if (parts.length < 3) return undefined\n const size = Number(parts[0])\n const mtime = Number(parts[1]) * 1000\n const ftype = parts.slice(2).join(' ')\n return {\n size,\n mtime,\n isDirectory: ftype.includes('directory'),\n isFile: ftype.includes('regular file') || ftype.includes('regular empty file'),\n isSymlink: ftype.includes('symbolic link'),\n }\n }\n\n async mkdir(path: string): Promise<void> {\n await this.exec(['mkdir', '-p', path], '/')\n }\n\n async remove(path: string): Promise<void> {\n await this.exec(['rm', '-f', path], '/')\n }\n\n async rename(src: string, dest: string): Promise<void> {\n await this.exec(['mv', src, dest], '/')\n }\n\n async pty(argv: string[], cwd: string, cols: number, rows: number, env: Record<string, string> = {}, terminalType = 'xterm-256color'): Promise<{ stream: NodeJS.ReadWriteStream; resize: (c: number, r: number) => void; kill: () => void }> {\n const environment = Object.entries(env).map(([key, value]) => `${key}=${value}`)\n if (!environment.some((entry) => entry.startsWith('TERM='))) environment.push(`TERM=${terminalType}`)\n const createRes = await dockerRequest('POST', `/containers/${this.containerId}/exec`, {\n AttachStdout: true,\n AttachStderr: true,\n AttachStdin: true,\n Tty: true,\n WorkingDir: cwd,\n Env: environment,\n Cmd: argv,\n })\n if (createRes.statusCode !== 201) throw new Error('docker exec create failed')\n const execId = JSON.parse(createRes.body.toString('utf8')).Id\n\n return new Promise((resolve, reject) => {\n const req = requestRaw(\n {\n method: 'POST',\n path: `/exec/${execId}/start`,\n headers: { 'content-type': 'application/json', connection: 'upgrade' },\n },\n (res) => {\n resolve({\n stream: res as unknown as NodeJS.ReadWriteStream,\n resize: (c: number, r: number) => {\n const r2 = requestRaw({ method: 'POST', path: `/exec/${execId}/resize?h=${r}&w=${c}`, headers: {} }, () => {})\n r2.end()\n },\n kill: () => {\n res.destroy()\n },\n })\n },\n )\n req.on('error', reject)\n req.write(JSON.stringify({ Detach: false, Tty: true }))\n req.end()\n })\n }\n\n async ensureRg(): Promise<void> {\n const result = await this.execShell('command -v rg || echo __missing__', '/')\n if (result.stdout.includes('__missing__')) {\n await this.execShell('apk add --no-cache ripgrep 2>/dev/null || apt-get install -y ripgrep 2>/dev/null || true', '/')\n }\n }\n}\n"],"mappings":"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"}
@@ -15,6 +15,10 @@ declare class DockerFileSystem {
15
15
  readText(target: string, _signal?: AbortSignal): Promise<string>;
16
16
  streamText(target: string, _signal?: AbortSignal): Promise<AsyncIterable<string>>;
17
17
  readBytes(target: string, _signal: AbortSignal | undefined, maxBytes: number): Promise<Uint8Array>;
18
+ readByteRange(target: string, range: {
19
+ offset: number;
20
+ length: number;
21
+ }, _signal?: AbortSignal): Promise<Uint8Array>;
18
22
  listDir(target: string, _signal?: AbortSignal): Promise<Array<{
19
23
  name: string;
20
24
  path: string;
@@ -52,6 +52,10 @@ var DockerFileSystem = class {
52
52
  const slice = (await this.client.readFile(target)).subarray(0, maxBytes);
53
53
  return new Uint8Array(slice.buffer, slice.byteOffset, slice.byteLength);
54
54
  }
55
+ async readByteRange(target, range, _signal) {
56
+ const data = await this.client.readFileRange(target, range.offset, range.length);
57
+ return new Uint8Array(data.buffer, data.byteOffset, data.byteLength);
58
+ }
55
59
  async listDir(target, _signal) {
56
60
  return (await this.client.listDir(target)).map((e) => ({
57
61
  name: e.name,
@@ -1 +1 @@
1
- {"version":3,"file":"fs-provider.js","names":[],"sources":["../../src/exec-docker/fs-provider.ts"],"sourcesContent":["import type { DockerBackend } from './docker-backend.js'\nimport { FsVersion, type FsInfo, type FsPathInfo } from '@deepseek-ai/dsh-fs'\n\nfunction versionOf(info: { size: number; mtime: number; isDirectory: boolean; isFile: boolean; isSymlink?: boolean }): FsInfo['version'] {\n const type = info.isDirectory ? 'directory' : info.isFile ? 'file' : info.isSymlink ? 'symlink' : 'other'\n return FsVersion(`${type}:${info.size}:${info.mtime}`)\n}\n\nexport class DockerFileSystem {\n constructor(private readonly client: DockerBackend) {}\n\n async resolve(path: string): Promise<string> {\n return path\n }\n\n processPath(target: string): string {\n return target\n }\n\n fileUrl(target: string): string {\n return `file://${target}`\n }\n\n contains(parent: string, child: string): boolean {\n return child === parent || child.startsWith(`${parent}/`)\n }\n\n async stat(target: string, _signal?: AbortSignal): Promise<FsInfo | undefined> {\n const info = await this.client.stat(target)\n if (!info) return undefined\n return {\n version: versionOf(info),\n type: info.isDirectory ? 'directory' : info.isFile ? 'file' : 'other',\n size: info.size,\n }\n }\n\n async lstat(path: string, _opts?: { cwd?: string }, _signal?: AbortSignal): Promise<FsPathInfo | undefined> {\n const info = await this.client.lstat(path)\n if (!info) return undefined\n return {\n version: versionOf(info),\n type: info.isSymlink ? 'symlink' : info.isDirectory ? 'directory' : info.isFile ? 'file' : 'other',\n size: info.size,\n }\n }\n\n async readText(target: string, _signal?: AbortSignal): Promise<string> {\n const data = await this.client.readFile(target)\n return data.toString('utf8')\n }\n\n async streamText(target: string, _signal?: AbortSignal): Promise<AsyncIterable<string>> {\n const data = await this.client.readFile(target)\n const text = data.toString('utf8')\n return (async function* () {\n yield text\n })()\n }\n\n async readBytes(target: string, _signal: AbortSignal | undefined, maxBytes: number): Promise<Uint8Array> {\n const data = await this.client.readFile(target)\n const slice = data.subarray(0, maxBytes)\n return new Uint8Array(slice.buffer, slice.byteOffset, slice.byteLength)\n }\n\n async listDir(target: string, _signal?: AbortSignal): Promise<Array<{ name: string; path: string; isDirectory: boolean; isFile: boolean; isSymlink: boolean; size: number; mtime: number }>> {\n const entries = await this.client.listDir(target)\n return entries.map((e) => ({\n name: e.name,\n path: `${target.replace(/\\/+$/, '')}/${e.name}`,\n isDirectory: e.isDir,\n isFile: e.isFile,\n isSymlink: e.isSymlink,\n size: e.size,\n mtime: e.mtime,\n }))\n }\n\n async writeText(target: string, content: string, _expected?: unknown, _signal?: AbortSignal, _policy?: unknown): Promise<{ operation: 'create' | 'update'; version: string; before: string | null; after: string }> {\n const normalizeLf = (s: string) => s.replace(/\\r\\n/g, '\\n')\n const beforeInfo = await this.client.stat(target)\n let before: string | null = null\n if (beforeInfo?.isFile) {\n try {\n before = normalizeLf((await this.client.readFile(target)).toString('utf8'))\n } catch {\n before = null\n }\n }\n const after = normalizeLf(content)\n await this.client.writeFile(target, content)\n const afterInfo = await this.client.stat(target)\n const version = `${afterInfo?.size ?? content.length}:${afterInfo?.mtime ?? Date.now()}`\n return { operation: before === null ? 'create' : 'update', version, before, after }\n }\n\n async editText(target: string, edit: unknown, _expected?: unknown, _signal?: AbortSignal, _policy?: unknown): Promise<{ version: string; before: string; after: string }> {\n const normalizeLf = (s: string) => s.replace(/\\r\\n/g, '\\n')\n const before = normalizeLf((await this.client.readFile(target)).toString('utf8'))\n const req = edit as { oldString: string; newString: string; replaceAll: boolean }\n const oldString = normalizeLf(String(req.oldString ?? ''))\n if (oldString.length === 0) throw new Error('edit oldString must be non-empty')\n const newString = normalizeLf(String(req.newString ?? ''))\n const matches = before.split(oldString).length - 1\n if (matches === 0) throw new Error('edit oldString not found in file')\n if (!req.replaceAll && matches !== 1) throw new Error(`edit oldString matched ${matches} times; expected exactly one`)\n const after = req.replaceAll ? before.split(oldString).join(newString) : before.replace(oldString, newString)\n await this.client.writeFile(target, after)\n const afterInfo = await this.client.stat(target)\n const version = `${afterInfo?.size ?? after.length}:${afterInfo?.mtime ?? Date.now()}`\n return { version, before, after }\n }\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"fs-provider.js","names":[],"sources":["../../src/exec-docker/fs-provider.ts"],"sourcesContent":["import type { DockerBackend } from './docker-backend.js'\nimport { FsVersion, type FsInfo, type FsPathInfo } from '@deepseek-ai/dsh-fs'\n\nfunction versionOf(info: { size: number; mtime: number; isDirectory: boolean; isFile: boolean; isSymlink?: boolean }): FsInfo['version'] {\n const type = info.isDirectory ? 'directory' : info.isFile ? 'file' : info.isSymlink ? 'symlink' : 'other'\n return FsVersion(`${type}:${info.size}:${info.mtime}`)\n}\n\nexport class DockerFileSystem {\n constructor(private readonly client: DockerBackend) {}\n\n async resolve(path: string): Promise<string> {\n return path\n }\n\n processPath(target: string): string {\n return target\n }\n\n fileUrl(target: string): string {\n return `file://${target}`\n }\n\n contains(parent: string, child: string): boolean {\n return child === parent || child.startsWith(`${parent}/`)\n }\n\n async stat(target: string, _signal?: AbortSignal): Promise<FsInfo | undefined> {\n const info = await this.client.stat(target)\n if (!info) return undefined\n return {\n version: versionOf(info),\n type: info.isDirectory ? 'directory' : info.isFile ? 'file' : 'other',\n size: info.size,\n }\n }\n\n async lstat(path: string, _opts?: { cwd?: string }, _signal?: AbortSignal): Promise<FsPathInfo | undefined> {\n const info = await this.client.lstat(path)\n if (!info) return undefined\n return {\n version: versionOf(info),\n type: info.isSymlink ? 'symlink' : info.isDirectory ? 'directory' : info.isFile ? 'file' : 'other',\n size: info.size,\n }\n }\n\n async readText(target: string, _signal?: AbortSignal): Promise<string> {\n const data = await this.client.readFile(target)\n return data.toString('utf8')\n }\n\n async streamText(target: string, _signal?: AbortSignal): Promise<AsyncIterable<string>> {\n const data = await this.client.readFile(target)\n const text = data.toString('utf8')\n return (async function* () {\n yield text\n })()\n }\n\n async readBytes(target: string, _signal: AbortSignal | undefined, maxBytes: number): Promise<Uint8Array> {\n const data = await this.client.readFile(target)\n const slice = data.subarray(0, maxBytes)\n return new Uint8Array(slice.buffer, slice.byteOffset, slice.byteLength)\n }\n\n async readByteRange(target: string, range: { offset: number; length: number }, _signal?: AbortSignal): Promise<Uint8Array> {\n const data = await this.client.readFileRange(target, range.offset, range.length)\n return new Uint8Array(data.buffer, data.byteOffset, data.byteLength)\n }\n\n async listDir(target: string, _signal?: AbortSignal): Promise<Array<{ name: string; path: string; isDirectory: boolean; isFile: boolean; isSymlink: boolean; size: number; mtime: number }>> {\n const entries = await this.client.listDir(target)\n return entries.map((e) => ({\n name: e.name,\n path: `${target.replace(/\\/+$/, '')}/${e.name}`,\n isDirectory: e.isDir,\n isFile: e.isFile,\n isSymlink: e.isSymlink,\n size: e.size,\n mtime: e.mtime,\n }))\n }\n\n async writeText(target: string, content: string, _expected?: unknown, _signal?: AbortSignal, _policy?: unknown): Promise<{ operation: 'create' | 'update'; version: string; before: string | null; after: string }> {\n const normalizeLf = (s: string) => s.replace(/\\r\\n/g, '\\n')\n const beforeInfo = await this.client.stat(target)\n let before: string | null = null\n if (beforeInfo?.isFile) {\n try {\n before = normalizeLf((await this.client.readFile(target)).toString('utf8'))\n } catch {\n before = null\n }\n }\n const after = normalizeLf(content)\n await this.client.writeFile(target, content)\n const afterInfo = await this.client.stat(target)\n const version = `${afterInfo?.size ?? content.length}:${afterInfo?.mtime ?? Date.now()}`\n return { operation: before === null ? 'create' : 'update', version, before, after }\n }\n\n async editText(target: string, edit: unknown, _expected?: unknown, _signal?: AbortSignal, _policy?: unknown): Promise<{ version: string; before: string; after: string }> {\n const normalizeLf = (s: string) => s.replace(/\\r\\n/g, '\\n')\n const before = normalizeLf((await this.client.readFile(target)).toString('utf8'))\n const req = edit as { oldString: string; newString: string; replaceAll: boolean }\n const oldString = normalizeLf(String(req.oldString ?? ''))\n if (oldString.length === 0) throw new Error('edit oldString must be non-empty')\n const newString = normalizeLf(String(req.newString ?? ''))\n const matches = before.split(oldString).length - 1\n if (matches === 0) throw new Error('edit oldString not found in file')\n if (!req.replaceAll && matches !== 1) throw new Error(`edit oldString matched ${matches} times; expected exactly one`)\n const after = req.replaceAll ? before.split(oldString).join(newString) : before.replace(oldString, newString)\n await this.client.writeFile(target, after)\n const afterInfo = await this.client.stat(target)\n const version = `${afterInfo?.size ?? after.length}:${afterInfo?.mtime ?? Date.now()}`\n return { version, before, after }\n }\n}\n"],"mappings":";;AAGA,SAAS,UAAU,MAAsH;CACvI,MAAM,OAAO,KAAK,cAAc,cAAc,KAAK,SAAS,SAAS,KAAK,YAAY,YAAY;CAClG,OAAO,UAAU,GAAG,KAAK,GAAG,KAAK,KAAK,GAAG,KAAK,OAAO;AACvD;AAEA,IAAa,mBAAb,MAA8B;CACC;CAA7B,YAAY,QAAwC;EAAvB,KAAA,SAAA;CAAwB;CAErD,MAAM,QAAQ,MAA+B;EAC3C,OAAO;CACT;CAEA,YAAY,QAAwB;EAClC,OAAO;CACT;CAEA,QAAQ,QAAwB;EAC9B,OAAO,UAAU;CACnB;CAEA,SAAS,QAAgB,OAAwB;EAC/C,OAAO,UAAU,UAAU,MAAM,WAAW,GAAG,OAAO,EAAE;CAC1D;CAEA,MAAM,KAAK,QAAgB,SAAoD;EAC7E,MAAM,OAAO,MAAM,KAAK,OAAO,KAAK,MAAM;EAC1C,IAAI,CAAC,MAAM,OAAO,KAAA;EAClB,OAAO;GACL,SAAS,UAAU,IAAI;GACvB,MAAM,KAAK,cAAc,cAAc,KAAK,SAAS,SAAS;GAC9D,MAAM,KAAK;EACb;CACF;CAEA,MAAM,MAAM,MAAc,OAA0B,SAAwD;EAC1G,MAAM,OAAO,MAAM,KAAK,OAAO,MAAM,IAAI;EACzC,IAAI,CAAC,MAAM,OAAO,KAAA;EAClB,OAAO;GACL,SAAS,UAAU,IAAI;GACvB,MAAM,KAAK,YAAY,YAAY,KAAK,cAAc,cAAc,KAAK,SAAS,SAAS;GAC3F,MAAM,KAAK;EACb;CACF;CAEA,MAAM,SAAS,QAAgB,SAAwC;EAErE,QAAO,MADY,KAAK,OAAO,SAAS,MAAM,EAAA,CAClC,SAAS,MAAM;CAC7B;CAEA,MAAM,WAAW,QAAgB,SAAuD;EAEtF,MAAM,QAAO,MADM,KAAK,OAAO,SAAS,MAAM,EAAA,CAC5B,SAAS,MAAM;EACjC,QAAQ,mBAAmB;GACzB,MAAM;EACR,EAAA,CAAG;CACL;CAEA,MAAM,UAAU,QAAgB,SAAkC,UAAuC;EAEvG,MAAM,SAAQ,MADK,KAAK,OAAO,SAAS,MAAM,EAAA,CAC3B,SAAS,GAAG,QAAQ;EACvC,OAAO,IAAI,WAAW,MAAM,QAAQ,MAAM,YAAY,MAAM,UAAU;CACxE;CAEA,MAAM,cAAc,QAAgB,OAA2C,SAA4C;EACzH,MAAM,OAAO,MAAM,KAAK,OAAO,cAAc,QAAQ,MAAM,QAAQ,MAAM,MAAM;EAC/E,OAAO,IAAI,WAAW,KAAK,QAAQ,KAAK,YAAY,KAAK,UAAU;CACrE;CAEA,MAAM,QAAQ,QAAgB,SAA+J;EAE3L,QAAO,MADe,KAAK,OAAO,QAAQ,MAAM,EAAA,CACjC,KAAK,OAAO;GACzB,MAAM,EAAE;GACR,MAAM,GAAG,OAAO,QAAQ,QAAQ,EAAE,EAAE,GAAG,EAAE;GACzC,aAAa,EAAE;GACf,QAAQ,EAAE;GACV,WAAW,EAAE;GACb,MAAM,EAAE;GACR,OAAO,EAAE;EACX,EAAE;CACJ;CAEA,MAAM,UAAU,QAAgB,SAAiB,WAAqB,SAAuB,SAAuH;EAClN,MAAM,eAAe,MAAc,EAAE,QAAQ,SAAS,IAAI;EAC1D,MAAM,aAAa,MAAM,KAAK,OAAO,KAAK,MAAM;EAChD,IAAI,SAAwB;EAC5B,IAAI,YAAY,QACd,IAAI;GACF,SAAS,aAAa,MAAM,KAAK,OAAO,SAAS,MAAM,EAAA,CAAG,SAAS,MAAM,CAAC;EAC5E,QAAQ;GACN,SAAS;EACX;EAEF,MAAM,QAAQ,YAAY,OAAO;EACjC,MAAM,KAAK,OAAO,UAAU,QAAQ,OAAO;EAC3C,MAAM,YAAY,MAAM,KAAK,OAAO,KAAK,MAAM;EAC/C,MAAM,UAAU,GAAG,WAAW,QAAQ,QAAQ,OAAO,GAAG,WAAW,SAAS,KAAK,IAAI;EACrF,OAAO;GAAE,WAAW,WAAW,OAAO,WAAW;GAAU;GAAS;GAAQ;EAAM;CACpF;CAEA,MAAM,SAAS,QAAgB,MAAe,WAAqB,SAAuB,SAAgF;EACxK,MAAM,eAAe,MAAc,EAAE,QAAQ,SAAS,IAAI;EAC1D,MAAM,SAAS,aAAa,MAAM,KAAK,OAAO,SAAS,MAAM,EAAA,CAAG,SAAS,MAAM,CAAC;EAChF,MAAM,MAAM;EACZ,MAAM,YAAY,YAAY,OAAO,IAAI,aAAa,EAAE,CAAC;EACzD,IAAI,UAAU,WAAW,GAAG,MAAM,IAAI,MAAM,kCAAkC;EAC9E,MAAM,YAAY,YAAY,OAAO,IAAI,aAAa,EAAE,CAAC;EACzD,MAAM,UAAU,OAAO,MAAM,SAAS,CAAC,CAAC,SAAS;EACjD,IAAI,YAAY,GAAG,MAAM,IAAI,MAAM,kCAAkC;EACrE,IAAI,CAAC,IAAI,cAAc,YAAY,GAAG,MAAM,IAAI,MAAM,0BAA0B,QAAQ,6BAA6B;EACrH,MAAM,QAAQ,IAAI,aAAa,OAAO,MAAM,SAAS,CAAC,CAAC,KAAK,SAAS,IAAI,OAAO,QAAQ,WAAW,SAAS;EAC5G,MAAM,KAAK,OAAO,UAAU,QAAQ,KAAK;EACzC,MAAM,YAAY,MAAM,KAAK,OAAO,KAAK,MAAM;EAE/C,OAAO;GAAE,SAAA,GADU,WAAW,QAAQ,MAAM,OAAO,GAAG,WAAW,SAAS,KAAK,IAAI;GACjE;GAAQ;EAAM;CAClC;AACF"}
@@ -22,6 +22,7 @@ declare class RemoteDockerCli implements DockerBackend {
22
22
  stderr: string;
23
23
  }>;
24
24
  readFile(path: string): Promise<Buffer>;
25
+ readFileRange(path: string, offset: number, length: number): Promise<Buffer>;
25
26
  writeFile(path: string, content: Buffer | string): Promise<void>;
26
27
  listDir(path: string): Promise<Array<{
27
28
  name: string;
@@ -31,14 +31,31 @@ var RemoteDockerCli = class {
31
31
  ], "/");
32
32
  }
33
33
  async readFile(path) {
34
+ const quoted = `'${path.replace(/'/g, "'\\''")}'`;
34
35
  const result = await this.ssh.exec([
35
36
  "docker",
36
37
  "exec",
37
38
  this.containerId,
38
- "cat",
39
- path
39
+ "bash",
40
+ "-c",
41
+ `base64 ${quoted}`
42
+ ], "/");
43
+ if (result.code !== 0) throw new Error(result.stderr || `failed to read file: ${path}`);
44
+ return Buffer.from(result.stdout, "base64");
45
+ }
46
+ async readFileRange(path, offset, length) {
47
+ if (length === 0) return Buffer.alloc(0);
48
+ const command = `set -o pipefail; dd if=${`'${path.replace(/'/g, "'\\''")}'`} iflag=skip_bytes,count_bytes skip=${offset} count=${length} status=none | base64`;
49
+ const result = await this.ssh.exec([
50
+ "docker",
51
+ "exec",
52
+ this.containerId,
53
+ "bash",
54
+ "-c",
55
+ command
40
56
  ], "/");
41
- return Buffer.from(result.stdout, "utf8");
57
+ if (result.code !== 0) throw new Error(result.stderr || `failed to read file range: ${path}`);
58
+ return Buffer.from(result.stdout, "base64");
42
59
  }
43
60
  async writeFile(path, content) {
44
61
  const b64 = (Buffer.isBuffer(content) ? content : Buffer.from(content, "utf8")).toString("base64");
@@ -1 +1 @@
1
- {"version":3,"file":"remote-docker-cli.js","names":[],"sources":["../../src/exec-docker/remote-docker-cli.ts"],"sourcesContent":["import type { DockerBackend } from './docker-backend.js'\n\ninterface SshExecutorLike {\n exec(argv: string[], cwd: string, env?: Record<string, string>, stdinData?: Buffer): Promise<{ code: number; stdout: string; stderr: string }>\n}\n\nexport class RemoteDockerCli implements DockerBackend {\n constructor(\n private readonly ssh: SshExecutorLike,\n private readonly containerId: string,\n ) {}\n\n async exec(argv: string[], cwd: string, env: Record<string, string> = {}, stdinData?: Buffer): Promise<{ code: number; stdout: string; stderr: string }> {\n const envFlags = Object.entries(env).map(([k, v]) => ['-e', `${k}=${v}`]).flat()\n return this.ssh.exec(['docker', 'exec', '-w', cwd, ...envFlags, this.containerId, ...argv], '/', {}, stdinData)\n }\n\n async execShell(command: string, cwd: string): Promise<{ code: number; stdout: string; stderr: string }> {\n return this.ssh.exec(['docker', 'exec', '-w', cwd, this.containerId, 'bash', '-c', command], '/')\n }\n\n async readFile(path: string): Promise<Buffer> {\n const result = await this.ssh.exec(['docker', 'exec', this.containerId, 'cat', path], '/')\n return Buffer.from(result.stdout, 'utf8')\n }\n\n async writeFile(path: string, content: Buffer | string): Promise<void> {\n const data = Buffer.isBuffer(content) ? content : Buffer.from(content, 'utf8')\n const b64 = data.toString('base64')\n await this.ssh.exec(['docker', 'exec', this.containerId, 'bash', '-c', `printf '%s' '${b64}' | base64 -d > '${path.replace(/'/g, \"'\\\\''\")}'`], '/')\n }\n\n async listDir(path: string): Promise<Array<{ name: string; isDir: boolean; isFile: boolean; isSymlink: boolean; size: number; mtime: number }>> {\n const result = await this.ssh.exec(['docker', 'exec', this.containerId, 'ls', '-1pa', '--time-style=+%s', path], '/')\n return result.stdout\n .split('\\n')\n .filter((line) => line.trim())\n .map((line) => {\n const name = line.replace(/[/@*]$/, '').trim()\n if (!name) return null\n const isDir = line.endsWith('/')\n const isSymlink = line.endsWith('@')\n return { name, isDir, isFile: !isDir && !isSymlink, isSymlink, size: 0, mtime: 0 }\n })\n .filter((e): e is NonNullable<typeof e> => e !== null)\n }\n\n async stat(path: string): Promise<{ size: number; mtime: number; isDirectory: boolean; isFile: boolean; isSymlink: boolean } | undefined> {\n const result = await this.ssh.exec(['docker', 'exec', this.containerId, 'stat', '-L', '-c', '%s %Y %F', path], '/')\n const line = result.stdout.trim()\n if (!line) return undefined\n const parts = line.split(/\\s+/)\n if (parts.length < 3) return undefined\n const size = Number(parts[0])\n const mtime = Number(parts[1]) * 1000\n const ftype = parts.slice(2).join(' ')\n return { size, mtime, isDirectory: ftype.includes('directory'), isFile: ftype.includes('regular'), isSymlink: ftype.includes('symbolic link') }\n }\n\n async lstat(path: string): Promise<{ size: number; mtime: number; isDirectory: boolean; isFile: boolean; isSymlink: boolean } | undefined> {\n const result = await this.ssh.exec(['docker', 'exec', this.containerId, 'stat', '-c', '%s %Y %F', path], '/')\n const line = result.stdout.trim()\n if (!line) return undefined\n const parts = line.split(/\\s+/)\n if (parts.length < 3) return undefined\n const size = Number(parts[0])\n const mtime = Number(parts[1]) * 1000\n const ftype = parts.slice(2).join(' ')\n return { size, mtime, isDirectory: ftype.includes('directory'), isFile: ftype.includes('regular'), isSymlink: ftype.includes('symbolic link') }\n }\n\n async mkdir(path: string): Promise<void> {\n await this.ssh.exec(['docker', 'exec', this.containerId, 'mkdir', '-p', path], '/')\n }\n\n async remove(path: string): Promise<void> {\n await this.ssh.exec(['docker', 'exec', this.containerId, 'rm', '-f', path], '/')\n }\n\n async rename(src: string, dest: string): Promise<void> {\n await this.ssh.exec(['docker', 'exec', this.containerId, 'mv', src, dest], '/')\n }\n\n async ensureRg(): Promise<void> {\n const result = await this.ssh.exec(['docker', 'exec', this.containerId, 'bash', '-c', 'command -v rg || echo __missing__'], '/')\n if (result.stdout.includes('__missing__')) {\n await this.ssh.exec(['docker', 'exec', this.containerId, 'bash', '-c', 'apk add --no-cache ripgrep 2>/dev/null || apt-get install -y ripgrep 2>/dev/null || true'], '/')\n }\n }\n\n async pty(_argv: string[], _cwd: string, _cols: number, _rows: number, _env?: Record<string, string>, _terminalType?: string): Promise<{ stream: NodeJS.ReadWriteStream; resize: (c: number, r: number) => void; kill: () => void }> {\n throw new Error('remote docker PTY not yet supported')\n }\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"remote-docker-cli.js","names":[],"sources":["../../src/exec-docker/remote-docker-cli.ts"],"sourcesContent":["import type { DockerBackend } from './docker-backend.js'\n\ninterface SshExecutorLike {\n exec(argv: string[], cwd: string, env?: Record<string, string>, stdinData?: Buffer): Promise<{ code: number; stdout: string; stderr: string }>\n}\n\nexport class RemoteDockerCli implements DockerBackend {\n constructor(\n private readonly ssh: SshExecutorLike,\n private readonly containerId: string,\n ) {}\n\n async exec(argv: string[], cwd: string, env: Record<string, string> = {}, stdinData?: Buffer): Promise<{ code: number; stdout: string; stderr: string }> {\n const envFlags = Object.entries(env).map(([k, v]) => ['-e', `${k}=${v}`]).flat()\n return this.ssh.exec(['docker', 'exec', '-w', cwd, ...envFlags, this.containerId, ...argv], '/', {}, stdinData)\n }\n\n async execShell(command: string, cwd: string): Promise<{ code: number; stdout: string; stderr: string }> {\n return this.ssh.exec(['docker', 'exec', '-w', cwd, this.containerId, 'bash', '-c', command], '/')\n }\n\n async readFile(path: string): Promise<Buffer> {\n const quoted = `'${path.replace(/'/g, \"'\\\\''\")}'`\n const result = await this.ssh.exec(['docker', 'exec', this.containerId, 'bash', '-c', `base64 ${quoted}`], '/')\n if (result.code !== 0) throw new Error(result.stderr || `failed to read file: ${path}`)\n return Buffer.from(result.stdout, 'base64')\n }\n\n async readFileRange(path: string, offset: number, length: number): Promise<Buffer> {\n if (length === 0) return Buffer.alloc(0)\n const quoted = `'${path.replace(/'/g, \"'\\\\''\")}'`\n const command = `set -o pipefail; dd if=${quoted} iflag=skip_bytes,count_bytes skip=${offset} count=${length} status=none | base64`\n const result = await this.ssh.exec(['docker', 'exec', this.containerId, 'bash', '-c', command], '/')\n if (result.code !== 0) throw new Error(result.stderr || `failed to read file range: ${path}`)\n return Buffer.from(result.stdout, 'base64')\n }\n\n async writeFile(path: string, content: Buffer | string): Promise<void> {\n const data = Buffer.isBuffer(content) ? content : Buffer.from(content, 'utf8')\n const b64 = data.toString('base64')\n await this.ssh.exec(['docker', 'exec', this.containerId, 'bash', '-c', `printf '%s' '${b64}' | base64 -d > '${path.replace(/'/g, \"'\\\\''\")}'`], '/')\n }\n\n async listDir(path: string): Promise<Array<{ name: string; isDir: boolean; isFile: boolean; isSymlink: boolean; size: number; mtime: number }>> {\n const result = await this.ssh.exec(['docker', 'exec', this.containerId, 'ls', '-1pa', '--time-style=+%s', path], '/')\n return result.stdout\n .split('\\n')\n .filter((line) => line.trim())\n .map((line) => {\n const name = line.replace(/[/@*]$/, '').trim()\n if (!name) return null\n const isDir = line.endsWith('/')\n const isSymlink = line.endsWith('@')\n return { name, isDir, isFile: !isDir && !isSymlink, isSymlink, size: 0, mtime: 0 }\n })\n .filter((e): e is NonNullable<typeof e> => e !== null)\n }\n\n async stat(path: string): Promise<{ size: number; mtime: number; isDirectory: boolean; isFile: boolean; isSymlink: boolean } | undefined> {\n const result = await this.ssh.exec(['docker', 'exec', this.containerId, 'stat', '-L', '-c', '%s %Y %F', path], '/')\n const line = result.stdout.trim()\n if (!line) return undefined\n const parts = line.split(/\\s+/)\n if (parts.length < 3) return undefined\n const size = Number(parts[0])\n const mtime = Number(parts[1]) * 1000\n const ftype = parts.slice(2).join(' ')\n return { size, mtime, isDirectory: ftype.includes('directory'), isFile: ftype.includes('regular'), isSymlink: ftype.includes('symbolic link') }\n }\n\n async lstat(path: string): Promise<{ size: number; mtime: number; isDirectory: boolean; isFile: boolean; isSymlink: boolean } | undefined> {\n const result = await this.ssh.exec(['docker', 'exec', this.containerId, 'stat', '-c', '%s %Y %F', path], '/')\n const line = result.stdout.trim()\n if (!line) return undefined\n const parts = line.split(/\\s+/)\n if (parts.length < 3) return undefined\n const size = Number(parts[0])\n const mtime = Number(parts[1]) * 1000\n const ftype = parts.slice(2).join(' ')\n return { size, mtime, isDirectory: ftype.includes('directory'), isFile: ftype.includes('regular'), isSymlink: ftype.includes('symbolic link') }\n }\n\n async mkdir(path: string): Promise<void> {\n await this.ssh.exec(['docker', 'exec', this.containerId, 'mkdir', '-p', path], '/')\n }\n\n async remove(path: string): Promise<void> {\n await this.ssh.exec(['docker', 'exec', this.containerId, 'rm', '-f', path], '/')\n }\n\n async rename(src: string, dest: string): Promise<void> {\n await this.ssh.exec(['docker', 'exec', this.containerId, 'mv', src, dest], '/')\n }\n\n async ensureRg(): Promise<void> {\n const result = await this.ssh.exec(['docker', 'exec', this.containerId, 'bash', '-c', 'command -v rg || echo __missing__'], '/')\n if (result.stdout.includes('__missing__')) {\n await this.ssh.exec(['docker', 'exec', this.containerId, 'bash', '-c', 'apk add --no-cache ripgrep 2>/dev/null || apt-get install -y ripgrep 2>/dev/null || true'], '/')\n }\n }\n\n async pty(_argv: string[], _cwd: string, _cols: number, _rows: number, _env?: Record<string, string>, _terminalType?: string): Promise<{ stream: NodeJS.ReadWriteStream; resize: (c: number, r: number) => void; kill: () => void }> {\n throw new Error('remote docker PTY not yet supported')\n }\n}\n"],"mappings":"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"}
@@ -1,6 +1,6 @@
1
1
  import { DockerBackend } from "./docker-backend.js";
2
2
  import { DockerFileSystem } from "./fs-provider.js";
3
- import { a as NodeTestReport, t as ExecutionWorldProvider } from "../types-DPP05ZsA.js";
3
+ import { a as NodeTestReport, t as ExecutionWorldProvider } from "../types-_0vpT-jm.js";
4
4
  import { DockerShellExecutor, DockerSubprocessRuntime } from "./process-provider.js";
5
5
  //#region src/exec-docker/world.d.ts
6
6
  interface DockerNodeConfig {
@@ -15,6 +15,7 @@ declare class SshExecutor {
15
15
  exec(argv: string[], cwd: string, env?: Record<string, string>, stdinData?: Buffer): Promise<ExecResult>;
16
16
  sftp(): Promise<import('ssh2').SFTPWrapper>;
17
17
  readFile(path: string): Promise<Buffer>;
18
+ readFileRange(path: string, offset: number, length: number, signal?: AbortSignal): Promise<Buffer>;
18
19
  writeFile(path: string, content: Buffer | string): Promise<void>;
19
20
  listDir(path: string): Promise<Array<{
20
21
  name: string;
@@ -62,6 +62,31 @@ var SshExecutor = class {
62
62
  sftp.readFile(path, (err, data) => err ? reject(err) : resolve(data));
63
63
  });
64
64
  }
65
+ async readFileRange(path, offset, length, signal) {
66
+ if (length === 0) return Buffer.alloc(0);
67
+ const sftp = await this.sftp();
68
+ return new Promise((resolve, reject) => {
69
+ const chunks = [];
70
+ const stream = sftp.createReadStream(path, {
71
+ start: offset,
72
+ end: offset + length - 1,
73
+ autoClose: true
74
+ });
75
+ const cleanup = () => signal?.removeEventListener("abort", abort);
76
+ const abort = () => stream.destroy(signal?.reason instanceof Error ? signal.reason : /* @__PURE__ */ new Error("The operation was aborted"));
77
+ stream.on("data", (chunk) => chunks.push(Buffer.from(chunk)));
78
+ stream.once("error", (error) => {
79
+ cleanup();
80
+ reject(error);
81
+ });
82
+ stream.once("end", () => {
83
+ cleanup();
84
+ resolve(Buffer.concat(chunks));
85
+ });
86
+ signal?.addEventListener("abort", abort, { once: true });
87
+ if (signal?.aborted) abort();
88
+ });
89
+ }
65
90
  async writeFile(path, content) {
66
91
  const sftp = await this.sftp();
67
92
  const tmp = `${path}.dshb-tmp-${process.pid}-${Date.now()}`;
@@ -1 +1 @@
1
- {"version":3,"file":"executor.js","names":[],"sources":["../../src/exec-ssh/executor.ts"],"sourcesContent":["import { existsSync, readFileSync } from 'node:fs'\nimport { homedir } from 'node:os'\nimport { join } from 'node:path'\nimport { encodeRunnerPayload, REMOTE_RUNNER_COMMAND, type RunnerPayload } from './runner.js'\nimport type { SshConnection } from './connection.js'\n\nexport interface ExecResult {\n code: number\n signal?: string\n stdout: string\n stderr: string\n}\n\nexport class SshExecutor {\n private sftpCache: Promise<import('ssh2').SFTPWrapper> | null = null\n\n constructor(\n private readonly connection: SshConnection,\n private readonly nodeId = 'unknown',\n private readonly audit?: (op: string, target: string, result: string) => void,\n ) {}\n\n async exec(argv: string[], cwd: string, env: Record<string, string> = {}, stdinData?: Buffer): Promise<ExecResult> {\n const client = await this.connection.getClient()\n const payload: RunnerPayload = { argv, cwd, env }\n const body = encodeRunnerPayload(payload)\n const input = stdinData ? Buffer.concat([body, stdinData]) : body\n return new Promise((resolve, reject) => {\n client.exec(REMOTE_RUNNER_COMMAND, (err, channel) => {\n if (err) {\n reject(err)\n return\n }\n const stdout: Buffer[] = []\n const stderr: Buffer[] = []\n channel.stdout.on('data', (chunk: Buffer) => stdout.push(chunk))\n channel.stderr.on('data', (chunk: Buffer) => stderr.push(chunk))\n channel.on('close', (code: number, signal: string) => {\n this.audit?.('exec', argv.join(' '), `code=${code ?? 0}`)\n resolve({\n code: code ?? 0,\n signal,\n stdout: Buffer.concat(stdout).toString('utf8'),\n stderr: Buffer.concat(stderr).toString('utf8'),\n })\n })\n channel.on('error', reject)\n channel.end(input)\n })\n })\n }\n\n async sftp(): Promise<import('ssh2').SFTPWrapper> {\n if (this.sftpCache) return this.sftpCache\n const client = await this.connection.getClient()\n this.sftpCache = new Promise((resolve, reject) => {\n client.sftp((err, sftp) => {\n if (err) reject(err)\n else resolve(sftp)\n })\n })\n return this.sftpCache\n }\n\n async readFile(path: string): Promise<Buffer> {\n const sftp = await this.sftp()\n return new Promise((resolve, reject) => {\n sftp.readFile(path, (err, data) => (err ? reject(err) : resolve(data)))\n })\n }\n\n async writeFile(path: string, content: Buffer | string): Promise<void> {\n const sftp = await this.sftp()\n const tmp = `${path}.dshb-tmp-${process.pid}-${Date.now()}`\n return new Promise((resolve, reject) => {\n sftp.writeFile(tmp, content, (writeErr) => {\n if (writeErr) {\n reject(writeErr)\n return\n }\n sftp.ext_openssh_rename(tmp, path, (renameErr) => {\n if (renameErr) reject(renameErr)\n else {\n this.audit?.('write', path, 'ok')\n resolve()\n }\n })\n })\n })\n }\n\n async listDir(path: string): Promise<Array<{ name: string; isDir: boolean; isFile: boolean; isSymlink: boolean; size: number; mtime: number }>> {\n const sftp = await this.sftp()\n return new Promise((resolve, reject) => {\n sftp.readdir(path, (err, list) => {\n if (err) {\n reject(err)\n return\n }\n resolve(\n list.map((e) => ({\n name: e.filename,\n isDir: e.attrs.isDirectory(),\n isFile: e.attrs.isFile(),\n isSymlink: e.attrs.isSymbolicLink(),\n size: Number(e.attrs.size),\n mtime: e.attrs.mtime * 1000,\n })),\n )\n })\n })\n }\n\n async stat(path: string): Promise<{ size: number; mtime: number; isDirectory: boolean; isFile: boolean } | undefined> {\n const sftp = await this.sftp()\n return new Promise((resolve) => {\n sftp.stat(path, (err, stats) => {\n if (err) {\n resolve(undefined)\n return\n }\n resolve({ size: Number(stats.size), mtime: stats.mtime * 1000, isDirectory: stats.isDirectory(), isFile: stats.isFile() })\n })\n })\n }\n\n async lstat(path: string): Promise<{ size: number; mtime: number; isDirectory: boolean; isFile: boolean; isSymlink: boolean } | undefined> {\n const sftp = await this.sftp()\n return new Promise((resolve) => {\n sftp.lstat(path, (err, stats) => {\n if (err) {\n resolve(undefined)\n return\n }\n resolve({\n size: Number(stats.size),\n mtime: stats.mtime * 1000,\n isDirectory: stats.isDirectory(),\n isFile: stats.isFile(),\n isSymlink: stats.isSymbolicLink(),\n })\n })\n })\n }\n\n async mkdir(path: string): Promise<void> {\n const sftp = await this.sftp()\n return new Promise((resolve, reject) => {\n sftp.mkdir(path, (err) => (err ? reject(err) : resolve()))\n })\n }\n\n async remove(path: string): Promise<void> {\n const sftp = await this.sftp()\n return new Promise((resolve, reject) => {\n sftp.unlink(path, (err) => {\n if (err) reject(err)\n else {\n this.audit?.('remove', path, 'ok')\n resolve()\n }\n })\n })\n }\n\n async rename(src: string, dest: string): Promise<void> {\n const sftp = await this.sftp()\n return new Promise((resolve, reject) => {\n sftp.ext_openssh_rename(src, dest, (err) => {\n if (err) reject(err)\n else {\n this.audit?.('move', `${src} -> ${dest}`, 'ok')\n resolve()\n }\n })\n })\n }\n\n async ensureRg(): Promise<void> {\n const result = await this.exec(['sh', '-c', 'command -v rg || echo \"__DShB_RG_MISSING__\"'], '/')\n if (result.stdout.includes('__DShB_RG_MISSING__')) {\n const localRg = process.env.DSHB_LOCAL_RG ?? findLocalRg()\n if (!localRg) return\n const remoteRgDir = '~/.dshb/bin'\n await this.exec(['mkdir', '-p', remoteRgDir], '/')\n const remoteRg = `${remoteRgDir}/rg`\n const content = readFileSync(localRg)\n await this.writeFile(remoteRg, content)\n await this.exec(['chmod', '+x', remoteRg], '/')\n }\n }\n\n async pty(argv: string[], cwd: string, env: Record<string, string>, cols: number, rows: number, terminalType = 'xterm-256color'): Promise<{ channel: import('ssh2').ClientChannel; resize: (cols: number, rows: number) => void; kill: () => void }> {\n const client = await this.connection.getClient()\n const q = (s: string) => `'${s.replace(/'/g, \"'\\\\''\")}'`\n const environment = Object.entries(env).map(([key, value]) => {\n if (!/^[A-Za-z_][A-Za-z0-9_]*$/.test(key)) throw new Error(`invalid terminal environment key: ${key}`)\n return `${key}=${value}`\n })\n const command = `bash -c 'cd \"$0\" && exec env -i HOME=\"$HOME\" PATH=\"\\${PATH:-/usr/local/bin:/usr/bin:/bin}\" \"$@\"' ${q(cwd)} ${[...environment, ...argv].map(q).join(' ')}`\n return new Promise((resolve, reject) => {\n client.exec(\n command,\n { pty: { cols, rows, term: terminalType } },\n (err, channel) => {\n if (err) {\n reject(err)\n return\n }\n resolve({\n channel,\n resize: (c, r) => channel.setWindow(c, r, 0, 0),\n kill: () => channel.close(),\n })\n },\n )\n })\n }\n}\n\nfunction findLocalRg(): string | undefined {\n const candidates = [\n join(homedir(), '.dsh', 'node_modules', '.bin', 'rg'),\n join(homedir(), '.dsh', 'node_modules', '@vscode', 'ripgrep', 'bin', 'rg'),\n join(homedir(), 'go', 'bin', 'rg'),\n '/usr/local/bin/rg',\n '/usr/bin/rg',\n ]\n for (const c of candidates) if (existsSync(c)) return c\n return undefined\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"executor.js","names":[],"sources":["../../src/exec-ssh/executor.ts"],"sourcesContent":["import { existsSync, readFileSync } from 'node:fs'\nimport { homedir } from 'node:os'\nimport { join } from 'node:path'\nimport { encodeRunnerPayload, REMOTE_RUNNER_COMMAND, type RunnerPayload } from './runner.js'\nimport type { SshConnection } from './connection.js'\n\nexport interface ExecResult {\n code: number\n signal?: string\n stdout: string\n stderr: string\n}\n\nexport class SshExecutor {\n private sftpCache: Promise<import('ssh2').SFTPWrapper> | null = null\n\n constructor(\n private readonly connection: SshConnection,\n private readonly nodeId = 'unknown',\n private readonly audit?: (op: string, target: string, result: string) => void,\n ) {}\n\n async exec(argv: string[], cwd: string, env: Record<string, string> = {}, stdinData?: Buffer): Promise<ExecResult> {\n const client = await this.connection.getClient()\n const payload: RunnerPayload = { argv, cwd, env }\n const body = encodeRunnerPayload(payload)\n const input = stdinData ? Buffer.concat([body, stdinData]) : body\n return new Promise((resolve, reject) => {\n client.exec(REMOTE_RUNNER_COMMAND, (err, channel) => {\n if (err) {\n reject(err)\n return\n }\n const stdout: Buffer[] = []\n const stderr: Buffer[] = []\n channel.stdout.on('data', (chunk: Buffer) => stdout.push(chunk))\n channel.stderr.on('data', (chunk: Buffer) => stderr.push(chunk))\n channel.on('close', (code: number, signal: string) => {\n this.audit?.('exec', argv.join(' '), `code=${code ?? 0}`)\n resolve({\n code: code ?? 0,\n signal,\n stdout: Buffer.concat(stdout).toString('utf8'),\n stderr: Buffer.concat(stderr).toString('utf8'),\n })\n })\n channel.on('error', reject)\n channel.end(input)\n })\n })\n }\n\n async sftp(): Promise<import('ssh2').SFTPWrapper> {\n if (this.sftpCache) return this.sftpCache\n const client = await this.connection.getClient()\n this.sftpCache = new Promise((resolve, reject) => {\n client.sftp((err, sftp) => {\n if (err) reject(err)\n else resolve(sftp)\n })\n })\n return this.sftpCache\n }\n\n async readFile(path: string): Promise<Buffer> {\n const sftp = await this.sftp()\n return new Promise((resolve, reject) => {\n sftp.readFile(path, (err, data) => (err ? reject(err) : resolve(data)))\n })\n }\n\n async readFileRange(path: string, offset: number, length: number, signal?: AbortSignal): Promise<Buffer> {\n if (length === 0) return Buffer.alloc(0)\n const sftp = await this.sftp()\n return new Promise((resolve, reject) => {\n const chunks: Buffer[] = []\n const stream = sftp.createReadStream(path, { start: offset, end: offset + length - 1, autoClose: true })\n const cleanup = () => signal?.removeEventListener('abort', abort)\n const abort = () => stream.destroy(signal?.reason instanceof Error ? signal.reason : new Error('The operation was aborted'))\n stream.on('data', (chunk: Buffer) => chunks.push(Buffer.from(chunk)))\n stream.once('error', (error: Error) => {\n cleanup()\n reject(error)\n })\n stream.once('end', () => {\n cleanup()\n resolve(Buffer.concat(chunks))\n })\n signal?.addEventListener('abort', abort, { once: true })\n if (signal?.aborted) abort()\n })\n }\n\n async writeFile(path: string, content: Buffer | string): Promise<void> {\n const sftp = await this.sftp()\n const tmp = `${path}.dshb-tmp-${process.pid}-${Date.now()}`\n return new Promise((resolve, reject) => {\n sftp.writeFile(tmp, content, (writeErr) => {\n if (writeErr) {\n reject(writeErr)\n return\n }\n sftp.ext_openssh_rename(tmp, path, (renameErr) => {\n if (renameErr) reject(renameErr)\n else {\n this.audit?.('write', path, 'ok')\n resolve()\n }\n })\n })\n })\n }\n\n async listDir(path: string): Promise<Array<{ name: string; isDir: boolean; isFile: boolean; isSymlink: boolean; size: number; mtime: number }>> {\n const sftp = await this.sftp()\n return new Promise((resolve, reject) => {\n sftp.readdir(path, (err, list) => {\n if (err) {\n reject(err)\n return\n }\n resolve(\n list.map((e) => ({\n name: e.filename,\n isDir: e.attrs.isDirectory(),\n isFile: e.attrs.isFile(),\n isSymlink: e.attrs.isSymbolicLink(),\n size: Number(e.attrs.size),\n mtime: e.attrs.mtime * 1000,\n })),\n )\n })\n })\n }\n\n async stat(path: string): Promise<{ size: number; mtime: number; isDirectory: boolean; isFile: boolean } | undefined> {\n const sftp = await this.sftp()\n return new Promise((resolve) => {\n sftp.stat(path, (err, stats) => {\n if (err) {\n resolve(undefined)\n return\n }\n resolve({ size: Number(stats.size), mtime: stats.mtime * 1000, isDirectory: stats.isDirectory(), isFile: stats.isFile() })\n })\n })\n }\n\n async lstat(path: string): Promise<{ size: number; mtime: number; isDirectory: boolean; isFile: boolean; isSymlink: boolean } | undefined> {\n const sftp = await this.sftp()\n return new Promise((resolve) => {\n sftp.lstat(path, (err, stats) => {\n if (err) {\n resolve(undefined)\n return\n }\n resolve({\n size: Number(stats.size),\n mtime: stats.mtime * 1000,\n isDirectory: stats.isDirectory(),\n isFile: stats.isFile(),\n isSymlink: stats.isSymbolicLink(),\n })\n })\n })\n }\n\n async mkdir(path: string): Promise<void> {\n const sftp = await this.sftp()\n return new Promise((resolve, reject) => {\n sftp.mkdir(path, (err) => (err ? reject(err) : resolve()))\n })\n }\n\n async remove(path: string): Promise<void> {\n const sftp = await this.sftp()\n return new Promise((resolve, reject) => {\n sftp.unlink(path, (err) => {\n if (err) reject(err)\n else {\n this.audit?.('remove', path, 'ok')\n resolve()\n }\n })\n })\n }\n\n async rename(src: string, dest: string): Promise<void> {\n const sftp = await this.sftp()\n return new Promise((resolve, reject) => {\n sftp.ext_openssh_rename(src, dest, (err) => {\n if (err) reject(err)\n else {\n this.audit?.('move', `${src} -> ${dest}`, 'ok')\n resolve()\n }\n })\n })\n }\n\n async ensureRg(): Promise<void> {\n const result = await this.exec(['sh', '-c', 'command -v rg || echo \"__DShB_RG_MISSING__\"'], '/')\n if (result.stdout.includes('__DShB_RG_MISSING__')) {\n const localRg = process.env.DSHB_LOCAL_RG ?? findLocalRg()\n if (!localRg) return\n const remoteRgDir = '~/.dshb/bin'\n await this.exec(['mkdir', '-p', remoteRgDir], '/')\n const remoteRg = `${remoteRgDir}/rg`\n const content = readFileSync(localRg)\n await this.writeFile(remoteRg, content)\n await this.exec(['chmod', '+x', remoteRg], '/')\n }\n }\n\n async pty(argv: string[], cwd: string, env: Record<string, string>, cols: number, rows: number, terminalType = 'xterm-256color'): Promise<{ channel: import('ssh2').ClientChannel; resize: (cols: number, rows: number) => void; kill: () => void }> {\n const client = await this.connection.getClient()\n const q = (s: string) => `'${s.replace(/'/g, \"'\\\\''\")}'`\n const environment = Object.entries(env).map(([key, value]) => {\n if (!/^[A-Za-z_][A-Za-z0-9_]*$/.test(key)) throw new Error(`invalid terminal environment key: ${key}`)\n return `${key}=${value}`\n })\n const command = `bash -c 'cd \"$0\" && exec env -i HOME=\"$HOME\" PATH=\"\\${PATH:-/usr/local/bin:/usr/bin:/bin}\" \"$@\"' ${q(cwd)} ${[...environment, ...argv].map(q).join(' ')}`\n return new Promise((resolve, reject) => {\n client.exec(\n command,\n { pty: { cols, rows, term: terminalType } },\n (err, channel) => {\n if (err) {\n reject(err)\n return\n }\n resolve({\n channel,\n resize: (c, r) => channel.setWindow(c, r, 0, 0),\n kill: () => channel.close(),\n })\n },\n )\n })\n }\n}\n\nfunction findLocalRg(): string | undefined {\n const candidates = [\n join(homedir(), '.dsh', 'node_modules', '.bin', 'rg'),\n join(homedir(), '.dsh', 'node_modules', '@vscode', 'ripgrep', 'bin', 'rg'),\n join(homedir(), 'go', 'bin', 'rg'),\n '/usr/local/bin/rg',\n '/usr/bin/rg',\n ]\n for (const c of candidates) if (existsSync(c)) return c\n return undefined\n}\n"],"mappings":"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"}
@@ -15,6 +15,10 @@ declare class SshFileSystem {
15
15
  readText(target: string, _signal?: AbortSignal): Promise<string>;
16
16
  streamText(target: string, _signal?: AbortSignal): Promise<AsyncIterable<string>>;
17
17
  readBytes(target: string, _signal: AbortSignal | undefined, maxBytes: number): Promise<Uint8Array>;
18
+ readByteRange(target: string, range: {
19
+ offset: number;
20
+ length: number;
21
+ }, signal?: AbortSignal): Promise<Uint8Array>;
18
22
  listDir(target: string, _signal?: AbortSignal): Promise<Array<{
19
23
  name: string;
20
24
  path: string;
@@ -52,6 +52,10 @@ var SshFileSystem = class {
52
52
  const slice = (await this.executor.readFile(target)).subarray(0, maxBytes);
53
53
  return new Uint8Array(slice.buffer, slice.byteOffset, slice.byteLength);
54
54
  }
55
+ async readByteRange(target, range, signal) {
56
+ const data = await this.executor.readFileRange(target, range.offset, range.length, signal);
57
+ return new Uint8Array(data.buffer, data.byteOffset, data.byteLength);
58
+ }
55
59
  async listDir(target, _signal) {
56
60
  return (await this.executor.listDir(target)).map((e) => ({
57
61
  name: e.name,
@@ -1 +1 @@
1
- {"version":3,"file":"fs-provider.js","names":[],"sources":["../../src/exec-ssh/fs-provider.ts"],"sourcesContent":["import type { SshExecutor } from './executor.js'\nimport { FsVersion, type FsInfo, type FsPathInfo } from '@deepseek-ai/dsh-fs'\n\nfunction versionOf(info: { size: number; mtime: number; isDirectory: boolean; isFile: boolean }): FsInfo['version'] {\n const type = info.isDirectory ? 'directory' : info.isFile ? 'file' : 'other'\n return FsVersion(`${type}:${info.size}:${info.mtime}`)\n}\n\nexport class SshFileSystem {\n constructor(private readonly executor: SshExecutor) {}\n\n async resolve(path: string): Promise<string> {\n return path\n }\n\n processPath(target: string): string {\n return target\n }\n\n fileUrl(target: string): string {\n return `file://${target}`\n }\n\n contains(parent: string, child: string): boolean {\n return child === parent || child.startsWith(`${parent}/`)\n }\n\n async stat(target: string, _signal?: AbortSignal): Promise<FsInfo | undefined> {\n const info = await this.executor.stat(target)\n if (!info) return undefined\n return {\n version: versionOf(info),\n type: info.isDirectory ? 'directory' : info.isFile ? 'file' : 'other',\n size: info.size,\n }\n }\n\n async lstat(path: string, _opts?: { cwd?: string }, _signal?: AbortSignal): Promise<FsPathInfo | undefined> {\n const info = await this.executor.lstat(path)\n if (!info) return undefined\n return {\n version: versionOf(info),\n type: info.isSymlink ? 'symlink' : info.isDirectory ? 'directory' : info.isFile ? 'file' : 'other',\n size: info.size,\n }\n }\n\n async readText(target: string, _signal?: AbortSignal): Promise<string> {\n const data = await this.executor.readFile(target)\n return data.toString('utf8')\n }\n\n async streamText(target: string, _signal?: AbortSignal): Promise<AsyncIterable<string>> {\n const data = await this.executor.readFile(target)\n const text = data.toString('utf8')\n return (async function* () {\n yield text\n })()\n }\n\n async readBytes(target: string, _signal: AbortSignal | undefined, maxBytes: number): Promise<Uint8Array> {\n const data = await this.executor.readFile(target)\n const slice = data.subarray(0, maxBytes)\n return new Uint8Array(slice.buffer, slice.byteOffset, slice.byteLength)\n }\n\n async listDir(target: string, _signal?: AbortSignal): Promise<Array<{ name: string; path: string; isDirectory: boolean; isFile: boolean; isSymlink: boolean; size: number; mtime: number }>> {\n const entries = await this.executor.listDir(target)\n return entries.map((e) => ({\n name: e.name,\n path: `${target.replace(/\\/+$/, '')}/${e.name}`,\n isDirectory: e.isDir,\n isFile: e.isFile,\n isSymlink: e.isSymlink,\n size: e.size,\n mtime: e.mtime,\n }))\n }\n\n async writeText(target: string, content: string, _expected?: unknown, _signal?: AbortSignal, _policy?: unknown): Promise<{ operation: 'create' | 'update'; version: string; before: string | null; after: string }> {\n const normalizeLf = (s: string) => s.replace(/\\r\\n/g, '\\n')\n const beforeInfo = await this.executor.stat(target)\n let before: string | null = null\n if (beforeInfo?.isFile) {\n try {\n before = normalizeLf((await this.executor.readFile(target)).toString('utf8'))\n } catch {\n before = null\n }\n }\n const after = normalizeLf(content)\n await this.executor.writeFile(target, content)\n const afterInfo = await this.executor.stat(target)\n const version = `${afterInfo?.size ?? content.length}:${afterInfo?.mtime ?? Date.now()}`\n return { operation: before === null ? 'create' : 'update', version, before, after }\n }\n\n async editText(target: string, edit: unknown, _expected?: unknown, _signal?: AbortSignal, _policy?: unknown): Promise<{ version: string; before: string; after: string }> {\n const normalizeLf = (s: string) => s.replace(/\\r\\n/g, '\\n')\n const before = normalizeLf((await this.executor.readFile(target)).toString('utf8'))\n const req = edit as { oldString: string; newString: string; replaceAll: boolean }\n const oldString = normalizeLf(String(req.oldString ?? ''))\n if (oldString.length === 0) throw new Error('edit oldString must be non-empty')\n const newString = normalizeLf(String(req.newString ?? ''))\n const matches = before.split(oldString).length - 1\n if (matches === 0) throw new Error('edit oldString not found in file')\n if (!req.replaceAll && matches !== 1) throw new Error(`edit oldString matched ${matches} times; expected exactly one`)\n const after = req.replaceAll ? before.split(oldString).join(newString) : before.replace(oldString, newString)\n await this.executor.writeFile(target, after)\n const afterInfo = await this.executor.stat(target)\n const version = `${afterInfo?.size ?? after.length}:${afterInfo?.mtime ?? Date.now()}`\n return { version, before, after }\n }\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"fs-provider.js","names":[],"sources":["../../src/exec-ssh/fs-provider.ts"],"sourcesContent":["import type { SshExecutor } from './executor.js'\nimport { FsVersion, type FsInfo, type FsPathInfo } from '@deepseek-ai/dsh-fs'\n\nfunction versionOf(info: { size: number; mtime: number; isDirectory: boolean; isFile: boolean }): FsInfo['version'] {\n const type = info.isDirectory ? 'directory' : info.isFile ? 'file' : 'other'\n return FsVersion(`${type}:${info.size}:${info.mtime}`)\n}\n\nexport class SshFileSystem {\n constructor(private readonly executor: SshExecutor) {}\n\n async resolve(path: string): Promise<string> {\n return path\n }\n\n processPath(target: string): string {\n return target\n }\n\n fileUrl(target: string): string {\n return `file://${target}`\n }\n\n contains(parent: string, child: string): boolean {\n return child === parent || child.startsWith(`${parent}/`)\n }\n\n async stat(target: string, _signal?: AbortSignal): Promise<FsInfo | undefined> {\n const info = await this.executor.stat(target)\n if (!info) return undefined\n return {\n version: versionOf(info),\n type: info.isDirectory ? 'directory' : info.isFile ? 'file' : 'other',\n size: info.size,\n }\n }\n\n async lstat(path: string, _opts?: { cwd?: string }, _signal?: AbortSignal): Promise<FsPathInfo | undefined> {\n const info = await this.executor.lstat(path)\n if (!info) return undefined\n return {\n version: versionOf(info),\n type: info.isSymlink ? 'symlink' : info.isDirectory ? 'directory' : info.isFile ? 'file' : 'other',\n size: info.size,\n }\n }\n\n async readText(target: string, _signal?: AbortSignal): Promise<string> {\n const data = await this.executor.readFile(target)\n return data.toString('utf8')\n }\n\n async streamText(target: string, _signal?: AbortSignal): Promise<AsyncIterable<string>> {\n const data = await this.executor.readFile(target)\n const text = data.toString('utf8')\n return (async function* () {\n yield text\n })()\n }\n\n async readBytes(target: string, _signal: AbortSignal | undefined, maxBytes: number): Promise<Uint8Array> {\n const data = await this.executor.readFile(target)\n const slice = data.subarray(0, maxBytes)\n return new Uint8Array(slice.buffer, slice.byteOffset, slice.byteLength)\n }\n\n async readByteRange(target: string, range: { offset: number; length: number }, signal?: AbortSignal): Promise<Uint8Array> {\n const data = await this.executor.readFileRange(target, range.offset, range.length, signal)\n return new Uint8Array(data.buffer, data.byteOffset, data.byteLength)\n }\n\n async listDir(target: string, _signal?: AbortSignal): Promise<Array<{ name: string; path: string; isDirectory: boolean; isFile: boolean; isSymlink: boolean; size: number; mtime: number }>> {\n const entries = await this.executor.listDir(target)\n return entries.map((e) => ({\n name: e.name,\n path: `${target.replace(/\\/+$/, '')}/${e.name}`,\n isDirectory: e.isDir,\n isFile: e.isFile,\n isSymlink: e.isSymlink,\n size: e.size,\n mtime: e.mtime,\n }))\n }\n\n async writeText(target: string, content: string, _expected?: unknown, _signal?: AbortSignal, _policy?: unknown): Promise<{ operation: 'create' | 'update'; version: string; before: string | null; after: string }> {\n const normalizeLf = (s: string) => s.replace(/\\r\\n/g, '\\n')\n const beforeInfo = await this.executor.stat(target)\n let before: string | null = null\n if (beforeInfo?.isFile) {\n try {\n before = normalizeLf((await this.executor.readFile(target)).toString('utf8'))\n } catch {\n before = null\n }\n }\n const after = normalizeLf(content)\n await this.executor.writeFile(target, content)\n const afterInfo = await this.executor.stat(target)\n const version = `${afterInfo?.size ?? content.length}:${afterInfo?.mtime ?? Date.now()}`\n return { operation: before === null ? 'create' : 'update', version, before, after }\n }\n\n async editText(target: string, edit: unknown, _expected?: unknown, _signal?: AbortSignal, _policy?: unknown): Promise<{ version: string; before: string; after: string }> {\n const normalizeLf = (s: string) => s.replace(/\\r\\n/g, '\\n')\n const before = normalizeLf((await this.executor.readFile(target)).toString('utf8'))\n const req = edit as { oldString: string; newString: string; replaceAll: boolean }\n const oldString = normalizeLf(String(req.oldString ?? ''))\n if (oldString.length === 0) throw new Error('edit oldString must be non-empty')\n const newString = normalizeLf(String(req.newString ?? ''))\n const matches = before.split(oldString).length - 1\n if (matches === 0) throw new Error('edit oldString not found in file')\n if (!req.replaceAll && matches !== 1) throw new Error(`edit oldString matched ${matches} times; expected exactly one`)\n const after = req.replaceAll ? before.split(oldString).join(newString) : before.replace(oldString, newString)\n await this.executor.writeFile(target, after)\n const afterInfo = await this.executor.stat(target)\n const version = `${afterInfo?.size ?? after.length}:${afterInfo?.mtime ?? Date.now()}`\n return { version, before, after }\n }\n}\n"],"mappings":"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"}
@@ -1,4 +1,4 @@
1
- import { a as NodeTestReport, t as ExecutionWorldProvider } from "../types-DPP05ZsA.js";
1
+ import { a as NodeTestReport, t as ExecutionWorldProvider } from "../types-_0vpT-jm.js";
2
2
  import { SshConnectionPool } from "./connection-pool.js";
3
3
  import { SshExecutor } from "./executor.js";
4
4
  import { SshFileSystem } from "./fs-provider.js";
@@ -9,6 +9,7 @@ function decorateFileSystem(fs) {
9
9
  const origReadText = fs.readText.bind(fs);
10
10
  const origStreamText = fs.streamText.bind(fs);
11
11
  const origReadBytes = fs.readBytes.bind(fs);
12
+ const origReadByteRange = typeof fs.readByteRange === "function" ? fs.readByteRange.bind(fs) : void 0;
12
13
  const origListDir = fs.listDir.bind(fs);
13
14
  const origWriteText = fs.writeText.bind(fs);
14
15
  const origEditText = fs.editText.bind(fs);
@@ -45,6 +46,16 @@ function decorateFileSystem(fs) {
45
46
  if (ref.kind === "unrouted") throw new Error(`node ${ref.nodeId} world not available`);
46
47
  return ref.provider.fs.readBytes(ref.remotePath, signal, maxBytes);
47
48
  };
49
+ fs.readByteRange = async (target, range, signal) => {
50
+ const ref = resolver.resolve(targetPath(target));
51
+ if (ref.kind === "local") {
52
+ if (origReadByteRange) return origReadByteRange(target, range, signal);
53
+ const end = range.offset + range.length;
54
+ return (await origReadBytes(target, signal, end)).subarray(range.offset, end);
55
+ }
56
+ if (ref.kind === "unrouted") throw new Error(`node ${ref.nodeId} world not available`);
57
+ return ref.provider.fs.readByteRange(ref.remotePath, range, signal);
58
+ };
48
59
  fs.listDir = async (target, signal) => {
49
60
  const parentPath = targetPath(target);
50
61
  const ref = resolver.resolve(parentPath);
@@ -81,6 +92,8 @@ function decorateFileSystem(fs) {
81
92
  fs.readText = origReadText;
82
93
  fs.streamText = origStreamText;
83
94
  fs.readBytes = origReadBytes;
95
+ if (origReadByteRange) fs.readByteRange = origReadByteRange;
96
+ else delete fs.readByteRange;
84
97
  fs.listDir = origListDir;
85
98
  fs.writeText = origWriteText;
86
99
  fs.editText = origEditText;
@@ -1 +1 @@
1
- {"version":3,"file":"decorate-fs.js","names":[],"sources":["../../src/router/decorate-fs.ts"],"sourcesContent":["import { join } from 'node:path'\nimport { FsTargetKey } from '@deepseek-ai/dsh-fs'\nimport type { FileSystem } from '@deepseek-ai/dsh-fs'\nimport type { FsEditOutcome, FsEditRequest, FsTarget, FsVersion, FsWriteIntent, FsWriteOutcome, FsDirEntry, FsInfo, FsPathInfo } from '@deepseek-ai/dsh-fs'\nimport type { SandboxExecutionPolicy } from '@deepseek-ai/dsh-sandbox'\nimport { sharedWorldResolver } from './resolve.js'\n\nexport function decorateFileSystem(fs: FileSystem): () => void {\n const resolver = sharedWorldResolver()\n\n const origStat = fs.stat.bind(fs)\n const origLstat = fs.lstat.bind(fs)\n const origReadText = fs.readText.bind(fs)\n const origStreamText = fs.streamText.bind(fs)\n const origReadBytes = fs.readBytes.bind(fs)\n const origListDir = fs.listDir.bind(fs)\n const origWriteText = fs.writeText.bind(fs)\n const origEditText = fs.editText.bind(fs)\n\n function targetPath(target: FsTarget): string {\n return fs.processPath(target)\n }\n\n fs.stat = async (target: FsTarget, signal?: AbortSignal): Promise<FsInfo | undefined> => {\n const ref = resolver.resolve(targetPath(target))\n if (ref.kind === 'local') return origStat(target, signal)\n if (ref.kind === 'unrouted') throw new Error(`node ${ref.nodeId} world not available`)\n return ref.provider.fs.stat(ref.remotePath, signal) as Promise<FsInfo | undefined>\n }\n\n fs.lstat = async (path: string, opts?: { cwd?: string }, signal?: AbortSignal): Promise<FsPathInfo | undefined> => {\n const ref = resolver.resolve(path)\n if (ref.kind === 'local') return origLstat(path, opts, signal)\n if (ref.kind === 'unrouted') throw new Error(`node ${ref.nodeId} world not available`)\n return ref.provider.fs.lstat(ref.remotePath, opts, signal) as Promise<FsPathInfo | undefined>\n }\n\n fs.readText = async (target: FsTarget, signal?: AbortSignal): Promise<string> => {\n const ref = resolver.resolve(targetPath(target))\n if (ref.kind === 'local') return origReadText(target, signal)\n if (ref.kind === 'unrouted') throw new Error(`node ${ref.nodeId} world not available`)\n return ref.provider.fs.readText(ref.remotePath, signal)\n }\n\n fs.streamText = async (target: FsTarget, signal?: AbortSignal): Promise<AsyncIterable<string>> => {\n const ref = resolver.resolve(targetPath(target))\n if (ref.kind === 'local') return origStreamText(target, signal)\n if (ref.kind === 'unrouted') throw new Error(`node ${ref.nodeId} world not available`)\n return ref.provider.fs.streamText(ref.remotePath, signal)\n }\n\n fs.readBytes = async (target: FsTarget, signal: AbortSignal | undefined, maxBytes: number): Promise<Uint8Array> => {\n const ref = resolver.resolve(targetPath(target))\n if (ref.kind === 'local') return origReadBytes(target, signal, maxBytes)\n if (ref.kind === 'unrouted') throw new Error(`node ${ref.nodeId} world not available`)\n return ref.provider.fs.readBytes(ref.remotePath, signal, maxBytes)\n }\n\n fs.listDir = async (target: FsTarget, signal?: AbortSignal): Promise<FsDirEntry[]> => {\n const parentPath = targetPath(target)\n const ref = resolver.resolve(parentPath)\n if (ref.kind === 'local') return origListDir(target, signal)\n if (ref.kind === 'unrouted') throw new Error(`node ${ref.nodeId} world not available`)\n const entries = await ref.provider.fs.listDir(ref.remotePath, signal) as Array<{\n name: string\n isDirectory?: boolean\n isFile?: boolean\n isSymlink?: boolean\n size?: number\n }>\n return entries.map((entry) => {\n const childPath = join(parentPath, entry.name)\n return {\n name: entry.name,\n type: entry.isDirectory ? 'directory' : entry.isFile ? 'file' : 'other',\n target: { targetKey: FsTargetKey(childPath), displayPath: childPath },\n ...(entry.size === undefined ? {} : { size: entry.size }),\n }\n })\n }\n\n fs.writeText = async (target: FsTarget, content: string, expected?: FsWriteIntent, signal?: AbortSignal, sandboxPolicy?: SandboxExecutionPolicy): Promise<FsWriteOutcome> => {\n const ref = resolver.resolve(targetPath(target))\n if (ref.kind === 'local') return origWriteText(target, content, expected, signal, sandboxPolicy)\n if (ref.kind === 'unrouted') throw new Error(`node ${ref.nodeId} world not available`)\n return ref.provider.fs.writeText(ref.remotePath, content, expected, signal, sandboxPolicy) as Promise<FsWriteOutcome>\n }\n\n fs.editText = async (target: FsTarget, edit: FsEditRequest, expected?: { version: FsVersion }, signal?: AbortSignal, sandboxPolicy?: SandboxExecutionPolicy): Promise<FsEditOutcome> => {\n const ref = resolver.resolve(targetPath(target))\n if (ref.kind === 'local') return origEditText(target, edit, expected, signal, sandboxPolicy)\n if (ref.kind === 'unrouted') throw new Error(`node ${ref.nodeId} world not available`)\n return ref.provider.fs.editText(ref.remotePath, edit, expected, signal, sandboxPolicy) as Promise<FsEditOutcome>\n }\n\n return () => {\n fs.stat = origStat\n fs.lstat = origLstat\n fs.readText = origReadText\n fs.streamText = origStreamText\n fs.readBytes = origReadBytes\n fs.listDir = origListDir\n fs.writeText = origWriteText\n fs.editText = origEditText\n }\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"decorate-fs.js","names":[],"sources":["../../src/router/decorate-fs.ts"],"sourcesContent":["import { join } from 'node:path'\nimport { FsTargetKey } from '@deepseek-ai/dsh-fs'\nimport type { FileSystem } from '@deepseek-ai/dsh-fs'\nimport type { FsEditOutcome, FsEditRequest, FsTarget, FsVersion, FsWriteIntent, FsWriteOutcome, FsDirEntry, FsInfo, FsPathInfo } from '@deepseek-ai/dsh-fs'\nimport type { SandboxExecutionPolicy } from '@deepseek-ai/dsh-sandbox'\nimport { sharedWorldResolver } from './resolve.js'\n\nexport function decorateFileSystem(fs: FileSystem): () => void {\n const resolver = sharedWorldResolver()\n\n const origStat = fs.stat.bind(fs)\n const origLstat = fs.lstat.bind(fs)\n const origReadText = fs.readText.bind(fs)\n const origStreamText = fs.streamText.bind(fs)\n const origReadBytes = fs.readBytes.bind(fs)\n const origReadByteRange = typeof fs.readByteRange === 'function' ? fs.readByteRange.bind(fs) : undefined\n const origListDir = fs.listDir.bind(fs)\n const origWriteText = fs.writeText.bind(fs)\n const origEditText = fs.editText.bind(fs)\n\n function targetPath(target: FsTarget): string {\n return fs.processPath(target)\n }\n\n fs.stat = async (target: FsTarget, signal?: AbortSignal): Promise<FsInfo | undefined> => {\n const ref = resolver.resolve(targetPath(target))\n if (ref.kind === 'local') return origStat(target, signal)\n if (ref.kind === 'unrouted') throw new Error(`node ${ref.nodeId} world not available`)\n return ref.provider.fs.stat(ref.remotePath, signal) as Promise<FsInfo | undefined>\n }\n\n fs.lstat = async (path: string, opts?: { cwd?: string }, signal?: AbortSignal): Promise<FsPathInfo | undefined> => {\n const ref = resolver.resolve(path)\n if (ref.kind === 'local') return origLstat(path, opts, signal)\n if (ref.kind === 'unrouted') throw new Error(`node ${ref.nodeId} world not available`)\n return ref.provider.fs.lstat(ref.remotePath, opts, signal) as Promise<FsPathInfo | undefined>\n }\n\n fs.readText = async (target: FsTarget, signal?: AbortSignal): Promise<string> => {\n const ref = resolver.resolve(targetPath(target))\n if (ref.kind === 'local') return origReadText(target, signal)\n if (ref.kind === 'unrouted') throw new Error(`node ${ref.nodeId} world not available`)\n return ref.provider.fs.readText(ref.remotePath, signal)\n }\n\n fs.streamText = async (target: FsTarget, signal?: AbortSignal): Promise<AsyncIterable<string>> => {\n const ref = resolver.resolve(targetPath(target))\n if (ref.kind === 'local') return origStreamText(target, signal)\n if (ref.kind === 'unrouted') throw new Error(`node ${ref.nodeId} world not available`)\n return ref.provider.fs.streamText(ref.remotePath, signal)\n }\n\n fs.readBytes = async (target: FsTarget, signal: AbortSignal | undefined, maxBytes: number): Promise<Uint8Array> => {\n const ref = resolver.resolve(targetPath(target))\n if (ref.kind === 'local') return origReadBytes(target, signal, maxBytes)\n if (ref.kind === 'unrouted') throw new Error(`node ${ref.nodeId} world not available`)\n return ref.provider.fs.readBytes(ref.remotePath, signal, maxBytes)\n }\n\n fs.readByteRange = async (target: FsTarget, range: { offset: number; length: number }, signal?: AbortSignal): Promise<Uint8Array> => {\n const ref = resolver.resolve(targetPath(target))\n if (ref.kind === 'local') {\n if (origReadByteRange) return origReadByteRange(target, range, signal)\n const end = range.offset + range.length\n const bytes = await origReadBytes(target, signal, end)\n return bytes.subarray(range.offset, end)\n }\n if (ref.kind === 'unrouted') throw new Error(`node ${ref.nodeId} world not available`)\n return ref.provider.fs.readByteRange(ref.remotePath, range, signal)\n }\n\n fs.listDir = async (target: FsTarget, signal?: AbortSignal): Promise<FsDirEntry[]> => {\n const parentPath = targetPath(target)\n const ref = resolver.resolve(parentPath)\n if (ref.kind === 'local') return origListDir(target, signal)\n if (ref.kind === 'unrouted') throw new Error(`node ${ref.nodeId} world not available`)\n const entries = await ref.provider.fs.listDir(ref.remotePath, signal) as Array<{\n name: string\n isDirectory?: boolean\n isFile?: boolean\n isSymlink?: boolean\n size?: number\n }>\n return entries.map((entry) => {\n const childPath = join(parentPath, entry.name)\n return {\n name: entry.name,\n type: entry.isDirectory ? 'directory' : entry.isFile ? 'file' : 'other',\n target: { targetKey: FsTargetKey(childPath), displayPath: childPath },\n ...(entry.size === undefined ? {} : { size: entry.size }),\n }\n })\n }\n\n fs.writeText = async (target: FsTarget, content: string, expected?: FsWriteIntent, signal?: AbortSignal, sandboxPolicy?: SandboxExecutionPolicy): Promise<FsWriteOutcome> => {\n const ref = resolver.resolve(targetPath(target))\n if (ref.kind === 'local') return origWriteText(target, content, expected, signal, sandboxPolicy)\n if (ref.kind === 'unrouted') throw new Error(`node ${ref.nodeId} world not available`)\n return ref.provider.fs.writeText(ref.remotePath, content, expected, signal, sandboxPolicy) as Promise<FsWriteOutcome>\n }\n\n fs.editText = async (target: FsTarget, edit: FsEditRequest, expected?: { version: FsVersion }, signal?: AbortSignal, sandboxPolicy?: SandboxExecutionPolicy): Promise<FsEditOutcome> => {\n const ref = resolver.resolve(targetPath(target))\n if (ref.kind === 'local') return origEditText(target, edit, expected, signal, sandboxPolicy)\n if (ref.kind === 'unrouted') throw new Error(`node ${ref.nodeId} world not available`)\n return ref.provider.fs.editText(ref.remotePath, edit, expected, signal, sandboxPolicy) as Promise<FsEditOutcome>\n }\n\n return () => {\n fs.stat = origStat\n fs.lstat = origLstat\n fs.readText = origReadText\n fs.streamText = origStreamText\n fs.readBytes = origReadBytes\n if (origReadByteRange) fs.readByteRange = origReadByteRange\n else delete (fs as unknown as { readByteRange?: unknown }).readByteRange\n fs.listDir = origListDir\n fs.writeText = origWriteText\n fs.editText = origEditText\n }\n}\n"],"mappings":"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"}
@@ -1,4 +1,4 @@
1
- import { o as ShellDelegate } from "../types-DPP05ZsA.js";
1
+ import { o as ShellDelegate } from "../types-_0vpT-jm.js";
2
2
  import { ShellExecSpec, ShellExecution, ShellExecutor } from "@deepseek-ai/dsh-shell";
3
3
  //#region src/router/decorate-shell.d.ts
4
4
  /**
@@ -1,4 +1,4 @@
1
- import { _ as WorkspaceBindings, a as NodeTestReport, t as ExecutionWorldProvider, v as WorldRef, y as WorldRegistry } from "../types-DPP05ZsA.js";
1
+ import { _ as WorkspaceBindings, a as NodeTestReport, t as ExecutionWorldProvider, v as WorldRef, y as WorldRegistry } from "../types-_0vpT-jm.js";
2
2
  import { sharedWorldResolver } from "./resolve.js";
3
3
  import { Context } from "@deepseek-ai/cordis";
4
4
  //#region src/router/index.d.ts
@@ -1,4 +1,4 @@
1
- import { _ as WorkspaceBindings, v as WorldRef, y as WorldRegistry } from "../types-DPP05ZsA.js";
1
+ import { _ as WorkspaceBindings, v as WorldRef, y as WorldRegistry } from "../types-_0vpT-jm.js";
2
2
  //#region src/router/resolve.d.ts
3
3
  interface BindingHit {
4
4
  nodeId: string;