{"version":3,"file":"runtime-bundle.d.ts","sourceRoot":"","sources":["../../../src/core/remote-execution/runtime-bundle.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAOH,eAAO,MAAM,6BAA6B,GAAa,CAAC;AACxD,iDAAiD;AACjD,eAAO,MAAM,wBAAwB,GAAa,CAAC;AAEnD,MAAM,WAAW,iBAAiB;IACjC,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,EAAE,MAAM,CAAC;CACd;AAED,MAAM,WAAW,qBAAqB;IACrC,aAAa,EAAE,OAAO,6BAA6B,CAAC;IACpD,SAAS,EAAE,MAAM,CAAC;IAClB,YAAY,EAAE,MAAM,CAAC;IACrB,sBAAsB,EAAE,OAAO,wBAAwB,CAAC;IACxD,uEAAuE;IACvE,8BAA8B,EAAE,MAAM,CAAC;IACvC,eAAe,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACxC,iEAAiE;IACjE,WAAW,EAAE,MAAM,CAAC;IACpB,4EAA4E;IAC5E,cAAc,EAAE,iBAAiB,EAAE,CAAC;CACpC;AAED;;;;GAIG;AACH,MAAM,WAAW,qBAAqB;IACrC,aAAa,EAAE,OAAO,6BAA6B,CAAC;IACpD,YAAY,EAAE,MAAM,CAAC;IACrB,sBAAsB,EAAE,OAAO,wBAAwB,CAAC;IACxD,8BAA8B,EAAE,MAAM,CAAC;IACvC,eAAe,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CACxC;AAED,MAAM,WAAW,oBAAoB;IACpC,iDAAiD;IACjD,GAAG,EAAE,MAAM,CAAC;IACZ,8CAA8C;IAC9C,OAAO,EAAE,MAAM,CAAC;IAChB,+CAA+C;IAC/C,OAAO,CAAC,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,WAAW,kBAAkB;IAClC,WAAW,EAAE,MAAM,CAAC;IACpB,YAAY,EAAE,MAAM,CAAC;IACrB,SAAS,EAAE,MAAM,CAAC;IAClB,WAAW,EAAE,MAAM,CAAC;IACpB,YAAY,EAAE,MAAM,CAAC;IACrB,WAAW,EAAE,MAAM,CAAC;IACpB,QAAQ,EAAE,qBAAqB,CAAC;IAChC,QAAQ,EAAE,qBAAqB,CAAC;CAChC;AAED,MAAM,WAAW,yBAAyB;IACzC,YAAY,EAAE,MAAM,CAAC;IACrB,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,CAAC,EAAE,oBAAoB,EAAE,CAAC;IAClC,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,8BAA8B,CAAC,EAAE,MAAM,CAAC;CACxC;AA6BD;;;;GAIG;AACH,wBAAsB,kBAAkB,CAAC,OAAO,EAAE,yBAAyB,GAAG,OAAO,CAAC,kBAAkB,CAAC,CAkHxG","sourcesContent":["/**\n * Remote runtime bundle (3.0.0 cross-host bridge).\n *\n * Builds a reproducible, content-addressed, self-contained runtime bundle from\n * the CURRENT checkout. The bundle is a directory tree (compiled workspace dist\n * + hoisted third-party node_modules) tarballed into one payload. Identity is\n * the tarball SHA-256: `runtimeId = <commitShort>-<hashPrefix>`.\n *\n * The bundle is code/runtime only. It never contains Qwen weights, llama.cpp,\n * API keys, or Evidence content.\n */\n\nimport { execFileSync } from \"node:child_process\";\nimport { createHash } from \"node:crypto\";\nimport * as fsp from \"node:fs/promises\";\nimport * as path from \"node:path\";\n\nexport const RUNTIME_BUNDLE_SCHEMA_VERSION = 1 as const;\n/** Remote-runtime handshake protocol version. */\nexport const RUNTIME_PROTOCOL_VERSION = 1 as const;\n\nexport interface RuntimeBundleFile {\n\tpath: string;\n\tsha256: string;\n\tbytes: number;\n}\n\nexport interface RuntimeBundleManifest {\n\tschemaVersion: typeof RUNTIME_BUNDLE_SCHEMA_VERSION;\n\truntimeId: string;\n\tjensenCommit: string;\n\truntimeProtocolVersion: typeof RUNTIME_PROTOCOL_VERSION;\n\t/** Shared-inference admission protocol version this runtime speaks. */\n\tsharedInferenceProtocolVersion: number;\n\tpackageVersions: Record<string, string>;\n\t/** Content-address of the transfer payload (tarball SHA-256). */\n\ttarballHash: string;\n\t/** Audit list of the workspace dist files (not the third-party closure). */\n\tworkspaceFiles: RuntimeBundleFile[];\n}\n\n/**\n * Non-circular identity written INSIDE the tarball so a remote runtime can be\n * verified after extraction without a separate sidecar write (which races with\n * Windows file-handle release + Defender scanning).\n */\nexport interface RuntimeBundleIdentity {\n\tschemaVersion: typeof RUNTIME_BUNDLE_SCHEMA_VERSION;\n\tjensenCommit: string;\n\truntimeProtocolVersion: typeof RUNTIME_PROTOCOL_VERSION;\n\tsharedInferenceProtocolVersion: number;\n\tpackageVersions: Record<string, string>;\n}\n\nexport interface RuntimeBundlePackage {\n\t/** Directory name under packages/, e.g. \"ai\". */\n\tdir: string;\n\t/** npm name, e.g. \"@apholdings/jensen-ai\". */\n\tnpmName: string;\n\t/** package.json version, read during build. */\n\tversion?: string;\n}\n\nexport interface BuiltRuntimeBundle {\n\ttarballPath: string;\n\tmanifestPath: string;\n\truntimeId: string;\n\ttarballHash: string;\n\tjensenCommit: string;\n\tcommitShort: string;\n\tmanifest: RuntimeBundleManifest;\n\tidentity: RuntimeBundleIdentity;\n}\n\nexport interface BuildRuntimeBundleOptions {\n\tcheckoutRoot: string;\n\toutDir: string;\n\tpackages?: RuntimeBundlePackage[];\n\tjensenCommit?: string;\n\tsharedInferenceProtocolVersion?: number;\n}\n\nfunction sha256OfBuffer(buffer: Buffer): string {\n\treturn createHash(\"sha256\").update(buffer).digest(\"hex\");\n}\n\nasync function copyTree(src: string, dst: string): Promise<void> {\n\tawait fsp.cp(src, dst, { recursive: true, force: true, verbatimSymlinks: false });\n}\n\nasync function fileSha256(p: string): Promise<{ sha256: string; bytes: number }> {\n\tconst buffer = await fsp.readFile(p);\n\treturn { sha256: createHash(\"sha256\").update(buffer).digest(\"hex\"), bytes: buffer.length };\n}\n\nasync function listFilesRecursive(root: string): Promise<string[]> {\n\tconst out: string[] = [];\n\tasync function walk(dir: string): Promise<void> {\n\t\tconst entries = await fsp.readdir(dir, { withFileTypes: true });\n\t\tfor (const entry of entries) {\n\t\t\tconst full = path.join(dir, entry.name);\n\t\t\tif (entry.isDirectory()) await walk(full);\n\t\t\telse if (entry.isFile()) out.push(full);\n\t\t}\n\t}\n\tawait walk(root);\n\treturn out;\n}\n\n/**\n * Build the runtime bundle + sidecar manifest. The third-party `node_modules`\n * closure is copied whole (self-contained; no reliance on the target's global\n * install). The workspace `@apholdings` packages are the freshly-built dist.\n */\nexport async function buildRuntimeBundle(options: BuildRuntimeBundleOptions): Promise<BuiltRuntimeBundle> {\n\tconst checkoutRoot = path.resolve(options.checkoutRoot);\n\tconst packages: RuntimeBundlePackage[] = options.packages ?? [\n\t\t{ dir: \"ai\", npmName: \"@apholdings/jensen-ai\" },\n\t\t{ dir: \"agent\", npmName: \"@apholdings/jensen-agent-core\" },\n\t\t{ dir: \"coding-agent\", npmName: \"@apholdings/jensen-code\" },\n\t\t{ dir: \"tui\", npmName: \"@apholdings/jensen-tui\" },\n\t];\n\n\tlet jensenCommit = options.jensenCommit;\n\tif (!jensenCommit) {\n\t\tjensenCommit = execFileSync(\"git\", [\"rev-parse\", \"HEAD\"], { cwd: checkoutRoot, encoding: \"utf8\" }).trim();\n\t}\n\tconst commitShort = jensenCommit.slice(0, 12);\n\n\tconst staging = path.join(options.outDir, `bundle-staging-${process.pid}-${Date.now().toString(36)}`);\n\tconst stagingNodeModules = path.join(staging, \"node_modules\");\n\tawait fsp.mkdir(stagingNodeModules, { recursive: true });\n\n\tconst packageVersions: Record<string, string> = {};\n\tfor (const pkg of packages) {\n\t\tconst pkgJsonPath = path.join(checkoutRoot, \"packages\", pkg.dir, \"package.json\");\n\t\tconst pkgJson = JSON.parse(await fsp.readFile(pkgJsonPath, \"utf8\"));\n\t\tpackageVersions[pkg.npmName] = pkgJson.version ?? \"unknown\";\n\t\tconst dst = path.join(stagingNodeModules, ...pkg.npmName.split(\"/\"));\n\n\t\t// Copy the package's publish surface (its `files` entries) + package.json.\n\t\t// This includes root shim files (e.g. ai's `bedrock-provider.js`) that the\n\t\t// `exports` map references outside `dist`, which a dist-only copy would miss.\n\t\tconst files: string[] = Array.isArray(pkgJson.files) && pkgJson.files.length > 0 ? pkgJson.files : [\"dist\"];\n\t\tfor (const entry of files) {\n\t\t\t// Normalise npm glob entries (e.g. `dist/**/*`) to their base directory.\n\t\t\tconst base = entry.split(\"*\")[0]!.replace(/\\/+$/, \"\");\n\t\t\tconst src = path.join(checkoutRoot, \"packages\", pkg.dir, base);\n\t\t\tconst target = path.join(dst, base);\n\t\t\tconst stat = await fsp.lstat(src).catch(() => undefined);\n\t\t\tif (!stat) continue;\n\t\t\tif (stat.isDirectory()) await copyTree(src, target);\n\t\t\telse {\n\t\t\t\tawait fsp.mkdir(path.dirname(target), { recursive: true });\n\t\t\t\tawait fsp.copyFile(src, target);\n\t\t\t}\n\t\t}\n\t\tawait fsp.copyFile(pkgJsonPath, path.join(dst, \"package.json\"));\n\n\t\t// Nested (non-hoisted) dependencies live under the workspace package's own\n\t\t// `node_modules`. Copy them so resolution matches the checkout exactly.\n\t\tconst nested = path.join(checkoutRoot, \"packages\", pkg.dir, \"node_modules\");\n\t\tconst nestedStat = await fsp.lstat(nested).catch(() => undefined);\n\t\tif (nestedStat?.isDirectory()) await copyTree(nested, path.join(dst, \"node_modules\"));\n\t}\n\n\t// Third-party closure (exclude workspace @apholdings symlinks; they are the\n\t// real dist we just materialised above).\n\tconst thirdParty = path.join(checkoutRoot, \"node_modules\");\n\tconst entries = await fsp.readdir(thirdParty, { withFileTypes: true });\n\tfor (const entry of entries) {\n\t\tif (entry.name === \"@apholdings\") continue;\n\t\tawait copyTree(path.join(thirdParty, entry.name), path.join(stagingNodeModules, entry.name));\n\t}\n\n\t// Non-circular identity written INSIDE the tarball (remote verification\n\t// without a sidecar write).\n\tconst identity: RuntimeBundleIdentity = {\n\t\tschemaVersion: RUNTIME_BUNDLE_SCHEMA_VERSION,\n\t\tjensenCommit,\n\t\truntimeProtocolVersion: RUNTIME_PROTOCOL_VERSION,\n\t\tsharedInferenceProtocolVersion: options.sharedInferenceProtocolVersion ?? 1,\n\t\tpackageVersions,\n\t};\n\tawait fsp.writeFile(path.join(staging, \"identity.json\"), JSON.stringify(identity, null, 2));\n\n\t// Tarball the staging directory, then content-address it.\n\tconst tmpTarball = path.join(options.outDir, `bundle-${process.pid}-${Date.now().toString(36)}.tgz`);\n\texecFileSync(\"tar\", [\"-czf\", tmpTarball, \"-C\", staging, \".\"], { stdio: \"inherit\" });\n\tconst tarballBuffer = await fsp.readFile(tmpTarball);\n\tconst tarballHash = sha256OfBuffer(tarballBuffer);\n\tconst runtimeId = `${commitShort}-${tarballHash.slice(0, 12)}`;\n\n\tconst tarballPath = path.join(options.outDir, `${runtimeId}.tgz`);\n\tif (path.resolve(tarballPath) !== path.resolve(tmpTarball)) {\n\t\tawait fsp.rename(tmpTarball, tarballPath);\n\t}\n\n\t// Audit the workspace dist files (identity audit, not the identity itself).\n\tconst workspaceFiles: RuntimeBundleFile[] = [];\n\tfor (const pkg of packages) {\n\t\tconst distRoot = path.join(stagingNodeModules, ...pkg.npmName.split(\"/\"), \"dist\");\n\t\tconst files = await listFilesRecursive(distRoot);\n\t\tfor (const file of files) {\n\t\t\tconst rel = path.relative(staging, file).replace(/\\\\/g, \"/\");\n\t\t\tconst { sha256, bytes } = await fileSha256(file);\n\t\t\tworkspaceFiles.push({ path: rel, sha256, bytes });\n\t\t}\n\t}\n\tworkspaceFiles.sort((a, b) => a.path.localeCompare(b.path));\n\n\tconst manifest: RuntimeBundleManifest = {\n\t\tschemaVersion: RUNTIME_BUNDLE_SCHEMA_VERSION,\n\t\truntimeId,\n\t\tjensenCommit,\n\t\truntimeProtocolVersion: RUNTIME_PROTOCOL_VERSION,\n\t\tsharedInferenceProtocolVersion: options.sharedInferenceProtocolVersion ?? 1,\n\t\tpackageVersions,\n\t\ttarballHash,\n\t\tworkspaceFiles,\n\t};\n\tconst manifestPath = path.join(options.outDir, `${runtimeId}.manifest.json`);\n\tawait fsp.writeFile(manifestPath, JSON.stringify(manifest, null, 2));\n\n\t// Clean up the staging directory (the tarball + manifest are authoritative).\n\tawait fsp.rm(staging, { recursive: true, force: true });\n\n\treturn { tarballPath, manifestPath, runtimeId, tarballHash, jensenCommit, commitShort, manifest, identity };\n}\n"]}