{"version":3,"file":"remote-runner-source.d.ts","sourceRoot":"","sources":["../../../src/core/remote-execution/remote-runner-source.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH,eAAO,MAAM,oBAAoB,EAAE,MAoHvB,CAAC","sourcesContent":["/**\n * Remote Execution — embedded protocol runner (2.14.0).\n *\n * A dependency-free Node program materialised onto the remote target and\n * executed there. It wraps the real remote child (the Jensen CLI) and frames\n * all output as JSONL protocol frames, so child stdout/stderr can never corrupt\n * the control channel. It is deliberately NOT an agent and NOT a scheduler: it\n * reports observations; the Bucephalus control plane remains authoritative.\n *\n * Kept as a line-joined source string (no template literal / escape ambiguity)\n * so it can be shipped inside a materialisation envelope without file-system\n * path resolution.\n */\n\nexport const REMOTE_RUNNER_SOURCE: string = [\n\t'\"use strict\";',\n\t'const { spawn } = require(\"node:child_process\");',\n\t'const { createHash } = require(\"node:crypto\");',\n\t'const fs = require(\"node:fs\");',\n\t'const os = require(\"node:os\");',\n\t'const path = require(\"node:path\");',\n\t\"\",\n\t\"function frame(type, payload) {\",\n\t'  process.stdout.write(JSON.stringify({ type, payload }) + \"\\\\n\");',\n\t\"}\",\n\t\"\",\n\t\"function readJson(file) {\",\n\t'  return JSON.parse(fs.readFileSync(file, \"utf8\"));',\n\t\"}\",\n\t\"\",\n\t\"function sha256(file) {\",\n\t\"  try {\",\n\t'    return createHash(\"sha256\").update(fs.readFileSync(file)).digest(\"hex\");',\n\t\"  } catch {\",\n\t\"    return null;\",\n\t\"  }\",\n\t\"}\",\n\t\"\",\n\t\"async function main() {\",\n\t\"  const envelope = readJson(process.argv[2]);\",\n\t\"  const location = {\",\n\t\"    host: os.hostname(),\",\n\t'    user: (os.userInfo() || {}).username || process.env.USERNAME || \"\",',\n\t\"    cwd: envelope.launch.cwd,\",\n\t\"    pid: process.pid,\",\n\t'    platform: process.platform + \"/\" + os.arch(),',\n\t\"  };\",\n\t\"\",\n\t'  frame(\"REMOTE_STARTED\", {',\n\t\"    protocolVersion: envelope.protocolVersion,\",\n\t\"    executionId: envelope.executionId,\",\n\t\"    remoteTargetId: envelope.remoteTargetId,\",\n\t\"    launchId: envelope.launchId,\",\n\t\"    fencing: envelope.fencing,\",\n\t\"    location: location,\",\n\t\"    command: { argv: [envelope.launch.command].concat(envelope.launch.args || []), cwd: envelope.launch.cwd },\",\n\t\"    startedAtMs: Date.now(),\",\n\t\"  });\",\n\t\"\",\n\t\"  const evidenceFiles = envelope.evidenceFiles || [];\",\n\t\"  const before = {};\",\n\t\"  for (const f of evidenceFiles) before[f] = sha256(path.resolve(envelope.launch.cwd, f));\",\n\t\"\",\n\t\"  const start = Date.now();\",\n\t\"  let child;\",\n\t\"  try {\",\n\t\"    child = spawn(envelope.launch.command, envelope.launch.args || [], {\",\n\t\"      cwd: envelope.launch.cwd,\",\n\t\"      shell: false,\",\n\t'      stdio: [\"ignore\", \"pipe\", \"pipe\"],',\n\t\"      env: Object.assign({}, process.env, envelope.launch.env || {}),\",\n\t\"    });\",\n\t\"  } catch (err) {\",\n\t'    frame(\"REMOTE_ERROR\", { code: \"LAUNCH_FAILED\", message: String((err && err.message) || err) });',\n\t'    frame(\"REMOTE_EXIT\", { executionId: envelope.executionId, exitCode: 1, signal: undefined, cancelled: false });',\n\t\"    process.exit(0);\",\n\t\"    return;\",\n\t\"  }\",\n\t\"\",\n\t\"  let settled = false;\",\n\t\"  const settle = () => { if (!settled) { settled = true; clearInterval(hb); } };\",\n\t\"  const hb = setInterval(() => {\",\n\t'    frame(\"REMOTE_HEARTBEAT\", { atMs: Date.now(), elapsedMs: Date.now() - start, alive: true });',\n\t\"  }, envelope.heartbeatMs || 3000);\",\n\t\"\",\n\t'  child.stdout.on(\"data\", (d) => frame(\"REMOTE_STDOUT\", { chunk: d.toString() }));',\n\t'  child.stderr.on(\"data\", (d) => frame(\"REMOTE_STDERR\", { chunk: d.toString() }));',\n\t'  child.on(\"error\", (err) => frame(\"REMOTE_ERROR\", { code: \"EXEC_FAILED\", message: String((err && err.message) || err) }));',\n\t\"\",\n\t\"  // If the control channel closes (controller SSH died), the next stdout\",\n\t\"  // write errors with EPIPE; kill the child so it cannot orphan. stdin is\",\n\t\"  // at EOF after materialisation and is never a control channel, so it must\",\n\t\"  // not be used to kill the child.\",\n\t'  process.stdout.on(\"error\", () => { try { child.kill(); } catch {} });',\n\t\"\",\n\t'  child.on(\"close\", (code, signal) => {',\n\t\"    settle();\",\n\t\"    const after = {};\",\n\t\"    for (const f of evidenceFiles) after[f] = sha256(path.resolve(envelope.launch.cwd, f));\",\n\t\"    const items = [];\",\n\t\"    for (const f of evidenceFiles) {\",\n\t'      items.push({ kind: \"file_hash\", path: f, sha256Before: before[f], sha256After: after[f] });',\n\t\"    }\",\n\t'    frame(\"REMOTE_EVIDENCE\", {',\n\t\"      executionId: envelope.executionId,\",\n\t\"      host: location.host,\",\n\t\"      user: location.user,\",\n\t\"      cwd: location.cwd,\",\n\t\"      items: items,\",\n\t\"    });\",\n\t'    frame(\"REMOTE_RESULT\", {',\n\t\"      executionId: envelope.executionId,\",\n\t\"      success: code === 0,\",\n\t'      summary: \"remote child exit \" + String(code),',\n\t\"    });\",\n\t'    frame(\"REMOTE_EXIT\", {',\n\t\"      executionId: envelope.executionId,\",\n\t\"      exitCode: code,\",\n\t\"      signal: signal || undefined,\",\n\t\"      cancelled: false,\",\n\t\"    });\",\n\t\"    process.exit(0);\",\n\t\"  });\",\n\t\"}\",\n\t\"\",\n\t\"main().catch((err) => {\",\n\t'  frame(\"REMOTE_ERROR\", { code: \"RUNNER_ERROR\", message: String((err && err.message) || err) });',\n\t'  frame(\"REMOTE_EXIT\", { executionId: \"(unknown)\", exitCode: 1, signal: undefined, cancelled: false });',\n\t\"  process.exit(0);\",\n\t\"});\",\n].join(\"\\n\");\n"]}