@rallycry/conveyor-agent 10.13.64 → 10.13.66

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.
Files changed (41) hide show
  1. package/dist/{boot-7OPX55AO.js → boot-UUPTBQ6R.js} +7 -8
  2. package/dist/{chunk-JIGG755T.js → chunk-6Q6LQBWO.js} +0 -1
  3. package/dist/{chunk-ULS4QPRE.js → chunk-6W6UZ4SJ.js} +0 -1
  4. package/dist/{chunk-YSALHJTS.js → chunk-75FUOUYX.js} +515 -214
  5. package/dist/{chunk-AXA55U4Z.js → chunk-E2SHIH6Y.js} +3 -4
  6. package/dist/{chunk-QOJTJCYZ.js → chunk-EXQ6AHOY.js} +2 -3
  7. package/dist/{chunk-5OQQSDVT.js → chunk-IA45XHOA.js} +0 -1
  8. package/dist/{chunk-QZN6HVUY.js → chunk-KG4ORL3Y.js} +1 -2
  9. package/dist/{chunk-4VUQ2NPF.js → chunk-KMB3BU4S.js} +0 -1
  10. package/dist/{chunk-NAK6FY5U.js → chunk-LE6ZUDZT.js} +37 -6
  11. package/dist/{chunk-6GS5ADIY.js → chunk-R3FDJQL6.js} +0 -1
  12. package/dist/cli.js +35 -43
  13. package/dist/{client-LRVVHTNG.js → client-IG6C5F2G.js} +3 -4
  14. package/dist/heartbeat-worker.js +1 -2
  15. package/dist/index.d.ts +95 -13
  16. package/dist/index.js +7 -8
  17. package/dist/{mode-6RL3SVMV.js → mode-ZJSOSLGU.js} +1 -2
  18. package/dist/{oom-watchdog-U7JERHA2.js → oom-watchdog-PAC5OJJG.js} +1 -2
  19. package/dist/{protocol-QLVS5W6O.js → protocol-QBCYO4GI.js} +1 -2
  20. package/dist/server-US2DDQSW.js +9 -0
  21. package/package.json +1 -1
  22. package/dist/boot-7OPX55AO.js.map +0 -1
  23. package/dist/chunk-4VUQ2NPF.js.map +0 -1
  24. package/dist/chunk-5OQQSDVT.js.map +0 -1
  25. package/dist/chunk-6GS5ADIY.js.map +0 -1
  26. package/dist/chunk-AXA55U4Z.js.map +0 -1
  27. package/dist/chunk-JIGG755T.js.map +0 -1
  28. package/dist/chunk-NAK6FY5U.js.map +0 -1
  29. package/dist/chunk-QOJTJCYZ.js.map +0 -1
  30. package/dist/chunk-QZN6HVUY.js.map +0 -1
  31. package/dist/chunk-ULS4QPRE.js.map +0 -1
  32. package/dist/chunk-YSALHJTS.js.map +0 -1
  33. package/dist/cli.js.map +0 -1
  34. package/dist/client-LRVVHTNG.js.map +0 -1
  35. package/dist/heartbeat-worker.js.map +0 -1
  36. package/dist/index.js.map +0 -1
  37. package/dist/mode-6RL3SVMV.js.map +0 -1
  38. package/dist/oom-watchdog-U7JERHA2.js.map +0 -1
  39. package/dist/protocol-QLVS5W6O.js.map +0 -1
  40. package/dist/server-ZACB5T5S.js +0 -10
  41. package/dist/server-ZACB5T5S.js.map +0 -1
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/boot/index.ts","../src/boot/bundle.ts","../src/boot/graphify.ts","../src/boot/logger.ts","../src/boot/types.ts","../src/boot/pipeline.ts","../src/boot/poll.ts","../src/boot/exec.ts","../src/boot/supervisor.ts","../src/boot/crash-report.ts","../src/boot/user-home.ts","../src/boot/sshd.ts","../src/boot/workspace-extras.ts","../src/boot/workbench-boot-deps.ts","../src/boot/workbench-boot.ts"],"sourcesContent":["/* oxlint-disable import/max-dependencies -- boot composition module: it wires the Task 1-8 pieces (poll/unpack/supervisor/user-home/workbench) into the two role pipelines; splitting the wiring would scatter the boot sequence */\n/**\n * `conveyor-agent boot` — the vnext pod boot entry. Composes the tested Task\n * 1-8 pieces into two role pipelines (agent / workbench) and the legacy-launch\n * tombstone predicate the CLI guards on. The pod runs the BAKED agent — there\n * is no runtime self-update (removed after it caused the 2026-07-19 eviction\n * outage); a new agent version ships via publish → auto-rebake.\n *\n * The bash entrypoint (runtime/entrypoint.sh) is retired: a split-only pod\n * runs `conveyor-agent boot` in each container, keyed by CONVEYOR_CONTAINER_ROLE.\n * Every network/spawn/fs seam is injected via `BootDeps` (production defaults in\n * `defaultBootDeps`) so the dispatch tests run offline.\n */\nimport { spawn } from \"node:child_process\";\nimport { statSync } from \"node:fs\";\nimport { hostname as osHostname } from \"node:os\";\nimport { readAgentVersion } from \"../runner/session-runner-helpers.js\";\nimport { workbenchPort } from \"../workbench/mode.js\";\nimport { DEFAULT_WORKBENCH_PORT } from \"../workbench/protocol.js\";\nimport { buildChildEnv, decodeJwtClaims } from \"./bundle.js\";\nimport { computeGraphifyEnv } from \"./graphify.js\";\nimport { createBootLogger } from \"./logger.js\";\nimport { runPipeline } from \"./pipeline.js\";\nimport { pollBootstrapBundle } from \"./poll.js\";\nimport { defaultExec, type ExecDeps } from \"./exec.js\";\nimport { superviseRunner } from \"./supervisor.js\";\nimport type { BootContext, BootLogger, BootRole, BootStep } from \"./types.js\";\nimport { reclaimHomeOwnership, wireUserHome } from \"./user-home.js\";\nimport { runWorkbenchBoot } from \"./workbench-boot.js\";\nimport { defaultWorkbenchBootDeps, type WorkbenchBootDeps } from \"./workbench-boot-deps.js\";\n\nconst HOME_DIR = \"/home/conveyor\";\nconst USER_HOME_MOUNT = \"/mnt/conveyor-users\";\n\n// Leaf module so cli.ts's guard doesn't eagerly load the whole boot graph.\nexport { isLegacyEntrypointLaunch, TOMBSTONE_MESSAGE } from \"./tombstone.js\";\n\n/** The single injectable seam bundle — network (fetch/poll), spawn, exec, and\n * identity — so dispatch/pipeline composition runs without side effects. */\nexport interface BootDeps {\n fetchFn: typeof fetch;\n sleepFn: (ms: number) => Promise<void>;\n spawnFn: typeof spawn;\n exec: ExecDeps[\"exec\"];\n dirExists: (path: string) => boolean;\n hostname: string;\n log: BootLogger;\n}\n\nexport function defaultBootDeps(): BootDeps {\n return {\n fetchFn: fetch,\n sleepFn: (ms) =>\n new Promise((resolve) => {\n setTimeout(resolve, ms);\n }),\n spawnFn: spawn,\n exec: defaultExec,\n dirExists: (path) => {\n try {\n return statSync(path).isDirectory();\n } catch {\n return false;\n }\n },\n hostname: osHostname(),\n log: createBootLogger(),\n };\n}\n\nfunction errMsg(err: unknown): string {\n return err instanceof Error ? err.message : String(err);\n}\n\n/** Resolves when `signal` aborts (or is already aborted); never rejects. */\nfunction whenAborted(signal: AbortSignal): Promise<void> {\n return new Promise((resolve) => {\n if (signal.aborted) {\n resolve();\n return;\n }\n signal.addEventListener(\"abort\", () => resolve(), { once: true });\n });\n}\n\n/**\n * The agent container's boot: poll bootstrap → unpack the bundle into the\n * runner's child env (with graphify env merged) → reclaim home ownership\n * (best-effort) → wire the persistent user-home. superviseRunner runs after\n * the pipeline — it IS the container's foreground process, not a step. Each\n * post-poll step no-ops when the bundle is absent (SIGTERM aborted standby).\n */\nexport function agentBootSteps(\n deps: BootDeps = defaultBootDeps(),\n signal: AbortSignal = new AbortController().signal,\n): BootStep[] {\n // Started alongside the bootstrap poll (it only touches local fs ownership,\n // ~0.7s of chown) so the two overlap; the reclaimHome step awaits it. Runs\n // even when standby is SIGTERM-aborted — a chown of home is harmless.\n let reclaim: Promise<void> | undefined;\n return [\n {\n name: \"pollBootstrap\",\n critical: true,\n run: async (ctx) => {\n reclaim = reclaimHomeOwnership(HOME_DIR, deps.exec, ctx.log).catch(() => undefined);\n const bundle = await pollBootstrapBundle(ctx.apiUrl, ctx.bootstrapToken, {\n fetchFn: deps.fetchFn,\n sleepFn: deps.sleepFn,\n log: ctx.log,\n signal,\n });\n if (bundle) ctx.bundle = bundle;\n },\n },\n {\n name: \"unpackBundle\",\n critical: true,\n run: (ctx) => {\n if (ctx.bundle) {\n const claims = decodeJwtClaims(ctx.bundle.sessionJwt);\n ctx.claims = claims;\n const childEnv = buildChildEnv({\n base: process.env,\n bundle: ctx.bundle,\n claims,\n hostname: ctx.hostname,\n log: ctx.log,\n });\n const graphifyEnv = computeGraphifyEnv(\n process.env,\n ctx.bundle.gitPlan.repoName,\n deps.dirExists,\n );\n ctx.childEnv = { ...childEnv, ...graphifyEnv };\n }\n return Promise.resolve();\n },\n },\n {\n name: \"reclaimHome\",\n critical: false,\n run: async (ctx) => {\n if (!ctx.bundle) return;\n // Already in flight since the poll started — this step is just the\n // barrier so wireUserHome never runs on unreclaimed ownership.\n await (reclaim ?? reclaimHomeOwnership(HOME_DIR, deps.exec, ctx.log));\n },\n },\n {\n name: \"wireUserHome\",\n critical: true,\n run: (ctx) => {\n if (ctx.bundle) {\n wireUserHome({\n home: HOME_DIR,\n userHomeMount: USER_HOME_MOUNT,\n userId: ctx.childEnv?.CONVEYOR_USER_ID,\n projectId: ctx.childEnv?.CONVEYOR_PROJECT_ID ?? ctx.childEnv?.PROJECT_ID,\n sharedDir: process.env.CONVEYOR_SHARED_DIR,\n log: ctx.log,\n });\n }\n return Promise.resolve();\n },\n },\n ];\n}\n\n/**\n * Declarative manifest of the workbench container's critical boot path\n * (poll → unpack → startDaemon), pinned by boot-dispatch.test.ts. The PRODUCTION\n * executor is `runWorkbenchBoot` (Task 7): it runs these three critical\n * operations AND the post-listen deferred jobs (ensure /workspaces/repo, git\n * prep, user-home, sshd, reclaim) that a flat pipeline cannot express. This\n * function documents the contract those three steps must honor; the run bodies\n * delegate to the same deps seam so it stays a faithful (if partial) executor.\n */\nexport function workbenchBootSteps(\n deps: WorkbenchBootDeps = defaultWorkbenchBootDeps(),\n): BootStep[] {\n return [\n {\n name: \"pollBootstrap\",\n critical: true,\n run: async (ctx) => {\n const bundle = await deps.pollBundle(ctx);\n if (bundle) ctx.bundle = bundle;\n },\n },\n {\n name: \"unpackBundle\",\n critical: true,\n run: (ctx) => {\n if (ctx.bundle) ctx.childEnv = deps.unpack(ctx, ctx.bundle);\n return Promise.resolve();\n },\n },\n {\n name: \"startDaemon\",\n critical: true,\n run: async (ctx) => {\n await deps.startDaemon({\n port: workbenchPort() ?? DEFAULT_WORKBENCH_PORT,\n token: ctx.bootstrapToken,\n version: readAgentVersion() ?? \"unknown\",\n // The manifest has no GitPrepJob, so it must never claim readiness —\n // \"pending\" is the safe answer if anyone ever executes these steps\n // directly instead of via runWorkbenchBoot (which serves live state).\n getGitStatus: () => ({ t: \"gitStatus\", state: \"pending\" }),\n });\n },\n },\n ];\n}\n\nasync function runAgentRole(\n ctx: BootContext,\n deps: BootDeps,\n signal: AbortSignal,\n): Promise<number> {\n try {\n await runPipeline(agentBootSteps(deps, signal), ctx);\n } catch (err) {\n // A critical step (incl. the poll's 401 BootstrapUnauthorizedError, wrapped\n // by runPipeline) — the pipeline already logged FATAL. Pod identity invalid.\n ctx.log.error(`[boot] agent boot failed: ${errMsg(err)}`);\n return 1;\n }\n if (signal.aborted || !ctx.bundle) {\n ctx.log.info(\"[boot] SIGTERM during standby — exiting.\");\n return 0;\n }\n // The pod runs whatever agent version was BAKED into the image — there is no\n // runtime self-update. A new agent ships via publish → auto-rebake → new\n // pods; a booting pod never downloads or version-checks the agent (the old\n // npm-preflight self-update caused the 2026-07-19 ephemeral-eviction outage\n // and is gone).\n // The agent container's /workspaces/repo is a dead overlay (entrypoint.sh:907)\n // — every workspace op is proxied to the workbench daemon; run from ~.\n process.chdir(HOME_DIR);\n // superviseRunner owns LAUNCH_MARKER (touched post-bind, before first spawn)\n // and the SIGTERM drain once the runner is live.\n return superviseRunner({\n childEnv: ctx.childEnv ?? {},\n cwd: HOME_DIR,\n apiUrl: ctx.apiUrl,\n bootstrapToken: ctx.bootstrapToken,\n log: ctx.log,\n spawnFn: deps.spawnFn,\n signal,\n });\n}\n\nasync function runWorkbenchRole(\n ctx: BootContext,\n deps: BootDeps,\n signal: AbortSignal,\n): Promise<number> {\n // The daemon runs the BAKED agent — no runtime self-update (see runAgentRole).\n // runWorkbenchBoot parks forever on success; the race resolves only when the\n // signal aborts (SIGTERM), which is our clean exit. Its internal poll gets the\n // same signal so a standby SIGTERM aborts the in-flight fetch too.\n try {\n await Promise.race([\n runWorkbenchBoot(ctx, {\n pollBundle: (workbenchCtx) =>\n pollBootstrapBundle(workbenchCtx.apiUrl, workbenchCtx.bootstrapToken, {\n fetchFn: deps.fetchFn,\n sleepFn: deps.sleepFn,\n log: workbenchCtx.log,\n signal,\n }),\n }),\n whenAborted(signal),\n ]);\n } catch (err) {\n ctx.log.error(`[boot] workbench boot failed: ${errMsg(err)}`);\n return 1;\n }\n // ctx.bundle distinguishes a serving workbench from one still in standby —\n // pod-death forensics reads this line.\n ctx.log.info(\n ctx.bundle\n ? \"[boot] SIGTERM — workbench shutting down.\"\n : \"[boot] SIGTERM during standby — exiting.\",\n );\n return 0;\n}\n\n/**\n * The `conveyor-agent boot` process. Resolves the pod exit code (cli.ts does\n * `process.exit(await runBoot(...))`).\n *\n * - `--preflight-update` → NO-OP (exit 0). There is no runtime self-update any\n * more: the pod runs the baked agent. Kept as an inert accepted flag only so\n * an OLD baked shim that still invokes it can't fail the boot.\n * - else: CONVEYOR_CONTAINER_ROLE selects the pipeline (fatal-loud on anything\n * but agent/workbench — vnext pods are split-only), after asserting the two\n * required identity env vars are present.\n */\nexport async function runBoot(argv: string[], deps: BootDeps = defaultBootDeps()): Promise<number> {\n const { log } = deps;\n\n if (argv.includes(\"--preflight-update\")) {\n log.info(\"[boot] --preflight-update is a no-op (baked agent, no runtime self-update)\");\n return 0;\n }\n\n const role = process.env.CONVEYOR_CONTAINER_ROLE;\n if (role !== \"agent\" && role !== \"workbench\") {\n log.error(\"[boot] FATAL missing/invalid CONVEYOR_CONTAINER_ROLE — vnext pods are split-only\");\n return 1;\n }\n\n const apiUrl = process.env.CONVEYOR_API_URL;\n const bootstrapToken = process.env.POD_BOOTSTRAP_TOKEN;\n if (!apiUrl || !bootstrapToken) {\n // Mirrors entrypoint.sh:161-162 — no identity, nothing to poll for.\n log.error(\"[boot] FATAL missing required env: CONVEYOR_API_URL + POD_BOOTSTRAP_TOKEN\");\n return 1;\n }\n\n // One controller drives every abort path: standby poll (resolves null →\n // exit 0), the workbench park race, and the live runner drain.\n const controller = new AbortController();\n const abort = (): void => controller.abort();\n process.on(\"SIGTERM\", abort);\n process.on(\"SIGINT\", abort);\n\n const ctx: BootContext = {\n role: role as BootRole,\n apiUrl,\n bootstrapToken,\n hostname: deps.hostname,\n log,\n };\n\n const code = await (role === \"workbench\"\n ? runWorkbenchRole(ctx, deps, controller.signal)\n : runAgentRole(ctx, deps, controller.signal));\n // Settle any concurrent work still in flight (e.g. the workbench's poll\n // promise if the role returned via SIGTERM before it settled) — nothing may\n // outlive the role's verdict.\n controller.abort();\n return code;\n}\n","import type { BootstrapBundle, GitPlan } from \"../setup/bootstrap-bundle-types.js\";\nimport type { BootLogger, SessionClaims } from \"./types.js\";\n\n/**\n * Ports entrypoint.sh:222-344 (bundle field extraction, JWT claim decode,\n * mode mapping, env injection + denylist).\n *\n * DELIBERATE DEVIATION from the bash on empty values: the literal entrypoint\n * exported most credential/identity fields UNCONDITIONALLY — `export\n * FOO=$(... | jq -r '.f // empty')` yields an empty-string-PRESENT variable\n * when the field is missing (jq's `// empty` only empties the substitution,\n * the `export` still happens). That covered CONVEYOR_TASK_TOKEN,\n * CONVEYOR_GITHUB_TOKEN/GH_TOKEN, ANTHROPIC_API_KEY,\n * CLOUDSDK_AUTH_ACCESS_TOKEN, REPO_OWNER/REPO_NAME/BRANCH,\n * CONVEYOR_SESSION_ID, and CONVEYOR_WORKSPACE_ID; only CONVEYOR_TASK_ID and\n * CONVEYOR_PROJECT_ID were truly absent-on-empty in bash too (guarded\n * export / explicit unset). vnext instead omits ANY empty-valued key\n * uniformly (`setIf` in buildChildEnv): absent is the safer contract —\n * presence-keyed consumers (gh, anthropic tooling) misread an empty-present\n * GH_TOKEN/ANTHROPIC_API_KEY as \"credentials configured\" — and every known\n * in-repo consumer checks truthiness, so the change is otherwise inert.\n *\n * `parseBundle` throws on a genuinely missing/wrong-type required field (so\n * boot fails loud instead of shipping \"null\" into a git URL or env var) and\n * normalizes `envVars: null` to `{}`. Unknown top-level keys are preserved\n * on the returned object — the bundle wire contract can grow fields this\n * package doesn't know about yet.\n */\nfunction requireString(source: Record<string, unknown>, field: string): string {\n const value = source[field];\n if (typeof value !== \"string\") {\n throw new Error(`bundle missing field: ${field}`);\n }\n return value;\n}\n\nfunction parseGitPlan(raw: unknown): GitPlan {\n if (typeof raw !== \"object\" || raw === null) {\n throw new Error(\"bundle missing field: gitPlan\");\n }\n const obj = raw as Record<string, unknown>;\n const provider = obj.provider === \"forgejo\" ? \"forgejo\" : \"github\";\n const repoOwner = requireString(obj, \"repoOwner\");\n const repoName = requireString(obj, \"repoName\");\n const plan: GitPlan = {\n provider,\n cloneUrl:\n typeof obj.cloneUrl === \"string\"\n ? obj.cloneUrl\n : `https://github.com/${repoOwner}/${repoName}.git`,\n repoOwner,\n repoName,\n branch: requireString(obj, \"branch\"),\n baseBranch: requireString(obj, \"baseBranch\"),\n };\n if (typeof obj.checkoutRef === \"string\") {\n plan.checkoutRef = obj.checkoutRef;\n }\n return plan;\n}\n\nexport function parseBundle(text: string): BootstrapBundle {\n const raw = JSON.parse(text) as Record<string, unknown>;\n const envVarsRaw = raw.envVars;\n const envVars: Record<string, string> =\n envVarsRaw !== null && envVarsRaw !== undefined && typeof envVarsRaw === \"object\"\n ? (envVarsRaw as Record<string, string>)\n : {};\n const githubToken = typeof raw.githubToken === \"string\" ? raw.githubToken : undefined;\n const credentialRaw = raw.gitCredential;\n const gitCredential =\n typeof credentialRaw === \"object\" && credentialRaw !== null\n ? {\n username: requireString(credentialRaw as Record<string, unknown>, \"username\"),\n secret: requireString(credentialRaw as Record<string, unknown>, \"secret\"),\n }\n : { username: \"x-access-token\", secret: githubToken ?? \"\" };\n if (!gitCredential.secret) throw new Error(\"bundle missing field: gitCredential\");\n const typed = {\n sessionJwt: requireString(raw, \"sessionJwt\"),\n ...(githubToken ? { githubToken } : {}),\n gitCredential,\n anthropicKey: requireString(raw, \"anthropicKey\"),\n gcpToken: requireString(raw, \"gcpToken\"),\n envVars,\n gitPlan: parseGitPlan(raw.gitPlan),\n };\n return { ...raw, ...typed } as BootstrapBundle;\n}\n\n/**\n * Decodes the non-secret session identity claims out of the task JWT's\n * middle segment — base64url alphabet (`-`/`_`), padding restored before\n * decode. Ports the inline `node -e` one-liner at entrypoint.sh:236. Any\n * failure (too few segments, invalid base64, non-JSON payload) yields `{}`\n * rather than throwing: an unreadable JWT degrades to \"no claims\", it never\n * aborts boot — the bootstrap poll step already validated the token against\n * the API before this runs.\n */\nexport function decodeJwtClaims(jwt: string): SessionClaims {\n try {\n const segment = jwt.split(\".\")[1] ?? \"\";\n const normalized = segment.replace(/-/g, \"+\").replace(/_/g, \"/\");\n const padded = normalized + \"=\".repeat((4 - (normalized.length % 4)) % 4);\n const json = Buffer.from(padded, \"base64\").toString(\"utf8\");\n const parsed: unknown = JSON.parse(json);\n if (typeof parsed !== \"object\" || parsed === null) {\n return {};\n }\n return parsed as SessionClaims;\n } catch {\n return {};\n }\n}\n\n// Mode mapping, comments carried from entrypoint.sh:261-287. Order is load-bearing —\n// a wrong mapping here crash-looped pods every 10s (agent CLI exits \"Invalid\n// CONVEYOR_MODE\"). Do not reorder without re-reading the history in that range.\nexport function resolveModeEnv(claims: SessionClaims): Record<string, string> {\n // Review sessions are keyed on the session MODE claim, not role; the runner\n // mode is spelled \"code-review\" — bare \"review\" crash-loops the CLI.\n if (claims.mode === \"review\") return { CONVEYOR_MODE: \"code-review\" };\n if (claims.mode === \"pack\") return { CONVEYOR_MODE: \"pack\" };\n // adhoc is also task-less with a projectId — it must win over the pm branch.\n if (claims.mode === \"adhoc\") return { CONVEYOR_MODE: \"adhoc\" };\n if (!claims.taskId && claims.projectId) return { CONVEYOR_MODE: \"pm\" };\n return {};\n}\n\n// A bundle key that isn't a valid shell/env identifier would make `export`\n// eval arbitrary content in the old bash world; the vnext child-env object\n// has no such injection risk, but we keep the same validation so a garbage\n// key can never silently shadow something unexpected. A handful of names\n// would hijack the boot itself (PATH swaps every binary below; LD_PRELOAD\n// injects code into them) — denylisted regardless of validity.\nexport const ENV_KEY_DENYLIST: ReadonlySet<string> = new Set([\n \"PATH\",\n \"HOME\",\n \"LD_PRELOAD\",\n \"SHELL\",\n]);\nconst IDENTIFIER_RE = /^[A-Za-z_][A-Za-z0-9_]*$/;\n\nexport interface BuildChildEnvOptions {\n base: NodeJS.ProcessEnv;\n bundle: BootstrapBundle;\n claims: SessionClaims;\n hostname: string;\n log: BootLogger;\n /** default \"/workspaces/repo\" */\n workspaceDir?: string;\n}\n\nexport function buildChildEnv(opts: BuildChildEnvOptions): Record<string, string> {\n const { base, bundle, claims, hostname, log } = opts;\n const child: Record<string, string> = {};\n for (const [k, v] of Object.entries(base)) {\n if (typeof v === \"string\") child[k] = v;\n }\n for (const [key, value] of Object.entries(bundle.envVars ?? {})) {\n if (!IDENTIFIER_RE.test(key)) {\n log.warn(`[boot] WARN skipping invalid env key from bundle: '${key}'`);\n continue;\n }\n if (ENV_KEY_DENYLIST.has(key)) {\n log.warn(`[boot] WARN skipping denylisted env key from bundle: '${key}'`);\n continue;\n }\n child[key] = value;\n }\n const setIf = (k: string, v: string | undefined): void => {\n if (v) child[k] = v;\n };\n setIf(\"CONVEYOR_TASK_TOKEN\", bundle.sessionJwt);\n setIf(\"CONVEYOR_GIT_PROVIDER\", bundle.gitPlan.provider);\n setIf(\"CONVEYOR_GIT_CLONE_URL\", bundle.gitPlan.cloneUrl);\n setIf(\"CONVEYOR_GIT_USERNAME\", bundle.gitCredential.username);\n setIf(\"CONVEYOR_GIT_SECRET\", bundle.gitCredential.secret);\n if (bundle.gitPlan.provider === \"github\") {\n setIf(\"CONVEYOR_GITHUB_TOKEN\", bundle.githubToken ?? bundle.gitCredential.secret);\n setIf(\"GH_TOKEN\", bundle.githubToken ?? bundle.gitCredential.secret);\n }\n setIf(\"ANTHROPIC_API_KEY\", bundle.anthropicKey);\n setIf(\"CLOUDSDK_AUTH_ACCESS_TOKEN\", bundle.gcpToken);\n setIf(\"REPO_OWNER\", bundle.gitPlan.repoOwner);\n setIf(\"REPO_NAME\", bundle.gitPlan.repoName);\n setIf(\"BRANCH\", bundle.gitPlan.branch);\n // CONVEYOR_TASK_ID is absent (never empty) for task-less sessions.\n setIf(\"CONVEYOR_TASK_ID\", claims.taskId);\n setIf(\"CONVEYOR_PROJECT_ID\", claims.projectId);\n setIf(\"CONVEYOR_SESSION_ID\", claims.sessionId);\n setIf(\"CONVEYOR_WORKSPACE_ID\", claims.workspaceId);\n Object.assign(child, resolveModeEnv(claims));\n child.CLAUDESPACE_NAME = hostname;\n child.CONVEYOR_WORKSPACE = opts.workspaceDir ?? \"/workspaces/repo\";\n child.CONVEYOR_BOOT_VNEXT = \"1\";\n // POD_BOOTSTRAP_TOKEN and CONVEYOR_API_URL are never touched here — they pass\n // through unchanged from `base` (copied above). POD_BOOTSTRAP_TOKEN is the\n // credential-refresh key: the agent re-polls with it to swap in a fresh\n // githubToken/sessionJwt in place (entrypoint.sh:338-340).\n return child;\n}\n","/**\n * Port of entrypoint.sh:494-519 (`configure_graphify_env`). A locally\n * published graph lives in the shared GCS-FUSE user-home bucket at a fixed\n * mount point, under `users/_shared/graphify/<repo>/latest` — this computes\n * the env var contract the rest of boot (and the agent process) consumes,\n * without touching the filesystem itself (that's `dirExists`, injected so\n * this stays pure/testable).\n */\nconst USER_HOME_MOUNT = \"/mnt/conveyor-users\";\n\nexport interface GraphifyEnvInput {\n CONVEYOR_GRAPHIFY_DISABLE?: string;\n CONVEYOR_GRAPHIFY_SLUG?: string;\n CONVEYOR_GRAPHIFY_SHARED_ROOT?: string;\n CONVEYOR_GRAPHIFY_DIR?: string;\n CONVEYOR_GRAPHIFY_GRAPH?: string;\n}\n\nexport function computeGraphifyEnv(\n env: GraphifyEnvInput,\n repoName: string | undefined,\n dirExists: (p: string) => boolean,\n): Record<string, string> {\n if (env.CONVEYOR_GRAPHIFY_DISABLE === \"1\") {\n return {};\n }\n\n const slug = env.CONVEYOR_GRAPHIFY_SLUG || repoName;\n if (!slug) {\n return {};\n }\n\n const primaryRoot = `${USER_HOME_MOUNT}/users/_shared/graphify`;\n const legacyRoot = `${USER_HOME_MOUNT}/_shared/graphify`;\n let sharedRoot = env.CONVEYOR_GRAPHIFY_SHARED_ROOT;\n if (!sharedRoot) {\n // Primary root wins whenever it exists (or legacy doesn't) — legacy is\n // only chosen when primary is missing AND legacy is actually present.\n sharedRoot = dirExists(primaryRoot) || !dirExists(legacyRoot) ? primaryRoot : legacyRoot;\n }\n\n const dir = env.CONVEYOR_GRAPHIFY_DIR || `${sharedRoot}/${slug}/latest`;\n const graph = env.CONVEYOR_GRAPHIFY_GRAPH || `${dir}/graph.json`;\n\n return {\n CONVEYOR_GRAPHIFY_SLUG: slug,\n CONVEYOR_GRAPHIFY_SHARED_ROOT: sharedRoot,\n CONVEYOR_GRAPHIFY_DIR: dir,\n CONVEYOR_GRAPHIFY_GRAPH: graph,\n };\n}\n","import { appendFileSync } from \"node:fs\";\nimport type { BootLogger } from \"./types.js\";\n\nexport const BOOTSTRAP_LOG_PATH = \"/tmp/claudespace-bootstrap.log\";\n\nexport function createBootLogger(filePath: string | undefined = BOOTSTRAP_LOG_PATH): BootLogger {\n const emit = (line: string): void => {\n process.stdout.write(`${line}\\n`);\n if (filePath) {\n try {\n appendFileSync(filePath, `${line}\\n`);\n } catch {\n // The file sink is best-effort; stdout is the durable channel (kubectl logs).\n }\n }\n };\n return { info: emit, warn: emit, error: emit };\n}\n","import type { BootstrapBundle } from \"../setup/bootstrap-bundle-types.js\";\n\nexport type BootRole = \"agent\" | \"workbench\";\n\nexport interface BootLogger {\n info(msg: string): void;\n warn(msg: string): void;\n error(msg: string): void;\n}\n\nexport interface SessionClaims {\n taskId?: string;\n projectId?: string;\n sessionId?: string;\n workspaceId?: string;\n mode?: string;\n}\n\nexport interface BootContext {\n role: BootRole;\n apiUrl: string;\n bootstrapToken: string;\n hostname: string;\n log: BootLogger;\n /** Filled by the poll step. */\n bundle?: BootstrapBundle;\n /** Filled by the unpack step. */\n claims?: SessionClaims;\n /** Filled by the unpack step — the env handed to children (runner/daemon jobs). */\n childEnv?: Record<string, string>;\n}\n\nexport interface BootStep {\n name: string;\n /** true → a throw aborts boot (fatal-loud). false → degrade-and-continue (warn). */\n critical: boolean;\n run(ctx: BootContext): Promise<void>;\n}\n\nexport class BootFatalError extends Error {\n constructor(\n public readonly step: string,\n message: string,\n options?: { cause?: unknown },\n ) {\n super(`[boot] FATAL step=${step}: ${message}`, options);\n this.name = \"BootFatalError\";\n }\n}\n","import { BootFatalError, type BootContext, type BootStep } from \"./types.js\";\n\nexport async function runPipeline(steps: BootStep[], ctx: BootContext): Promise<void> {\n const t0 = Date.now();\n const timings: string[] = [];\n for (const step of steps) {\n const s0 = Date.now();\n try {\n await step.run(ctx);\n } catch (err) {\n const msg = err instanceof Error ? err.message : String(err);\n if (step.critical) {\n ctx.log.error(`[boot] FATAL step=${step.name}: ${msg}`);\n throw err instanceof BootFatalError\n ? err\n : new BootFatalError(step.name, msg, { cause: err });\n }\n ctx.log.warn(`[boot] WARN step=${step.name} degraded: ${msg}`);\n }\n const ms = Date.now() - s0;\n timings.push(`${step.name}:${ms}`);\n ctx.log.info(`[boot] step=${step.name} ms=${ms}`);\n }\n ctx.log.info(\n `[boot] timeline role=${ctx.role} total=${Date.now() - t0} steps=${timings.join(\",\")}`,\n );\n}\n","import type { BootstrapBundle } from \"../setup/bootstrap-bundle-types.js\";\nimport { parseBundle } from \"./bundle.js\";\nimport type { BootLogger } from \"./types.js\";\n\n/**\n * Ports the standby poll loop (entrypoint.sh:186-220): poll\n * `GET ${apiUrl}/api/v3/pods/bootstrap` until the reconciler binds this pod\n * (200 + bundle body) or the bootstrap token is rejected (401, fatal). 204\n * (still unbound) and any network error both retry after `pollIntervalMs`.\n *\n * DELIBERATELY NOT `setup/bootstrap-poll.ts` (`pollUntilBound`) — that\n * helper has deadline/throw semantics for a different caller. Boot instead\n * needs abort support (SIGTERM during standby → clean exit) and periodic\n * progress logging, so it gets its own poll loop.\n */\nexport class BootstrapUnauthorizedError extends Error {\n constructor(message: string) {\n super(message);\n this.name = \"BootstrapUnauthorizedError\";\n }\n}\n\nexport interface PollDeps {\n fetchFn: typeof fetch;\n sleepFn: (ms: number) => Promise<void>;\n log: BootLogger;\n /** Aborted (e.g. SIGTERM during standby) → resolves null; caller exits 0. */\n signal?: AbortSignal;\n /** default 2000 */\n pollIntervalMs?: number;\n}\n\nconst PROGRESS_LOG_EVERY = 30;\nconst DEFAULT_POLL_INTERVAL_MS = 2_000;\n\n/**\n * Resolves after `ms` via `sleepFn`, or immediately when `signal` aborts —\n * whichever comes first. Without the race, a SIGTERM landing mid-sleep would\n * ride out the full poll interval before the loop-top abort check could run.\n * The abort listener is removed on normal completion so repeated polls don't\n * accumulate listeners on a long-lived signal.\n */\nfunction sleepUnlessAborted(\n sleepFn: (ms: number) => Promise<void>,\n ms: number,\n signal?: AbortSignal,\n): Promise<void> {\n if (!signal) return sleepFn(ms);\n if (signal.aborted) return Promise.resolve();\n return new Promise<void>((resolve) => {\n const onAbort = (): void => {\n resolve();\n };\n signal.addEventListener(\"abort\", onAbort, { once: true });\n void sleepFn(ms).then(() => {\n signal.removeEventListener(\"abort\", onAbort);\n resolve();\n });\n });\n}\n\n/**\n * Resolves the parsed bundle once bound, or `null` if `deps.signal` is\n * aborted first. Never logs the response body — it carries credentials.\n */\nexport async function pollBootstrapBundle(\n apiUrl: string,\n token: string,\n deps: PollDeps,\n): Promise<BootstrapBundle | null> {\n const interval = deps.pollIntervalMs ?? DEFAULT_POLL_INTERVAL_MS;\n let pollCount = 0;\n\n while (true) {\n if (deps.signal?.aborted) return null;\n\n let res: Response | undefined;\n try {\n // The signal is threaded into fetch so a SIGTERM during shutdown can\n // abort a hung request instead of blocking past the grace period.\n res = await deps.fetchFn(`${apiUrl}/api/v3/pods/bootstrap`, {\n headers: { Authorization: `Bearer ${token}` },\n signal: deps.signal,\n });\n } catch {\n // An abort mid-fetch surfaces here as an AbortError rejection —\n // that's shutdown, not a network failure; bail out immediately.\n if (deps.signal?.aborted) return null;\n // Network error — treat the same as a still-unbound (204) poll and retry.\n res = undefined;\n }\n\n if (res?.status === 200) {\n return parseBundle(await res.text());\n }\n if (res?.status === 401) {\n throw new BootstrapUnauthorizedError(\"bootstrap token rejected (401) — pod identity invalid\");\n }\n\n pollCount++;\n if (pollCount % PROGRESS_LOG_EVERY === 0) {\n deps.log.info(`[boot] Still waiting for bind (poll #${pollCount})...`);\n }\n await sleepUnlessAborted(deps.sleepFn, interval, deps.signal);\n }\n}\n","import { execFile } from \"node:child_process\";\nimport { promisify } from \"node:util\";\nimport type { BootLogger } from \"./types.js\";\n\n/**\n * The injectable exec seam shared across the boot steps (home reclaim, sshd,\n * user-home wiring). Formerly lived in a `self-update` module alongside an npm\n * preflight; the runtime self-update is gone (the pod runs the baked agent —\n * see boot/index.ts), so this is now purely the exec plumbing.\n */\nexport interface ExecDeps {\n exec(cmd: string, args: string[], opts: { timeoutMs: number }): Promise<{ stdout: string }>;\n log: BootLogger;\n}\n\nconst execFileAsync = promisify(execFile);\n\nexport function defaultExec(\n cmd: string,\n args: string[],\n opts: { timeoutMs: number },\n): Promise<{ stdout: string }> {\n return execFileAsync(cmd, args, { timeout: opts.timeoutMs });\n}\n","import { spawn as spawnChildProcess, type ChildProcess } from \"node:child_process\";\nimport { appendFileSync, statSync, writeFileSync } from \"node:fs\";\nimport { join } from \"node:path\";\nimport { reportAgentCrash } from \"./crash-report.js\";\nimport type { BootLogger } from \"./types.js\";\n\n/**\n * Ports the supervised launch loop (entrypoint.sh:901-978): spawn\n * `conveyor-agent`, restart it on a nonzero exit up to `AGENT_CRASH_MAX`\n * times with a backoff between attempts, and report every crash to the API.\n * A clean exit (0) or an aborted (SIGTERM) supervision both return without\n * further restarts.\n *\n * Liveness contract: this supervisor writes the markers\n * `apps/api/.../dockerfiles/liveness.sh` reads — `LAUNCH_MARKER`\n * (/tmp/conveyor-agent-launching) once supervision starts (post-bind, before\n * the first spawn), arming the probe, and `RESTARTING_MARKER` touched before\n * each restart backoff so the probe stays green across backoff sleeps.\n * (Same names the late-era bash entrypoint used, so one liveness.sh serves\n * both worlds during the rollout window.)\n */\n// Mirrors bootstrap-route.ts:35.\nexport const AGENT_CRASH_MAX = 3;\nexport const AGENT_LOG_PATH = \"/tmp/claudespace-agent.log\";\n// Cap the agent log so a long-lived, chatty pod can't fill the 10Gi ephemeral\n// disk and get evicted. Rotation is a single truncate-on-overflow (no rolling\n// N files): pod stdout already tees the full stream, so the file is only a\n// same-pod convenience tail, not the archive.\nexport const AGENT_LOG_MAX_BYTES = 16 * 1024 * 1024;\n\nconst LAUNCH_MARKER_NAME = \"conveyor-agent-launching\";\nconst RESTARTING_MARKER_NAME = \"conveyor-agent-restarting\";\nexport const LAUNCH_MARKER = `/tmp/${LAUNCH_MARKER_NAME}`;\nexport const RESTARTING_MARKER = `/tmp/${RESTARTING_MARKER_NAME}`;\n\nconst DEFAULT_CRASH_BACKOFF_MS = 10_000;\n// Under the pod's 60s terminationGracePeriodSeconds.\nconst DEFAULT_DRAIN_TIMEOUT_MS = 45_000;\nconst DEFAULT_MARKER_DIR = \"/tmp\";\n\nexport interface SuperviseOptions {\n childEnv: Record<string, string>;\n cwd: string;\n apiUrl: string;\n bootstrapToken: string;\n log: BootLogger;\n /** Injected in tests to substitute a fake binary for `conveyor-agent`. */\n spawnFn?: typeof spawnChildProcess;\n reportFn?: typeof reportAgentCrash;\n crashBackoffMs?: number;\n drainTimeoutMs?: number;\n /** default \"/tmp\"; tests point this at a tmpdir. */\n markerDir?: string;\n /**\n * Test-visible SIGTERM stand-in. Production wiring (`process.on(\"SIGTERM\",\n * () => controller.abort())`) happens in Task 9 — this module only reacts\n * to the signal, it never installs the process listener itself.\n */\n signal?: AbortSignal;\n}\n\n/** Touch (create-or-refresh) a liveness marker. Best-effort — a marker-dir\n * permission problem must never take down agent supervision. */\nfunction touchMarker(path: string, log: BootLogger): void {\n try {\n writeFileSync(path, \"\");\n } catch (err) {\n log.warn(\n `[boot] WARN failed to write liveness marker ${path}: ${err instanceof Error ? err.message : String(err)}`,\n );\n }\n}\n\n/**\n * Build a size-bounded appender for the agent log. Tracks the running file size\n * (seeded from any pre-existing file so a restart doesn't forget prior growth)\n * and, once the next chunk would cross `maxBytes`, truncates the file and starts\n * fresh with that chunk rather than growing without bound. Best-effort\n * throughout (`report_agent_crash`-style `|| true`): a full disk or permission\n * error must never kill supervision.\n */\nexport function createLogAppender(path: string, maxBytes: number): (chunk: Buffer) => void {\n let size = 0;\n try {\n size = statSync(path).size;\n } catch {\n size = 0;\n }\n return (chunk: Buffer): void => {\n try {\n if (size + chunk.length > maxBytes) {\n // Rotate by truncation — drop the old log and start over with this\n // chunk (pod stdout still carries the full stream).\n writeFileSync(path, chunk);\n size = chunk.length;\n } else {\n appendFileSync(path, chunk);\n size += chunk.length;\n }\n } catch {\n // Best-effort log file — see doc comment above.\n }\n };\n}\n\n/**\n * Tees the child's stdout/stderr to our own stdout (so pod logs still show\n * it) and appends to the size-bounded agent log via `appendLog`. Chunks are\n * written as they arrive — no ordering guarantee is claimed or needed beyond\n * \"as they arrive\".\n */\nfunction teeChildOutput(child: ChildProcess, appendLog: (chunk: Buffer) => void): void {\n const onData = (chunk: Buffer): void => {\n process.stdout.write(chunk);\n appendLog(chunk);\n };\n child.stdout?.on(\"data\", onData);\n child.stderr?.on(\"data\", onData);\n}\n\nfunction spawnAgent(spawnFn: typeof spawnChildProcess, opts: SuperviseOptions): ChildProcess {\n // argv `[]`, PATH-resolved (no shell) so a preflight npm-install update\n // lands on the next spawn without any code here caring about it.\n return spawnFn(\"conveyor-agent\", [], {\n cwd: opts.cwd,\n env: opts.childEnv,\n stdio: [\"ignore\", \"pipe\", \"pipe\"],\n });\n}\n\n/** Resolves the child's exit code. A signal-kill (code null) counts as\n * nonzero — the only caller that kills the child itself (drain) already\n * short-circuits before this matters for restart bookkeeping. */\nfunction waitForExit(child: ChildProcess): Promise<number> {\n return new Promise((resolve) => {\n child.once(\"exit\", (code, signal) => {\n resolve(code ?? (signal ? 1 : 0));\n });\n child.once(\"error\", () => resolve(1));\n });\n}\n\n/** Never resolves unless `signal` fires (or is already aborted). */\nfunction whenAborted(signal: AbortSignal | undefined): Promise<void> {\n return new Promise((resolve) => {\n if (!signal) return;\n if (signal.aborted) {\n resolve();\n return;\n }\n signal.addEventListener(\"abort\", () => resolve(), { once: true });\n });\n}\n\n/**\n * Sleeps `ms`, resolving early if `signal` aborts — mirrors poll.ts's\n * module-private `sleepUnlessAborted` so a SIGTERM landing mid-backoff\n * doesn't ride out the full `crashBackoffMs` before draining can start.\n */\nfunction sleepUnlessAborted(ms: number, signal?: AbortSignal): Promise<void> {\n if (!signal) {\n return new Promise((resolve) => {\n setTimeout(resolve, ms);\n });\n }\n if (signal.aborted) return Promise.resolve();\n return new Promise((resolve) => {\n const onAbort = (): void => {\n clearTimeout(timer);\n resolve();\n };\n const timer = setTimeout(() => {\n signal.removeEventListener(\"abort\", onAbort);\n resolve();\n }, ms);\n signal.addEventListener(\"abort\", onAbort, { once: true });\n });\n}\n\n/**\n * Forward SIGTERM to the child, wait up to `drainTimeoutMs` for it to exit\n * on its own, then SIGKILL. Always resolves 0 — draining is a clean\n * shutdown from the supervisor's point of view regardless of the child's\n * final exit code.\n */\nasync function drainChild(\n child: ChildProcess,\n exitPromise: Promise<number>,\n drainTimeoutMs: number,\n log: BootLogger,\n): Promise<number> {\n log.info(\"[boot] SIGTERM received, draining agent...\");\n child.kill(\"SIGTERM\");\n\n const TIMED_OUT = Symbol(\"drain-timeout\");\n let timer: ReturnType<typeof setTimeout> | undefined;\n const timeout = new Promise<typeof TIMED_OUT>((resolve) => {\n timer = setTimeout(() => resolve(TIMED_OUT), drainTimeoutMs);\n });\n const winner = await Promise.race([exitPromise, timeout]);\n if (timer) clearTimeout(timer);\n\n if (winner === TIMED_OUT) {\n log.warn(`[boot] agent did not exit within drainTimeoutMs=${drainTimeoutMs}ms — SIGKILL`);\n child.kill(\"SIGKILL\");\n await exitPromise;\n }\n return 0;\n}\n\nexport async function superviseRunner(opts: SuperviseOptions): Promise<number> {\n const spawnFn = opts.spawnFn ?? spawnChildProcess;\n const reportFn = opts.reportFn ?? reportAgentCrash;\n const crashBackoffMs = opts.crashBackoffMs ?? DEFAULT_CRASH_BACKOFF_MS;\n const drainTimeoutMs = opts.drainTimeoutMs ?? DEFAULT_DRAIN_TIMEOUT_MS;\n const markerDir = opts.markerDir ?? DEFAULT_MARKER_DIR;\n const launchMarkerPath = join(markerDir, LAUNCH_MARKER_NAME);\n const restartingMarkerPath = join(markerDir, RESTARTING_MARKER_NAME);\n\n // Supervision starting (post-bind) arms the liveness probe — see the\n // liveness-contract doc comment above.\n touchMarker(launchMarkerPath, opts.log);\n\n // One size-bounded appender shared across every restart so the cap tracks the\n // whole supervision lifetime, not a single spawn.\n const appendLog = createLogAppender(AGENT_LOG_PATH, AGENT_LOG_MAX_BYTES);\n\n let attempt = 0;\n while (true) {\n attempt++;\n opts.log.info(\"[boot] Launching agent...\");\n const child = spawnAgent(spawnFn, opts);\n teeChildOutput(child, appendLog);\n const exitPromise = waitForExit(child);\n\n const winner = await Promise.race([\n exitPromise.then((code) => ({ type: \"exit\" as const, code })),\n whenAborted(opts.signal).then(() => ({ type: \"abort\" as const })),\n ]);\n\n if (winner.type === \"abort\") {\n return await drainChild(child, exitPromise, drainTimeoutMs, opts.log);\n }\n\n const exitCode = winner.code;\n if (exitCode === 0) {\n opts.log.info(\"[boot] agent exited cleanly (code 0), shutting down pod.\");\n return 0;\n }\n\n if (attempt >= AGENT_CRASH_MAX) {\n opts.log.error(\n `[boot] agent crashed (code ${exitCode}) — attempt cap ${AGENT_CRASH_MAX} reached, giving up.`,\n );\n await reportFn(opts.apiUrl, opts.bootstrapToken, { exitCode, attempt, final: true });\n return 1;\n }\n\n touchMarker(restartingMarkerPath, opts.log);\n await reportFn(opts.apiUrl, opts.bootstrapToken, { exitCode, attempt, final: false });\n opts.log.warn(\n `[boot] agent crashed (code ${exitCode}), retrying in ${crashBackoffMs}ms (attempt ${attempt}/${AGENT_CRASH_MAX})...`,\n );\n await sleepUnlessAborted(crashBackoffMs, opts.signal);\n if (opts.signal?.aborted) {\n // Aborted during backoff — no child is running; draining means \"stop\n // trying to restart\", nothing to SIGTERM/await here.\n return 0;\n }\n }\n}\n","/**\n * Ports `report_agent_crash` (entrypoint.sh:936-943): the pod-side crash\n * supervisor's best-effort POST to the API so the crash gets posted to the\n * task's activity log (which also keeps the pod alive through recovery) and,\n * on the final attempt, a durable notification. Payload contract matches\n * `bootstrap-route.ts`'s `parseCrashReport`: `{ exitCode, attempt, final }`.\n *\n * Best-effort, mirrors the bash `|| true`: a failed or slow report must never\n * block the supervisor's restart loop, so every path swallows errors and this\n * never throws.\n */\nconst CRASH_REPORT_TIMEOUT_MS = 10_000;\n\nexport interface CrashReport {\n exitCode: number;\n attempt: number;\n final: boolean;\n}\n\nexport async function reportAgentCrash(\n apiUrl: string,\n bootstrapToken: string,\n report: CrashReport,\n fetchFn: typeof fetch = fetch,\n): Promise<void> {\n const controller = new AbortController();\n const timer = setTimeout(() => controller.abort(), CRASH_REPORT_TIMEOUT_MS);\n try {\n await fetchFn(`${apiUrl}/api/v3/pods/agent-crash`, {\n method: \"POST\",\n headers: {\n \"content-type\": \"application/json\",\n authorization: `Bearer ${bootstrapToken}`,\n },\n body: JSON.stringify(report),\n signal: controller.signal,\n });\n } catch {\n // Best-effort — never let a network failure or timeout block the retry loop.\n } finally {\n clearTimeout(timer);\n }\n}\n","import {\n chmodSync,\n copyFileSync,\n existsSync,\n lstatSync,\n mkdirSync,\n readFileSync,\n readdirSync,\n rmSync,\n statSync,\n symlinkSync,\n writeFileSync,\n} from \"node:fs\";\nimport { dirname, join } from \"node:path\";\nimport type { ExecDeps } from \"./exec.js\";\nimport type { BootLogger } from \"./types.js\";\n\n/**\n * Ports entrypoint.sh:120-143 (`reclaim_home_ownership`), 346-379\n * (`seed_claude_json`), and 381-487 (mount wiring / pod-local ~/.claude.json /\n * credential symlink / opencode links / no-mount fallback).\n *\n * `.claude/rules/claude-session-persistence.md` invariant carried verbatim:\n * ~/.claude.json is pod-local and NEVER symlinked into the mount — the CLI\n * rewrites it wholesale at startup/during runs, and a burst of concurrent pods\n * for the same user+project racing one shared file produces last-writer-wins\n * lost updates (root cause of a live TUI startup-dialog park and a poisoned\n * customApiKeyResponses write bricking every future pod). ~/.claude itself IS\n * symlinked into the mount when present — it holds the shared, append-mostly\n * transcript/plan/memory state --resume depends on.\n */\n\n// ── seed_claude_json ────────────────────────────────────────────────────────\n\nexport const SEED_GATES: Record<string, unknown> = {\n hasCompletedOnboarding: true,\n bypassPermissionsModeAccepted: true,\n projects: {\n \"/workspaces/repo\": { hasTrustDialogAccepted: true },\n },\n};\n\n// Fable is entitlement-gated in the CLI's /model picker via\n// additionalModelOptionsCache, normally populated by a bootstrap fetch the\n// pods' synthesized credentials don't satisfy — without this row the picker\n// never offers Fable in a pod. The literal value is entitlement-load-bearing;\n// it mirrors FABLE_MODEL (\"claude-fable-5\") in packages/shared/src/constants/\n// models.ts plus the CLI's \"[1m]\" 1M-context variant suffix.\nexport const SEED_FABLE_OPTION: {\n value: \"claude-fable-5[1m]\";\n label: \"Fable\";\n description: string;\n} = {\n value: \"claude-fable-5[1m]\",\n label: \"Fable\",\n description: \"Fable 5 - most capable for your hardest and longest-running tasks\",\n};\n\nfunction isPlainObject(value: unknown): value is Record<string, unknown> {\n return typeof value === \"object\" && value !== null && !Array.isArray(value);\n}\n\nfunction deepMerge(\n target: Record<string, unknown>,\n patch: Record<string, unknown>,\n): Record<string, unknown> {\n const out = { ...target };\n for (const [k, v] of Object.entries(patch)) {\n const cur = out[k];\n out[k] = isPlainObject(cur) && isPlainObject(v) ? deepMerge(cur, v) : v;\n }\n return out;\n}\n\nfunction hasFableEntry(cache: unknown[]): boolean {\n return cache.some((entry) => {\n if (!isPlainObject(entry)) return false;\n const value = entry.value;\n return typeof value === \"string\" && value.toLowerCase().includes(\"fable\");\n });\n}\n\n/**\n * Pre-answer every interactive first-run gate the CLI shows on a fresh (or\n * partially initialized) config — the onboarding wizard, the\n * bypass-permissions warning, and the folder-trust dialog for the repo\n * workspace — plus append the Fable model-picker entitlement row. Merge via\n * deep-merge, never clobber: CLI-owned keys (cache keys, firstStartTime,\n * other projects' trust entries) survive. A missing or corrupt file becomes a\n * fresh seed (Claude Code treats a 0-byte/unparseable file as fatal, so\n * replacing is a repair, not data loss). Theme only seeds fresh configs — a\n * user-picked theme is never overridden. Never throws: seeding must not kill\n * boot under a single bad stat/write.\n */\nexport function seedClaudeJson(targetPath: string): void {\n try {\n let config: Record<string, unknown> | null = null;\n try {\n const parsed: unknown = JSON.parse(readFileSync(targetPath, \"utf8\"));\n if (isPlainObject(parsed)) config = parsed;\n } catch {\n // Missing or corrupt → fresh seed (repair), not a throw.\n }\n let merged = config ? deepMerge(config, SEED_GATES) : { ...SEED_GATES, theme: \"dark\" };\n const cache = Array.isArray(merged.additionalModelOptionsCache)\n ? merged.additionalModelOptionsCache\n : [];\n if (!hasFableEntry(cache)) {\n merged = { ...merged, additionalModelOptionsCache: [...cache, SEED_FABLE_OPTION] };\n }\n writeFileSync(targetPath, JSON.stringify(merged));\n } catch {\n // Seeding must never kill boot.\n }\n}\n\n// ── wire_user_home ──────────────────────────────────────────────────────────\n\nexport interface WireUserHomeOptions {\n /** \"/home/conveyor\" in prod, a tmpdir in tests. */\n home: string;\n /** \"/mnt/conveyor-users\" — the GCS FUSE CSI mount point. */\n userHomeMount: string;\n userId?: string;\n projectId?: string;\n /** CONVEYOR_SHARED_DIR — always set in split pods. */\n sharedDir?: string;\n /** Pre-created agent log-redirect dir; overridable for tests. */\n scratchDir?: string;\n log: BootLogger;\n}\n\nfunction isDir(path: string): boolean {\n try {\n return statSync(path).isDirectory();\n } catch {\n return false;\n }\n}\n\nfunction isRegularFile(path: string): boolean {\n try {\n return lstatSync(path).isFile();\n } catch {\n return false;\n }\n}\n\nfunction safely(log: BootLogger, label: string, fn: () => void): void {\n try {\n fn();\n } catch (err) {\n log.warn(`[boot] WARN ${label} failed: ${err instanceof Error ? err.message : String(err)}`);\n }\n}\n\n/** `ln -sfn target link` semantics: replace whatever is at `link`, creating\n * its parent dir first. */\nfunction relink(target: string, link: string): void {\n mkdirSync(dirname(link), { recursive: true });\n rmSync(link, { recursive: true, force: true });\n symlinkSync(target, link);\n}\n\ninterface CredentialWireOptions {\n home: string;\n root: string;\n sharedDir?: string;\n log: BootLogger;\n}\n\n/**\n * Pod-local ~/.claude/.credentials.json: concurrent pods under different keys\n * clobbering one shared file cross-wired usage attribution between accounts\n * (seen live). The shared dir keeps a SYMLINK to an absolute pod-local path,\n * which resolves per-pod; racing pods all write the identical symlink value,\n * so the symlink itself cannot lose data. A pre-existing REGULAR (non-symlink)\n * shared file is legacy — carry its contents into the pod-local target first\n * so auth survives the cutover.\n */\nfunction wireCredentials(opts: CredentialWireOptions): void {\n const { home, root, sharedDir, log } = opts;\n const shared = join(root, \".claude\", \".credentials.json\");\n const podLocal = sharedDir\n ? join(sharedDir, \"claude-credentials.pod.json\")\n : join(home, \".claude-credentials.pod.json\");\n safely(log, \"carry legacy shared credentials\", () => {\n if (isRegularFile(shared)) copyFileSync(shared, podLocal);\n });\n safely(log, \"repoint credentials symlink\", () => relink(podLocal, shared));\n safely(log, \"chmod pod-local credentials\", () => chmodSync(podLocal, 0o600));\n}\n\n/**\n * The agent's memory dir, project-scoped and shared across users.\n *\n * Memory used to be keyed (userId, projectId). The 2026-07-27 fleet audit found\n * 6 such silos holding 67 memories with ZERO topics in common: the same pod-env\n * lessons (chromium `executablePath`, review-guide anchors, which gates OOM)\n * were discovered the hard way and written down independently in each silo,\n * because a memory written by one user's pod is invisible to the next. Project\n * scope is the right boundary — memories are project-technical facts, it\n * matches the ACL boundary, and it mirrors the `users/_shared/graphify`\n * convention already living on this mount.\n *\n * One-file-per-memory keeps concurrent pods collision-free; migration never\n * overwrites an existing shared file, so a stale per-user copy cannot clobber\n * what another pod already published.\n */\nconst AGENT_MEMORY_PROJECT_DIR = \"-workspaces-repo\";\n\nfunction migrateMemories(from: string, to: string, log: BootLogger): void {\n let entries: string[];\n try {\n entries = readdirSync(from);\n } catch {\n return;\n }\n for (const entry of entries) {\n const target = join(to, entry);\n if (existsSync(target)) continue;\n safely(log, `migrate memory ${entry}`, () => copyFileSync(join(from, entry), target));\n }\n}\n\nfunction wireSharedMemory(\n root: string,\n userHomeMount: string,\n projectId: string,\n log: BootLogger,\n): void {\n const shared = join(userHomeMount, \"users\", \"_shared\", \"memory\", projectId);\n const link = join(root, \".claude\", \"projects\", AGENT_MEMORY_PROJECT_DIR, \"memory\");\n safely(log, \"mkdir shared memory dir\", () => mkdirSync(shared, { recursive: true }));\n // Only migrate a REAL directory — on the second boot `link` is already the\n // symlink we created, and copying it onto itself would be a no-op at best.\n safely(log, \"migrate per-user memories\", () => {\n if (existsSync(link) && !lstatSync(link).isSymbolicLink()) migrateMemories(link, shared, log);\n });\n safely(log, \"wire shared memory symlink\", () => relink(shared, link));\n}\n\ninterface MountedWireOptions {\n home: string;\n userHomeMount: string;\n userId: string;\n projectId: string;\n sharedDir?: string;\n log: BootLogger;\n}\n\nfunction wireMountedUserHome(opts: MountedWireOptions): void {\n const { home, userHomeMount, userId, projectId, sharedDir, log } = opts;\n const root = join(userHomeMount, \"users\", userId, projectId);\n safely(log, \"mkdir user-home root\", () => {\n mkdirSync(join(root, \".claude\"), { recursive: true });\n mkdirSync(join(root, \".config\", \"claude\"), { recursive: true });\n });\n safely(log, \"wire ~/.claude symlink\", () => relink(join(root, \".claude\"), join(home, \".claude\")));\n safely(log, \"wire ~/.config/claude symlink\", () =>\n relink(join(root, \".config\", \"claude\"), join(home, \".config\", \"claude\")),\n );\n safely(log, \"seed pod-local ~/.claude.json\", () => {\n rmSync(join(home, \".claude.json\"), { recursive: true, force: true });\n seedClaudeJson(join(home, \".claude.json\"));\n });\n wireSharedMemory(root, userHomeMount, projectId, log);\n wireCredentials({ home, root, sharedDir, log });\n safely(log, \"mkdir opencode root\", () => {\n mkdirSync(join(root, \".local\", \"share\", \"opencode\"), { recursive: true });\n mkdirSync(join(root, \".config\", \"opencode\"), { recursive: true });\n });\n safely(log, \"wire opencode data symlink\", () =>\n relink(join(root, \".local\", \"share\", \"opencode\"), join(home, \".local\", \"share\", \"opencode\")),\n );\n safely(log, \"wire opencode config symlink\", () =>\n relink(join(root, \".config\", \"opencode\"), join(home, \".config\", \"opencode\")),\n );\n}\n\ninterface NoMountWireOptions {\n home: string;\n sharedDir?: string;\n log: BootLogger;\n}\n\nfunction wireNoMountUserHome(opts: NoMountWireOptions): void {\n const { home, sharedDir, log } = opts;\n // No persistent home — the CLI reads the pod-local config; seed it there so\n // non-FUSE pods get the same first-run gate suppression as the mounted path.\n safely(log, \"seed pod-local ~/.claude.json (no mount)\", () =>\n seedClaudeJson(join(home, \".claude.json\")),\n );\n if (sharedDir) {\n // Split-mode pods without a persistent user-home: ~/.claude must still\n // cross the container boundary (claude writes transcripts in the\n // workbench; the agent's tailer reads them), so it lives on the shared\n // emptyDir.\n safely(log, \"wire ~/.claude to shared emptyDir\", () => {\n const target = join(sharedDir, \"claude-home\", \".claude\");\n mkdirSync(target, { recursive: true });\n relink(target, join(home, \".claude\"));\n });\n }\n}\n\n/**\n * When the pod has the GCS FUSE user-home mount present with a resolvable\n * userId+projectId, symlink ~/.claude, ~/.config/claude, and the opencode\n * dirs into the user's persistent subdir, seed a pod-local ~/.claude.json,\n * and repoint the shared credentials file to a pod-local symlink target.\n * Otherwise fall back to a pod-local-only seed (plus a shared-emptyDir\n * ~/.claude symlink for split pods). Every sub-step degrades independently —\n * one failure never aborts the rest.\n */\nexport function wireUserHome(opts: WireUserHomeOptions): void {\n const { home, userHomeMount, userId, projectId, sharedDir, log } = opts;\n // Agents habitually redirect gate logs into /tmp/claude-cmd despite docs\n // saying it doesn't exist (55 sessions hit the ENOENT in the 2026-07-27\n // fleet audit) — pre-create it so the redirect just works.\n safely(log, \"mkdir agent scratch dir\", () =>\n mkdirSync(opts.scratchDir ?? \"/tmp/claude-cmd\", { recursive: true }),\n );\n if (userId && projectId && isDir(userHomeMount)) {\n wireMountedUserHome({ home, userHomeMount, userId, projectId, sharedDir, log });\n } else {\n wireNoMountUserHome({ home, sharedDir, log });\n }\n}\n\n// ── reclaim_home_ownership ──────────────────────────────────────────────────\n\nconst RECLAIM_FIND_TIMEOUT_MS = 10_000;\n\n/**\n * Bake steps that run as root while ENV HOME points at the pod home can leave\n * root-owned entries under ~/.config / ~/.local (a uv install receipt once\n * bricked every pod boot fleet-wide). Reclaims them so a baked ownership slip\n * can't EACCES the entrypoint. CONDITIONAL, not a recursive chown: a\n * `find … ! -user conveyor` walk is stat-only when the tree is already clean\n * (the common case), so it triggers no overlayfs copy-up — an unconditional\n * `chown -R` here once cost ~16s of I/O-bound copy-up on every boot even when\n * nothing was root-owned.\n */\nexport async function reclaimHomeOwnership(\n home: string,\n exec: ExecDeps[\"exec\"],\n log: BootLogger,\n): Promise<void> {\n for (const dir of [join(home, \".config\"), join(home, \".local\")]) {\n if (!isDir(dir)) continue;\n try {\n await exec(\n \"sudo\",\n [\n \"-n\",\n \"find\",\n dir,\n \"!\",\n \"-user\",\n \"conveyor\",\n \"-exec\",\n \"chown\",\n \"conveyor:conveyor\",\n \"{}\",\n \"+\",\n ],\n { timeoutMs: RECLAIM_FIND_TIMEOUT_MS },\n );\n } catch (err) {\n log.warn(\n `[boot] WARN reclaim_home_ownership failed for ${dir}: ${err instanceof Error ? err.message : String(err)}`,\n );\n }\n }\n}\n","import { chmodSync, closeSync, copyFileSync, existsSync, mkdirSync, openSync } from \"node:fs\";\nimport { join } from \"node:path\";\nimport type { ExecDeps } from \"./exec.js\";\nimport type { BootLogger } from \"./types.js\";\n\n// Persistent authorized_keys copy on the shared emptyDir (mounted in both\n// containers, survives workbench restarts for the pod's life). installWorkspaceSshKey\n// (apps/api .../workspace-ssh-key.ts) writes it there; we re-hydrate the live\n// ~/.ssh/authorized_keys from it on every workbench start so a key installed before\n// an OOM restart survives. Kept in sync with SHARED_MOUNT_DEFAULT there.\nfunction persistedAuthorizedKeys(): string {\n return join(\n process.env.CONVEYOR_SHARED_DIR || \"/var/run/conveyor-workbench\",\n \"ssh\",\n \"authorized_keys\",\n );\n}\n\n/**\n * Ports `start_workspace_sshd` (entrypoint.sh:12-31). Warn-only end to end —\n * SSH attach is a convenience surface; its absence must never degrade boot.\n *\n * Binds all interfaces deliberately: the preview-router tunnels in from\n * outside the pod to the pod IP. Access is gated by the attach token + SSH\n * public-key auth, not by network reachability (no public ingress).\n */\nexport async function startWorkspaceSshd(\n exec: ExecDeps[\"exec\"],\n log: BootLogger,\n port: string = process.env.CONVEYOR_WORKSPACE_SSH_PORT || \"2222\",\n home = \"/home/conveyor\",\n): Promise<void> {\n try {\n const sshDir = join(home, \".ssh\");\n mkdirSync(sshDir, { recursive: true });\n chmodSync(sshDir, 0o700);\n const authorizedKeys = join(sshDir, \"authorized_keys\");\n const persisted = persistedAuthorizedKeys();\n if (existsSync(persisted)) {\n // Restore keys installed before a workbench restart wiped /home/conveyor.\n copyFileSync(persisted, authorizedKeys);\n } else if (!existsSync(authorizedKeys)) {\n closeSync(openSync(authorizedKeys, \"a\"));\n }\n chmodSync(authorizedKeys, 0o600);\n } catch (err) {\n log.warn(\n `[boot] WARN: workspace SSHD key setup failed: ${err instanceof Error ? err.message : String(err)}`,\n );\n return;\n }\n if (!existsSync(\"/usr/sbin/sshd\")) {\n log.warn(\"[boot] WARN: workspace SSHD unavailable\");\n return;\n }\n try {\n await exec(\"sudo\", [\"mkdir\", \"-p\", \"/run/sshd\"], { timeoutMs: 10_000 });\n await exec(\"sudo\", [\"/usr/sbin/sshd\", \"-o\", \"ListenAddress=0.0.0.0\", \"-o\", `Port=${port}`], {\n timeoutMs: 10_000,\n });\n log.info(`[boot] Workspace SSHD listening on 0.0.0.0:${port}`);\n } catch {\n log.warn(\"[boot] WARN: workspace SSHD failed to start\");\n }\n}\n","import {\n appendFileSync,\n existsSync,\n lstatSync,\n mkdirSync,\n readdirSync,\n readFileSync,\n readlinkSync,\n rmSync,\n statSync,\n symlinkSync,\n} from \"node:fs\";\nimport { join } from \"node:path\";\nimport type { BootstrapBundle } from \"../setup/bootstrap-bundle-types.js\";\nimport { redactToken, type GitFn } from \"./git-prep.js\";\nimport { computeGraphifyEnv } from \"./graphify.js\";\nimport type { BootLogger } from \"./types.js\";\n\n/**\n * Ports the best-effort workspace extras that ride the git-ready gate:\n * - `bind_graphify_bundle` (entrypoint.sh:521-562)\n * - `ensure_grimoire_submodule` + `link_grimoire_skills` (entrypoint.sh:576-621)\n * - the reference-repos clone block (entrypoint.sh:793-827)\n *\n * Ordering invariant (mark_git_ready, entrypoint.sh:623-628): graphify bind +\n * grimoire submodule + skill links run BEFORE the ready flip (Claude must not\n * spawn before skills exist — fleet-wide 2026-07-10 incident); reference\n * repos run AFTER ready (supplementary context must never block Claude).\n * GitPrepJob's onReady/afterReady hooks carry that split.\n *\n * Every helper is best-effort: always returns cleanly, never fails the gate.\n */\n\nfunction errText(err: unknown): string {\n return redactToken(err instanceof Error ? err.message : String(err));\n}\n\nfunction isDir(path: string): boolean {\n try {\n return statSync(path).isDirectory();\n } catch {\n return false;\n }\n}\n\nfunction isSymlink(path: string): boolean {\n try {\n return lstatSync(path).isSymbolicLink();\n } catch {\n return false;\n }\n}\n\nconst GRAPHIFY_BUNDLE_FILES = [\n \"graph.json\",\n \"GRAPH_REPORT.md\",\n \"manifest.json\",\n \".graphify_analysis.json\",\n \".graphify_labels.json\",\n \"publish-manifest.json\",\n \"cost.json\",\n];\n\n/** Symlink the shared graphify bundle into <workspace>/graphify-out.\n * `graphifyEnv` is computeGraphifyEnv's output — empty (disabled/no slug)\n * makes this a no-op. */\nexport function bindGraphifyBundle(\n workspace: string,\n graphifyEnv: Record<string, string>,\n log: BootLogger,\n): void {\n const slug = graphifyEnv.CONVEYOR_GRAPHIFY_SLUG;\n const sourceDir = graphifyEnv.CONVEYOR_GRAPHIFY_DIR;\n const graphFile = graphifyEnv.CONVEYOR_GRAPHIFY_GRAPH;\n if (!slug || !sourceDir || !graphFile) return;\n if (!existsSync(graphFile)) {\n log.info(`[boot] Graphify bundle not found for '${slug}' at ${sourceDir}`);\n return;\n }\n const targetDir = join(workspace, \"graphify-out\");\n try {\n mkdirSync(targetDir, { recursive: true });\n } catch {\n log.warn(`[boot] WARN: unable to create graphify-out at ${targetDir}`);\n return;\n }\n for (const rel of GRAPHIFY_BUNDLE_FILES) {\n const source = join(sourceDir, rel);\n const target = join(targetDir, rel);\n if (!existsSync(source)) continue;\n // -e || -L in bash: never clobber an existing entry, including a broken\n // symlink (existsSync follows links, hence the extra lstat check).\n if (existsSync(target) || isSymlink(target)) continue;\n try {\n symlinkSync(source, target);\n } catch {\n /* best-effort, bash `|| true` */\n }\n }\n log.info(`[boot] Bound graphify bundle '${slug}' into ${targetDir}`);\n}\n\n/** Materialize the .claude/grimoire submodule when the repo declares it and\n * the bake didn't populate it. Always returns cleanly. */\nexport async function ensureGrimoireSubmodule(\n workspace: string,\n githubToken: string,\n git: GitFn,\n log: BootLogger,\n): Promise<void> {\n try {\n if (!existsSync(join(workspace, \".gitmodules\"))) return;\n let paths = \"\";\n try {\n ({ stdout: paths } = await git(\n [\"config\", \"--file\", \".gitmodules\", \"--get-regexp\", String.raw`submodule\\..*\\.path`],\n { cwd: workspace, timeoutMs: 10_000 },\n ));\n } catch {\n // No submodule entries (config exits nonzero on no match).\n return;\n }\n if (!paths.split(\"\\n\").some((line) => line.trim().endsWith(\".claude/grimoire\"))) return;\n // Already materialized (pod-image bake ran conveyor-prebake successfully).\n if (isDir(join(workspace, \".claude\", \"grimoire\", \"skills\"))) return;\n if (!githubToken) {\n log.warn(\"[boot] WARN: grimoire submodule absent and no token to fetch it\");\n return;\n }\n // insteadOf injects the installation token for the submodule's https URL\n // the same way syncTaskBranchToRepo authenticates the main repo remote.\n try {\n await git(\n [\n \"-c\",\n `url.https://x-access-token:${githubToken}@github.com/.insteadOf=https://github.com/`,\n \"submodule\",\n \"update\",\n \"--init\",\n \".claude/grimoire\",\n ],\n { cwd: workspace, timeoutMs: 300_000 },\n );\n log.info(\"[boot] Initialized grimoire submodule\");\n } catch (err) {\n log.warn(\n `[boot] WARN: grimoire submodule init failed (baked pods may lack rc-* skills): ${errText(err)}`,\n );\n }\n } catch (err) {\n log.warn(`[boot] WARN: grimoire submodule check failed: ${errText(err)}`);\n }\n}\n\n/** Register pod-local skill symlinks in .git/info/exclude so `git add -A`\n * (create_pull_request, WIP flush) can never sweep them into a commit.\n * Repo .gitignore only covers `rc-*`; grimoire skills without that prefix\n * (e.g. conveyor-plan) leaked into real PRs — 2026-07-27 fleet audit. */\nfunction excludeLinkedSkills(workspace: string, names: string[], log: BootLogger): void {\n if (names.length === 0) return;\n const infoDir = join(workspace, \".git\", \"info\");\n // A worktree checkout has a .git FILE — skip rather than misplace the exclude.\n if (!isDir(join(workspace, \".git\"))) return;\n try {\n mkdirSync(infoDir, { recursive: true });\n const excludePath = join(infoDir, \"exclude\");\n const existing = existsSync(excludePath) ? readFileSync(excludePath, \"utf8\") : \"\";\n const missing = names\n .map((name) => `.claude/skills/${name}`)\n .filter((line) => !existing.split(\"\\n\").includes(line));\n if (missing.length === 0) return;\n const prefix = existing.length > 0 && !existing.endsWith(\"\\n\") ? \"\\n\" : \"\";\n appendFileSync(excludePath, `${prefix}${missing.join(\"\\n\")}\\n`);\n } catch (err) {\n log.warn(`[boot] WARN: unable to exclude grimoire skill links: ${errText(err)}`);\n }\n}\n\nfunction symlinkTargetsGrimoire(path: string): boolean {\n try {\n return readlinkSync(path).includes(join(\"grimoire\", \"skills\"));\n } catch {\n return false;\n }\n}\n\n/** Link grimoire skills into the repo's project skill dir (pod-local; each\n * link is registered in .git/info/exclude so it can never be committed).\n * Never clobbers a real (repo-tracked) skill dir or a symlink owned by\n * something else (e.g. @rallycry/conveyor-skills links into node_modules). */\nexport function linkGrimoireSkills(workspace: string, log: BootLogger): void {\n const sourceDir = join(workspace, \".claude\", \"grimoire\", \"skills\");\n const targetDir = join(workspace, \".claude\", \"skills\");\n if (!isDir(sourceDir)) return;\n try {\n mkdirSync(targetDir, { recursive: true });\n } catch {\n log.warn(`[boot] WARN: unable to create ${targetDir}; skipping grimoire skill links`);\n return;\n }\n const linkedNames: string[] = [];\n let names: string[] = [];\n try {\n names = readdirSync(sourceDir);\n } catch {\n return;\n }\n for (const name of names) {\n if (!isDir(join(sourceDir, name))) continue;\n const target = join(targetDir, name);\n // bash: [ -e ] && [ ! -L ] — a broken symlink is replaceable, a real dir is not.\n if (existsSync(target) && !isSymlink(target)) {\n log.warn(`[boot] WARN: ${target} exists and is not a symlink; skipping`);\n continue;\n }\n // A repo-owned symlink (e.g. a committed link into node_modules/@rallycry/\n // conveyor-skills) is TRACKED content — rewriting it to point at grimoire\n // would surface as a modification that `git add -A` sweeps into commits.\n // Only grimoire-owned links (broken or not) are ours to refresh.\n if (isSymlink(target) && !symlinkTargetsGrimoire(target)) continue;\n try {\n // ln -sfn: replace an existing symlink in place.\n rmSync(target, { force: true });\n symlinkSync(join(\"..\", \"grimoire\", \"skills\", name), target);\n linkedNames.push(name);\n } catch {\n /* best-effort */\n }\n }\n excludeLinkedSkills(workspace, linkedNames, log);\n log.info(`[boot] Linked ${linkedNames.length} grimoire skills into ${targetDir}`);\n}\n\n/** Mark the grimoire submodule gitlink assume-unchanged so pointer drift after\n * a pod resume never shows as `M .claude/grimoire` nor gets committed by the\n * `git add -A` paths (create_pull_request, WIP flush). Agents hand-ran this in\n * 8 sessions of the 2026-07-27 fleet audit; in-pod submodule fetches fail\n * (no credentials on the submodule remote), so the pointer is noise here. */\nexport async function neutralizeGrimoirePointer(\n workspace: string,\n git: GitFn,\n log: BootLogger,\n): Promise<void> {\n if (!existsSync(join(workspace, \".claude\", \"grimoire\"))) return;\n try {\n await git([\"update-index\", \"--assume-unchanged\", \".claude/grimoire\"], {\n cwd: workspace,\n timeoutMs: 10_000,\n });\n log.info(\"[boot] Marked .claude/grimoire assume-unchanged (submodule pointer drift is noise)\");\n } catch (err) {\n // Path not in the index (repo without the submodule) or index lock — noise either way.\n log.warn(`[boot] WARN: grimoire assume-unchanged failed: ${errText(err)}`);\n }\n}\n\ninterface ReferenceRepo {\n slug: string;\n owner: string;\n name: string;\n branch: string;\n token: string;\n}\n\nfunction parseReferenceRepos(refsJson: string): ReferenceRepo[] {\n let parsed: unknown;\n try {\n parsed = JSON.parse(refsJson);\n } catch {\n // bash parity: jq parse errors were sent to /dev/null.\n return [];\n }\n if (!Array.isArray(parsed)) return [];\n const refs: ReferenceRepo[] = [];\n for (const entry of parsed) {\n if (typeof entry !== \"object\" || entry === null) continue;\n const raw = entry as Record<string, unknown>;\n const str = (key: string): string => (typeof raw[key] === \"string\" ? (raw[key] as string) : \"\");\n const ref: ReferenceRepo = {\n slug: str(\"slug\"),\n owner: str(\"owner\"),\n name: str(\"name\"),\n branch: str(\"branch\") || \"main\",\n token: str(\"token\"),\n };\n if (ref.token && ref.slug && ref.owner && ref.name) refs.push(ref);\n }\n return refs;\n}\n\n/**\n * GitPrepJob.onReady production composition — the three pre-ready binds in\n * the pinned mark_git_ready order (graphify bind, grimoire submodule, skill\n * links). Best-effort like its parts; a throw here is downgraded to a warn by\n * GitPrepJob anyway.\n */\nexport async function runPreReadyBinds(\n workspace: string,\n bundle: BootstrapBundle,\n git: GitFn,\n log: BootLogger,\n): Promise<void> {\n const graphifyEnv = computeGraphifyEnv(process.env, bundle.gitPlan.repoName, isDir);\n bindGraphifyBundle(workspace, graphifyEnv, log);\n await ensureGrimoireSubmodule(workspace, bundle.githubToken ?? \"\", git, log);\n linkGrimoireSkills(workspace, log);\n await neutralizeGrimoirePointer(workspace, git, log);\n}\n\n/**\n * Shallow-clone reference repos (from REFERENCE_REPOS_JSON in bundle.envVars)\n * as read-only context. Runs strictly AFTER the ready flip. Failures are\n * non-fatal — the task proceeds even when a reference project's GitHub App is\n * uninstalled or its token mint failed.\n */\nexport async function cloneReferenceRepos(\n refsJson: string | undefined,\n git: GitFn,\n log: BootLogger,\n referencesDir = \"/workspaces/references\",\n): Promise<void> {\n if (!refsJson) return;\n const refs = parseReferenceRepos(refsJson);\n if (refs.length === 0) return;\n try {\n mkdirSync(referencesDir, { recursive: true });\n } catch {\n return;\n }\n for (const ref of refs) {\n const dest = join(referencesDir, ref.slug);\n if (existsSync(join(dest, \".git\"))) continue;\n try {\n await git(\n [\n \"clone\",\n \"--depth\",\n \"1\",\n \"--single-branch\",\n \"--branch\",\n ref.branch,\n `https://x-access-token:${ref.token}@github.com/${ref.owner}/${ref.name}.git`,\n dest,\n ],\n { timeoutMs: 600_000 },\n );\n // Strip the token from the cloned remote so it never surfaces via\n // `git remote -v` when the agent inspects the reference repo.\n try {\n await git(\n [\"remote\", \"set-url\", \"origin\", `https://github.com/${ref.owner}/${ref.name}.git`],\n { cwd: dest, timeoutMs: 10_000 },\n );\n } catch {\n /* best-effort */\n }\n log.info(`[boot] cloned reference ${ref.slug} (${ref.owner}/${ref.name}@${ref.branch})`);\n } catch {\n log.warn(`[boot] WARN: reference clone failed: ${ref.slug}`);\n }\n }\n}\n","import type { BootstrapBundle } from \"../setup/bootstrap-bundle-types.js\";\nimport {\n startWorkbenchServer,\n type WorkbenchServerHandle,\n type WorkbenchServerOptions,\n} from \"../workbench/server.js\";\nimport { buildChildEnv, decodeJwtClaims } from \"./bundle.js\";\nimport { defaultGit, ensureDir, type GitPrepDeps } from \"./git-prep.js\";\nimport { pollBootstrapBundle } from \"./poll.js\";\nimport { defaultExec } from \"./exec.js\";\nimport { startWorkspaceSshd } from \"./sshd.js\";\nimport type { BootContext } from \"./types.js\";\nimport { reclaimHomeOwnership, wireUserHome } from \"./user-home.js\";\nimport { cloneReferenceRepos, runPreReadyBinds } from \"./workspace-extras.js\";\n\n/**\n * The injectable step surface of `runWorkbenchBoot` (workbench-boot.ts) plus\n * its production defaults — split out so tests exercise the boot sequencing\n * without network, sockets, or real git, and so the composition module stays\n * within the dependency budget.\n */\nexport interface WorkbenchBootDeps {\n /** Task 4 poll — throws BootstrapUnauthorizedError on 401 (fatal), resolves\n * null when aborted (SIGTERM during standby). */\n pollBundle(ctx: BootContext): Promise<BootstrapBundle | null>;\n /** Task 3 unpack — claims decode + child env. */\n unpack(ctx: BootContext, bundle: BootstrapBundle): Record<string, string>;\n startDaemon(opts: WorkbenchServerOptions): Promise<WorkbenchServerHandle>;\n gitPrepDeps(ctx: BootContext, bundle: BootstrapBundle): GitPrepDeps;\n /** GitPrepJob.onReady — graphify bind + grimoire submodule + skill links. */\n bindsBeforeReady(ctx: BootContext, bundle: BootstrapBundle): Promise<void>;\n /** GitPrepJob.afterReady — reference repos, never blocking Claude. */\n cloneReferences(ctx: BootContext, bundle: BootstrapBundle): Promise<void>;\n /** mkdir -p /workspaces/repo before git prep — the daemon serves shell cwd\n * from it, and the no-git-plan prep path never creates it (bash did so\n * unconditionally, entrypoint.sh:848). */\n ensureWorkspaceDir(ctx: BootContext): void;\n wireHome(ctx: BootContext): void;\n startSshd(ctx: BootContext): Promise<void>;\n reclaimOwnership(ctx: BootContext): Promise<void>;\n /** Daemon lifetime — resolves never; SIGTERM → exit is Task 9's wiring. */\n park(ctx: BootContext): Promise<never>;\n}\n\nconst WORKSPACE_DIR = \"/workspaces/repo\";\nconst HOME_DIR = \"/home/conveyor\";\nconst USER_HOME_MOUNT = \"/mnt/conveyor-users\";\n\nfunction sleep(ms: number): Promise<void> {\n return new Promise((resolve) => {\n setTimeout(resolve, ms);\n });\n}\n\nexport function defaultWorkbenchBootDeps(): WorkbenchBootDeps {\n return {\n pollBundle: (ctx) =>\n pollBootstrapBundle(ctx.apiUrl, ctx.bootstrapToken, {\n fetchFn: fetch,\n sleepFn: sleep,\n log: ctx.log,\n }),\n unpack: (ctx, bundle) => {\n const claims = decodeJwtClaims(bundle.sessionJwt);\n ctx.claims = claims;\n return buildChildEnv({\n base: process.env,\n bundle,\n claims,\n hostname: ctx.hostname,\n log: ctx.log,\n });\n },\n startDaemon: (opts) => startWorkbenchServer(opts),\n gitPrepDeps: (ctx, bundle) => ({\n git: defaultGit,\n bundle,\n podImage: process.env.CONVEYOR_POD_IMAGE === \"1\",\n log: ctx.log,\n }),\n bindsBeforeReady: (ctx, bundle) => runPreReadyBinds(WORKSPACE_DIR, bundle, defaultGit, ctx.log),\n cloneReferences: (ctx, bundle) =>\n cloneReferenceRepos(bundle.envVars?.REFERENCE_REPOS_JSON, defaultGit, ctx.log),\n ensureWorkspaceDir: (ctx) => {\n ensureDir(WORKSPACE_DIR);\n ctx.log.info(`[boot] ensured workspace dir ${WORKSPACE_DIR}`);\n },\n wireHome: (ctx) => {\n // Same id sources as the bash (entrypoint.sh:342-343, 388): userId from\n // the pod env / bundle envVars, projectId from the claim with a pod-env\n // fallback — all merged into childEnv by the unpack step.\n wireUserHome({\n home: HOME_DIR,\n userHomeMount: USER_HOME_MOUNT,\n userId: ctx.childEnv?.CONVEYOR_USER_ID,\n projectId: ctx.childEnv?.CONVEYOR_PROJECT_ID ?? ctx.childEnv?.PROJECT_ID,\n sharedDir: process.env.CONVEYOR_SHARED_DIR,\n log: ctx.log,\n });\n },\n startSshd: (ctx) => startWorkspaceSshd(defaultExec, ctx.log),\n reclaimOwnership: (ctx) => reclaimHomeOwnership(HOME_DIR, defaultExec, ctx.log),\n park: () =>\n new Promise<never>(() => {\n /* parked for the daemon's lifetime */\n }),\n };\n}\n","import { readAgentVersion } from \"../runner/session-runner-helpers.js\";\nimport { reportBootMilestone } from \"../setup/boot-milestone.js\";\nimport { oomWatchdogOptionsFromEnv } from \"../workbench/oom-watchdog.js\";\nimport { workbenchPort } from \"../workbench/mode.js\";\nimport { DEFAULT_WORKBENCH_PORT, type GitStatusFrame } from \"../workbench/protocol.js\";\nimport { GitPrepJob, type GitPrepState } from \"./git-prep.js\";\nimport type { BootContext } from \"./types.js\";\nimport { defaultWorkbenchBootDeps, type WorkbenchBootDeps } from \"./workbench-boot-deps.js\";\n\n/**\n * The workbench container's vnext boot: acquire the bundle, start the daemon\n * (whose listen socket IS the pod's startupProbe), then run everything else —\n * git prep, user-home wiring, sshd, ownership reclaim — strictly after the\n * probe can pass. Replaces the bash entrypoint's workbench tail\n * (entrypoint.sh:835-899): the marker files become the GitPrepJob state\n * served over the daemon's `gitStatus` op.\n *\n * Task 9 composes this through runPipeline; every step is injectable via the\n * deps object (`workbench-boot-deps.ts` carries the production defaults) so\n * tests run without network, sockets, or real git.\n */\n\nexport type { WorkbenchBootDeps } from \"./workbench-boot-deps.js\";\n\nexport function toGitStatusFrame(state: GitPrepState): GitStatusFrame {\n return state.state === \"failed\"\n ? { t: \"gitStatus\", state: \"failed\", reason: state.reason }\n : { t: \"gitStatus\", state: state.state };\n}\n\nfunction warnErr(ctx: BootContext, label: string): (err: unknown) => void {\n return (err) => {\n ctx.log.warn(\n `[boot] WARN ${label} failed: ${err instanceof Error ? err.message : String(err)}`,\n );\n };\n}\n\nexport async function runWorkbenchBoot(\n ctx: BootContext,\n overrides: Partial<WorkbenchBootDeps> = {},\n): Promise<never> {\n const deps: WorkbenchBootDeps = { ...defaultWorkbenchBootDeps(), ...overrides };\n // ── CRITICAL PATH ──────────────────────────────────────────────────────\n // The daemon's listen socket is the pod's startupProbe — it gates both the\n // agent container start and Provisioning→Running. Nothing may run before\n // it besides bundle acquisition and env unpack.\n //\n // The poll's 401 (BootstrapUnauthorizedError) propagates — fatal-loud.\n const bundle = await deps.pollBundle(ctx);\n if (!bundle) {\n // Aborted during standby (SIGTERM). Task 9's signal wiring owns process\n // exit; parking preserves the Promise<never> contract meanwhile.\n ctx.log.info(\"[boot] standby poll aborted — parking for shutdown\");\n return deps.park(ctx);\n }\n ctx.bundle = bundle;\n ctx.childEnv = deps.unpack(ctx, bundle);\n const job = new GitPrepJob(deps.gitPrepDeps(ctx, bundle), {\n // Order pinned by the old entrypoint-grimoire test: binds BEFORE the\n // ready flip (Claude must not spawn before skills exist), references after.\n onReady: () => {\n // Must still return this promise — GitPrepJob awaits onReady() before\n // flipping status to \"ready\" (the daemon's gitStatus gate), so binds\n // completing before the ready flip is preserved. The milestone report\n // is fire-and-forget and never delays that flip.\n const binds = deps.bindsBeforeReady(ctx, bundle);\n void reportBootMilestone({ key: \"repo_synced\" });\n return binds;\n },\n afterReady: () => deps.cloneReferences(ctx, bundle),\n });\n await deps.startDaemon({\n port: workbenchPort() ?? DEFAULT_WORKBENCH_PORT,\n token: ctx.bootstrapToken,\n version: readAgentVersion() ?? \"unknown\",\n getGitStatus: () => toGitStatusFrame(job.status),\n oomWatchdog: oomWatchdogOptionsFromEnv(ctx.log),\n });\n ctx.log.info(\"[boot] step=startDaemon listen socket up (startupProbe gate open)\");\n void reportBootMilestone({ key: \"workbench_ready\" });\n\n // ── DEFERRED (post-listen) ─────────────────────────────────────────────\n // Everything below runs after the probe can pass; each degrades alone.\n // Ensure the repo cwd exists BEFORE the git prep job — the no-git-plan path\n // returns ready without creating it, and the daemon serves shell cwd from here.\n try {\n deps.ensureWorkspaceDir(ctx);\n } catch (err) {\n warnErr(ctx, \"ensureWorkspaceDir\")(err);\n }\n job.start();\n try {\n deps.wireHome(ctx);\n } catch (err) {\n warnErr(ctx, \"wireUserHome\")(err);\n }\n void deps.startSshd(ctx).catch(warnErr(ctx, \"startWorkspaceSshd\"));\n void deps.reclaimOwnership(ctx).catch(warnErr(ctx, \"reclaimHomeOwnership\"));\n return deps.park(ctx);\n}\n"],"mappings":"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","names":["statSync","appendFileSync","appendFileSync","sleepUnlessAborted","statSync","writeFileSync","join","chmodSync","copyFileSync","existsSync","mkdirSync","join","appendFileSync","existsSync","lstatSync","mkdirSync","readdirSync","readFileSync","rmSync","statSync","symlinkSync","join","isDir","statSync","lstatSync","existsSync","join","mkdirSync","symlinkSync","readFileSync","appendFileSync","readdirSync","rmSync","USER_HOME_MOUNT","HOME_DIR","USER_HOME_MOUNT","statSync","whenAborted"]}
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/workbench/mode.ts"],"sourcesContent":["/**\n * Split-mode detection. The pod spec sets CONVEYOR_WORKBENCH_PORT (+ role,\n * shared dir, bootstrap token) on both containers when the workbench split is\n * enabled for the project; with the env absent, every routing seam falls back\n * to today's local behavior — monolith pods, codespaces, and local dev are\n * byte-identical to the pre-split code paths.\n */\n\nexport function workbenchPort(): number | null {\n const raw = process.env.CONVEYOR_WORKBENCH_PORT;\n if (!raw) return null;\n const port = Number(raw);\n return Number.isInteger(port) && port > 0 ? port : null;\n}\n\nexport function workbenchEnabled(): boolean {\n // The workbench container itself must never route back into the launcher —\n // its supervisor/setup code (if any ever runs there) executes locally.\n if (process.env.CONVEYOR_MODE === \"workbench\") return false;\n return workbenchPort() !== null;\n}\n\n/** Shared secret for the launcher. The pod bootstrap token is already present\n * in both containers' env; CONVEYOR_WORKBENCH_TOKEN wins for tests. */\nexport function workbenchToken(): string {\n return process.env.CONVEYOR_WORKBENCH_TOKEN ?? process.env.POD_BOOTSTRAP_TOKEN ?? \"\";\n}\n\n/** Shared emptyDir visible to both containers (hook sockets, PTY temp dirs,\n * git-ready markers). Null outside split mode. */\nexport function sharedDir(): string | null {\n return process.env.CONVEYOR_SHARED_DIR ?? null;\n}\n"],"mappings":";AAQO,SAAS,gBAA+B;AAC7C,QAAM,MAAM,QAAQ,IAAI;AACxB,MAAI,CAAC,IAAK,QAAO;AACjB,QAAM,OAAO,OAAO,GAAG;AACvB,SAAO,OAAO,UAAU,IAAI,KAAK,OAAO,IAAI,OAAO;AACrD;AAEO,SAAS,mBAA4B;AAG1C,MAAI,QAAQ,IAAI,kBAAkB,YAAa,QAAO;AACtD,SAAO,cAAc,MAAM;AAC7B;AAIO,SAAS,iBAAyB;AACvC,SAAO,QAAQ,IAAI,4BAA4B,QAAQ,IAAI,uBAAuB;AACpF;AAIO,SAAS,YAA2B;AACzC,SAAO,QAAQ,IAAI,uBAAuB;AAC5C;","names":[]}
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../../shared/dist/socket-core/index.js","../src/connection/loop-lag.ts"],"sourcesContent":["// src/socket-core/socket-options.ts\nfunction buildConveyorSocketOptions(auth) {\n return {\n auth,\n transports: [\"websocket\"],\n reconnection: true,\n reconnectionAttempts: Infinity,\n reconnectionDelay: 2e3,\n reconnectionDelayMax: 3e4,\n randomizationFactor: 0.3,\n extraHeaders: { \"ngrok-skip-browser-warning\": \"true\" }\n };\n}\n\n// src/socket-core/call-with-ack.ts\nfunction callWithAck(socket, event, payload, options) {\n const { timeoutMs, requireData = false, makeTimeoutError, makeFailureError } = options;\n return new Promise((resolve, reject) => {\n let settled = false;\n const timer = setTimeout(() => {\n if (settled) return;\n settled = true;\n reject(makeTimeoutError());\n }, timeoutMs);\n socket.emit(event, payload, (response) => {\n if (settled) return;\n settled = true;\n clearTimeout(timer);\n if (response.success && (!requireData || response.data !== void 0)) {\n resolve(response.data);\n } else {\n reject(makeFailureError(response.error));\n }\n });\n });\n}\nfunction waitForConnected(socket, timeoutMs, makeTimeoutError) {\n if (socket.connected) return Promise.resolve();\n return new Promise((resolve, reject) => {\n const cleanup = () => {\n clearTimeout(timer);\n socket.off(\"connect\", onConnect);\n };\n const onConnect = () => {\n cleanup();\n resolve();\n };\n const timer = setTimeout(() => {\n cleanup();\n reject(makeTimeoutError());\n }, timeoutMs);\n socket.once(\"connect\", onConnect);\n });\n}\nexport {\n buildConveyorSocketOptions,\n callWithAck,\n waitForConnected\n};\n","/**\n * Main event-loop lag measurement, shared with the heartbeat worker.\n *\n * The monitor owns a SharedArrayBuffer the main thread refreshes on a fast\n * timer. Two consumers read it:\n * - the MAIN-loop heartbeat reports `takeMaxLagMs()` — the worst stall\n * observed since the last beat (post-hoc: by the time the heartbeat timer\n * fires after a stall, the refresher has already run, so instantaneous\n * lag would read ~0);\n * - the heartbeat WORKER reads the buffer live from its own thread, where a\n * stalled main loop shows up as a stale tick — that is the signal that\n * keeps the session lease alive while the main loop is starved.\n *\n * Layout (Float64Array): [0] = last main-loop tick (epoch ms),\n * [1] = status index into LOOP_STATUS_VALUES (what the worker should report).\n */\n\nexport const LOOP_TICK_INTERVAL_MS = 250;\n/** Stalls at or above this are logged (and remembered for the next beat). */\nexport const LOOP_STALL_LOG_MS = 1_000;\n\n/** Append-only: the index into this array is what crosses the SharedArrayBuffer\n * to the heartbeat worker, so reordering would silently remap live statuses. */\nexport const LOOP_STATUS_VALUES = [\"active\", \"idle\", \"building\", \"waiting\"] as const;\nexport type LoopStatus = (typeof LOOP_STATUS_VALUES)[number];\n\n/** What a `LoopStatus` reports as on the wire (`AgentHeartbeat.status`).\n *\n * `waiting` — the runner's turn is over but background work it launched is\n * still running in the pod (see runner/background-work.ts) — reports as\n * `active`, deliberately:\n * - The API's `AgentHeartbeatSchema` enum is `active | idle | building`. A\n * newer agent sending a value an older API doesn't know would have its\n * heartbeat REJECTED, which stops renewing the session lease — strictly\n * worse than the idle-sleep bug this whole mechanism exists to fix.\n * - `active` is also the semantically correct answer for the one consumer\n * that matters: `heartbeatBumps` (apps/api/src/services/workspace/\n * activity-clock.ts) is `status !== \"idle\"`, so the workspace activity\n * clock stays fresh and the reconciler can't sleep the pod mid-gate. */\nexport function heartbeatStatusFor(status: LoopStatus): \"active\" | \"idle\" | \"building\" {\n return status === \"waiting\" ? \"active\" : status;\n}\n\n/**\n * The runner states (`AgentRunnerStatus`) that mean the agent is actually doing\n * work in the pod. Everything else is a state where the pod is burning compute\n * with nothing running in it.\n *\n * `waiting_for_input` is deliberately NOT here. It is a prefilled-but-\n * unsubmitted Connected TUI (or an agent question) parked on a HUMAN — the CLI\n * process is alive and repainting, but no turn is in flight. Reporting it as\n * `active` renewed `Workspace.activityExpiresAt` on every 30s beat, so a card\n * whose agent was waiting on a person never idled out and stayed \"active\" on\n * the board until the runner's own 30-minute idle timer fired, overriding the\n * project's (much shorter) inactivity window. A human who comes back wakes the\n * card the normal way — a chat post or `ensurePrimaryWorkspace` intent write\n * beats the clock outright — and live keystrokes in the terminal bump it\n * directly (agent-session-service `bumpPtyInputActivity`).\n */\nconst WORKING_RUNNER_STATUSES: ReadonlySet<string> = new Set([\n \"connecting\",\n \"connected\",\n \"setup\",\n \"fetching_context\",\n \"running\",\n]);\n\n/** Runner states where the pod is parked: the process is alive, nothing runs. */\nconst RESTING_RUNNER_STATUSES: ReadonlySet<string> = new Set([\n \"idle\",\n \"waiting_for_input\",\n \"stopping\",\n \"finished\",\n \"disconnected\",\n \"error\",\n]);\n\n/**\n * Classify an `AgentRunnerStatus` (taken as a plain string so this module stays\n * dependency-free) into the loop status its heartbeats should carry.\n *\n * FAIL OPEN: an unrecognized status reports `active`. A status this classifier\n * has never heard of — a newer runner state, a value from an older build — must\n * keep the pod alive, because over-filtering reaps a pod that is genuinely\n * working, which is strictly worse than a pod that sleeps late.\n */\nexport function loopStatusForRunnerStatus(status: string | null | undefined): LoopStatus {\n if (status === null || status === undefined) return \"idle\";\n if (WORKING_RUNNER_STATUSES.has(status)) return \"active\";\n // Known-parked states — idle, waiting_for_input, stopping, finished,\n // disconnected, error — and only those, report idle.\n if (RESTING_RUNNER_STATUSES.has(status)) return \"idle\";\n return \"active\";\n}\n\n/** Live lag as seen from ANY thread holding the buffer: how stale the main\n * loop's tick is beyond its refresh interval. ~0 while the loop is healthy. */\nexport function lagFromBuffer(view: Float64Array): number {\n const last = view[0];\n if (!last) return 0;\n return Math.max(0, Date.now() - last - LOOP_TICK_INTERVAL_MS);\n}\n\nexport function statusFromBuffer(view: Float64Array): LoopStatus {\n return LOOP_STATUS_VALUES[view[1]] ?? \"active\";\n}\n\nexport class LoopLagMonitor {\n readonly sharedBuffer = new SharedArrayBuffer(2 * Float64Array.BYTES_PER_ELEMENT);\n private readonly view = new Float64Array(this.sharedBuffer);\n private timer: NodeJS.Timeout | null = null;\n private maxLagMs = 0;\n\n start(): void {\n if (this.timer) return;\n this.view[0] = Date.now();\n this.timer = setInterval(() => {\n const now = Date.now();\n const lag = Math.max(0, now - this.view[0] - LOOP_TICK_INTERVAL_MS);\n if (lag >= LOOP_STALL_LOG_MS) {\n this.maxLagMs = Math.max(this.maxLagMs, lag);\n process.stderr.write(\n `[conveyor-agent] event-loop stall: ${Math.round(lag)}ms (${new Date().toISOString()})\\n`,\n );\n }\n this.view[0] = now;\n }, LOOP_TICK_INTERVAL_MS);\n // Never keep the process alive just to measure it.\n this.timer.unref();\n }\n\n stop(): void {\n if (this.timer) clearInterval(this.timer);\n this.timer = null;\n }\n\n /** Mirror the runner's current status for the worker to report. */\n setStatus(status: LoopStatus): void {\n const idx = LOOP_STATUS_VALUES.indexOf(status);\n this.view[1] = idx === -1 ? 0 : idx;\n }\n\n /** Worst stall observed since the previous call (then reset). Includes the\n * live lag so a beat sent mid-stall from this thread — possible when the\n * heartbeat timer fires before the refresher in the same tick batch — is\n * still truthful. */\n takeMaxLagMs(): number {\n const max = Math.max(this.maxLagMs, lagFromBuffer(this.view));\n this.maxLagMs = 0;\n return max;\n }\n}\n"],"mappings":";AACA,SAAS,2BAA2B,MAAM;AACxC,SAAO;AAAA,IACL;AAAA,IACA,YAAY,CAAC,WAAW;AAAA,IACxB,cAAc;AAAA,IACd,sBAAsB;AAAA,IACtB,mBAAmB;AAAA,IACnB,sBAAsB;AAAA,IACtB,qBAAqB;AAAA,IACrB,cAAc,EAAE,8BAA8B,OAAO;AAAA,EACvD;AACF;AAGA,SAAS,YAAY,QAAQ,OAAO,SAAS,SAAS;AACpD,QAAM,EAAE,WAAW,cAAc,OAAO,kBAAkB,iBAAiB,IAAI;AAC/E,SAAO,IAAI,QAAQ,CAAC,SAAS,WAAW;AACtC,QAAI,UAAU;AACd,UAAM,QAAQ,WAAW,MAAM;AAC7B,UAAI,QAAS;AACb,gBAAU;AACV,aAAO,iBAAiB,CAAC;AAAA,IAC3B,GAAG,SAAS;AACZ,WAAO,KAAK,OAAO,SAAS,CAAC,aAAa;AACxC,UAAI,QAAS;AACb,gBAAU;AACV,mBAAa,KAAK;AAClB,UAAI,SAAS,YAAY,CAAC,eAAe,SAAS,SAAS,SAAS;AAClE,gBAAQ,SAAS,IAAI;AAAA,MACvB,OAAO;AACL,eAAO,iBAAiB,SAAS,KAAK,CAAC;AAAA,MACzC;AAAA,IACF,CAAC;AAAA,EACH,CAAC;AACH;AACA,SAAS,iBAAiB,QAAQ,WAAW,kBAAkB;AAC7D,MAAI,OAAO,UAAW,QAAO,QAAQ,QAAQ;AAC7C,SAAO,IAAI,QAAQ,CAAC,SAAS,WAAW;AACtC,UAAM,UAAU,MAAM;AACpB,mBAAa,KAAK;AAClB,aAAO,IAAI,WAAW,SAAS;AAAA,IACjC;AACA,UAAM,YAAY,MAAM;AACtB,cAAQ;AACR,cAAQ;AAAA,IACV;AACA,UAAM,QAAQ,WAAW,MAAM;AAC7B,cAAQ;AACR,aAAO,iBAAiB,CAAC;AAAA,IAC3B,GAAG,SAAS;AACZ,WAAO,KAAK,WAAW,SAAS;AAAA,EAClC,CAAC;AACH;;;ACpCO,IAAM,wBAAwB;AAE9B,IAAM,oBAAoB;AAI1B,IAAM,qBAAqB,CAAC,UAAU,QAAQ,YAAY,SAAS;AAgBnE,SAAS,mBAAmB,QAAoD;AACrF,SAAO,WAAW,YAAY,WAAW;AAC3C;AAkBA,IAAM,0BAA+C,oBAAI,IAAI;AAAA,EAC3D;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,CAAC;AAGD,IAAM,0BAA+C,oBAAI,IAAI;AAAA,EAC3D;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,CAAC;AAWM,SAAS,0BAA0B,QAA+C;AACvF,MAAI,WAAW,QAAQ,WAAW,OAAW,QAAO;AACpD,MAAI,wBAAwB,IAAI,MAAM,EAAG,QAAO;AAGhD,MAAI,wBAAwB,IAAI,MAAM,EAAG,QAAO;AAChD,SAAO;AACT;AAIO,SAAS,cAAc,MAA4B;AACxD,QAAM,OAAO,KAAK,CAAC;AACnB,MAAI,CAAC,KAAM,QAAO;AAClB,SAAO,KAAK,IAAI,GAAG,KAAK,IAAI,IAAI,OAAO,qBAAqB;AAC9D;AAEO,SAAS,iBAAiB,MAAgC;AAC/D,SAAO,mBAAmB,KAAK,CAAC,CAAC,KAAK;AACxC;AAEO,IAAM,iBAAN,MAAqB;AAAA,EACjB,eAAe,IAAI,kBAAkB,IAAI,aAAa,iBAAiB;AAAA,EAC/D,OAAO,IAAI,aAAa,KAAK,YAAY;AAAA,EAClD,QAA+B;AAAA,EAC/B,WAAW;AAAA,EAEnB,QAAc;AACZ,QAAI,KAAK,MAAO;AAChB,SAAK,KAAK,CAAC,IAAI,KAAK,IAAI;AACxB,SAAK,QAAQ,YAAY,MAAM;AAC7B,YAAM,MAAM,KAAK,IAAI;AACrB,YAAM,MAAM,KAAK,IAAI,GAAG,MAAM,KAAK,KAAK,CAAC,IAAI,qBAAqB;AAClE,UAAI,OAAO,mBAAmB;AAC5B,aAAK,WAAW,KAAK,IAAI,KAAK,UAAU,GAAG;AAC3C,gBAAQ,OAAO;AAAA,UACb,sCAAsC,KAAK,MAAM,GAAG,CAAC,QAAO,oBAAI,KAAK,GAAE,YAAY,CAAC;AAAA;AAAA,QACtF;AAAA,MACF;AACA,WAAK,KAAK,CAAC,IAAI;AAAA,IACjB,GAAG,qBAAqB;AAExB,SAAK,MAAM,MAAM;AAAA,EACnB;AAAA,EAEA,OAAa;AACX,QAAI,KAAK,MAAO,eAAc,KAAK,KAAK;AACxC,SAAK,QAAQ;AAAA,EACf;AAAA;AAAA,EAGA,UAAU,QAA0B;AAClC,UAAM,MAAM,mBAAmB,QAAQ,MAAM;AAC7C,SAAK,KAAK,CAAC,IAAI,QAAQ,KAAK,IAAI;AAAA,EAClC;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,eAAuB;AACrB,UAAM,MAAM,KAAK,IAAI,KAAK,UAAU,cAAc,KAAK,IAAI,CAAC;AAC5D,SAAK,WAAW;AAChB,WAAO;AAAA,EACT;AACF;","names":[]}
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/boot/tombstone.ts"],"sourcesContent":["/**\n * Legacy-launch tombstone — a LEAF module (no boot-graph imports) so cli.ts\n * can evaluate the guard without eagerly loading the whole boot composition\n * (workbench server, git prep, supervisor…) on every legacy/child invocation.\n */\nexport const TOMBSTONE_MESSAGE =\n \"FATAL: image predates boot vnext — rebake this project (legacy bash entrypoint launch detected)\";\n\n/**\n * A pod (CLAUDESPACE_NAME) whose process never went through the vnext boot\n * handshake (buildChildEnv stamps CONVEYOR_BOOT_VNEXT=1 on every child). True\n * ⟺ an old image's bash entrypoint launched `conveyor-agent` directly.\n * Codespaces (CODESPACE_NAME) and local runs have no CLAUDESPACE_NAME → false.\n */\nexport function isLegacyEntrypointLaunch(env: NodeJS.ProcessEnv): boolean {\n return Boolean(env.CLAUDESPACE_NAME) && env.CONVEYOR_BOOT_VNEXT !== \"1\";\n}\n"],"mappings":";AAKO,IAAM,oBACX;AAQK,SAAS,yBAAyB,KAAiC;AACxE,SAAO,QAAQ,IAAI,gBAAgB,KAAK,IAAI,wBAAwB;AACtE;","names":[]}
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/workbench/server.ts"],"sourcesContent":["/**\n * The workbench launcher — PID 1 of the workbench container in split-mode\n * pods (CONVEYOR_MODE=workbench). A token-authed, loopback-only exec daemon:\n * the agent container drives every workspace-touching operation through it\n * (shell commands, the claude PTY, git, file reads) while staying insulated\n * from workload OOMs in its own container cgroup.\n *\n * One connection = one operation. A dropped connection kills the operation's\n * process (group) — the agent dying, restarting, or timing out must never\n * leave orphans accumulating against the workbench memory budget.\n */\n\nimport { spawn, type ChildProcess } from \"node:child_process\";\nimport { readdir, readFile, stat } from \"node:fs/promises\";\nimport { createServer, type Socket } from \"node:net\";\nimport { terminateProcessGroup } from \"../setup/commands.js\";\nimport { loadPtySpawn, type PtyProcess } from \"../harness/pty/pty-support.js\";\nimport {\n startOomWatchdog,\n type OomKillEvent,\n type OomWatchdogHandle,\n type OomWatchdogOptions,\n} from \"./oom-watchdog.js\";\nimport {\n FrameReader,\n timingSafeTokenEqual,\n writeFrame,\n type GitStatusFrame,\n type WorkbenchFrame,\n type WorkbenchRequest,\n} from \"./protocol.js\";\n\nconst READ_CHUNK_BYTES = 64 * 1024;\n\nexport interface WorkbenchServerOptions {\n port: number;\n token: string;\n version: string;\n /**\n * Live view of the workbench's background git preparation (boot/git-prep\n * GitPrepJob). Absent → the `gitStatus` op answers `ready`: non-pod servers\n * (tests, local dev, future embedders) have no git prep to wait for, and a\n * daemon that answered `pending` forever would wedge every caller gating\n * Claude spawn on git readiness.\n */\n getGitStatus?: () => GitStatusFrame;\n /**\n * Arm the early-OOM watchdog (see oom-watchdog.ts). Opt-in: production pod\n * boots pass it; tests and embedders that don't want a process killer\n * polling their cgroup simply omit it. The server supplies the kill\n * reporting (`onKill`) — a kill lands on the victim operation's own\n * connection as an stderr/data frame, so the agent transcript shows WHAT\n * died and why instead of a silent whole-container OOM.\n */\n oomWatchdog?: Omit<OomWatchdogOptions, \"onKill\">;\n}\n\nexport interface WorkbenchServerHandle {\n port: number;\n close(): Promise<void>;\n /** Write the OOM-kill notice to the victim pgid's owning connection, if the\n * group belongs to a live exec/pty operation. Returns whether it did.\n * Called by the watchdog just before the SIGKILL; exposed for tests. */\n notifyOomKill(event: OomKillEvent): boolean;\n /** Snapshot of the live exec/pty operations, keyed by process-group id. */\n liveOps(): Array<{ pgid: number; kind: \"exec\" | \"pty\"; label: string }>;\n}\n\n/** A live exec/pty operation: the direct child is its own process-group\n * leader (detached spawn / pty setsid), so its pid doubles as the pgid the\n * watchdog scores and kills. */\ninterface LiveOp {\n kind: \"exec\" | \"pty\";\n label: string;\n socket: Socket;\n}\n\ntype LiveOps = Map<number, LiveOp>;\n\nfunction oomKillMessage(event: OomKillEvent, label: string): string {\n const mib = (n: number): number => Math.round(n / 1048576);\n return (\n `\\n[workbench] OOM watchdog killed this process group (${label}, rss ~${mib(event.victimRssBytes)}MiB); ` +\n `container memory ${mib(event.usedBytes)}/${mib(event.limitBytes)}MiB, reason=${event.reason}. ` +\n `Killing the largest workload keeps the workbench container (and every other process in it) alive.\\n`\n );\n}\n\nfunction errCode(err: unknown): string | undefined {\n return typeof (err as { code?: unknown })?.code === \"string\"\n ? (err as { code: string }).code\n : undefined;\n}\n\nfunction sendError(socket: Socket, err: unknown): void {\n const frame: WorkbenchFrame = {\n t: \"error\",\n message: err instanceof Error ? err.message : String(err),\n ...(errCode(err) ? { code: errCode(err) } : {}),\n };\n writeFrame(socket, frame);\n}\n\n/** Merge request env over the daemon's own (mirrors the local spawn paths,\n * which inherit process.env and let callers override). */\nfunction mergedEnv(overrides?: Record<string, string>): NodeJS.ProcessEnv {\n return overrides ? { ...process.env, ...overrides } : { ...process.env };\n}\n\n/**\n * Post-dispatch inbound frames for the connection's operation. Handlers\n * assign `onNext`; frames arriving before assignment are queued so an input\n * racing the (async) pty spawn is never dropped.\n */\nclass FrameSink {\n private pending: WorkbenchFrame[] = [];\n private handler: ((frame: WorkbenchFrame) => void) | null = null;\n\n push(frame: WorkbenchFrame): void {\n if (this.handler) this.handler(frame);\n else this.pending.push(frame);\n }\n\n setHandler(handler: (frame: WorkbenchFrame) => void): void {\n this.handler = handler;\n for (const frame of this.pending.splice(0)) handler(frame);\n }\n}\n\nexport async function startWorkbenchServer(\n opts: WorkbenchServerOptions,\n): Promise<WorkbenchServerHandle> {\n const liveOps: LiveOps = new Map();\n const server = createServer((socket) => handleConnection(socket, opts, liveOps));\n await new Promise<void>((resolve, reject) => {\n server.once(\"error\", reject);\n server.listen(opts.port, \"127.0.0.1\", () => resolve());\n });\n const address = server.address();\n const port = typeof address === \"object\" && address ? address.port : opts.port;\n const notifyOomKill = (event: OomKillEvent): boolean => {\n const op = liveOps.get(event.pgid);\n if (!op) return false;\n const message = Buffer.from(oomKillMessage(event, op.label), \"utf8\").toString(\"base64\");\n writeFrame(\n op.socket,\n op.kind === \"exec\" ? { t: \"out\", s: \"stderr\", d: message } : { t: \"data\", d: message },\n );\n return true;\n };\n let watchdog: OomWatchdogHandle | null = null;\n if (opts.oomWatchdog) {\n watchdog = startOomWatchdog({ ...opts.oomWatchdog, onKill: notifyOomKill });\n }\n return {\n port,\n notifyOomKill,\n liveOps: () => Array.from(liveOps, ([pgid, op]) => ({ pgid, kind: op.kind, label: op.label })),\n close: () => {\n watchdog?.stop();\n return new Promise<void>((resolve) => {\n server.close(() => resolve());\n });\n },\n };\n}\n\nfunction handleConnection(socket: Socket, opts: WorkbenchServerOptions, liveOps: LiveOps): void {\n socket.on(\"error\", () => undefined);\n const sink = new FrameSink();\n let dispatched = false;\n const reader = new FrameReader((frame) => {\n if (dispatched) {\n sink.push(frame as unknown as WorkbenchFrame);\n return;\n }\n dispatched = true;\n dispatch(socket, frame as unknown as WorkbenchRequest, sink, opts, liveOps);\n });\n socket.on(\"data\", (chunk: Buffer) => reader.push(chunk));\n}\n\nfunction dispatch(\n socket: Socket,\n req: WorkbenchRequest,\n sink: FrameSink,\n opts: WorkbenchServerOptions,\n liveOps: LiveOps,\n): void {\n if (!req || typeof req !== \"object\" || !timingSafeTokenEqual(req.token ?? \"\", opts.token)) {\n writeFrame(socket, { t: \"error\", message: \"unauthorized\", code: \"unauthorized\" });\n socket.destroy();\n return;\n }\n switch (req.op) {\n case \"ping\":\n writeFrame(socket, { t: \"pong\", version: opts.version });\n socket.end();\n return;\n case \"gitStatus\":\n // No provider → default ready (see WorkbenchServerOptions.getGitStatus).\n writeFrame(socket, opts.getGitStatus?.() ?? { t: \"gitStatus\", state: \"ready\" });\n socket.end();\n return;\n case \"exec\":\n runExec(socket, req, sink, liveOps);\n return;\n case \"pty\":\n void runPty(socket, req, sink, liveOps);\n return;\n case \"readFile\":\n void runReadFile(socket, req);\n return;\n case \"stat\":\n void runStat(socket, req);\n return;\n case \"readdir\":\n void runReaddir(socket, req);\n return;\n default:\n writeFrame(socket, { t: \"error\", message: \"unknown op\", code: \"bad_request\" });\n socket.end();\n }\n}\n\nfunction runExec(\n socket: Socket,\n req: Extract<WorkbenchRequest, { op: \"exec\" }>,\n sink: FrameSink,\n liveOps: LiveOps,\n): void {\n let child: ChildProcess;\n try {\n // Argv form (git and friends) spawns directly with no shell; shell form\n // (setup/start commands) mirrors the local `spawn(\"sh\", [\"-c\", cmd])`.\n const [argvFile, ...argvRest] = req.argv ?? [];\n child = argvFile\n ? spawn(argvFile, argvRest, {\n cwd: req.cwd,\n stdio: [\"ignore\", \"pipe\", \"pipe\"],\n detached: true,\n env: mergedEnv(req.env),\n })\n : spawn(\"sh\", [\"-c\", req.command ?? \"\"], {\n cwd: req.cwd,\n stdio: [\"ignore\", \"pipe\", \"pipe\"],\n detached: true,\n env: mergedEnv(req.env),\n });\n } catch (err) {\n sendError(socket, err);\n socket.end();\n return;\n }\n\n if (child.pid) {\n liveOps.set(child.pid, {\n kind: \"exec\",\n label: (req.argv?.join(\" \") ?? req.command ?? \"\").slice(0, 120),\n socket,\n });\n }\n let settled = false;\n sink.setHandler((frame) => {\n if (frame.t === \"signal\" && frame.mode === \"term-group\" && !settled) {\n void terminateProcessGroup(child);\n }\n });\n child.stdout?.on(\"data\", (chunk: Buffer) => {\n writeFrame(socket, { t: \"out\", s: \"stdout\", d: chunk.toString(\"base64\") });\n });\n child.stderr?.on(\"data\", (chunk: Buffer) => {\n writeFrame(socket, { t: \"out\", s: \"stderr\", d: chunk.toString(\"base64\") });\n });\n child.on(\"close\", (code, signal) => {\n settled = true;\n if (child.pid) liveOps.delete(child.pid);\n writeFrame(socket, { t: \"exit\", code, signal });\n socket.end();\n });\n child.on(\"error\", (err) => {\n if (settled) return;\n settled = true;\n sendError(socket, err);\n socket.end();\n });\n // Agent gone (crash, timeout-abort, container death) → no orphans.\n socket.on(\"close\", () => {\n if (!settled) void terminateProcessGroup(child);\n });\n}\n\nasync function runPty(\n socket: Socket,\n req: Extract<WorkbenchRequest, { op: \"pty\" }>,\n sink: FrameSink,\n liveOps: LiveOps,\n): Promise<void> {\n let pty: PtyProcess;\n try {\n const ptySpawn = await loadPtySpawn();\n pty = ptySpawn(req.file, req.args, {\n name: \"xterm-color\",\n cols: req.cols,\n rows: req.rows,\n cwd: req.cwd,\n env: req.env,\n });\n } catch (err) {\n sendError(socket, err);\n socket.end();\n return;\n }\n\n // node-pty children get their own session (forkpty → setsid), so pid==pgid.\n const ptyPid = pty.pid;\n if (ptyPid !== undefined) {\n liveOps.set(ptyPid, {\n kind: \"pty\",\n label: [req.file, ...req.args].join(\" \").slice(0, 120),\n socket,\n });\n }\n let exited = false;\n sink.setHandler((frame) => {\n try {\n if (frame.t === \"input\" && typeof frame.d === \"string\") {\n pty.write(Buffer.from(frame.d, \"base64\").toString(\"utf8\"));\n } else if (\n frame.t === \"resize\" &&\n typeof frame.cols === \"number\" &&\n typeof frame.rows === \"number\"\n ) {\n pty.resize(frame.cols, frame.rows);\n } else if (frame.t === \"kill\") {\n pty.kill(typeof frame.sig === \"string\" ? frame.sig : undefined);\n }\n } catch {\n /* pty already dead — exit handler owns teardown */\n }\n });\n pty.onData((data) => {\n writeFrame(socket, { t: \"data\", d: Buffer.from(data, \"utf8\").toString(\"base64\") });\n });\n pty.onExit((event) => {\n exited = true;\n if (ptyPid !== undefined) liveOps.delete(ptyPid);\n writeFrame(socket, { t: \"exit\", code: event.exitCode, signal: null });\n socket.end();\n });\n socket.on(\"close\", () => {\n if (!exited) {\n try {\n pty.kill();\n } catch {\n /* already dead */\n }\n }\n });\n}\n\nasync function runReadFile(\n socket: Socket,\n req: Extract<WorkbenchRequest, { op: \"readFile\" }>,\n): Promise<void> {\n try {\n const content = await readFile(req.path);\n for (let offset = 0; offset < content.length; offset += READ_CHUNK_BYTES) {\n writeFrame(socket, {\n t: \"data\",\n d: content.subarray(offset, offset + READ_CHUNK_BYTES).toString(\"base64\"),\n });\n }\n writeFrame(socket, { t: \"end\" });\n } catch (err) {\n sendError(socket, err);\n }\n socket.end();\n}\n\nasync function runStat(\n socket: Socket,\n req: Extract<WorkbenchRequest, { op: \"stat\" }>,\n): Promise<void> {\n try {\n const s = await stat(req.path);\n writeFrame(socket, {\n t: \"stat\",\n exists: true,\n isFile: s.isFile(),\n isDirectory: s.isDirectory(),\n size: s.size,\n mtimeMs: s.mtimeMs,\n });\n } catch (err) {\n if (errCode(err) === \"ENOENT\") {\n writeFrame(socket, {\n t: \"stat\",\n exists: false,\n isFile: false,\n isDirectory: false,\n size: 0,\n mtimeMs: 0,\n });\n } else {\n sendError(socket, err);\n }\n }\n socket.end();\n}\n\nasync function runReaddir(\n socket: Socket,\n req: Extract<WorkbenchRequest, { op: \"readdir\" }>,\n): Promise<void> {\n try {\n writeFrame(socket, { t: \"entries\", names: await readdir(req.path) });\n } catch (err) {\n sendError(socket, err);\n }\n socket.end();\n}\n"],"mappings":"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","names":[]}
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/workbench/protocol.ts"],"sourcesContent":["/**\n * Wire protocol for the workbench launcher — the loopback control channel\n * between the protected agent container and the restartable workbench\n * container (see docs/superpowers/specs/2026-07-16-workbench-container-split-design.md).\n *\n * Newline-delimited JSON frames over a plain TCP socket. Every connection\n * carries exactly ONE operation: the first client frame is a WorkbenchRequest\n * (with the shared token); everything after is a WorkbenchFrame in either\n * direction. Binary payloads (pty bytes, file contents) ride base64 `d`\n * fields — intra-pod loopback makes the 33% inflation cheap,\n * and NDJSON keeps the framing trivially robust across partial reads.\n */\n\nimport crypto from \"node:crypto\";\nimport type { Socket } from \"node:net\";\n\n/** First frame of every connection (client → server). */\nexport type WorkbenchRequest =\n | { op: \"ping\"; token: string }\n | {\n op: \"exec\";\n token: string;\n /** Shell form — spawned as `sh -c command` (setup/start commands). */\n command?: string;\n /** Argv form — spawned directly, no shell (git and friends). */\n argv?: string[];\n cwd: string;\n env?: Record<string, string>;\n }\n | {\n op: \"pty\";\n token: string;\n file: string;\n args: string[];\n cwd: string;\n env: Record<string, string>;\n cols: number;\n rows: number;\n }\n | { op: \"readFile\"; token: string; path: string }\n | { op: \"stat\"; token: string; path: string }\n | { op: \"readdir\"; token: string; path: string }\n | { op: \"gitStatus\"; token: string };\n\n/** State of the workbench-owned background git preparation. Mirrors\n * GitPrepState in boot/git-prep.ts, flattened onto the wire frame. */\nexport type GitPrepStateKind = \"pending\" | \"ready\" | \"failed\";\n\n/** Response to the `gitStatus` op — the agent container polls it to gate\n * spawning Claude on the workbench finishing its git checkout. */\nexport interface GitStatusFrame {\n t: \"gitStatus\";\n state: GitPrepStateKind;\n reason?: string;\n}\n\n/** Mid-stream frames (either direction after the opening request). */\nexport type WorkbenchFrame =\n | { t: \"out\"; s: \"stdout\" | \"stderr\"; d: string }\n | { t: \"exit\"; code: number | null; signal: string | null }\n | { t: \"data\"; d: string }\n | { t: \"end\" }\n | { t: \"input\"; d: string }\n | { t: \"resize\"; cols: number; rows: number }\n | { t: \"kill\"; sig?: string }\n | { t: \"signal\"; mode: \"term-group\" }\n | {\n t: \"stat\";\n exists: boolean;\n isFile: boolean;\n isDirectory: boolean;\n size: number;\n mtimeMs: number;\n }\n | { t: \"entries\"; names: string[] }\n | { t: \"pong\"; version: string }\n | GitStatusFrame\n | { t: \"error\"; message: string; code?: string };\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === \"object\" && value !== null;\n}\n\n/** Parse one NDJSON line into a frame-shaped record, or null when malformed. */\nexport function parseLine(line: string): Record<string, unknown> | null {\n if (!line) return null;\n try {\n const parsed: unknown = JSON.parse(line);\n return isRecord(parsed) ? parsed : null;\n } catch {\n return null;\n }\n}\n\n/**\n * Incremental NDJSON reader. Feed raw socket chunks; complete lines are\n * parsed and handed to the callback (malformed lines are dropped — the same\n * contract as the hook socket's envelope parsing).\n */\nexport class FrameReader {\n private buffer = \"\";\n\n constructor(private readonly onFrame: (frame: Record<string, unknown>) => void) {}\n\n push(chunk: Buffer | string): void {\n this.buffer += chunk.toString();\n let index = this.buffer.indexOf(\"\\n\");\n while (index >= 0) {\n const line = this.buffer.slice(0, index);\n this.buffer = this.buffer.slice(index + 1);\n const frame = parseLine(line);\n if (frame) this.onFrame(frame);\n index = this.buffer.indexOf(\"\\n\");\n }\n }\n}\n\n/** Serialize + write one frame. Write errors are the socket's problem —\n * callers handle teardown via the socket's own error/close events. */\nexport function writeFrame(\n socket: Pick<Socket, \"write\">,\n frame: WorkbenchRequest | WorkbenchFrame,\n): void {\n try {\n socket.write(`${JSON.stringify(frame)}\\n`);\n } catch {\n /* socket already destroyed — close handling owns cleanup */\n }\n}\n\nexport const DEFAULT_WORKBENCH_PORT = 7411;\n\n/** Matches the timingSafeEqualStr helper used for every other shared-secret\n * comparison in the codebase (preview-resolve.ts, workspace-attach-token.ts,\n * card-image-link.ts) — constant-time so the loopback auth check can't leak\n * the token via response-time. */\nexport function timingSafeTokenEqual(a: string, b: string): boolean {\n const ab = Buffer.from(a);\n const bb = Buffer.from(b);\n if (ab.length !== bb.length) return false;\n return crypto.timingSafeEqual(ab, bb);\n}\n"],"mappings":";AAaA,OAAO,YAAY;AAkEnB,SAAS,SAAS,OAAkD;AAClE,SAAO,OAAO,UAAU,YAAY,UAAU;AAChD;AAGO,SAAS,UAAU,MAA8C;AACtE,MAAI,CAAC,KAAM,QAAO;AAClB,MAAI;AACF,UAAM,SAAkB,KAAK,MAAM,IAAI;AACvC,WAAO,SAAS,MAAM,IAAI,SAAS;AAAA,EACrC,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AAOO,IAAM,cAAN,MAAkB;AAAA,EAGvB,YAA6B,SAAmD;AAAnD;AAAA,EAAoD;AAAA,EAApD;AAAA,EAFrB,SAAS;AAAA,EAIjB,KAAK,OAA8B;AACjC,SAAK,UAAU,MAAM,SAAS;AAC9B,QAAI,QAAQ,KAAK,OAAO,QAAQ,IAAI;AACpC,WAAO,SAAS,GAAG;AACjB,YAAM,OAAO,KAAK,OAAO,MAAM,GAAG,KAAK;AACvC,WAAK,SAAS,KAAK,OAAO,MAAM,QAAQ,CAAC;AACzC,YAAM,QAAQ,UAAU,IAAI;AAC5B,UAAI,MAAO,MAAK,QAAQ,KAAK;AAC7B,cAAQ,KAAK,OAAO,QAAQ,IAAI;AAAA,IAClC;AAAA,EACF;AACF;AAIO,SAAS,WACd,QACA,OACM;AACN,MAAI;AACF,WAAO,MAAM,GAAG,KAAK,UAAU,KAAK,CAAC;AAAA,CAAI;AAAA,EAC3C,QAAQ;AAAA,EAER;AACF;AAEO,IAAM,yBAAyB;AAM/B,SAAS,qBAAqB,GAAW,GAAoB;AAClE,QAAM,KAAK,OAAO,KAAK,CAAC;AACxB,QAAM,KAAK,OAAO,KAAK,CAAC;AACxB,MAAI,GAAG,WAAW,GAAG,OAAQ,QAAO;AACpC,SAAO,OAAO,gBAAgB,IAAI,EAAE;AACtC;","names":[]}
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/harness/pty/pty-support.ts","../src/harness/pty/spawn-args.ts","../src/boot/git-credential.ts","../src/utils/sleep.ts","../src/setup/commands.ts"],"sourcesContent":["/**\n * Pure support helpers for PtySession — node-pty process loading, prompt-byte\n * encoding, the per-turn options projection, and the AskUserQuestion parser.\n * Extracted from `session.ts` so the orchestrator stays focused on lifecycle;\n * everything here is side-effect-free (or a thin wrapper over one) and unit\n * tested in isolation.\n */\n\nimport { stat } from \"node:fs/promises\";\nimport { tmpdir } from \"node:os\";\nimport type { HarnessQueryOptions, HarnessUserQuestion } from \"../types.js\";\n// spawn-args does not import this module, so this direction introduces no cycle.\nimport { cleanTerminalOutput } from \"./spawn-args.js\";\nimport { ghHostsManagedByConveyor, githubTokenFilePath } from \"../../boot/git-credential.js\";\n\n// Cap on the rolling tail of raw terminal output retained for diagnostics when\n// `claude` exits without a result. Bounded so a long-running session can't grow\n// this unboundedly; only the most recent bytes (where a failure surfaces) matter.\nexport const MAX_DIAGNOSTIC_OUTPUT = 4000;\n\n// Cap on transcript events buffered while no turn is draining (the parked\n// window between a completed turn and the next beginTurn / a passive turn).\n// Bounded, drop-oldest — a stale buffered `result` must never terminate a\n// fresh turn, and beginTurn clears the buffer anyway.\nexport const MAX_BETWEEN_TURN_BUFFER = 500;\n\n/** Settle window between the paste write and the submitting Enter write. */\nexport const SUBMIT_SETTLE_MS = 300;\n// Re-press cadence/bounds for the submit nudge (see armSubmitNudge). Fast\n// presses cover the Enter-swallowed-at-startup race; the slow phase covers\n// startup dialogs that render tens of seconds in on a cold pod (folder\n// trust, onboarding) — observed live 2026-07-02 parking code reviews ~13min+\n// when the trust dialog mounted after the fast window had expired.\nexport const SUBMIT_NUDGE_INTERVAL_MS = 2000;\nexport const SUBMIT_NUDGE_MAX_PRESSES = 5;\nexport const SUBMIT_NUDGE_SLOW_INTERVAL_MS = 5000;\nexport const SUBMIT_NUDGE_WINDOW_MS = 90_000;\n\n// How many times a failed prompt submission is recovered by tearing the CLI\n// down and respawning it with the same prompt. The nudge only presses Enter,\n// which cannot clear a startup dialog needing an arrow-key selection (the\n// 2026-07-22 fable-model-picker outage parked TUIs exactly that way) — a fresh\n// process is the only in-pod escape from that state. Bounded because a spawn\n// that reliably parks will park again, and an unbounded respawn loop would burn\n// the pod instead of surfacing the failure.\nexport const SUBMIT_REDELIVERY_MAX_ATTEMPTS = 2;\n\n// Plan-dialog auto-accept press cadence/bounds (see armPlanDialogAutoAccept).\n// The dialog usually renders within a second of the ExitPlanMode hook verdict,\n// but the window is deliberately wide: the only stop signal is the ExitPlanMode\n// PostToolUse envelope, and a missed dialog parks an auto card indefinitely\n// (observed live 2026-07-07: an approval dialog sat 42 minutes until a human\n// pressed Enter).\nexport const PLAN_DIALOG_FIRST_PRESS_MS = 700;\nexport const PLAN_DIALOG_INTERVAL_MS = 1500;\nexport const PLAN_DIALOG_SLOW_INTERVAL_MS = 5000;\nexport const PLAN_DIALOG_FAST_WINDOW_MS = 10_000;\nexport const PLAN_DIALOG_WINDOW_MS = 90_000;\n\n// Input-readiness DETECTION before the FIRST prompt write to a raw-relay TUI.\n//\n// A structured-events TUI recovers a prompt lost to startup via the submit\n// nudge (re-press Enter until a transcript record proves the turn began). A raw\n// adapter has no such evidence, so the nudge is never armed for it (see\n// armSubmitNudge's caller) — the single paste + Enter it gets must therefore\n// land on an input box that is actually accepting keystrokes, or the turn is\n// lost silently with the prompt nowhere and the card parked on an empty box.\n//\n// We DETECT that state rather than infer it from elapsed time. The method:\n// type a short sentinel, and watch for the TUI to render THAT SENTINEL back.\n// Only a live input box echoes typed characters into its own repaint, so a\n// rendered sentinel is positive proof; then it is erased and the real prompt is\n// pasted exactly once.\n//\n// Signals that were measured and rejected (opencode 1.18.15 / Claude Code\n// 2.1.226, macOS, driving node-pty directly):\n// - DECSET 2004 (bracketed-paste enable). Both TUIs set it in their FIRST\n// paint — opencode at 554ms, Claude at 267ms — ~2s before opencode accepts\n// input. It reports terminal setup, not input readiness.\n// - \"quiet since the last output frame\". opencode's startup paint contains an\n// internal ~1175ms gap (1172/1174/1177ms over three runs), so any quiet\n// window short enough to be responsive fires INSIDE the paint.\n// - \"any output right after our keystroke\". While the app is still painting,\n// output arrives coincidentally rather than causally: this reported ready at\n// 981ms and the prompt was then dropped. The ack must be the sentinel\n// itself, not merely activity.\n// - echoing the PROMPT back. opencode renders a multi-line paste as\n// \"[Pasted ~4 lines]\" and never shows the text, so the prompt cannot serve\n// as its own acknowledgement.\n//\n// Sentinel content is deliberately three inert lowercase letters: no digits\n// (Claude Code's folder-trust dialog is a numbered menu, where a stray \"1\"\n// would select \"Yes, I trust this folder\"), and no Enter/Esc/arrow keys.\nexport const RAW_TUI_PROBE_SENTINEL = \"zqx\";\n/** How long a probe waits for the TUI to render the sentinel back. */\nexport const RAW_TUI_PROBE_ACK_MS = 500;\n/** Gap between unacknowledged probes. */\nexport const RAW_TUI_PROBE_RETRY_MS = 250;\n/** Poll cadence while watching for the sentinel / for first output. */\nexport const RAW_TUI_PROBE_POLL_MS = 20;\n/** Bound on waiting for the process to paint anything at all. */\nexport const RAW_TUI_FIRST_OUTPUT_MAX_MS = 10_000;\n// Overall cap. On expiry we paste anyway rather than hang the turn: the adapter's\n// own exit diagnostics are a better failure signal than a silent stall.\nexport const RAW_TUI_INPUT_LIVE_MAX_MS = 20_000;\n\nfunction envMs(name: string, fallback: number): number {\n const raw = Number(process.env[name]);\n return Number.isFinite(raw) && raw > 0 ? raw : fallback;\n}\n\nexport function resolveSubmitSettleMs(): number {\n return envMs(\"CONVEYOR_PTY_SUBMIT_SETTLE_MS\", SUBMIT_SETTLE_MS);\n}\n\n/**\n * Effective raw-TUI input-probe timing. Overridable for ops and so tests can\n * drive the detector in milliseconds instead of seconds of real wall-clock.\n * `sentinel` is overridable too, for a TUI that ever treats \"zqx\" specially.\n */\nexport function resolveRawTuiProbeTiming(): {\n sentinel: string;\n ackMs: number;\n retryMs: number;\n pollMs: number;\n firstOutputMaxMs: number;\n maxMs: number;\n} {\n const sentinel = process.env.CONVEYOR_PTY_RAW_PROBE_SENTINEL;\n return {\n // A sentinel must be non-empty (it is what we search for) and must stay\n // free of digits and control chars — see the note on menu dialogs above.\n sentinel: sentinel && /^[a-z]{1,8}$/.test(sentinel) ? sentinel : RAW_TUI_PROBE_SENTINEL,\n ackMs: envMs(\"CONVEYOR_PTY_RAW_PROBE_ACK_MS\", RAW_TUI_PROBE_ACK_MS),\n retryMs: envMs(\"CONVEYOR_PTY_RAW_PROBE_RETRY_MS\", RAW_TUI_PROBE_RETRY_MS),\n pollMs: envMs(\"CONVEYOR_PTY_RAW_PROBE_POLL_MS\", RAW_TUI_PROBE_POLL_MS),\n firstOutputMaxMs: envMs(\"CONVEYOR_PTY_RAW_FIRST_OUTPUT_MAX_MS\", RAW_TUI_FIRST_OUTPUT_MAX_MS),\n maxMs: envMs(\"CONVEYOR_PTY_RAW_INPUT_LIVE_MAX_MS\", RAW_TUI_INPUT_LIVE_MAX_MS),\n };\n}\n\n/**\n * Did a TUI render our probe sentinel back? Positive proof that a live input box\n * consumed the keystrokes — the one signal that discriminated correctly in\n * testing (see the rejected-signals note above).\n *\n * Pure so the matching rule is testable without a pty. Compares against ANSI-\n * stripped output because the sentinel is repainted inside styling runs.\n *\n * PRECONDITION: the child must already own the terminal. Until a TUI switches\n * the tty out of canonical mode the line discipline echoes our own keystrokes\n * straight back, which is indistinguishable from a repaint and false-positives\n * immediately. `sawTerminalSetup` is the gate for that — always wait for it\n * first.\n */\nexport function sentinelEchoed(rawOutput: string, sentinel: string): boolean {\n return cleanTerminalOutput(rawOutput, Number.MAX_SAFE_INTEGER).includes(sentinel);\n}\n\n/** DEC private mode set/reset — `ESC [ ? <params> h|l`. */\nconst DEC_PRIVATE_MODE = new RegExp(`${String.fromCharCode(27)}\\\\[\\\\?[0-9;]+[hl]`);\n\n/**\n * Has the child taken control of the terminal?\n *\n * A DEC private-mode write (alternate screen, bracketed paste, mouse tracking,\n * cursor visibility…) is the child reconfiguring the tty for full-screen use,\n * which is also when it drops canonical mode and echo. Both TUIs do it in their\n * first paint — opencode at 554ms, Claude Code at 267ms — so this is a cheap,\n * observed precondition rather than another timer.\n *\n * It is deliberately NOT used as a readiness signal on its own: opencode sets\n * bracketed-paste ~2s before it will accept input. It only tells us our own\n * keystrokes will no longer be echoed by the kernel, making a sentinel echo\n * attributable to the app.\n */\nexport function sawTerminalSetup(rawOutput: string): boolean {\n return DEC_PRIVATE_MODE.test(rawOutput);\n}\n\n/**\n * Does this adapter's first prompt write need the readiness gate?\n *\n * A per-adapter capability, NOT derived from structuredEvents: opencode keeps\n * the gate even with the events plugin wired, because its TUI silently\n * discards early stdin while painting — the pasted text itself is lost, which\n * no submit nudge can recover. Claude skips it (the nudge suffices, and its\n * numbered startup menus make stray probe keystrokes actively unsafe).\n * Exported so the contract is pinned by a test rather than resting on one\n * `if` inside the session.\n */\nexport function needsRawReadyGate(caps: { rawPromptGate: boolean }): boolean {\n return caps.rawPromptGate;\n}\n\n/**\n * Effective submit-nudge timing. The exported constants above are the production\n * defaults; `CONVEYOR_PTY_NUDGE_INTERVAL_MS` / `_SLOW_INTERVAL_MS` / `_WINDOW_MS`\n * override them. Resolved at nudge-arm time (not import) so an integration test\n * can drive the two-phase fast→slow behavior in milliseconds instead of ~15s of\n * real wall-clock. `maxPresses` is deliberately NOT overridable — the fast→slow\n * phase-boundary count is the behavior under test.\n */\nexport function resolveSubmitNudgeTiming(): {\n intervalMs: number;\n slowIntervalMs: number;\n maxPresses: number;\n windowMs: number;\n} {\n return {\n intervalMs: envMs(\"CONVEYOR_PTY_NUDGE_INTERVAL_MS\", SUBMIT_NUDGE_INTERVAL_MS),\n slowIntervalMs: envMs(\"CONVEYOR_PTY_NUDGE_SLOW_INTERVAL_MS\", SUBMIT_NUDGE_SLOW_INTERVAL_MS),\n maxPresses: SUBMIT_NUDGE_MAX_PRESSES,\n windowMs: envMs(\"CONVEYOR_PTY_NUDGE_WINDOW_MS\", SUBMIT_NUDGE_WINDOW_MS),\n };\n}\n\n/**\n * How many respawn-and-redeliver attempts a failed prompt submission gets.\n * `CONVEYOR_PTY_SUBMIT_REDELIVERY_MAX` overrides it; `0` disables recovery\n * entirely (the failure is still detected and reported). Resolved at use time\n * so tests and ops can change it without a restart-order dependency.\n */\nexport function resolveSubmitRedeliveryMaxAttempts(): number {\n const value = process.env.CONVEYOR_PTY_SUBMIT_REDELIVERY_MAX;\n // An empty string is \"unset\", not \"0\" — `Number(\"\")` is 0, which would\n // silently disable recovery for anyone exporting the var blank.\n if (!value) return SUBMIT_REDELIVERY_MAX_ATTEMPTS;\n const raw = Number(value);\n return Number.isFinite(raw) && raw >= 0 ? raw : SUBMIT_REDELIVERY_MAX_ATTEMPTS;\n}\n\nexport function resolvePlanDialogTiming(): {\n firstPressMs: number;\n intervalMs: number;\n slowIntervalMs: number;\n fastWindowMs: number;\n windowMs: number;\n} {\n return {\n firstPressMs: envMs(\"CONVEYOR_PTY_PLAN_DIALOG_FIRST_PRESS_MS\", PLAN_DIALOG_FIRST_PRESS_MS),\n intervalMs: envMs(\"CONVEYOR_PTY_PLAN_DIALOG_INTERVAL_MS\", PLAN_DIALOG_INTERVAL_MS),\n slowIntervalMs: envMs(\n \"CONVEYOR_PTY_PLAN_DIALOG_SLOW_INTERVAL_MS\",\n PLAN_DIALOG_SLOW_INTERVAL_MS,\n ),\n fastWindowMs: envMs(\"CONVEYOR_PTY_PLAN_DIALOG_FAST_WINDOW_MS\", PLAN_DIALOG_FAST_WINDOW_MS),\n windowMs: envMs(\"CONVEYOR_PTY_PLAN_DIALOG_WINDOW_MS\", PLAN_DIALOG_WINDOW_MS),\n };\n}\n\n/**\n * The per-turn subset of HarnessQueryOptions — everything that legitimately\n * changes from one turn to the next while the same `claude` process serves them\n * all. The process-level fields (model, cwd, sessionId, appendSystemPrompt, …)\n * are fixed at spawn and captured separately in the spawn options.\n */\nexport interface TurnOptions {\n canUseTool?: HarnessQueryOptions[\"canUseTool\"];\n promptDelivery?: \"submit\" | \"prefill\";\n planDialogAutoAccept?: boolean;\n abortController?: AbortController;\n}\n\nexport function turnOptionsFrom(options: HarnessQueryOptions): TurnOptions {\n return {\n canUseTool: options.canUseTool,\n promptDelivery: options.promptDelivery,\n planDialogAutoAccept: options.planDialogAutoAccept,\n abortController: options.abortController,\n };\n}\n\nexport interface PtyProcess {\n /** node-pty exposes the child's pid (its own session/process-group leader\n * via forkpty). Optional because test fakes don't model a real process. */\n pid?: number;\n onData(listener: (data: string) => void): void;\n onExit(listener: (event: { exitCode: number }) => void): void;\n write(data: string): void;\n resize(cols: number, rows: number): void;\n kill(signal?: string): void;\n}\n\nexport const KILL_ESCALATION_MS = 5_000;\n\n/**\n * Kill a pty, escalating to SIGKILL if it hasn't exited within `escalationMs`.\n * node-pty's default kill (SIGHUP) can be ignored by a wedged `claude` (e.g.\n * mid-OOM), and a fire-and-forget kill would then leak the ~1GB process on the\n * pod. `hasExited()` (backed by the caller's onExit handler) cancels the\n * escalation on a clean exit; the timer is unref'd so it never holds the\n * process open on its own.\n */\nexport function killPtyWithEscalation(\n pty: Pick<PtyProcess, \"kill\">,\n hasExited: () => boolean,\n escalationMs: number = KILL_ESCALATION_MS,\n): void {\n try {\n pty.kill();\n } catch {\n /* already exited */\n }\n const timer = setTimeout(() => {\n if (hasExited()) return;\n try {\n pty.kill(\"SIGKILL\");\n } catch {\n /* already exited */\n }\n }, escalationMs);\n timer.unref?.();\n}\n\nexport interface PtySpawnOptions {\n name: string;\n cols: number;\n rows: number;\n cwd: string;\n env: Record<string, string>;\n}\n\nexport type PtySpawn = (file: string, args: string[], options: PtySpawnOptions) => PtyProcess;\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === \"object\" && value !== null;\n}\n\nfunction extractSpawn(mod: unknown): PtySpawn | null {\n if (!isRecord(mod)) return null;\n if (typeof mod.spawn === \"function\") return mod.spawn as PtySpawn;\n const def = mod.default;\n if (isRecord(def) && typeof def.spawn === \"function\") return def.spawn as PtySpawn;\n return null;\n}\n\nexport async function loadPtySpawn(): Promise<PtySpawn> {\n const mod: unknown = await import(\"node-pty\");\n const spawn = extractSpawn(mod);\n if (!spawn) throw new Error(\"node-pty: spawn export not found\");\n return spawn;\n}\n\n/**\n * The spawn implementation for this pod topology: node-pty locally, or the\n * workbench launcher's remote PTY in split-mode pods (the CLI must run in the\n * workbench container, next to the repo and inside the restartable cgroup).\n * Same PtySpawn contract either way — PtySession is topology-blind.\n */\nexport async function resolvePtySpawn(): Promise<PtySpawn> {\n const { workbenchEnabled } = await import(\"../../workbench/mode.js\");\n if (!workbenchEnabled()) return loadPtySpawn();\n const { getWorkbenchClient } = await import(\"../../workbench/client.js\");\n return (file, args, options) => getWorkbenchClient().spawnPty(file, args, options);\n}\n\n/**\n * Base directory for a PTY session's control files (settings.json,\n * mcp-config, hook socket). In split-mode pods this MUST be the shared\n * emptyDir — the spawned CLI reads these paths from the workbench container,\n * and the hook helper connects back over the unix socket, which crosses\n * containers only on a shared volume.\n */\nexport function sessionTempBase(): string {\n return process.env.CONVEYOR_SHARED_DIR ?? tmpdir();\n}\n\nexport function inheritedEnv(socketPath?: string): Record<string, string> {\n const env: Record<string, string> = {};\n for (const [key, value] of Object.entries(process.env)) {\n if (typeof value === \"string\") env[key] = value;\n }\n // Do NOT force CLAUDE_CONFIG_DIR. Forcing it to ~/.claude makes the CLI read\n // <dir>/.claude.json — a fresh, un-onboarded config — instead of the user's\n // real ~/.claude.json, which re-triggers the first-run theme picker + login\n // even though valid credentials already exist in ~/.claude/. The loop above\n // already propagates an explicitly-set CLAUDE_CONFIG_DIR (pods/codespaces),\n // and the config dir is unchanged either way, so transcript tailing (which\n // uses claudeConfigHome()) still resolves to the same projects/ directory.\n // Belt-and-braces against the ANTHROPIC_API_KEY leak: when a subscription\n // OAuth token is in play, the TUI authenticates via the synthesized\n // credentials.json — an inherited ANTHROPIC_API_KEY would be treated as an\n // EXTERNAL API key and, once interactively approved, poison auth (the token\n // is sent via x-api-key → 401 forever). The API bundle already stops serving\n // the OAuth token under that name; stripping it here means no upstream leak\n // can reach the spawned CLI regardless. The two auth modes are mutually\n // exclusive (see session-runner's onApiKeyUpdate), so a real api_key launch\n // has no CLAUDE_CODE_OAUTH_TOKEN and keeps its ANTHROPIC_API_KEY.\n if (env.CLAUDE_CODE_OAUTH_TOKEN) {\n delete env.ANTHROPIC_API_KEY;\n }\n // A GitHub App installation token dies at ~1h, but this CLI is spawned once\n // and parked for hours — its env is a snapshot we can never update. Handing\n // it `GH_TOKEN`/`GITHUB_TOKEN` therefore guarantees a 401 on every `gh` call\n // past the first hour, and `gh` prefers those variables over its config\n // file, so a fresh hosts.yml would be ignored while they are set. Drop them\n // and let `gh` read the file the refresh timer keeps current. Only ever done\n // when Conveyor owns that file — otherwise dropping them leaves no\n // credential at all. `CONVEYOR_KEEP_GH_TOKEN_ENV=1` restores the old\n // behavior for debugging.\n if (ghHostsManagedByConveyor() && env.CONVEYOR_KEEP_GH_TOKEN_ENV !== \"1\") {\n delete env.GH_TOKEN;\n delete env.GITHUB_TOKEN;\n delete env.CONVEYOR_GITHUB_TOKEN;\n // Shells and scripts that need the value read it from here, fresh.\n env.CONVEYOR_GITHUB_TOKEN_FILE = githubTokenFilePath();\n }\n if (socketPath) {\n env.CONVEYOR_HOOK_SOCKET = socketPath;\n }\n // The conveyor MCP tool handlers proxy over the agent's API socket, and a\n // mid-flap call can block up to ~50s (20s reconnect-wait + 30s ack) before\n // failing with a retryable error. The CLI's default MCP timeouts are\n // shorter — hitting them made it abandon the MCP session mid-flap\n // (2026-07-08 wedge). Default both timeouts past the worst-case hang;\n // operator-set values win via the inherit loop above.\n env.MCP_TIMEOUT ??= \"60000\";\n env.MCP_TOOL_TIMEOUT ??= \"180000\";\n return env;\n}\n\n/**\n * The bracketed-paste bytes written to the CLI's stdin to deliver a prompt.\n * The text is wrapped in paste markers so embedded newlines land as literal\n * lines in the input box instead of submitting it early. The submitting Enter\n * is deliberately NOT part of these bytes: deliverPrompt() sends it as a\n * separate write after a settle window, because an Enter folded into the same\n * chunk as the paste can be swallowed by the CLI's paste-burst handling —\n * observed in production as an auto-mode prompt parked unsubmitted in the\n * input box. \"prefill\" delivery never sends an Enter at all.\n */\nexport function buildPromptBytes(text: string): string {\n return `\\x1b[200~${text}\\x1b[201~`;\n}\n\n/**\n * Render structured (multimodal) prompt content as pasteable text. Image\n * blocks are replaced with a tool reference — their base64 payload must NEVER\n * be pasted into the TUI input box. Upstream prompt builders already skip\n * image blocks for the PTY harness; this is the last line of defense.\n */\nexport function renderPromptContentText(content: unknown[]): string {\n return content\n .map((block) => {\n const b = block as { type?: string; text?: string };\n if (b?.type === \"text\" && typeof b.text === \"string\") return b.text;\n if (b?.type === \"image\") {\n return `[Image attachment — use list_task_files / get_attachment to view]`;\n }\n return JSON.stringify(block);\n })\n .join(\"\\n\\n\");\n}\n\nexport { sleep } from \"../../utils/sleep.js\";\n\n/** Current size of the transcript, or 0 if it does not exist yet. */\nexport async function transcriptSize(path: string): Promise<number> {\n try {\n return (await stat(path)).size;\n } catch {\n return 0;\n }\n}\n\n/**\n * Defensive extraction of AskUserQuestion's `questions` input from the hook\n * payload. Malformed entries are dropped rather than thrown — the event is\n * observe-only, so an empty list still arms the waiting_for_input report.\n */\nexport function parseUserQuestions(input: Record<string, unknown>): HarnessUserQuestion[] {\n if (!Array.isArray(input.questions)) return [];\n const questions: HarnessUserQuestion[] = [];\n for (const entry of input.questions) {\n if (!isRecord(entry)) continue;\n if (typeof entry.question !== \"string\") continue;\n const options = Array.isArray(entry.options)\n ? entry.options\n .filter(isRecord)\n .filter((o) => typeof o.label === \"string\")\n .map((o) => ({\n label: o.label as string,\n description: typeof o.description === \"string\" ? o.description : \"\",\n }))\n : [];\n questions.push({\n question: entry.question,\n header: typeof entry.header === \"string\" ? entry.header : \"\",\n options,\n ...(typeof entry.multiSelect === \"boolean\" ? { multiSelect: entry.multiSelect } : {}),\n });\n }\n return questions;\n}\n","/**\n * The `claude` CLI process boundary: resolve the binary, build its argv, and\n * interpret its exit when it dies before producing a result. Kept pure so it\n * can be unit-tested without a real spawn.\n */\n\nexport interface SpawnArgsInput {\n resume?: string;\n sessionId?: string;\n model: string;\n permissionMode: \"plan\" | \"bypassPermissions\";\n settingsPath: string;\n /** Extra system-prompt text appended via `--append-system-prompt`. */\n appendSystemPrompt?: string;\n mcpConfigPath?: string;\n /** When set with mcpConfigPath, the CLI uses ONLY that config and ignores the\n * user's `~/.claude.json` / project `.mcp.json` servers. */\n strictMcpConfig?: boolean;\n}\n\nexport function resolveClaudeBinary(): string {\n return process.env.CONVEYOR_CLAUDE_BIN ?? \"claude\";\n}\n\nexport function buildSpawnArgs(input: SpawnArgsInput): string[] {\n const args: string[] = [];\n if (input.resume) {\n args.push(\"--resume\", input.resume);\n } else if (input.sessionId) {\n args.push(\"--session-id\", input.sessionId);\n }\n args.push(\"--model\", input.model);\n if (input.permissionMode === \"bypassPermissions\") {\n args.push(\"--dangerously-skip-permissions\");\n } else {\n args.push(\"--permission-mode\", \"plan\");\n }\n args.push(\"--settings\", input.settingsPath);\n if (input.appendSystemPrompt) {\n args.push(\"--append-system-prompt\", input.appendSystemPrompt);\n }\n if (input.mcpConfigPath) {\n args.push(\"--mcp-config\", input.mcpConfigPath);\n if (input.strictMcpConfig) {\n args.push(\"--strict-mcp-config\");\n }\n }\n return args;\n}\n\n/**\n * A stable fingerprint of the spawn arguments that a REUSED (kept-alive) CLI\n * process cannot change — model, permission mode, appended system prompt, and\n * cwd are all baked into the live `claude` process at spawn. When a follow-up\n * turn would spawn with different values (most commonly a drifted\n * `appendSystemPrompt` after a plan/status edit), the parked process cannot\n * serve it, so the harness respawns instead of reusing. Keeping this beside\n * `buildSpawnArgs` ensures the two can't silently diverge.\n *\n * `settingsPath`, `mcpConfigPath`, and the hook socket path are deliberately\n * excluded: they are per-process resources that stay stable for the process's\n * whole life, so they never differentiate one turn from the next.\n */\nexport function spawnOptionsFingerprint(input: {\n model: string;\n permissionMode: \"plan\" | \"bypassPermissions\";\n appendSystemPrompt?: string;\n cwd: string;\n}): string {\n return JSON.stringify([\n input.model,\n input.permissionMode,\n input.appendSystemPrompt ?? \"\",\n input.cwd,\n ]);\n}\n\n// ─── Exit diagnostics ──────────────────────────────────────────────────────\n// The PTY harness never turns raw `claude` stdout/stderr into HarnessEvents —\n// it relays them to the S5 terminal instead. So when `claude` dies before\n// emitting a transcript result, the captured agent logs show only the generic\n// \"claude exited (code N) without a result\"; the real reason (a missing binary,\n// an auth/onboarding stop, an unknown CLI flag) scrolled past in the live\n// terminal only. These helpers fold a bounded, ANSI-stripped tail of that\n// scrollback back into the error so the failure is self-describing in the logs.\n\n// ESC (0x1B) built via fromCharCode so no control char appears as a source\n// literal (keeps the no-control-regex lint rule happy). Matches CSI sequences\n// (ESC [ ... final-byte) — the bulk of TUI escape noise; any stray ESC bytes\n// from other sequences are removed by the control-char pass below.\nconst ANSI_CSI = new RegExp(`${String.fromCharCode(27)}\\\\[[0-9;?]*[ -/]*[@-~]`, \"g\");\n\n/**\n * Strip ANSI escapes + terminal control noise and collapse to the last few\n * readable lines, capped at `maxChars`. Returns \"\" when nothing printable\n * remains (e.g. the process produced no output at all).\n */\nexport function cleanTerminalOutput(raw: string, maxChars = 1200): string {\n const noAnsi = raw.replace(ANSI_CSI, \"\");\n // Drop C0 control chars (and DEL) except tab; fold CR into newline.\n let out = \"\";\n for (const ch of noAnsi) {\n const code = ch.charCodeAt(0);\n if (ch === \"\\r\" || ch === \"\\n\") out += \"\\n\";\n else if (ch === \"\\t\") out += ch;\n else if (code < 0x20 || code === 0x7f) continue;\n else out += ch;\n }\n const lines = out\n .split(\"\\n\")\n .map((line) => line.trimEnd())\n .filter((line) => line.trim().length > 0);\n const text = lines.join(\"\\n\").trim();\n return text.length > maxChars ? `…${text.slice(-maxChars)}` : text;\n}\n\n/**\n * True when the terminal tail shows node-pty failed to exec the target binary\n * (missing `claude` CLI on PATH). node-pty prints `execvp(3) failed.: No such\n * file or directory` to the pty and the child exits with code 1.\n */\nexport function isMissingBinaryFailure(tail: string): boolean {\n return /execvp\\(\\d+\\) failed|no such file or directory|command not found/i.test(tail);\n}\n\n/**\n * Build the `errors[]` for a `claude` process that exited before emitting a\n * transcript result. The first entry is kept byte-for-byte stable so existing\n * consumers/log scrapers that key off it keep working; richer context is\n * appended after it.\n */\nexport function buildExitErrors(exitCode: number, rawOutput: string, binary: string): string[] {\n const errors = [`claude exited (code ${exitCode}) without a result`];\n const tail = cleanTerminalOutput(rawOutput);\n if (isMissingBinaryFailure(tail)) {\n errors.push(\n `The \\`${binary}\\` CLI could not be started — it is not installed or not on PATH. ` +\n `Install the Claude Code CLI in this environment (npm i -g @anthropic-ai/claude-code) ` +\n `or set CONVEYOR_CLAUDE_BIN to its absolute path.`,\n );\n }\n if (tail) {\n errors.push(`Last terminal output before exit:\\n${tail}`);\n }\n return errors;\n}\n","import { createHash } from \"node:crypto\";\nimport { chmodSync, existsSync, mkdirSync, renameSync, writeFileSync } from \"node:fs\";\nimport { homedir } from \"node:os\";\nimport { dirname, join } from \"node:path\";\nimport type { GitCredential } from \"@project/shared\";\n\nfunction credentialDir(cwd: string): string {\n return join(dirname(cwd), \".conveyor-git-credentials\");\n}\n\n/** Per-checkout key: the credential directory is shared by every repo that\n * sits beside this one, so both files it holds must be keyed by the checkout\n * they belong to. */\nfunction credentialKey(cwd: string): string {\n return createHash(\"sha256\").update(cwd).digest(\"hex\").slice(0, 16);\n}\n\nexport function gitCredentialFile(cwd: string): string {\n return join(credentialDir(cwd), `${credentialKey(cwd)}.store`);\n}\n\n/** Path of the managed helper script (see `writeGitCredentialHelperScript`). */\nexport function gitCredentialHelperScript(cwd: string): string {\n return join(credentialDir(cwd), `conveyor-credential-helper-${credentialKey(cwd)}.sh`);\n}\n\n/**\n * What `credential.helper` should be set to for this checkout.\n *\n * The managed script when it exists, and the plain `store` helper otherwise —\n * a pod that could not write the script (read-only dir, no `sh`) must keep the\n * behavior it had before rather than losing git auth entirely.\n */\nexport function gitCredentialHelper(cwd: string): string {\n const script = gitCredentialHelperScript(cwd);\n if (existsSync(script)) return script;\n return `store --file=${gitCredentialFile(cwd)}`;\n}\n\n/**\n * Rewrite a clone URL into the credential-free HTTPS form the store needs.\n *\n * Throwing here used to make a whole token refresh a silent no-op: an origin\n * carrying embedded credentials (`https://x-access-token:ghs_…@github.com/…`)\n * or an `ssh://`/`git@host:path` remote failed the check, and the caller's\n * blanket catch discarded the error. Normalizing covers every form we can\n * legitimately authenticate over HTTPS; only a genuinely unusable URL throws.\n */\nexport function normalizeCloneUrl(cloneUrl: string): string {\n const scp = /^(?:ssh:\\/\\/)?(?:[^@/]+@)?([^:/]+):(?!\\/)(.+)$/.exec(cloneUrl.trim());\n const candidate = scp ? `https://${scp[1]}/${scp[2]}` : cloneUrl.trim();\n let url: URL;\n try {\n url = new URL(candidate.replace(/^ssh:\\/\\//, \"https://\"));\n } catch {\n throw new Error(\"Git clone URL is not a valid URL\");\n }\n if (!/^https?:$/.test(url.protocol)) {\n throw new Error(\"Git clone URL must be an HTTP(S) or SSH GitHub URL\");\n }\n url.username = \"\";\n url.password = \"\";\n url.search = \"\";\n url.hash = \"\";\n return url.toString();\n}\n\n/**\n * Persist a pod-lifetime credential outside the repository with owner-only\n * permissions, and install the managed helper script alongside it. Returns the\n * normalized (credential-free) clone URL so the caller can re-point `origin`.\n */\nexport function writeGitCredential(\n cwd: string,\n cloneUrl: string,\n credential: GitCredential,\n): string {\n const cleanUrl = new URL(normalizeCloneUrl(cloneUrl));\n if (!credential.username || !credential.secret) {\n throw new Error(\"Git credential username and secret are required\");\n }\n\n const dir = credentialDir(cwd);\n mkdirSync(dir, { recursive: true, mode: 0o700 });\n chmodSync(dir, 0o700);\n\n const normalized = cleanUrl.toString();\n // `host` carries the port when the remote uses one, which is exactly what git\n // puts in the credential request's `host=` line.\n const remoteHost = cleanUrl.host;\n cleanUrl.username = credential.username;\n cleanUrl.password = credential.secret;\n cleanUrl.pathname = \"/\";\n\n const target = gitCredentialFile(cwd);\n writeSecretFile(target, `${cleanUrl.toString()}\\n`);\n try {\n writeGitCredentialHelperScript(cwd, { username: credential.username, host: remoteHost });\n } catch {\n // best effort — `gitCredentialHelper` falls back to the `store` helper\n }\n return normalized;\n}\n\n/**\n * Install the credential helper git actually runs.\n *\n * `git credential-store` implements `erase`, and git invokes it whenever the\n * host rejects a credential. An expired installation token therefore made git\n * DELETE its own credential, leaving pods with a 0-byte store and no path back\n * — the exact failure this script exists to prevent. It:\n *\n * - answers `get` from `~/.conveyor/github-token`, the same file every token\n * refresh rewrites, so git is always as fresh as `gh` — but ONLY when this\n * checkout's remote is on the GitHub host, because that file holds a GitHub\n * token and Conveyor also drives self-hosted Forgejo remotes;\n * - falls back to the static store file, which every credential write\n * refreshes for GitHub and Forgejo alike;\n * - answers only for the host of its own remote, so a token can never leak to\n * a third-party remote or submodule;\n * - exits without writing for `store` and `erase`, so a rejection can never\n * wipe the credential again.\n */\nexport interface CredentialHelperOptions {\n /** Username to answer with. Defaults to the pod's configured git username. */\n username?: string;\n /**\n * Host the helper answers for, port included when the remote uses one.\n * Defaults to the GitHub host. Passing the remote's own host is what keeps\n * Forgejo projects working: the guard rejected every non-`github.com` remote\n * and left those pods with a helper that answered nothing at all.\n */\n host?: string;\n}\n\nexport function writeGitCredentialHelperScript(\n cwd: string,\n options: CredentialHelperOptions = {},\n): string {\n const script = gitCredentialHelperScript(cwd);\n const user = options.username || process.env.CONVEYOR_GIT_USERNAME || \"x-access-token\";\n const host = options.host || githubHost();\n // A self-hosted forge must never be handed the GitHub token file. Its secret\n // lives only in the store file, which `writeGitCredential` refreshes on every\n // token refresh, so freshness is preserved either way.\n const tokenFile = host === githubHost() ? githubTokenFilePath() : \"\";\n const contents = [\n \"#!/bin/sh\",\n \"# Written by conveyor-agent. Do not edit — every credential write rewrites it.\",\n \"# `get` reads the current token; `store`/`erase` are deliberately no-ops so a\",\n \"# rejected (expired) token can never delete the pod's git credential.\",\n 'if [ \"$1\" != \"get\" ]; then exit 0; fi',\n `TOKEN_FILE=${shellQuote(tokenFile)}`,\n `STORE_FILE=${shellQuote(gitCredentialFile(cwd))}`,\n `EXPECTED_HOST=${shellQuote(host)}`,\n `GIT_USERNAME=${shellQuote(user)}`,\n \"host=\",\n \"while IFS= read -r line; do\",\n ' case \"$line\" in',\n \" host=*) host=${line#host=} ;;\",\n \" esac\",\n ' [ -n \"$line\" ] || break',\n \"done\",\n 'if [ -n \"$host\" ] && [ \"$host\" != \"$EXPECTED_HOST\" ]; then exit 0; fi',\n \"secret=\",\n 'if [ -n \"$TOKEN_FILE\" ] && [ -r \"$TOKEN_FILE\" ]; then',\n ` secret=$(tr -d '\\\\r\\\\n' < \"$TOKEN_FILE\")`,\n \"fi\",\n 'if [ -z \"$secret\" ] && [ -r \"$STORE_FILE\" ]; then',\n ` secret=$(sed -n '1s|.*://[^:]*:\\\\([^@]*\\\\)@.*|\\\\1|p' \"$STORE_FILE\")`,\n \"fi\",\n 'if [ -z \"$secret\" ]; then exit 0; fi',\n 'echo \"username=$GIT_USERNAME\"',\n 'echo \"password=$secret\"',\n \"\",\n ].join(\"\\n\");\n writeSecretFile(script, contents);\n chmodSync(script, 0o700);\n return script;\n}\n\n/** Single-quote a value for safe interpolation into the helper script. */\nfunction shellQuote(value: string): string {\n return `'${value.replace(/'/g, `'\\\\''`)}'`;\n}\n\nfunction githubHost(): string {\n return process.env.GH_HOST || \"github.com\";\n}\n\n// ── Refreshable GitHub credential files ────────────────────────────────────\n//\n// A GitHub App installation token dies at ~1h, but the pod's `claude` CLI is\n// spawned once and parked for hours (keep-alive PTY). A child process cannot\n// see its parent's later `process.env` writes, so a token exported into the\n// child env at spawn time is frozen: every `gh` call after the first hour 401s\n// and no re-export inside the agent's shell can fix it.\n//\n// The fix is to keep the credential in FILES that every refresh path rewrites,\n// the same way `writeGitCredential` above already keeps `git push` working:\n// - `~/.config/gh/hosts.yml` — what the `gh` CLI reads when no token env var\n// shadows it (see `inheritedEnv` in harness/pty/pty-support.ts).\n// - `~/.conveyor/github-token` — a plain file any shell or script can read\n// for a value that is fresh right now.\n\n/** Plain-text file holding the current GitHub token, for shells and scripts. */\nexport function githubTokenFilePath(): string {\n return process.env.CONVEYOR_GITHUB_TOKEN_FILE || join(homedir(), \".conveyor\", \"github-token\");\n}\n\nfunction ghConfigDir(): string {\n return process.env.GH_CONFIG_DIR || join(homedir(), \".config\", \"gh\");\n}\n\n/** The `gh` CLI's credential file. */\nexport function ghHostsFilePath(): string {\n return join(ghConfigDir(), \"hosts.yml\");\n}\n\n/** Marker proving the hosts.yml alongside it is ours, not a user's own login. */\nfunction ghManagedMarkerPath(): string {\n return join(ghConfigDir(), \".conveyor-managed\");\n}\n\n/** gh's general settings file. We only care that it declares a config version. */\nfunction ghConfigFilePath(): string {\n return join(ghConfigDir(), \"config.yml\");\n}\n\n/**\n * A test run must never write real credential files into the machine's home.\n * The suites that exercise these writers point `GH_CONFIG_DIR` /\n * `CONVEYOR_GITHUB_TOKEN_FILE` at a temp dir; without an override, a vitest\n * process writes nothing. This is not theoretical — a `pushToOrigin` unit test\n * refreshing to the literal token \"new-token\" wrote that value into a live\n * pod's `~/.config/gh/hosts.yml` and broke `gh` for the whole session.\n */\nfunction blockedDefaultWrite(overrideEnvKey: string): boolean {\n return Boolean(process.env.VITEST) && !process.env[overrideEnvKey];\n}\n\n/**\n * True when Conveyor owns the `gh` credential file. Callers use this to decide\n * whether dropping a frozen token env var is safe: without our own hosts.yml,\n * removing it would leave `gh` with no credential at all.\n */\nexport function ghHostsManagedByConveyor(): boolean {\n return existsSync(ghManagedMarkerPath()) && existsSync(ghHostsFilePath());\n}\n\n/**\n * True when a `gh` login this pod did not create owns `hosts.yml`.\n *\n * `writeGhHostsConfig` deliberately declines to overwrite that file — a GitHub\n * Codespace and a developer's own machine both authenticate `gh` for us. A\n * refresh that skips it there is working as designed, so callers must not\n * report it as a failed credential copy.\n */\nexport function ghHostsExternallyOwned(): boolean {\n return existsSync(ghHostsFilePath()) && !existsSync(ghManagedMarkerPath());\n}\n\n/** Atomic owner-only write, creating the parent directory owner-only. */\nfunction writeSecretFile(target: string, contents: string): void {\n const dir = dirname(target);\n mkdirSync(dir, { recursive: true, mode: 0o700 });\n chmodSync(dir, 0o700);\n const temporary = `${target}.${process.pid}.tmp`;\n writeFileSync(temporary, contents, { encoding: \"utf8\", mode: 0o600 });\n chmodSync(temporary, 0o600);\n renameSync(temporary, target);\n}\n\nfunction writeGithubTokenFile(token: string): boolean {\n if (blockedDefaultWrite(\"CONVEYOR_GITHUB_TOKEN_FILE\")) return false;\n writeSecretFile(githubTokenFilePath(), `${token}\\n`);\n return true;\n}\n\n/**\n * Declare the config version so gh treats hosts.yml as already migrated.\n * Without this, every gh invocation runs the multi-account migration, which\n * calls the API to resolve the account name — and an expired token then fails\n * the whole command with \"cowardly refusing to continue with multi account\n * migration\" instead of a plain 401. The agent is told to answer a 401 by\n * refreshing the token, so the plain 401 is the failure we want. Only written\n * when absent: config.yml also holds user preferences we must not overwrite.\n */\nfunction ensureGhConfigVersion(): void {\n if (existsSync(ghConfigFilePath())) return;\n writeSecretFile(ghConfigFilePath(), 'version: \"1\"\\n');\n}\n\n/**\n * Write the `gh` CLI credential file. Both the top-level keys and the `users`\n * map are emitted: gh reads the flat `oauth_token` and newer versions also\n * expect the per-user entry, so writing both keeps one file valid for either.\n */\nfunction writeGhHostsConfig(token: string): boolean {\n if (blockedDefaultWrite(\"GH_CONFIG_DIR\")) return false;\n // Never clobber a `gh` login this pod did not create — an environment that\n // authenticates gh for us (a GitHub Codespace, a developer's own machine)\n // keeps its own file, and the env-stripping caller then leaves that\n // environment's token variables in place.\n if (existsSync(ghHostsFilePath()) && !existsSync(ghManagedMarkerPath())) return false;\n const host = process.env.GH_HOST || \"github.com\";\n const user = process.env.CONVEYOR_GIT_USERNAME || \"x-access-token\";\n const contents = [\n \"# Written by conveyor-agent — refreshed on every GitHub token refresh.\",\n \"# Edits are overwritten. Do not add a second host entry here by hand.\",\n `${host}:`,\n ` oauth_token: ${token}`,\n ` user: ${user}`,\n \" git_protocol: https\",\n \" users:\",\n ` ${user}:`,\n ` oauth_token: ${token}`,\n \"\",\n ].join(\"\\n\");\n writeSecretFile(ghHostsFilePath(), contents);\n writeSecretFile(ghManagedMarkerPath(), \"conveyor-agent\\n\");\n ensureGhConfigVersion();\n return true;\n}\n\nexport interface GithubTokenFileSync {\n tokenFile: boolean;\n ghHosts: boolean;\n}\n\n/**\n * Refresh every file-based copy of the GitHub token. Never throws — a failed\n * write must not break the caller's real work (a push, a boot, a refresh\n * tick); the return value says which copies are now current.\n */\nexport function syncGithubTokenFiles(token: string | undefined): GithubTokenFileSync {\n const result: GithubTokenFileSync = { tokenFile: false, ghHosts: false };\n if (!token) return result;\n try {\n result.tokenFile = writeGithubTokenFile(token);\n } catch {\n // best effort\n }\n try {\n result.ghHosts = writeGhHostsConfig(token);\n } catch {\n // best effort\n }\n return result;\n}\n","export function sleep(ms: number): Promise<void> {\n return new Promise((resolve) => {\n setTimeout(resolve, ms);\n });\n}\n","import { spawn, execSync, type ChildProcess } from \"node:child_process\";\n\n/**\n * The structural surface of a start-command child the supervisor manages —\n * satisfied by a local ChildProcess and by the workbench client's\n * RemoteProcessHandle (split-mode pods, where the process lives in the\n * workbench container).\n */\nexport interface ManagedChildProcess extends NodeJS.EventEmitter {\n pid?: number | undefined;\n exitCode: number | null;\n kill(signal?: NodeJS.Signals | number): boolean;\n}\n\nconst PROCESS_TERMINATION_GRACE_MS = 5_000;\n\nfunction abortError(): Error {\n const error = new Error(\"Operation aborted\");\n error.name = \"AbortError\";\n return error;\n}\n\nfunction signalProcessGroup(child: ManagedChildProcess, signal: NodeJS.Signals): void {\n try {\n if (child.pid) process.kill(-child.pid, signal);\n else child.kill(signal);\n } catch {\n try {\n child.kill(signal);\n } catch {\n // The process already exited.\n }\n }\n}\n\nexport function terminateProcessGroup(\n // ChildProcess structurally satisfies ManagedChildProcess, so the single type\n // covers both callers; keeping them as a union makes the shared EventEmitter\n // methods (once/removeListener) non-callable under @types/node's overloads.\n child: ManagedChildProcess,\n graceMs = PROCESS_TERMINATION_GRACE_MS,\n): Promise<void> {\n if (child.exitCode !== null) return Promise.resolve();\n return new Promise((resolve) => {\n let settled = false;\n const finish = (): void => {\n if (settled) return;\n settled = true;\n clearTimeout(timer);\n child.removeListener(\"exit\", finish);\n resolve();\n };\n const timer = setTimeout(() => {\n signalProcessGroup(child, \"SIGKILL\");\n finish();\n }, graceMs);\n timer.unref();\n child.once(\"exit\", finish);\n signalProcessGroup(child, \"SIGTERM\");\n });\n}\n\nexport function runSetupCommand(\n cmd: string,\n cwd: string,\n onOutput: (stream: \"stdout\" | \"stderr\", data: string) => void,\n signal?: AbortSignal,\n): Promise<void> {\n if (signal?.aborted) return Promise.reject(abortError());\n return new Promise((resolve, reject) => {\n const child = spawn(\"sh\", [\"-c\", cmd], {\n cwd,\n stdio: [\"ignore\", \"pipe\", \"pipe\"],\n detached: true,\n env: { ...process.env },\n });\n let settled = false;\n let aborting = false;\n const cleanup = (): void => signal?.removeEventListener(\"abort\", onAbort);\n const settle = (error?: Error): void => {\n if (settled) return;\n settled = true;\n cleanup();\n if (error) reject(error);\n else resolve();\n };\n const onAbort = (): void => {\n if (settled || aborting) return;\n aborting = true;\n void terminateProcessGroup(child).then(() => settle(abortError()));\n };\n signal?.addEventListener(\"abort\", onAbort, { once: true });\n if (signal?.aborted) onAbort();\n\n child.stdout.on(\"data\", (chunk: Buffer) => {\n if (aborting || signal?.aborted) return;\n onOutput(\"stdout\", chunk.toString());\n });\n\n child.stderr.on(\"data\", (chunk: Buffer) => {\n if (aborting || signal?.aborted) return;\n onOutput(\"stderr\", chunk.toString());\n });\n\n child.on(\"close\", (code) => {\n if (aborting) return;\n settle(code === 0 ? undefined : new Error(`Setup command exited with code ${code}`));\n });\n\n child.on(\"error\", (err) => {\n if (!aborting) settle(err);\n });\n });\n}\n\nconst AUTH_TOKEN_TIMEOUT_MS = 30_000;\n\nexport function runAuthTokenCommand(cmd: string, userEmail: string, cwd: string): string | null {\n try {\n const output = execSync(`${cmd} ${JSON.stringify(userEmail)}`, {\n cwd,\n timeout: AUTH_TOKEN_TIMEOUT_MS,\n stdio: [\"ignore\", \"pipe\", \"ignore\"],\n env: { ...process.env },\n });\n const token = output.toString().trim();\n return token || null;\n } catch {\n return null;\n }\n}\n\nexport function runStartCommand(\n cmd: string,\n cwd: string,\n onOutput: (stream: \"stdout\" | \"stderr\", data: string) => void,\n): ChildProcess {\n const child = spawn(\"sh\", [\"-c\", cmd], {\n cwd,\n stdio: [\"ignore\", \"pipe\", \"pipe\"],\n detached: true,\n env: { ...process.env },\n });\n\n child.stdout.on(\"data\", (chunk: Buffer) => {\n onOutput(\"stdout\", chunk.toString());\n });\n\n child.stderr.on(\"data\", (chunk: Buffer) => {\n onOutput(\"stderr\", chunk.toString());\n });\n\n child.unref();\n return child;\n}\n"],"mappings":"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","names":["spawn"]}