@artenx/dshb 0.1.7 → 0.1.9
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/lib/auth/index.js +2 -1
- package/lib/auth/index.js.map +1 -1
- package/lib/client.js +1 -1
- package/lib/client.js.map +1 -1
- package/lib/core/index.js +1 -1
- package/lib/core/workspace-instruction-context.d.ts +281 -122
- package/lib/core/workspace-instruction-context.js +1 -1
- package/lib/exec-docker/docker-backend.d.ts +1 -1
- package/lib/exec-docker/docker-client.d.ts +1 -1
- package/lib/exec-docker/docker-client.js +4 -1
- package/lib/exec-docker/docker-client.js.map +1 -1
- package/lib/exec-docker/process-provider.d.ts +3 -1
- package/lib/exec-docker/process-provider.js +68 -5
- package/lib/exec-docker/process-provider.js.map +1 -1
- package/lib/exec-docker/remote-docker-cli.d.ts +1 -1
- package/lib/exec-docker/remote-docker-cli.js +1 -1
- package/lib/exec-docker/remote-docker-cli.js.map +1 -1
- package/lib/exec-docker/world.d.ts +1 -1
- package/lib/exec-ssh/executor.d.ts +1 -2
- package/lib/exec-ssh/executor.js +7 -5
- package/lib/exec-ssh/executor.js.map +1 -1
- package/lib/exec-ssh/process-provider.d.ts +3 -1
- package/lib/exec-ssh/process-provider.js +65 -5
- package/lib/exec-ssh/process-provider.js.map +1 -1
- package/lib/exec-ssh/world.d.ts +1 -1
- package/lib/router/decorate-shell.d.ts +11 -2
- package/lib/router/decorate-shell.js +80 -18
- package/lib/router/decorate-shell.js.map +1 -1
- package/lib/router/index.d.ts +1 -1
- package/lib/router/index.js +20 -0
- package/lib/router/index.js.map +1 -1
- package/lib/router/resolve.d.ts +1 -1
- package/lib/router/router-shell.d.ts +2 -3
- package/lib/router/router-shell.js +4 -13
- package/lib/router/router-shell.js.map +1 -1
- package/lib/router/types.d.ts +1 -1
- package/lib/{types-C0TtEO7z.d.ts → types-ekJQQ4x6.d.ts} +4 -2
- package/lib/{workspace-instruction-context-CxUldQkP.js → workspace-instruction-context-DzHFTN87.js} +156 -76
- package/lib/workspace-instruction-context-DzHFTN87.js.map +1 -0
- package/package.json +34 -33
- package/lib/workspace-instruction-context-CxUldQkP.js.map +0 -1
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
import "./workspace-instructions.js";
|
|
2
|
-
import { a as registerWorkspaceInstructionInjector, i as name, n as escapeInstructionFrameBody, o as renderInstructionMessageText, r as inject, s as resolveSnapshotForSession, t as createInstructionMessage } from "../workspace-instruction-context-
|
|
2
|
+
import { a as registerWorkspaceInstructionInjector, i as name, n as escapeInstructionFrameBody, o as renderInstructionMessageText, r as inject, s as resolveSnapshotForSession, t as createInstructionMessage } from "../workspace-instruction-context-DzHFTN87.js";
|
|
3
3
|
export { createInstructionMessage, escapeInstructionFrameBody, inject, name, registerWorkspaceInstructionInjector, renderInstructionMessageText, resolveSnapshotForSession };
|
|
@@ -30,7 +30,7 @@ interface DockerBackend {
|
|
|
30
30
|
remove(path: string): Promise<void>;
|
|
31
31
|
rename(src: string, dest: string): Promise<void>;
|
|
32
32
|
ensureRg(): Promise<void>;
|
|
33
|
-
pty(argv: string[], cwd: string, cols: number, rows: number): Promise<{
|
|
33
|
+
pty(argv: string[], cwd: string, cols: number, rows: number, env?: Record<string, string>, terminalType?: string): Promise<{
|
|
34
34
|
stream: NodeJS.ReadWriteStream;
|
|
35
35
|
resize: (c: number, r: number) => void;
|
|
36
36
|
kill: () => void;
|
|
@@ -34,7 +34,7 @@ declare class DockerClient implements DockerBackend {
|
|
|
34
34
|
mkdir(path: string): Promise<void>;
|
|
35
35
|
remove(path: string): Promise<void>;
|
|
36
36
|
rename(src: string, dest: string): Promise<void>;
|
|
37
|
-
pty(argv: string[], cwd: string, cols: number, rows: number): Promise<{
|
|
37
|
+
pty(argv: string[], cwd: string, cols: number, rows: number, env?: Record<string, string>, terminalType?: string): Promise<{
|
|
38
38
|
stream: NodeJS.ReadWriteStream;
|
|
39
39
|
resize: (c: number, r: number) => void;
|
|
40
40
|
kill: () => void;
|
|
@@ -250,13 +250,16 @@ var DockerClient = class {
|
|
|
250
250
|
dest
|
|
251
251
|
], "/");
|
|
252
252
|
}
|
|
253
|
-
async pty(argv, cwd, cols, rows) {
|
|
253
|
+
async pty(argv, cwd, cols, rows, env = {}, terminalType = "xterm-256color") {
|
|
254
|
+
const environment = Object.entries(env).map(([key, value]) => `${key}=${value}`);
|
|
255
|
+
if (!environment.some((entry) => entry.startsWith("TERM="))) environment.push(`TERM=${terminalType}`);
|
|
254
256
|
const createRes = await dockerRequest("POST", `/containers/${this.containerId}/exec`, {
|
|
255
257
|
AttachStdout: true,
|
|
256
258
|
AttachStderr: true,
|
|
257
259
|
AttachStdin: true,
|
|
258
260
|
Tty: true,
|
|
259
261
|
WorkingDir: cwd,
|
|
262
|
+
Env: environment,
|
|
260
263
|
Cmd: argv
|
|
261
264
|
});
|
|
262
265
|
if (createRes.statusCode !== 201) throw new Error("docker exec create failed");
|
|
@@ -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 } | undefined> {\n const result = await this.execShell(`stat -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 }\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): Promise<{ stream: NodeJS.ReadWriteStream; resize: (c: number, r: number) => void; kill: () => void }> {\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 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":";;;;AAMA,MAAM,SAAS,gBAAgB;AAa/B,SAAS,WACP,MACA,YACe;CACf,MAAM,SAAS,EAAE,GAAG,KAAK;CACzB,IAAI,OAAO,YAAY,OAAOA,QAAY;EAAE,GAAG;EAAQ,YAAY,OAAO;CAAW,GAAY,UAAU;CAE3G,QADY,OAAO,MAAMC,YAAeD,QAAAA,CAC7B;EAAE,GAAG;EAAQ,UAAU,OAAO;EAAM,MAAM,OAAO;CAAK,GAAY,UAAU;AACzF;AAEA,SAAS,cAAc,QAAgB,MAAc,MAAgG;CACnJ,MAAM,UAAU,OAAO,EAAE,gBAAgB,mBAAmB,IAAI,CAAC;CACjE,IAAI,OAAO,YACT,OAAO,IAAI,SAAS,SAAS,WAAW;EACtC,MAAM,MAAMA,QAAY;GAAE;GAAQ;GAAM,YAAY,OAAO;GAAsB;EAAQ,IAAI,QAAQ;GACnG,MAAM,SAAmB,CAAC;GAC1B,IAAI,GAAG,SAAS,MAAc,OAAO,KAAK,CAAC,CAAC;GAC5C,IAAI,GAAG,aAAa;IAClB,QAAQ;KACN,YAAY,IAAI,cAAc;KAC9B,SAAS,IAAI;KACb,MAAM,OAAO,OAAO,MAAM;IAC5B,CAAC;GACH,CAAC;GACD,IAAI,GAAG,SAAS,MAAM;EACxB,CAAC;EACD,IAAI,GAAG,SAAS,MAAM;EACtB,IAAI,MAAM,IAAI,MAAM,KAAK,UAAU,IAAI,CAAC;EACxC,IAAI,IAAI;CACV,CAAC;CAEH,MAAM,MAAM,OAAO,MAAMC,YAAeD;CACxC,OAAO,IAAI,SAAS,SAAS,WAAW;EACtC,MAAM,MAAM,IAAI;GAAE;GAAQ;GAAM,UAAU,OAAO;GAAM,MAAM,OAAO;GAAM;EAAQ,IAAI,QAAQ;GAC5F,MAAM,SAAmB,CAAC;GAC1B,IAAI,GAAG,SAAS,MAAc,OAAO,KAAK,CAAC,CAAC;GAC5C,IAAI,GAAG,aAAa;IAClB,QAAQ;KACN,YAAY,IAAI,cAAc;KAC9B,SAAS,IAAI;KACb,MAAM,OAAO,OAAO,MAAM;IAC5B,CAAC;GACH,CAAC;GACD,IAAI,GAAG,SAAS,MAAM;EACxB,CAAC;EACD,IAAI,GAAG,SAAS,MAAM;EACtB,IAAI,MAAM,IAAI,MAAM,KAAK,UAAU,IAAI,CAAC;EACxC,IAAI,IAAI;CACV,CAAC;AACH;AAEA,SAAS,MAAM,MAAuB;CACpC,MAAM,SAAmB,CAAC;CAC1B,MAAM,SAAmB,CAAC;CAC1B,IAAI,MAAM;CACV,OAAO,MAAM,KAAK,KAAK,QAAQ;EAC7B,MAAM,aAAa,KAAK;EACxB,MAAM,SAAS,KAAK,aAAa,MAAM,CAAC;EACxC,OAAO;EACP,IAAI,MAAM,SAAS,KAAK,QAAQ;EAChC,MAAM,UAAU,KAAK,SAAS,KAAK,MAAM,MAAM;EAC/C,OAAO;EACP,IAAI,eAAe,GAAG,OAAO,KAAK,OAAO;OACpC,IAAI,eAAe,GAAG,OAAO,KAAK,OAAO;CAChD;CACA,OAAO;EAAE;EAAQ;CAAO;AAC1B;AAEA,SAAS,WAAW,GAAmB;CACrC,OAAO,IAAI,EAAE,QAAQ,MAAM,OAAO,EAAE;AACtC;AAEA,IAAa,eAAb,MAAmD;CACjD;CAEA,YAAY,aAAqB;EAC/B,KAAK,cAAc;CACrB;CAEA,MAAM,OAAyB;EAC7B,IAAI;GAEF,QAAO,MADW,cAAc,OAAO,QAAQ,EAAA,CACpC,eAAe;EAC5B,QAAQ;GACN,OAAO;EACT;CACF;CAEA,MAAM,UAAmE;EACvE,MAAM,MAAM,MAAM,cAAc,OAAO,eAAe,KAAK,YAAY,MAAM;EAC7E,IAAI,IAAI,eAAe,KAAK,OAAO,KAAA;EACnC,MAAM,OAAO,KAAK,MAAM,IAAI,KAAK,SAAS,MAAM,CAAC;EACjD,OAAO;GAAE,SAAS,KAAK,OAAO,WAAW;GAAO,MAAM,KAAK,QAAQ,KAAK;EAAY;CACtF;CAEA,MAAM,KAAK,MAAgB,KAAa,MAA8B,CAAC,GAAG,WAAyC;EACjH,MAAM,YAAY,MAAM,cAAc,QAAQ,eAAe,KAAK,YAAY,QAAQ;GACpF,cAAc;GACd,cAAc;GACd,aAAa,cAAc,KAAA;GAC3B,KAAK;GACL,YAAY;GACZ,KAAK,OAAO,QAAQ,GAAG,CAAC,CAAC,KAAK,CAAC,GAAG,OAAO,GAAG,EAAE,GAAG,GAAG;GACpD,KAAK;EACP,CAAC;EACD,IAAI,UAAU,eAAe,KAE3B,OAAO;GAAE,MAAM;GAAK,QAAQ;GAAI,QAAQ,8BAD5B,UAAU,KAAK,SAAS,MACoC;EAAI;EAE9E,MAAM,SAAS,KAAK,MAAM,UAAU,KAAK,SAAS,MAAM,CAAC,CAAC,CAAC;EAC3D,IAAI,CAAC,QAAQ,OAAO;GAAE,MAAM;GAAK,QAAQ;GAAI,QAAQ;EAA8B;EAEnF,OAAO,IAAI,SAAqB,SAAS,WAAW;GAClD,MAAM,MAAM,WACV;IACE,QAAQ;IACR,MAAM,SAAS,OAAO;IACtB,SAAS;KAAE,gBAAgB;KAAoB,YAAY;IAAU;GACvE,IACC,QAAQ;IACP,MAAM,SAAmB,CAAC;IAC1B,IAAI,GAAG,SAAS,MAAc,OAAO,KAAK,CAAC,CAAC;IAC5C,IAAI,GAAG,aAAa;KAElB,MAAM,UAAU,MADJ,OAAO,OAAO,MACF,CAAC;KACzB,MAAM,aAAa,WACjB;MAAE,QAAQ;MAAO,MAAM,SAAS,OAAO;MAAQ,SAAS,CAAC;KAAE,IAC1D,eAAe;MACd,MAAM,UAAoB,CAAC;MAC3B,WAAW,GAAG,SAAS,MAAc,QAAQ,KAAK,CAAC,CAAC;MACpD,WAAW,GAAG,aAAa;OACzB,IAAI,OAAO;OACX,IAAI;QAEF,OADa,KAAK,MAAM,OAAO,OAAO,OAAO,CAAC,CAAC,SAAS,MAAM,CACpD,CAAC,CAAC,YAAY;OAC1B,QAAQ,CAAC;OACT,QAAQ;QACN;QACA,QAAQ,OAAO,OAAO,QAAQ,MAAM,CAAC,CAAC,SAAS,MAAM;QACrD,QAAQ,OAAO,OAAO,QAAQ,MAAM,CAAC,CAAC,SAAS,MAAM;OACvD,CAAC;MACH,CAAC;MACD,WAAW,GAAG,eAAe,QAAQ;OAAE,MAAM;OAAG,QAAQ,OAAO,OAAO,QAAQ,MAAM,CAAC,CAAC,SAAS,MAAM;OAAG,QAAQ,OAAO,OAAO,QAAQ,MAAM,CAAC,CAAC,SAAS,MAAM;MAAE,CAAC,CAAC;KACnK,CACF;KACA,WAAW,GAAG,eAAe,QAAQ;MAAE,MAAM;MAAG,QAAQ,OAAO,OAAO,QAAQ,MAAM,CAAC,CAAC,SAAS,MAAM;MAAG,QAAQ,OAAO,OAAO,QAAQ,MAAM,CAAC,CAAC,SAAS,MAAM;KAAE,CAAC,CAAC;KACjK,WAAW,IAAI;IACjB,CAAC;IACD,IAAI,GAAG,SAAS,MAAM;GACxB,CACF;GACA,IAAI,GAAG,SAAS,MAAM;GACtB,IAAI,MAAM,KAAK,UAAU;IAAE,QAAQ;IAAO,KAAK;GAAM,CAAC,CAAC;GACvD,IAAI,WAAW,IAAI,MAAM,SAAS;GAClC,IAAI,IAAI;EACV,CAAC;CACH;CAEA,MAAM,UAAU,SAAiB,KAAkC;EACjE,OAAO,KAAK,KAAK;GAAC;GAAQ;GAAM;EAAO,GAAG,GAAG;CAC/C;CAEA,MAAM,SAAS,MAA+B;EAC5C,MAAM,SAAS,MAAM,KAAK,KAAK,CAAC,OAAO,IAAI,GAAG,GAAG;EACjD,OAAO,OAAO,KAAK,OAAO,QAAQ,MAAM;CAC1C;CAEA,MAAM,UAAU,MAAc,SAAyC;EAErE,MAAM,OADO,OAAO,SAAS,OAAO,IAAI,UAAU,OAAO,KAAK,SAAS,MAAM,EAAA,CAC5D,SAAS,QAAQ;EAClC,MAAM,KAAK,UAAU,gBAAgB,IAAI,kBAAkB,WAAW,IAAI,KAAK,GAAG;CACpF;CAEA,MAAM,QAAQ,MAAkI;EAE9I,QAAO,MADc,KAAK,UAAU,4BAA4B,WAAW,IAAI,EAAE,eAAe,GAAG,EAAA,CACrF,OACX,MAAM,IAAI,CAAC,CACX,QAAQ,SAAS,KAAK,KAAK,CAAC,CAAC,CAC7B,KAAK,SAAS;GACb,MAAM,OAAO,KAAK,QAAQ,UAAU,EAAE,CAAC,CAAC,KAAK;GAC7C,IAAI,CAAC,MAAM,OAAO;GAClB,MAAM,QAAQ,KAAK,SAAS,GAAG;GAC/B,MAAM,YAAY,KAAK,SAAS,GAAG;GACnC,OAAO;IACL;IACA;IACA,QAAQ,CAAC,SAAS,CAAC;IACnB;IACA,MAAM;IACN,OAAO;GACT;EACF,CAAC,CAAC,CACD,QAAQ,MAAkC,MAAM,IAAI;CACzD;CAEA,MAAM,KAAK,MAA2G;EAEpH,MAAM,QAAO,MADQ,KAAK,UAAU,sBAAsB,WAAW,IAAI,EAAE,eAAe,GAAG,EAAA,CACzE,OAAO,KAAK;EAChC,IAAI,CAAC,MAAM,OAAO,KAAA;EAClB,MAAM,QAAQ,KAAK,MAAM,KAAK;EAC9B,IAAI,MAAM,SAAS,GAAG,OAAO,KAAA;EAC7B,MAAM,OAAO,OAAO,MAAM,EAAE;EAC5B,MAAM,QAAQ,OAAO,MAAM,EAAE,IAAI;EACjC,MAAM,QAAQ,MAAM,MAAM,CAAC,CAAC,CAAC,KAAK,GAAG;EACrC,OAAO;GACL;GACA;GACA,aAAa,MAAM,SAAS,WAAW;GACvC,QAAQ,MAAM,SAAS,cAAc,KAAK,MAAM,SAAS,oBAAoB;EAC/E;CACF;CAEA,MAAM,MAAM,MAA6B;EACvC,MAAM,KAAK,KAAK;GAAC;GAAS;GAAM;EAAI,GAAG,GAAG;CAC5C;CAEA,MAAM,OAAO,MAA6B;EACxC,MAAM,KAAK,KAAK;GAAC;GAAM;GAAM;EAAI,GAAG,GAAG;CACzC;CAEA,MAAM,OAAO,KAAa,MAA6B;EACrD,MAAM,KAAK,KAAK;GAAC;GAAM;GAAK;EAAI,GAAG,GAAG;CACxC;CAEA,MAAM,IAAI,MAAgB,KAAa,MAAc,MAAqH;EACxK,MAAM,YAAY,MAAM,cAAc,QAAQ,eAAe,KAAK,YAAY,QAAQ;GACpF,cAAc;GACd,cAAc;GACd,aAAa;GACb,KAAK;GACL,YAAY;GACZ,KAAK;EACP,CAAC;EACD,IAAI,UAAU,eAAe,KAAK,MAAM,IAAI,MAAM,2BAA2B;EAC7E,MAAM,SAAS,KAAK,MAAM,UAAU,KAAK,SAAS,MAAM,CAAC,CAAC,CAAC;EAE3D,OAAO,IAAI,SAAS,SAAS,WAAW;GACtC,MAAM,MAAM,WACV;IACE,QAAQ;IACR,MAAM,SAAS,OAAO;IACtB,SAAS;KAAE,gBAAgB;KAAoB,YAAY;IAAU;GACvE,IACC,QAAQ;IACP,QAAQ;KACN,QAAQ;KACR,SAAS,GAAW,MAAc;MAEhC,WADsB;OAAE,QAAQ;OAAQ,MAAM,SAAS,OAAO,YAAY,EAAE,KAAK;OAAK,SAAS,CAAC;MAAE,SAAS,CAAC,CAC3G,CAAC,CAAC,IAAI;KACT;KACA,YAAY;MACV,IAAI,QAAQ;KACd;IACF,CAAC;GACH,CACF;GACA,IAAI,GAAG,SAAS,MAAM;GACtB,IAAI,MAAM,KAAK,UAAU;IAAE,QAAQ;IAAO,KAAK;GAAK,CAAC,CAAC;GACtD,IAAI,IAAI;EACV,CAAC;CACH;CAEA,MAAM,WAA0B;EAE9B,KAAI,MADiB,KAAK,UAAU,qCAAqC,GAAG,EAAA,CACjE,OAAO,SAAS,aAAa,GACtC,MAAM,KAAK,UAAU,4FAA4F,GAAG;CAExH;AACF"}
|
|
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 } | undefined> {\n const result = await this.execShell(`stat -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 }\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,5 +1,5 @@
|
|
|
1
1
|
import { DockerBackend } from "./docker-backend.js";
|
|
2
|
-
import { SubprocessHandle, SubprocessSpawnSpec, SubprocessTerminalHandle, SubprocessTerminalSpawnSpec } from "@deepseek-ai/dsh-subprocess";
|
|
2
|
+
import { SubprocessHandle, SubprocessSpawnSpec, SubprocessTerminalEnvironment, SubprocessTerminalHandle, SubprocessTerminalSpawnSpec } from "@deepseek-ai/dsh-subprocess";
|
|
3
3
|
import { ShellExecRequest, ShellExecSpec, ShellProcess, ShellRunResult } from "@deepseek-ai/dsh-shell";
|
|
4
4
|
//#region src/exec-docker/process-provider.d.ts
|
|
5
5
|
declare class DockerShellExecutor {
|
|
@@ -12,6 +12,8 @@ declare class DockerShellExecutor {
|
|
|
12
12
|
declare class DockerSubprocessRuntime {
|
|
13
13
|
private readonly client;
|
|
14
14
|
constructor(client: DockerBackend);
|
|
15
|
+
terminalEnvironment(signal?: AbortSignal): Promise<SubprocessTerminalEnvironment>;
|
|
16
|
+
resolveExecutable(command: string, env?: Readonly<Record<string, string>>, signal?: AbortSignal): Promise<string>;
|
|
15
17
|
spawn(spec: SubprocessSpawnSpec): SubprocessHandle;
|
|
16
18
|
spawnTerminal(spec: SubprocessTerminalSpawnSpec): Promise<SubprocessTerminalHandle>;
|
|
17
19
|
}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { SubprocessExecutableNotFoundError } from "@deepseek-ai/dsh-subprocess";
|
|
1
2
|
//#region src/exec-docker/process-provider.ts
|
|
2
3
|
var DockerShellExecutor = class {
|
|
3
4
|
client;
|
|
@@ -49,6 +50,34 @@ var DockerSubprocessRuntime = class {
|
|
|
49
50
|
constructor(client) {
|
|
50
51
|
this.client = client;
|
|
51
52
|
}
|
|
53
|
+
async terminalEnvironment(signal) {
|
|
54
|
+
signal?.throwIfAborted();
|
|
55
|
+
const result = await this.client.exec([
|
|
56
|
+
"sh",
|
|
57
|
+
"-c",
|
|
58
|
+
`user=$(id -un); shell=$(getent passwd "$user" 2>/dev/null | cut -d: -f7); if [ -z "$shell" ]; then shell=$(awk -F: -v user="$user" '$1 == user {print $7; exit}' /etc/passwd); fi; printf "%s" "$shell"`
|
|
59
|
+
], "/");
|
|
60
|
+
signal?.throwIfAborted();
|
|
61
|
+
const defaultShell = result.stdout.trim().split("\n")[0];
|
|
62
|
+
return {
|
|
63
|
+
platform: "posix",
|
|
64
|
+
...defaultShell ? { defaultShell } : {}
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
async resolveExecutable(command, env = {}, signal) {
|
|
68
|
+
signal?.throwIfAborted();
|
|
69
|
+
const result = await this.client.exec([
|
|
70
|
+
"sh",
|
|
71
|
+
"-c",
|
|
72
|
+
"command -v \"$1\"",
|
|
73
|
+
"sh",
|
|
74
|
+
command
|
|
75
|
+
], "/", { ...env });
|
|
76
|
+
signal?.throwIfAborted();
|
|
77
|
+
const path = result.stdout.trim().split("\n")[0];
|
|
78
|
+
if (result.code !== 0 || !path) throw new SubprocessExecutableNotFoundError(`executable not found in Docker node: ${command}`);
|
|
79
|
+
return path;
|
|
80
|
+
}
|
|
52
81
|
spawn(spec) {
|
|
53
82
|
return {
|
|
54
83
|
pid: -1,
|
|
@@ -60,12 +89,46 @@ var DockerSubprocessRuntime = class {
|
|
|
60
89
|
};
|
|
61
90
|
}
|
|
62
91
|
async spawnTerminal(spec) {
|
|
63
|
-
|
|
92
|
+
if (spec.signal?.aborted) throw spec.signal.reason;
|
|
93
|
+
const pty = await this.client.pty(spec.argv, spec.cwd, spec.cols, spec.rows, spec.env, spec.terminalType);
|
|
94
|
+
if (spec.signal?.aborted) {
|
|
95
|
+
pty.kill();
|
|
96
|
+
throw spec.signal.reason;
|
|
97
|
+
}
|
|
98
|
+
let settled = false;
|
|
99
|
+
const done = new Promise((resolve, reject) => {
|
|
100
|
+
const finish = () => {
|
|
101
|
+
if (settled) return;
|
|
102
|
+
settled = true;
|
|
103
|
+
resolve({
|
|
104
|
+
exitCode: null,
|
|
105
|
+
signal: null
|
|
106
|
+
});
|
|
107
|
+
};
|
|
108
|
+
pty.stream.once("end", finish);
|
|
109
|
+
pty.stream.once("close", finish);
|
|
110
|
+
pty.stream.once("error", reject);
|
|
111
|
+
});
|
|
64
112
|
return {
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
113
|
+
pid: -1,
|
|
114
|
+
output: pty.stream,
|
|
115
|
+
done,
|
|
116
|
+
write: (data) => new Promise((resolve, reject) => {
|
|
117
|
+
pty.stream.write(data, (error) => error ? reject(error) : resolve());
|
|
118
|
+
}),
|
|
119
|
+
resize: async (cols, rows) => pty.resize(cols, rows),
|
|
120
|
+
inspectForeground: async () => void 0,
|
|
121
|
+
inspectActivity: async () => ({
|
|
122
|
+
state: "unknown",
|
|
123
|
+
revision: 0
|
|
124
|
+
}),
|
|
125
|
+
signalForeground: async () => {
|
|
126
|
+
throw new Error("foreground signaling is unsupported for Docker terminals");
|
|
127
|
+
},
|
|
128
|
+
terminate: async () => {
|
|
129
|
+
pty.kill();
|
|
130
|
+
await done.catch(() => {});
|
|
131
|
+
}
|
|
69
132
|
};
|
|
70
133
|
}
|
|
71
134
|
};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"process-provider.js","names":[],"sources":["../../src/exec-docker/process-provider.ts"],"sourcesContent":["import type { DockerBackend } from './docker-backend.js'\nimport type { ShellExecRequest, ShellExecSpec, ShellProcess, ShellRunResult } from '@deepseek-ai/dsh-shell'\nimport type
|
|
1
|
+
{"version":3,"file":"process-provider.js","names":[],"sources":["../../src/exec-docker/process-provider.ts"],"sourcesContent":["import type { DockerBackend } from './docker-backend.js'\nimport type { ShellExecRequest, ShellExecSpec, ShellProcess, ShellRunResult } from '@deepseek-ai/dsh-shell'\nimport { SubprocessExecutableNotFoundError, type CollectedOutput, type SubprocessHandle, type SubprocessOutcome, type SubprocessSpawnSpec, type SubprocessTerminalEnvironment, type SubprocessTerminalHandle, type SubprocessTerminalSpawnSpec } from '@deepseek-ai/dsh-subprocess'\n\nexport class DockerShellExecutor {\n constructor(private readonly client: DockerBackend) {}\n\n resolve(request: ShellExecRequest): ShellExecSpec {\n return {\n command: request.command,\n workdir: request.workdir ?? '/',\n timeoutMs: request.timeoutMs ?? 60_000,\n stdoutMaxBytes: request.stdoutMaxBytes ?? 1024 * 1024,\n signal: request.signal,\n stdin: request.stdin,\n env: request.env,\n sandboxPolicy: request.sandboxPolicy,\n } as ShellExecSpec\n }\n\n async run(spec: ShellExecSpec): Promise<ShellRunResult> {\n const result = await this.client.exec(['bash', '-c', spec.command], spec.workdir, (spec.env ?? {}) as Record<string, string>, spec.stdin !== undefined ? Buffer.from(spec.stdin) : undefined)\n const collect = (text: string): CollectedOutput => ({ text, truncated: false })\n return {\n exitCode: result.code,\n signal: null,\n timedOut: false,\n aborted: false,\n timeoutMs: spec.timeoutMs,\n stdout: collect(result.stdout),\n stderr: collect(result.stderr),\n } as unknown as ShellRunResult\n }\n\n start(spec: ShellExecSpec): ShellProcess {\n const promise = this.run(spec)\n return {\n pid: -1,\n done: promise,\n kill: () => {\n void promise\n },\n } as unknown as ShellProcess\n }\n}\n\nexport class DockerSubprocessRuntime {\n constructor(private readonly client: DockerBackend) {}\n\n async terminalEnvironment(signal?: AbortSignal): Promise<SubprocessTerminalEnvironment> {\n signal?.throwIfAborted()\n const script = `user=$(id -un); shell=$(getent passwd \"$user\" 2>/dev/null | cut -d: -f7); if [ -z \"$shell\" ]; then shell=$(awk -F: -v user=\"$user\" '$1 == user {print $7; exit}' /etc/passwd); fi; printf \"%s\" \"$shell\"`\n const result = await this.client.exec(['sh', '-c', script], '/')\n signal?.throwIfAborted()\n const defaultShell = result.stdout.trim().split('\\n')[0]\n return { platform: 'posix', ...(defaultShell ? { defaultShell } : {}) }\n }\n\n async resolveExecutable(command: string, env: Readonly<Record<string, string>> = {}, signal?: AbortSignal): Promise<string> {\n signal?.throwIfAborted()\n const result = await this.client.exec(['sh', '-c', 'command -v \"$1\"', 'sh', command], '/', { ...env })\n signal?.throwIfAborted()\n const path = result.stdout.trim().split('\\n')[0]\n if (result.code !== 0 || !path) throw new SubprocessExecutableNotFoundError(`executable not found in Docker node: ${command}`)\n return path\n }\n\n spawn(spec: SubprocessSpawnSpec): SubprocessHandle {\n const promise = this.client.exec(spec.argv as string[], spec.cwd, (spec.env ?? {}) as Record<string, string>)\n const handle: SubprocessHandle = {\n pid: -1,\n done: promise.then((r) => ({ exitCode: r.code, signal: null as NodeJS.Signals | null })),\n kill: () => {\n void promise\n },\n } as unknown as SubprocessHandle\n return handle\n }\n\n async spawnTerminal(spec: SubprocessTerminalSpawnSpec): Promise<SubprocessTerminalHandle> {\n if (spec.signal?.aborted) throw spec.signal.reason\n const pty = await this.client.pty(spec.argv as string[], spec.cwd, spec.cols, spec.rows, spec.env, spec.terminalType)\n if (spec.signal?.aborted) {\n pty.kill()\n throw spec.signal.reason\n }\n let settled = false\n const done = new Promise<SubprocessOutcome>((resolve, reject) => {\n const finish = (): void => {\n if (settled) return\n settled = true\n resolve({ exitCode: null, signal: null })\n }\n pty.stream.once('end', finish)\n pty.stream.once('close', finish)\n pty.stream.once('error', reject)\n })\n const handle: SubprocessTerminalHandle = {\n pid: -1,\n output: pty.stream as unknown as import('node:stream').Readable,\n done,\n write: (data) => new Promise<void>((resolve, reject) => {\n pty.stream.write(data, (error) => error ? reject(error) : resolve())\n }),\n resize: async (cols, rows) => pty.resize(cols, rows),\n inspectForeground: async () => undefined,\n inspectActivity: async () => ({ state: 'unknown', revision: 0 }),\n signalForeground: async () => { throw new Error('foreground signaling is unsupported for Docker terminals') },\n terminate: async () => {\n pty.kill()\n await done.catch(() => {})\n },\n }\n return handle\n }\n}\n"],"mappings":"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"}
|
|
@@ -41,7 +41,7 @@ declare class RemoteDockerCli implements DockerBackend {
|
|
|
41
41
|
remove(path: string): Promise<void>;
|
|
42
42
|
rename(src: string, dest: string): Promise<void>;
|
|
43
43
|
ensureRg(): Promise<void>;
|
|
44
|
-
pty(
|
|
44
|
+
pty(_argv: string[], _cwd: string, _cols: number, _rows: number, _env?: Record<string, string>, _terminalType?: string): Promise<{
|
|
45
45
|
stream: NodeJS.ReadWriteStream;
|
|
46
46
|
resize: (c: number, r: number) => void;
|
|
47
47
|
kill: () => void;
|
|
@@ -145,7 +145,7 @@ var RemoteDockerCli = class {
|
|
|
145
145
|
"apk add --no-cache ripgrep 2>/dev/null || apt-get install -y ripgrep 2>/dev/null || true"
|
|
146
146
|
], "/");
|
|
147
147
|
}
|
|
148
|
-
async pty(
|
|
148
|
+
async pty(_argv, _cwd, _cols, _rows, _env, _terminalType) {
|
|
149
149
|
throw new Error("remote docker PTY not yet supported");
|
|
150
150
|
}
|
|
151
151
|
};
|
|
@@ -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 } | 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') }\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(
|
|
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 } | 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') }\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-
|
|
3
|
+
import { a as NodeTestReport, t as ExecutionWorldProvider } from "../types-ekJQQ4x6.js";
|
|
4
4
|
import { DockerShellExecutor, DockerSubprocessRuntime } from "./process-provider.js";
|
|
5
5
|
//#region src/exec-docker/world.d.ts
|
|
6
6
|
interface DockerNodeConfig {
|
|
@@ -34,9 +34,8 @@ declare class SshExecutor {
|
|
|
34
34
|
remove(path: string): Promise<void>;
|
|
35
35
|
rename(src: string, dest: string): Promise<void>;
|
|
36
36
|
ensureRg(): Promise<void>;
|
|
37
|
-
pty(argv: string[], cwd: string,
|
|
37
|
+
pty(argv: string[], cwd: string, env: Record<string, string>, cols: number, rows: number, terminalType?: string): Promise<{
|
|
38
38
|
channel: import('ssh2').ClientChannel;
|
|
39
|
-
onData: (cb: (chunk: Buffer) => void) => void;
|
|
40
39
|
resize: (cols: number, rows: number) => void;
|
|
41
40
|
kill: () => void;
|
|
42
41
|
}>;
|
package/lib/exec-ssh/executor.js
CHANGED
|
@@ -171,16 +171,19 @@ var SshExecutor = class {
|
|
|
171
171
|
], "/");
|
|
172
172
|
}
|
|
173
173
|
}
|
|
174
|
-
async pty(argv, cwd,
|
|
174
|
+
async pty(argv, cwd, env, cols, rows, terminalType = "xterm-256color") {
|
|
175
175
|
const client = await this.connection.getClient();
|
|
176
176
|
const q = (s) => `'${s.replace(/'/g, "'\\''")}'`;
|
|
177
|
-
const
|
|
178
|
-
|
|
177
|
+
const environment = Object.entries(env).map(([key, value]) => {
|
|
178
|
+
if (!/^[A-Za-z_][A-Za-z0-9_]*$/.test(key)) throw new Error(`invalid terminal environment key: ${key}`);
|
|
179
|
+
return `${key}=${value}`;
|
|
180
|
+
});
|
|
181
|
+
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(" ")}`;
|
|
179
182
|
return new Promise((resolve, reject) => {
|
|
180
183
|
client.exec(command, { pty: {
|
|
181
184
|
cols,
|
|
182
185
|
rows,
|
|
183
|
-
term:
|
|
186
|
+
term: terminalType
|
|
184
187
|
} }, (err, channel) => {
|
|
185
188
|
if (err) {
|
|
186
189
|
reject(err);
|
|
@@ -188,7 +191,6 @@ var SshExecutor = class {
|
|
|
188
191
|
}
|
|
189
192
|
resolve({
|
|
190
193
|
channel,
|
|
191
|
-
onData: (cb) => channel.on("data", cb),
|
|
192
194
|
resize: (c, r) => channel.setWindow(c, r, 0, 0),
|
|
193
195
|
kill: () => channel.close()
|
|
194
196
|
});
|
|
@@ -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 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): Promise<{ channel: import('ssh2').ClientChannel; onData: (cb: (chunk: Buffer) => void) => void; 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 cmdParts = argv.map(q)\n const command = `bash -c 'cd \"$0\" && exec env -i HOME=\"$HOME\" PATH=\"\\${PATH:-/usr/local/bin:/usr/bin:/bin}\" \"$@\"' ${q(cwd)} ${cmdParts.join(' ')}`\n return new Promise((resolve, reject) => {\n client.exec(\n command,\n { pty: { cols, rows, term: 'xterm-256color' } },\n (err, channel) => {\n if (err) {\n reject(err)\n return\n }\n resolve({\n channel,\n onData: (cb) => channel.on('data', cb),\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 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 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,5 +1,5 @@
|
|
|
1
1
|
import { SshExecutor } from "./executor.js";
|
|
2
|
-
import { SubprocessHandle, SubprocessSpawnSpec, SubprocessTerminalHandle, SubprocessTerminalSpawnSpec } from "@deepseek-ai/dsh-subprocess";
|
|
2
|
+
import { SubprocessHandle, SubprocessSpawnSpec, SubprocessTerminalEnvironment, SubprocessTerminalHandle, SubprocessTerminalSpawnSpec } from "@deepseek-ai/dsh-subprocess";
|
|
3
3
|
import { ShellExecRequest, ShellExecSpec, ShellProcess, ShellRunResult } from "@deepseek-ai/dsh-shell";
|
|
4
4
|
//#region src/exec-ssh/process-provider.d.ts
|
|
5
5
|
declare class SshShellExecutor {
|
|
@@ -12,6 +12,8 @@ declare class SshShellExecutor {
|
|
|
12
12
|
declare class SshSubprocessRuntime {
|
|
13
13
|
private readonly executor;
|
|
14
14
|
constructor(executor: SshExecutor);
|
|
15
|
+
terminalEnvironment(signal?: AbortSignal): Promise<SubprocessTerminalEnvironment>;
|
|
16
|
+
resolveExecutable(command: string, env?: Readonly<Record<string, string>>, signal?: AbortSignal): Promise<string>;
|
|
15
17
|
spawn(spec: SubprocessSpawnSpec): SubprocessHandle;
|
|
16
18
|
spawnTerminal(spec: SubprocessTerminalSpawnSpec): Promise<SubprocessTerminalHandle>;
|
|
17
19
|
}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { SubprocessExecutableNotFoundError } from "@deepseek-ai/dsh-subprocess";
|
|
1
2
|
//#region src/exec-ssh/process-provider.ts
|
|
2
3
|
var SshShellExecutor = class {
|
|
3
4
|
executor;
|
|
@@ -49,6 +50,34 @@ var SshSubprocessRuntime = class {
|
|
|
49
50
|
constructor(executor) {
|
|
50
51
|
this.executor = executor;
|
|
51
52
|
}
|
|
53
|
+
async terminalEnvironment(signal) {
|
|
54
|
+
signal?.throwIfAborted();
|
|
55
|
+
const result = await this.executor.exec([
|
|
56
|
+
"sh",
|
|
57
|
+
"-c",
|
|
58
|
+
`user=$(id -un); shell=$(getent passwd "$user" 2>/dev/null | cut -d: -f7); if [ -z "$shell" ]; then shell=$(awk -F: -v user="$user" '$1 == user {print $7; exit}' /etc/passwd); fi; printf "%s" "$shell"`
|
|
59
|
+
], "/");
|
|
60
|
+
signal?.throwIfAborted();
|
|
61
|
+
const defaultShell = result.stdout.trim().split("\n")[0];
|
|
62
|
+
return {
|
|
63
|
+
platform: "posix",
|
|
64
|
+
...defaultShell ? { defaultShell } : {}
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
async resolveExecutable(command, env = {}, signal) {
|
|
68
|
+
signal?.throwIfAborted();
|
|
69
|
+
const result = await this.executor.exec([
|
|
70
|
+
"sh",
|
|
71
|
+
"-c",
|
|
72
|
+
"command -v \"$1\"",
|
|
73
|
+
"sh",
|
|
74
|
+
command
|
|
75
|
+
], "/", { ...env });
|
|
76
|
+
signal?.throwIfAborted();
|
|
77
|
+
const path = result.stdout.trim().split("\n")[0];
|
|
78
|
+
if (result.code !== 0 || !path) throw new SubprocessExecutableNotFoundError(`executable not found on SSH node: ${command}`);
|
|
79
|
+
return path;
|
|
80
|
+
}
|
|
52
81
|
spawn(spec) {
|
|
53
82
|
return {
|
|
54
83
|
pid: -1,
|
|
@@ -60,12 +89,43 @@ var SshSubprocessRuntime = class {
|
|
|
60
89
|
};
|
|
61
90
|
}
|
|
62
91
|
async spawnTerminal(spec) {
|
|
63
|
-
|
|
92
|
+
if (spec.signal?.aborted) throw spec.signal.reason;
|
|
93
|
+
const pty = await this.executor.pty(spec.argv, spec.cwd, spec.env ?? {}, spec.cols, spec.rows, spec.terminalType);
|
|
94
|
+
if (spec.signal?.aborted) {
|
|
95
|
+
pty.kill();
|
|
96
|
+
throw spec.signal.reason;
|
|
97
|
+
}
|
|
98
|
+
let exitCode = null;
|
|
99
|
+
const done = new Promise((resolve, reject) => {
|
|
100
|
+
pty.channel.once("exit", (code) => {
|
|
101
|
+
if (typeof code === "number") exitCode = code;
|
|
102
|
+
});
|
|
103
|
+
pty.channel.once("error", reject);
|
|
104
|
+
pty.channel.once("close", () => resolve({
|
|
105
|
+
exitCode,
|
|
106
|
+
signal: null
|
|
107
|
+
}));
|
|
108
|
+
});
|
|
64
109
|
return {
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
110
|
+
pid: -1,
|
|
111
|
+
output: pty.channel,
|
|
112
|
+
done,
|
|
113
|
+
write: (data) => new Promise((resolve, reject) => {
|
|
114
|
+
pty.channel.write(data, (error) => error ? reject(error) : resolve());
|
|
115
|
+
}),
|
|
116
|
+
resize: async (cols, rows) => pty.resize(cols, rows),
|
|
117
|
+
inspectForeground: async () => void 0,
|
|
118
|
+
inspectActivity: async () => ({
|
|
119
|
+
state: "unknown",
|
|
120
|
+
revision: 0
|
|
121
|
+
}),
|
|
122
|
+
signalForeground: async () => {
|
|
123
|
+
throw new Error("foreground signaling is unsupported for SSH terminals");
|
|
124
|
+
},
|
|
125
|
+
terminate: async () => {
|
|
126
|
+
pty.kill();
|
|
127
|
+
await done.catch(() => {});
|
|
128
|
+
}
|
|
69
129
|
};
|
|
70
130
|
}
|
|
71
131
|
};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"process-provider.js","names":[],"sources":["../../src/exec-ssh/process-provider.ts"],"sourcesContent":["import type { ShellExecRequest, ShellExecSpec, ShellProcess, ShellRunResult } from '@deepseek-ai/dsh-shell'\nimport type
|
|
1
|
+
{"version":3,"file":"process-provider.js","names":[],"sources":["../../src/exec-ssh/process-provider.ts"],"sourcesContent":["import type { ShellExecRequest, ShellExecSpec, ShellProcess, ShellRunResult } from '@deepseek-ai/dsh-shell'\nimport { SubprocessExecutableNotFoundError, type CollectedOutput, type SubprocessHandle, type SubprocessOutcome, type SubprocessSpawnSpec, type SubprocessTerminalEnvironment, type SubprocessTerminalHandle, type SubprocessTerminalSpawnSpec } from '@deepseek-ai/dsh-subprocess'\nimport type { SshExecutor } from './executor.js'\n\nexport class SshShellExecutor {\n constructor(private readonly executor: SshExecutor) {}\n\n resolve(request: ShellExecRequest): ShellExecSpec {\n return {\n command: request.command,\n workdir: request.workdir ?? '/',\n timeoutMs: request.timeoutMs ?? 60_000,\n stdoutMaxBytes: request.stdoutMaxBytes ?? 1024 * 1024,\n signal: request.signal,\n stdin: request.stdin,\n env: request.env,\n sandboxPolicy: request.sandboxPolicy,\n } as ShellExecSpec\n }\n\n async run(spec: ShellExecSpec): Promise<ShellRunResult> {\n const result = await this.executor.exec(['sh', '-c', spec.command], spec.workdir, (spec.env ?? {}) as Record<string, string>, spec.stdin !== undefined ? Buffer.from(spec.stdin) : undefined)\n const collect = (text: string): CollectedOutput => ({ text, truncated: false })\n return {\n exitCode: result.code,\n signal: (result.signal ?? null) as NodeJS.Signals | null,\n timedOut: false,\n aborted: false,\n timeoutMs: spec.timeoutMs,\n stdout: collect(result.stdout),\n stderr: collect(result.stderr),\n } as unknown as ShellRunResult\n }\n\n start(spec: ShellExecSpec): ShellProcess {\n const promise = this.run(spec)\n const proc: ShellProcess = {\n pid: -1,\n done: promise,\n kill: () => {\n void promise\n },\n } as unknown as ShellProcess\n return proc\n }\n}\n\nexport class SshSubprocessRuntime {\n constructor(private readonly executor: SshExecutor) {}\n\n async terminalEnvironment(signal?: AbortSignal): Promise<SubprocessTerminalEnvironment> {\n signal?.throwIfAborted()\n const script = `user=$(id -un); shell=$(getent passwd \"$user\" 2>/dev/null | cut -d: -f7); if [ -z \"$shell\" ]; then shell=$(awk -F: -v user=\"$user\" '$1 == user {print $7; exit}' /etc/passwd); fi; printf \"%s\" \"$shell\"`\n const result = await this.executor.exec(['sh', '-c', script], '/')\n signal?.throwIfAborted()\n const defaultShell = result.stdout.trim().split('\\n')[0]\n return { platform: 'posix', ...(defaultShell ? { defaultShell } : {}) }\n }\n\n async resolveExecutable(command: string, env: Readonly<Record<string, string>> = {}, signal?: AbortSignal): Promise<string> {\n signal?.throwIfAborted()\n const result = await this.executor.exec(['sh', '-c', 'command -v \"$1\"', 'sh', command], '/', { ...env })\n signal?.throwIfAborted()\n const path = result.stdout.trim().split('\\n')[0]\n if (result.code !== 0 || !path) throw new SubprocessExecutableNotFoundError(`executable not found on SSH node: ${command}`)\n return path\n }\n\n spawn(spec: SubprocessSpawnSpec): SubprocessHandle {\n const promise = this.executor.exec(spec.argv as string[], spec.cwd, (spec.env ?? {}) as Record<string, string>)\n const handle: SubprocessHandle = {\n pid: -1,\n done: promise.then((r) => ({ exitCode: r.code, signal: (r.signal ?? null) as NodeJS.Signals | null })),\n kill: () => {\n void promise\n },\n } as unknown as SubprocessHandle\n return handle\n }\n\n async spawnTerminal(spec: SubprocessTerminalSpawnSpec): Promise<SubprocessTerminalHandle> {\n if (spec.signal?.aborted) throw spec.signal.reason\n const pty = await this.executor.pty(spec.argv as string[], spec.cwd, spec.env ?? {}, spec.cols, spec.rows, spec.terminalType)\n if (spec.signal?.aborted) {\n pty.kill()\n throw spec.signal.reason\n }\n let exitCode: number | null = null\n const done = new Promise<SubprocessOutcome>((resolve, reject) => {\n pty.channel.once('exit', (code) => {\n if (typeof code === 'number') exitCode = code\n })\n pty.channel.once('error', reject)\n pty.channel.once('close', () => resolve({ exitCode, signal: null }))\n })\n const handle: SubprocessTerminalHandle = {\n pid: -1,\n output: pty.channel,\n done,\n write: (data) => new Promise<void>((resolve, reject) => {\n pty.channel.write(data, (error) => error ? reject(error) : resolve())\n }),\n resize: async (cols, rows) => pty.resize(cols, rows),\n inspectForeground: async () => undefined,\n inspectActivity: async () => ({ state: 'unknown', revision: 0 }),\n signalForeground: async () => { throw new Error('foreground signaling is unsupported for SSH terminals') },\n terminate: async () => {\n pty.kill()\n await done.catch(() => {})\n },\n }\n return handle\n }\n}\n"],"mappings":"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"}
|
package/lib/exec-ssh/world.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { a as NodeTestReport, t as ExecutionWorldProvider } from "../types-
|
|
1
|
+
import { a as NodeTestReport, t as ExecutionWorldProvider } from "../types-ekJQQ4x6.js";
|
|
2
2
|
import { SshConnectionPool } from "./connection-pool.js";
|
|
3
3
|
import { SshExecutor } from "./executor.js";
|
|
4
4
|
import { SshFileSystem } from "./fs-provider.js";
|
|
@@ -1,6 +1,15 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { o as ShellDelegate } from "../types-ekJQQ4x6.js";
|
|
2
|
+
import { ShellExecSpec, ShellExecution, ShellExecutor } from "@deepseek-ai/dsh-shell";
|
|
2
3
|
//#region src/router/decorate-shell.d.ts
|
|
4
|
+
/**
|
|
5
|
+
* DSH 0.1.6+ collapses the shell seam into `resolve`/`execute`, and the
|
|
6
|
+
* `ShellExecution` handle replaces the old `run`/`start` pair. Remote worlds
|
|
7
|
+
* still expose a foreground `run`, so this adapts one remote run into a live
|
|
8
|
+
* execution handle: the foreground projection is the run's result, and the
|
|
9
|
+
* handle's status, exit classification, and collectors settle from it.
|
|
10
|
+
*/
|
|
11
|
+
declare function executionFromRemoteRun(shell: ShellDelegate, spec: ShellExecSpec): Promise<ShellExecution>;
|
|
3
12
|
declare function decorateShell(shell: ShellExecutor): () => void;
|
|
4
13
|
//#endregion
|
|
5
|
-
export { decorateShell };
|
|
14
|
+
export { decorateShell, executionFromRemoteRun };
|
|
6
15
|
//# sourceMappingURL=decorate-shell.d.ts.map
|