@integrity-labs/agt-cli 0.28.1005 → 0.28.1007

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 (32) hide show
  1. package/dist/bin/agt.js +6 -5
  2. package/dist/bin/agt.js.map +1 -1
  3. package/dist/chunk-EL5MWGGH.js +160 -0
  4. package/dist/chunk-EL5MWGGH.js.map +1 -0
  5. package/dist/{chunk-6U4NB3LJ.js → chunk-FLJNRC3D.js} +71 -9
  6. package/dist/chunk-FLJNRC3D.js.map +1 -0
  7. package/dist/{chunk-TGTUE7Y2.js → chunk-SQX4ZGD5.js} +73 -8
  8. package/dist/chunk-SQX4ZGD5.js.map +1 -0
  9. package/dist/{chunk-DVSYT4MU.js → chunk-XWMUQR65.js} +52 -1
  10. package/dist/{chunk-DVSYT4MU.js.map → chunk-XWMUQR65.js.map} +1 -1
  11. package/dist/{claude-code-updater-45MB2AXE.js → claude-code-updater-NEJCTT7S.js} +7 -3
  12. package/dist/claude-code-updater-NEJCTT7S.js.map +1 -0
  13. package/dist/{claude-pair-runtime-DDSYAHQW.js → claude-pair-runtime-X27QVAXL.js} +172 -18
  14. package/dist/claude-pair-runtime-X27QVAXL.js.map +1 -0
  15. package/dist/lib/manager-worker.js +547 -227
  16. package/dist/lib/manager-worker.js.map +1 -1
  17. package/dist/mcp/direct-chat-channel.js +57 -2
  18. package/dist/mcp/index.js +51 -0
  19. package/dist/mcp/origami.js +51 -0
  20. package/dist/mcp/slack-channel.js +57 -2
  21. package/dist/mcp/telegram-channel.js +57 -2
  22. package/dist/{persistent-session-4PP3ON7H.js → persistent-session-5KWYODZB.js} +6 -3
  23. package/dist/{responsiveness-probe-6NWZAXIZ.js → responsiveness-probe-J2DYGC7T.js} +4 -3
  24. package/dist/{responsiveness-probe-6NWZAXIZ.js.map → responsiveness-probe-J2DYGC7T.js.map} +1 -1
  25. package/dist/{session-auth-dead-2V2BOXAG.js → session-auth-dead-4SE5Q7MU.js} +2 -2
  26. package/package.json +1 -1
  27. package/dist/chunk-6U4NB3LJ.js.map +0 -1
  28. package/dist/chunk-TGTUE7Y2.js.map +0 -1
  29. package/dist/claude-code-updater-45MB2AXE.js.map +0 -1
  30. package/dist/claude-pair-runtime-DDSYAHQW.js.map +0 -1
  31. /package/dist/{persistent-session-4PP3ON7H.js.map → persistent-session-5KWYODZB.js.map} +0 -0
  32. /package/dist/{session-auth-dead-2V2BOXAG.js.map → session-auth-dead-4SE5Q7MU.js.map} +0 -0
@@ -2,6 +2,9 @@ import {
2
2
  CLAUDE_AGENT_PROCESS_PATTERN,
3
3
  parsePsRows
4
4
  } from "./chunk-XWVM4KPK.js";
5
+ import {
6
+ effectiveClaudeJsonPath
7
+ } from "./chunk-EL5MWGGH.js";
5
8
  import "./chunk-DGUM43GV.js";
6
9
 
7
10
  // src/lib/claude-code-updater.ts
@@ -149,10 +152,11 @@ async function maybeUpdateClaudeCode(deps) {
149
152
  }
150
153
  multiInstallWarned = true;
151
154
  }
152
- const ownership = await resolveNativeOwnership(run, realPath, readClaudeJson(`${home}/.claude.json`));
155
+ const claudeJsonPath = effectiveClaudeJsonPath(home);
156
+ const ownership = await resolveNativeOwnership(run, realPath, readClaudeJson(claudeJsonPath));
153
157
  if (!ownership.native && ownership.configSaysNative) {
154
158
  log(
155
- `[claude-code-updater] ~/.claude.json says installMethod=native but the binary this host runs (${ownership.binary}) is not a native install \u2014 ignoring it and converging to the pin`
159
+ `[claude-code-updater] ${claudeJsonPath} says installMethod=native but the binary this host runs (${ownership.binary}) is not a native install \u2014 ignoring it and converging to the pin`
156
160
  );
157
161
  }
158
162
  if (ownership.native) {
@@ -240,4 +244,4 @@ export {
240
244
  probeClaudeInstalls,
241
245
  readsAsNativeInstall
242
246
  };
243
- //# sourceMappingURL=claude-code-updater-45MB2AXE.js.map
247
+ //# sourceMappingURL=claude-code-updater-NEJCTT7S.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/lib/claude-code-updater.ts"],"sourcesContent":["// ENG-5672: idle-gated, server-pinned Claude Code auto-updater.\n// ENG-5798: multi-install drift handling — detect coexisting claude binaries,\n// short-circuit on native autoupdater hosts, verify against absolute\n// install path, hard-error after N consecutive verify mismatches.\n//\n// Hosts install @anthropic-ai/claude-code once at bootstrap (host-bootstrap.ts)\n// and never refresh it, so a long-lived host runs a stale Claude Code forever.\n// This module brings a host to its *desired* version, which the server pins via\n// /host/refresh (per-host override, else fleet default, else 'latest').\n//\n// Safety properties (this runs npm install -g on live prod hosts):\n// • Idle-gated — we never install while a `claude --name agt-*` agent process\n// is running on the host (a running process holds the old binary anyway;\n// replacing it under a live session is pointless and risky). Busy → defer\n// to a later cycle.\n// • No-op when already on target — a concrete target equal to the installed\n// version short-circuits; a dist-tag ('latest') is resolved to a concrete\n// version via `npm view` and compared, so we don't reinstall every cycle.\n// • Host-global throttle — at most one check per interval (default 6h), so the\n// manager's ~10-30s poll doesn't shell out to npm/ps constantly.\n// • Log-and-continue — every failure is logged and swallowed; an error here\n// must never break the manager poll cycle.\n// • Multi-install awareness (ENG-5798) — on hosts where Anthropic's native\n// autoupdater owns /usr/bin/claude, this updater short-circuits cleanly\n// instead of fighting it. On hosts where multiple binaries coexist (e.g. a\n// brew cask alongside the npm global), we log every path+version once so\n// drift is visible in manager.log without spamming every cycle.\n//\n// The *installed* version is reported separately as hosts.framework_version\n// (the claude-code framework adapter's getVersion() == `claude --version`), so\n// this module does not report it itself.\n\nimport { execFile, execFileSync } from 'node:child_process';\nimport { existsSync, readFileSync, realpathSync } from 'node:fs';\nimport { homedir } from 'node:os';\nimport { promisify } from 'node:util';\nimport { effectiveClaudeJsonPath } from './claude-config-location.js';\nimport { parsePsRows, CLAUDE_AGENT_PROCESS_PATTERN } from './orphan-channel-mcp-reaper.js';\n\nconst execFileAsync = promisify(execFile);\n\nconst NPM_PKG = '@anthropic-ai/claude-code';\n\n/** Default host-global check interval. Overridable via env for tuning. */\nconst DEFAULT_INTERVAL_MS = 6 * 60 * 60 * 1000; // 6h\n\n/**\n * ENG-5798: After this many consecutive verify mismatches against the same\n * target version, emit a distinct one-shot error code that the\n * synthetic-probe / CloudWatch alarm pipeline can latch onto, instead of\n * silently re-failing forever. Overridable via env for testing.\n */\nconst DEFAULT_VERIFY_MISMATCH_THRESHOLD = 5;\n\nfunction intervalMs(): number {\n const raw = Number.parseInt(process.env['AGT_CLAUDE_UPDATE_INTERVAL_MS'] ?? '', 10);\n return Number.isFinite(raw) && raw > 0 ? raw : DEFAULT_INTERVAL_MS;\n}\n\nfunction verifyMismatchThreshold(): number {\n const raw = Number.parseInt(process.env['AGT_CLAUDE_UPDATE_VERIFY_THRESHOLD'] ?? '', 10);\n return Number.isFinite(raw) && raw > 0 ? raw : DEFAULT_VERIFY_MISMATCH_THRESHOLD;\n}\n\n/** Extract a version string (leading semver) from `claude --version` / npm output. */\nexport function parseVersion(output: string): string | null {\n const m = output.match(/\\d+\\.\\d+\\.\\d+[A-Za-z0-9.+-]*/);\n return m ? m[0] : null;\n}\n\n/** A target spec is \"concrete\" when it already names an exact version. */\nexport function isConcreteVersion(spec: string): boolean {\n return /^\\d+\\.\\d+\\.\\d+[A-Za-z0-9.+-]*$/.test(spec.trim());\n}\n\n/**\n * Well-known absolute paths where a `claude` binary may live on managed hosts.\n * Probed alongside the PATH-resolved binary so we can see all install slots,\n * even ones not on the manager systemd unit's PATH.\n */\nexport const WELL_KNOWN_CLAUDE_PATHS: readonly string[] = [\n '/usr/bin/claude', // Anthropic native autoupdater (Linux deb/rpm)\n '/usr/local/bin/claude', // npm global on macOS Intel + Linux without nvm\n '/opt/homebrew/bin/claude', // Homebrew on macOS Apple Silicon\n '/home/linuxbrew/.linuxbrew/bin/claude', // Linuxbrew (used on agt-aws-* hosts)\n];\n\nexport interface ClaudeCodeUpdaterDeps {\n /** Server-pinned target: a concrete version or an npm dist-tag like 'latest'. */\n desiredVersion: string;\n /** Currently-installed version, if the caller already knows it (e.g. the\n * manager's cachedFrameworkVersion). When omitted we run `claude --version`. */\n currentVersion?: string | null;\n log: (msg: string) => void;\n /** Override \"now\" for tests. */\n now?: () => number;\n /** Run a command; injected in tests. Defaults to execFile. */\n runCommand?: (cmd: string, args: string[]) => Promise<{ stdout: string; stderr: string }>;\n /** Raw `ps -eo pid,ppid,args` snapshot; injected in tests. */\n psSnapshot?: () => string;\n /** Sync existence probe for binary detection; injected in tests. */\n fileExists?: (path: string) => boolean;\n /** Read ~/.claude.json contents (raw); returns null when absent. Injected in tests. */\n readClaudeJson?: (path: string) => string | null;\n /** Resolve the user's home directory; injected in tests. */\n homeDir?: () => string;\n /** Resolve symlinks to a real path (throws when absent); injected in tests. */\n realPath?: (path: string) => string;\n}\n\nexport type UpdaterOutcome =\n | { action: 'throttled' }\n | { action: 'up_to_date'; version: string }\n | { action: 'deferred_busy'; target: string }\n | { action: 'skipped_native_install'; target: string }\n | { action: 'updated'; from: string | null; to: string }\n | { action: 'failed'; reason: string };\n\n// ── Host-global state (module-level; one updater per host process) ──────────\nlet lastCheckAt = 0;\n/** Per-process one-shot guard for the multi-install warn line. */\nlet multiInstallWarned = false;\n/**\n * Sticky counter for consecutive `post-install verify mismatch` against the\n * same target version. Reset on success, on target change, or after the\n * threshold error has been emitted (so we emit it exactly once per stuck run).\n */\nlet verifyMismatchState: { target: string; count: number; alarmed: boolean } | null = null;\n\n/** Test helper — reset module state. Not for production use. */\nexport function _resetClaudeCodeUpdaterState(): void {\n lastCheckAt = 0;\n multiInstallWarned = false;\n verifyMismatchState = null;\n}\n\nfunction defaultPsSnapshot(): string {\n return execFileSync('ps', ['-eo', 'pid,ppid,args'], { encoding: 'utf-8', maxBuffer: 16 * 1024 * 1024 });\n}\n\nfunction defaultReadClaudeJson(path: string): string | null {\n try {\n return readFileSync(path, 'utf-8');\n } catch {\n return null;\n }\n}\n\n/** True when at least one `claude --name agt-*` agent process is live. */\nexport function hostHasLiveAgent(psOutput: string): boolean {\n return parsePsRows(psOutput).some((r) => CLAUDE_AGENT_PROCESS_PATTERN.test(r.args));\n}\n\n/**\n * ENG-5798: returns true when ~/.claude.json reports `installMethod: \"native\"`,\n * meaning Anthropic's native autoupdater owns the on-disk binary. In that case\n * the brew/npm update path is actively harmful — it installs a second binary\n * the host's PATH never resolves to, and verify reads from yet a third.\n */\nexport function readsAsNativeInstall(raw: string | null): boolean {\n if (!raw) return false;\n try {\n const parsed = JSON.parse(raw) as { installMethod?: unknown };\n return parsed.installMethod === 'native';\n } catch {\n return false;\n }\n}\n\n/**\n * ENG-10620: true when `realPath` is a binary owned by Claude Code's native\n * installer, which keeps every version under `…/claude/versions/<semver>`\n * (e.g. `~/.local/share/claude/versions/2.1.274`, symlinked from\n * `~/.local/bin/claude`). An npm global resolves into\n * `…/node_modules/@anthropic-ai/claude-code/` instead.\n */\nexport function isNativeInstallPath(realPath: string): boolean {\n return /\\/claude\\/versions\\/\\d+\\.\\d+\\.\\d+[^/]*$/.test(realPath);\n}\n\n/**\n * ENG-10620: decide native ownership from the binary this host ACTUALLY runs,\n * not from `~/.claude.json`.\n *\n * `installMethod` is written by whichever `claude` last started, not by the\n * one the manager launches. On agt-aws-1 an operator shell's PATH resolved a\n * native `~/.local/bin/claude` while agents ran the npm global at\n * `/usr/bin/claude`; that one manual run flipped `installMethod` to `native`\n * and this updater stopped converging the host to its pin for good, while the\n * binary it would have updated was never native at all.\n *\n * Resolution mirrors how agent sessions pick their binary (resolveClaudeBinary:\n * CLAUDE_PATH, then PATH). The config key remains only as a fallback for a\n * host where no binary can be resolved.\n */\nasync function resolveNativeOwnership(\n run: (cmd: string, args: string[]) => Promise<{ stdout: string; stderr: string }>,\n realPath: (path: string) => string,\n claudeJson: string | null,\n): Promise<{ native: boolean; binary: string | null; configSaysNative: boolean }> {\n const configSaysNative = readsAsNativeInstall(claudeJson);\n let resolved: string | null = null;\n const override = process.env['CLAUDE_PATH'];\n if (override) {\n resolved = override;\n } else {\n try {\n const { stdout } = await run('which', ['claude']);\n resolved = stdout.trim().split('\\n')[0]?.trim() || null;\n } catch {\n resolved = null;\n }\n }\n if (resolved) {\n try {\n const real = realPath(resolved);\n return { native: isNativeInstallPath(real), binary: real, configSaysNative };\n } catch {\n /* unresolvable — fall back to the config key */\n }\n }\n return { native: configSaysNative, binary: null, configSaysNative };\n}\n\n/**\n * Resolve the absolute filesystem path that `npm install -g <pkg>` will write\n * the `claude` binary to. Returns null when `npm prefix -g` fails or is empty,\n * in which case the caller should fall back to PATH-based verify.\n *\n * We deliberately use `npm prefix -g` rather than `which claude` because the\n * latter resolves through PATH, which on agt-aws-* hosts can land on\n * /usr/bin/claude (the native autoupdater slot) — exactly the misalignment\n * ENG-5798 is fixing.\n */\nasync function resolveInstallTargetPath(\n run: (cmd: string, args: string[]) => Promise<{ stdout: string; stderr: string }>,\n): Promise<string | null> {\n try {\n const { stdout } = await run('npm', ['prefix', '-g']);\n const prefix = stdout.trim();\n if (!prefix) return null;\n return `${prefix}/bin/claude`;\n } catch {\n return null;\n }\n}\n\n/**\n * Enumerate every `claude` binary visible to the host: PATH-resolved + the\n * well-known absolute slots + `~/.claude/local/claude`. Returns one entry per\n * unique resolved path with the version each reports (or null if it failed to\n * execute). Used purely for the one-shot drift warn line.\n */\nexport async function probeClaudeInstalls(\n run: (cmd: string, args: string[]) => Promise<{ stdout: string; stderr: string }>,\n fileExists: (path: string) => boolean,\n home: string,\n): Promise<Array<{ path: string; version: string | null }>> {\n const candidates = new Set<string>(WELL_KNOWN_CLAUDE_PATHS);\n candidates.add(`${home}/.claude/local/claude`);\n // PATH-resolved. `which` is portable enough across the Linux + macOS hosts\n // we target; errors here just mean \"no claude on PATH\", which is fine.\n try {\n const { stdout } = await run('which', ['claude']);\n const p = stdout.trim().split('\\n')[0]?.trim();\n if (p) candidates.add(p);\n } catch {\n /* no-op */\n }\n\n const results: Array<{ path: string; version: string | null }> = [];\n for (const p of candidates) {\n if (!fileExists(p)) continue;\n let version: string | null = null;\n try {\n const { stdout } = await run(p, ['--version']);\n version = parseVersion(stdout);\n } catch {\n /* binary exists but failed to execute — record path with null version */\n }\n results.push({ path: p, version });\n }\n return results;\n}\n\n/**\n * Bring Claude Code to the desired version if the host is idle and not already\n * on target. Safe to call every manager poll — internally throttled. Returns an\n * outcome describing what happened (for logging/observability/tests).\n */\nexport async function maybeUpdateClaudeCode(deps: ClaudeCodeUpdaterDeps): Promise<UpdaterOutcome> {\n const now = deps.now ? deps.now() : Date.now();\n const run = deps.runCommand ?? ((cmd, args) => execFileAsync(cmd, args, { maxBuffer: 16 * 1024 * 1024 }));\n const ps = deps.psSnapshot ?? defaultPsSnapshot;\n const fileExists = deps.fileExists ?? existsSync;\n const readClaudeJson = deps.readClaudeJson ?? defaultReadClaudeJson;\n const realPath = deps.realPath ?? realpathSync;\n const home = (deps.homeDir ?? homedir)();\n const { log } = deps;\n\n // 1. Host-global throttle.\n if (now - lastCheckAt < intervalMs()) {\n return { action: 'throttled' };\n }\n lastCheckAt = now;\n\n try {\n const spec = (deps.desiredVersion || 'latest').trim();\n\n // 2. ENG-5798 multi-install drift detection. Emit one warn line per\n // process listing every claude binary + version we can see. This is\n // independent of the install decision below — even on hosts where we\n // proceed normally, surfacing drift is the difference between a 3-day\n // silent loop and a one-line lead.\n if (!multiInstallWarned) {\n const installs = await probeClaudeInstalls(run, fileExists, home);\n if (installs.length > 1) {\n const summary = installs\n .map((i) => `${i.path}=${i.version ?? 'unknown'}`)\n .join(', ');\n log(`[claude-code-updater] multiple claude installs detected: ${summary}`);\n }\n multiInstallWarned = true;\n }\n\n // 3. ENG-5798 native-install short-circuit. When Anthropic's native\n // autoupdater owns the host binary, the brew/npm update path is\n // actively harmful (it installs a second binary the manager's PATH\n // never resolves to, then verifies against yet a third). Defer.\n // ENG-10869: the path the AGENT actually reads, not the legacy one.\n // Once this host migrates, `~/.claude.json` is ARCHIVED, so a hardcoded\n // read here returns nothing forever and `configSaysNative` is permanently\n // false — silently re-opening the ENG-5798 harm this short-circuit exists\n // to prevent (brew/npm installing a second binary on a host Anthropic's\n // native autoupdater owns). The resolver falls back to the legacy file, so\n // an unmigrated host is unaffected.\n const claudeJsonPath = effectiveClaudeJsonPath(home);\n const ownership = await resolveNativeOwnership(run, realPath, readClaudeJson(claudeJsonPath));\n if (!ownership.native && ownership.configSaysNative) {\n log(\n `[claude-code-updater] ${claudeJsonPath} says installMethod=native but the binary this host runs (${ownership.binary}) is not a native install — ignoring it and converging to the pin`,\n );\n }\n if (ownership.native) {\n // One-shot info line per process — same throttle as the warn above, but\n // we re-use the throttle window to keep this from logging every poll.\n log('[claude-code-updater] native install detected, deferring to Anthropic autoupdater');\n // Reset any sticky verify-mismatch state — it can't apply once we stop\n // attempting installs.\n verifyMismatchState = null;\n return { action: 'skipped_native_install', target: spec };\n }\n\n // 4. Resolve the target to a concrete version (read-only). A concrete spec\n // is its own target; a dist-tag is resolved via `npm view`.\n let target: string;\n if (isConcreteVersion(spec)) {\n target = spec;\n } else {\n const { stdout } = await run('npm', ['view', `${NPM_PKG}@${spec}`, 'version']);\n const resolved = parseVersion(stdout);\n if (!resolved) {\n log(`[claude-code-updater] could not resolve ${NPM_PKG}@${spec} to a version; skipping`);\n return { action: 'failed', reason: 'unresolved_target' };\n }\n target = resolved;\n }\n\n // 5. Determine the installed version (prefer the caller's cached value).\n let current = deps.currentVersion ?? null;\n if (!current) {\n try {\n const { stdout } = await run('claude', ['--version']);\n current = parseVersion(stdout);\n } catch {\n current = null; // claude missing/broken — treat as \"needs install\".\n }\n }\n\n // 6. Already on target → nothing to do.\n if (current && current === target) {\n // Successful no-op — clear sticky verify mismatch state for this target.\n if (verifyMismatchState && verifyMismatchState.target === target) {\n verifyMismatchState = null;\n }\n return { action: 'up_to_date', version: target };\n }\n\n // 7. Idle gate — never replace the binary under a live agent session.\n if (hostHasLiveAgent(ps())) {\n log(`[claude-code-updater] host busy (live agent) — deferring update to ${target} to a later cycle`);\n return { action: 'deferred_busy', target };\n }\n\n // 8. Install the target, then verify against the *absolute* path of the\n // binary npm just wrote — not via PATH. PATH resolution is the original\n // ENG-5798 bug: on hosts with multiple claude installs, PATH lands on a\n // different binary than the one npm overwrites, so verify never agrees.\n const installTargetPath = await resolveInstallTargetPath(run);\n log(`[claude-code-updater] updating Claude Code ${current ?? '(none)'} → ${target}`);\n await run('npm', ['install', '-g', `${NPM_PKG}@${target}`]);\n\n // Prefer the absolute install target; fall back to PATH only when\n // `npm prefix -g` failed (in which case verify is best-effort).\n const verifyTarget = installTargetPath ?? 'claude';\n let installed: string | null = null;\n try {\n const { stdout } = await run(verifyTarget, ['--version']);\n installed = parseVersion(stdout);\n } catch {\n installed = null;\n }\n\n if (installed !== target) {\n log(\n `[claude-code-updater] post-install verify mismatch: expected ${target}, got ${installed ?? 'unknown'} from ${verifyTarget}`,\n );\n\n // ENG-5798: hard-error after N consecutive mismatches against the same\n // target. Reset the counter when the target changes — a new pin is a\n // new event, not a continuation of the old stuck run.\n if (!verifyMismatchState || verifyMismatchState.target !== target) {\n verifyMismatchState = { target, count: 1, alarmed: false };\n } else {\n verifyMismatchState.count += 1;\n }\n\n const threshold = verifyMismatchThreshold();\n if (verifyMismatchState.count >= threshold && !verifyMismatchState.alarmed) {\n verifyMismatchState.alarmed = true;\n log(\n `[claude-code-updater] ERROR verify_mismatch_stuck: ${verifyMismatchState.count} consecutive verify failures for target ${target} (verify path: ${verifyTarget}) — install is not taking effect, investigate host install slots`,\n );\n return { action: 'failed', reason: 'verify_mismatch_threshold' };\n }\n return { action: 'failed', reason: 'verify_mismatch' };\n }\n\n // Successful install — clear sticky state.\n verifyMismatchState = null;\n log(`[claude-code-updater] updated Claude Code ${current ?? '(none)'} → ${installed}`);\n return { action: 'updated', from: current, to: installed };\n } catch (err) {\n // Log-and-continue: an updater failure must never break the manager tick.\n log(`[claude-code-updater] update attempt failed: ${err instanceof Error ? err.message : String(err)}`);\n return { action: 'failed', reason: 'exception' };\n }\n}\n"],"mappings":"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","names":[]}
@@ -2,12 +2,19 @@ import {
2
2
  consumerTermsOffKeys,
3
3
  selectRowKeys
4
4
  } from "./chunk-DHWNVVX4.js";
5
+ import {
6
+ CLAUDE_CONFIG_DIR_ENV,
7
+ claudeConfigDirFor,
8
+ claudeJsonPathFor,
9
+ effectiveClaudeJsonPath,
10
+ inspectClaudeConfig
11
+ } from "./chunk-EL5MWGGH.js";
5
12
  import "./chunk-DGUM43GV.js";
6
13
 
7
14
  // src/lib/claude-pair-runtime.ts
8
15
  import { execFile } from "child_process";
9
16
  import { promisify } from "util";
10
- import { existsSync, statSync } from "fs";
17
+ import { existsSync, readFileSync, statSync } from "fs";
11
18
  import { join } from "path";
12
19
  import { homedir, userInfo } from "os";
13
20
 
@@ -259,13 +266,15 @@ async function spawnPairSession(session) {
259
266
  return { ok: true };
260
267
  } catch {
261
268
  }
262
- const { resolveClaudeBinary } = await import("./persistent-session-4PP3ON7H.js");
269
+ const { resolveClaudeBinary } = await import("./persistent-session-5KWYODZB.js");
263
270
  const claudeBin = resolveClaudeBinary();
264
271
  const pairEnv = {
265
272
  ...process.env,
266
273
  HOME: process.env.HOME?.trim() || homedir(),
267
274
  USER: process.env.USER?.trim() || userInfo().username
268
275
  };
276
+ const pairConfigDir = claudeConfigDirFor();
277
+ const pairConfigEnvArgs = inspectClaudeConfig(claudeJsonPathFor()) === "usable" ? ["-e", `${CLAUDE_CONFIG_DIR_ENV}=${pairConfigDir}`] : [];
269
278
  try {
270
279
  await execFileAsync(
271
280
  "tmux",
@@ -276,6 +285,7 @@ async function spawnPairSession(session) {
276
285
  "240",
277
286
  "-y",
278
287
  "50",
288
+ ...pairConfigEnvArgs,
279
289
  "-s",
280
290
  session,
281
291
  claudeBin,
@@ -314,17 +324,116 @@ async function spawnPairSession(session) {
314
324
  }
315
325
  return { ok: true };
316
326
  }
327
+ function pairConfigLandedSince(path, sinceMtimeMs) {
328
+ try {
329
+ if (!existsSync(path)) return false;
330
+ if (statSync(path).mtimeMs <= sinceMtimeMs) return false;
331
+ JSON.parse(readFileSync(path, "utf-8"));
332
+ return true;
333
+ } catch {
334
+ return false;
335
+ }
336
+ }
337
+ async function confirmPairFailure(opts) {
338
+ const graceMs = opts.graceMs ?? 3e3;
339
+ const nap = opts.sleep ?? sleep;
340
+ const log = opts.log ?? (() => {
341
+ });
342
+ if (pairConfigLandedSince(opts.configPath, opts.sinceMtimeMs)) {
343
+ log(`[claude-pair] a usable config was already present at ${opts.configPath} \u2014 not a failure`);
344
+ return "landed-after-all";
345
+ }
346
+ await nap(graceMs);
347
+ if (pairConfigLandedSince(opts.configPath, opts.sinceMtimeMs)) {
348
+ log(
349
+ `[claude-pair] ${opts.configPath} landed during the ${graceMs}ms confirm window \u2014 the pairing worked after all, reporting success`
350
+ );
351
+ return "landed-after-all";
352
+ }
353
+ return "confirmed";
354
+ }
355
+ var PAIR_FINALIZE_ERROR_CODES = {
356
+ "config-corrupt": "onboarding_config_corrupt",
357
+ "config-absent": "onboarding_config_absent",
358
+ "config-path-is-directory": "onboarding_config_path_is_directory",
359
+ "not-flushed": "onboarding_not_flushed",
360
+ "pane-gone": "onboarding_pane_gone",
361
+ "send-failed": "onboarding_send_failed"
362
+ };
363
+ function describePairFinalizeFailure(reason, configPath) {
364
+ const valid = "The OAuth tokens themselves are valid, so this is not a credential problem.";
365
+ const retry = "Start over to re-run pairing.";
366
+ switch (reason) {
367
+ case "config-corrupt":
368
+ return `Signed in, but ${configPath} on this host is unreadable and Claude Code did not replace it, so agents here would start on the login picker. ${valid} ${retry} If it recurs on this host, contact support \u2014 the host's config is being corrupted by something outside pairing.`;
369
+ case "config-path-is-directory":
370
+ return `Signed in, but ${configPath} on this host is a DIRECTORY, not a file, so Claude Code cannot write its config there and agents here would start on the login picker. ${valid} Re-pairing will NOT fix this on its own: remove that directory on the host, then pair again.`;
371
+ case "config-absent":
372
+ return `Signed in, but Claude Code never wrote ${configPath}, so agents on this host would start on the login picker. ${valid} ${retry}`;
373
+ case "not-flushed":
374
+ return `Signed in, but Claude Code did not write ${configPath} within the onboarding window, so agents on this host would start on the login picker. ${valid} ${retry}`;
375
+ case "pane-gone":
376
+ return `Signed in, but the temporary Claude Code session on the host closed before it wrote ${configPath}, so agents here would start on the login picker. ${valid} ${retry}`;
377
+ case "send-failed":
378
+ return `Signed in, but tmux refused a keystroke needed to clear Claude Code's post-login dialogs, so ${configPath} was never written and agents here would start on the login picker. ${valid} ${retry}`;
379
+ }
380
+ }
381
+ function finalizeWatchPath(env = process.env) {
382
+ if (env["CLAUDE_CONFIG_DIR"]?.trim()) return pairClaudeJsonPath(env);
383
+ return effectiveClaudeJsonPath();
384
+ }
385
+ function pairClaudeJsonPath(env = process.env) {
386
+ const configDir = env["CLAUDE_CONFIG_DIR"]?.trim();
387
+ if (configDir) return join(configDir, ".claude.json");
388
+ return join(homedir(), ".claude.json");
389
+ }
390
+ function snapshotPairConfig(claudeJsonPath = finalizeWatchPath()) {
391
+ return {
392
+ path: claudeJsonPath,
393
+ mtimeMs: existsSync(claudeJsonPath) ? statSync(claudeJsonPath).mtimeMs : 0
394
+ };
395
+ }
317
396
  async function finalizeClaudePairOnboarding(session, log, opts = {}) {
318
397
  const maxIterations = opts.maxIterations ?? 10;
398
+ const graceIterations = opts.graceIterations ?? 10;
319
399
  const intervalMs = opts.intervalMs ?? 1500;
320
- const claudeJsonPath = opts.claudeJsonPath ?? join(homedir(), ".claude.json");
321
- const initialMtime = existsSync(claudeJsonPath) ? statSync(claudeJsonPath).mtimeMs : 0;
400
+ const claudeJsonPath = opts.claudeJsonPath ?? finalizeWatchPath();
401
+ let initialMtime;
402
+ if (opts.baseline && opts.baseline.path === claudeJsonPath) {
403
+ initialMtime = opts.baseline.mtimeMs;
404
+ } else {
405
+ if (opts.baseline) {
406
+ log(
407
+ `[claude-pair] finalize: IGNORING a baseline for ${opts.baseline.path} \u2014 this run watches ${claudeJsonPath}. A write that landed before the code was submitted may now read as a failure.`
408
+ );
409
+ }
410
+ initialMtime = existsSync(claudeJsonPath) ? statSync(claudeJsonPath).mtimeMs : 0;
411
+ }
412
+ const counts = { consumerTerms: 0, dialogEnter: 0, noMatch: 0 };
413
+ const flushed = () => pairConfigLandedSince(claudeJsonPath, initialMtime);
414
+ const classifyAtGiveUp = () => {
415
+ if (!existsSync(claudeJsonPath)) return "config-absent";
416
+ try {
417
+ if (statSync(claudeJsonPath).isDirectory()) return "config-path-is-directory";
418
+ JSON.parse(readFileSync(claudeJsonPath, "utf-8"));
419
+ return "not-flushed";
420
+ } catch {
421
+ return "config-corrupt";
422
+ }
423
+ };
424
+ const describeCounts = () => `consumerTerms=${counts.consumerTerms} dialogEnter=${counts.dialogEnter} noMatch=${counts.noMatch}`;
322
425
  for (let i = 0; i < maxIterations; i++) {
323
426
  await sleep(intervalMs);
427
+ if (flushed()) {
428
+ log(
429
+ `[claude-pair] finalize: ${claudeJsonPath} updated (after ${i + 1} dialog dismissal(s); ${describeCounts()})`
430
+ );
431
+ return { finalized: true, iterations: i + 1, graceWaits: 0, counts, configPath: claudeJsonPath };
432
+ }
324
433
  const dead = await checkPairPaneAlive(session);
325
434
  if (dead) {
326
435
  log(`[claude-pair] finalize: pair session no longer running (${dead.kind}); aborting onboarding flow`);
327
- return { finalized: false, iterations: i };
436
+ return { finalized: false, iterations: i, graceWaits: 0, counts, reason: "pane-gone", configPath: claudeJsonPath };
328
437
  }
329
438
  let pane;
330
439
  try {
@@ -332,33 +441,71 @@ async function finalizeClaudePairOnboarding(session, log, opts = {}) {
332
441
  } catch (err) {
333
442
  const classified = classifyTmuxError(err);
334
443
  log(`[claude-pair] finalize: capture-pane failed (${classified.kind}); aborting onboarding flow`);
335
- return { finalized: false, iterations: i };
444
+ return { finalized: false, iterations: i, graceWaits: 0, counts, reason: "pane-gone", configPath: claudeJsonPath };
336
445
  }
337
446
  try {
338
- if (await answerConsumerTermsDialog(session, pane)) continue;
447
+ if (await answerConsumerTermsDialog(session, pane)) {
448
+ counts.consumerTerms += 1;
449
+ continue;
450
+ }
339
451
  } catch {
340
452
  log("[claude-pair] finalize: send-keys failed on consumer-terms dialog; aborting onboarding flow");
341
- return { finalized: false, iterations: i };
453
+ return { finalized: false, iterations: i, graceWaits: 0, counts, reason: "send-failed", configPath: claudeJsonPath };
342
454
  }
343
455
  if (pane.includes("Login successful") || pane.includes("Logged in as") || /Press Enter to continue/i.test(pane) || pane.includes("Security notes")) {
344
456
  try {
345
457
  await sendKeys(session, "C-m");
346
458
  } catch {
347
459
  log("[claude-pair] finalize: send-keys failed; aborting onboarding flow");
348
- return { finalized: false, iterations: i };
460
+ return { finalized: false, iterations: i, graceWaits: 0, counts, reason: "send-failed", configPath: claudeJsonPath };
349
461
  }
462
+ counts.dialogEnter += 1;
350
463
  continue;
351
464
  }
352
- if (existsSync(claudeJsonPath)) {
353
- const mtime = statSync(claudeJsonPath).mtimeMs;
354
- if (mtime > initialMtime) {
355
- log(`[claude-pair] finalize: ~/.claude.json updated (after ${i + 1} dialog dismissal(s))`);
356
- return { finalized: true, iterations: i + 1 };
357
- }
465
+ counts.noMatch += 1;
466
+ }
467
+ log(
468
+ `[claude-pair] finalize: ${maxIterations} dialog iterations elapsed (${describeCounts()}); waiting up to ${graceIterations} more intervals for the config`
469
+ );
470
+ let graceWaits = 0;
471
+ let graceAborted = null;
472
+ for (let g = 0; g < graceIterations; g++) {
473
+ await sleep(intervalMs);
474
+ graceWaits = g + 1;
475
+ if (flushed()) {
476
+ log(
477
+ `[claude-pair] finalize: ${claudeJsonPath} updated during grace pass (wait ${graceWaits}; ${describeCounts()})`
478
+ );
479
+ return { finalized: true, iterations: maxIterations, graceWaits, counts, configPath: claudeJsonPath };
480
+ }
481
+ const dead = await checkPairPaneAlive(session);
482
+ if (dead) {
483
+ log(`[claude-pair] finalize: pair session ended during grace pass (${dead.kind})`);
484
+ graceAborted = "pane-gone";
485
+ break;
486
+ }
487
+ let pane;
488
+ try {
489
+ pane = await capturePane(session);
490
+ } catch {
491
+ graceAborted = "pane-gone";
492
+ break;
493
+ }
494
+ try {
495
+ if (await answerConsumerTermsDialog(session, pane)) counts.consumerTerms += 1;
496
+ } catch {
497
+ graceAborted = "send-failed";
498
+ break;
358
499
  }
359
500
  }
360
- log(`[claude-pair] finalize: reached ${maxIterations} iterations without ~/.claude.json being updated`);
361
- return { finalized: false, iterations: maxIterations };
501
+ const observed = classifyAtGiveUp();
502
+ const selfExplaining = observed === "config-corrupt" || observed === "config-path-is-directory";
503
+ const reason = !selfExplaining && graceAborted ? graceAborted : observed;
504
+ const hint = reason === "config-corrupt" ? " \u2014 see ENG-10140 (shared-config corruption on containerised hosts)" : reason === "config-path-is-directory" ? " \u2014 a docker bind created a directory at this path; see ENG-10869" : "";
505
+ log(
506
+ `[claude-pair] finalize: giving up after ${maxIterations} dialog iterations + ${graceWaits} grace wait(s) \u2014 reason=${reason} (${describeCounts()}); ${claudeJsonPath} was not updated${hint}`
507
+ );
508
+ return { finalized: false, iterations: maxIterations, graceWaits, counts, reason, configPath: claudeJsonPath };
362
509
  }
363
510
  async function killPairSession(session) {
364
511
  try {
@@ -568,17 +715,24 @@ async function getClaudePairStatus(session) {
568
715
  }
569
716
  export {
570
717
  DEAD_PANE_STATUS_WAIT_MS,
718
+ PAIR_FINALIZE_ERROR_CODES,
571
719
  PAIR_REMAIN_ON_EXIT_FORMAT,
572
720
  awaitDeadPaneVerdict,
721
+ confirmPairFailure,
573
722
  describeClaudeExit,
723
+ describePairFinalizeFailure,
574
724
  finalizeClaudePairOnboarding,
725
+ finalizeWatchPath,
575
726
  getClaudePairStatus,
576
727
  isInteractivePromptReady,
577
728
  isTrustDialogVisible,
578
729
  killPairSession,
730
+ pairClaudeJsonPath,
731
+ pairConfigLandedSince,
579
732
  pairTmuxSession,
733
+ snapshotPairConfig,
580
734
  spawnPairSession,
581
735
  startClaudePair,
582
736
  submitClaudePairCode
583
737
  };
584
- //# sourceMappingURL=claude-pair-runtime-DDSYAHQW.js.map
738
+ //# sourceMappingURL=claude-pair-runtime-X27QVAXL.js.map