@spotpatch/bridge 0.1.0 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -542,11 +542,11 @@ function createSpotPatchBridgeClient(cwd = process.cwd()) {
542
542
  }
543
543
 
544
544
  // src/cli-runner.ts
545
- import path6 from "path";
545
+ import path9 from "path";
546
546
  import {
547
- ERROR_CODES as ERROR_CODES10,
548
- EXTERNAL_HANDOFF_LIMITS as EXTERNAL_HANDOFF_LIMITS9,
549
- SpotPatchError as SpotPatchError10
547
+ ERROR_CODES as ERROR_CODES11,
548
+ EXTERNAL_HANDOFF_LIMITS as EXTERNAL_HANDOFF_LIMITS10,
549
+ SpotPatchError as SpotPatchError11
550
550
  } from "@spotpatch/shared";
551
551
 
552
552
  // src/active/claude/channel-adapter.ts
@@ -802,7 +802,7 @@ import { z as z3 } from "zod";
802
802
  // package.json
803
803
  var package_default = {
804
804
  name: "@spotpatch/bridge",
805
- version: "0.1.0",
805
+ version: "0.2.0",
806
806
  description: "Local MCP inbox and explicit active Agent connectors for SpotPatch handoffs.",
807
807
  license: "MIT",
808
808
  repository: {
@@ -853,6 +853,7 @@ var package_default = {
853
853
  },
854
854
  dependencies: {
855
855
  "@modelcontextprotocol/server": "2.0.0",
856
+ "@spotpatch/agent": "workspace:^",
856
857
  "@spotpatch/shared": "workspace:^",
857
858
  zod: "4.4.3"
858
859
  },
@@ -1836,15 +1837,19 @@ async function applyBridgeSetupPlan(plan) {
1836
1837
 
1837
1838
  // src/active/codex/errors.ts
1838
1839
  var CODEX_ADAPTER_ERROR_CODES = Object.freeze({
1840
+ AUTH_REQUIRED: "CODEX_AUTH_REQUIRED",
1839
1841
  BUSY: "CODEX_ADAPTER_BUSY",
1840
1842
  CLOSED: "CODEX_ADAPTER_CLOSED",
1843
+ CONFIG_ISOLATION_UNSUPPORTED: "CODEX_CONFIG_ISOLATION_UNSUPPORTED",
1841
1844
  EXECUTABLE_NOT_FOUND: "CODEX_EXECUTABLE_NOT_FOUND",
1842
1845
  EXECUTABLE_UNTRUSTED: "CODEX_EXECUTABLE_UNTRUSTED",
1843
1846
  MCP_NOT_READY: "CODEX_MCP_NOT_READY",
1847
+ MODEL_UNAVAILABLE: "CODEX_MODEL_UNAVAILABLE",
1844
1848
  PROCESS_EXITED: "CODEX_PROCESS_EXITED",
1845
1849
  PROTOCOL: "CODEX_APP_SERVER_PROTOCOL_ERROR",
1846
1850
  REQUEST_FAILED: "CODEX_APP_SERVER_REQUEST_FAILED",
1847
1851
  REQUEST_TIMEOUT: "CODEX_APP_SERVER_REQUEST_TIMEOUT",
1852
+ THREAD_CLEANUP_INCOMPLETE: "CODEX_THREAD_CLEANUP_INCOMPLETE",
1848
1853
  UNSUPPORTED_VERSION: "CODEX_UNSUPPORTED_VERSION",
1849
1854
  WORKSPACE_WRITE_REQUIRED: "CODEX_WORKSPACE_WRITE_REQUIRED"
1850
1855
  });
@@ -1862,9 +1867,14 @@ import { constants as fsConstants } from "fs";
1862
1867
  import { access, realpath as realpath3, stat } from "fs/promises";
1863
1868
  import path4 from "path";
1864
1869
  import { spawn } from "child_process";
1865
- var SUPPORTED_CODEX_VERSION = "0.149.0";
1866
1870
  var VERSION_OUTPUT_LIMIT_BYTES = 8 * 1024;
1867
1871
  var VERSION_PROBE_TIMEOUT_MS = 5e3;
1872
+ function supportedVersion(value) {
1873
+ const match = /^codex-cli (0)\.(149)\.(\d+)$/u.exec(value);
1874
+ if (match === null) return void 0;
1875
+ const patchVersion = Number(match[3]);
1876
+ return Number.isSafeInteger(patchVersion) ? `0.149.${String(patchVersion)}` : void 0;
1877
+ }
1868
1878
  function isWithin(root, candidate) {
1869
1879
  const relative = path4.relative(root, candidate);
1870
1880
  return relative === "" || !relative.startsWith(`..${path4.sep}`) && relative !== "..";
@@ -1955,24 +1965,24 @@ async function readCodexVersion(executable) {
1955
1965
  });
1956
1966
  }
1957
1967
  async function resolveCodexExecutable(projectRoot, options = {}) {
1958
- const canonicalRoot = await realpath3(projectRoot);
1959
- if (!(await stat(canonicalRoot)).isDirectory()) {
1968
+ const canonicalRoot2 = await realpath3(projectRoot);
1969
+ if (!(await stat(canonicalRoot2)).isDirectory()) {
1960
1970
  throw new CodexAdapterError(CODEX_ADAPTER_ERROR_CODES.EXECUTABLE_UNTRUSTED);
1961
1971
  }
1962
1972
  const executable = await findOnTrustedPath(
1963
1973
  options.pathValue ?? process.env.PATH ?? ""
1964
1974
  );
1965
- if (isWithin(canonicalRoot, executable)) {
1975
+ if (isWithin(canonicalRoot2, executable)) {
1966
1976
  throw new CodexAdapterError(CODEX_ADAPTER_ERROR_CODES.EXECUTABLE_UNTRUSTED);
1967
1977
  }
1968
1978
  const output = await readCodexVersion(executable);
1969
- const expectedOutput = `codex-cli ${SUPPORTED_CODEX_VERSION}`;
1970
- if (output !== expectedOutput) {
1979
+ const version = supportedVersion(output);
1980
+ if (version === void 0) {
1971
1981
  throw new CodexAdapterError(CODEX_ADAPTER_ERROR_CODES.UNSUPPORTED_VERSION);
1972
1982
  }
1973
1983
  return Object.freeze({
1974
1984
  path: executable,
1975
- version: SUPPORTED_CODEX_VERSION
1985
+ version
1976
1986
  });
1977
1987
  }
1978
1988
 
@@ -2844,178 +2854,1173 @@ async function connectCodexAppServer(options) {
2844
2854
  return CodexAppServerAdapter.connect(options);
2845
2855
  }
2846
2856
 
2847
- // src/cli-runner.ts
2848
- function optionValue(arguments_, name) {
2849
- const index = arguments_.indexOf(name);
2850
- if (index === -1) return void 0;
2851
- const value = arguments_[index + 1];
2852
- if (value === void 0 || value.startsWith("--")) {
2853
- throw new SpotPatchError10(ERROR_CODES10.INVALID_REQUEST);
2857
+ // src/active/codex/managed-adapter.ts
2858
+ import { spawn as spawn3 } from "child_process";
2859
+ import { realpath as realpath7, stat as stat3 } from "fs/promises";
2860
+ import path8 from "path";
2861
+ import {
2862
+ ERROR_CODES as ERROR_CODES10,
2863
+ EXTERNAL_HANDOFF_LIMITS as EXTERNAL_HANDOFF_LIMITS9,
2864
+ SpotPatchError as SpotPatchError10
2865
+ } from "@spotpatch/shared";
2866
+
2867
+ // src/active/codex/managed-runtime.ts
2868
+ import { randomBytes as randomBytes4 } from "crypto";
2869
+ import { lstat as lstat4, readlink, realpath as realpath6, rename as rename3, rm as rm2, symlink } from "fs/promises";
2870
+ import os2 from "os";
2871
+ import path7 from "path";
2872
+
2873
+ // src/supervisor/private-store.ts
2874
+ import { randomBytes as randomBytes3 } from "crypto";
2875
+ import { lstat as lstat3, mkdir as mkdir2, open as open3, readFile, realpath as realpath5, rename as rename2, rm } from "fs/promises";
2876
+ import os from "os";
2877
+ import path6 from "path";
2878
+ function defaultConfigBase() {
2879
+ if (process.platform === "darwin") {
2880
+ return path6.join(os.homedir(), "Library", "Application Support", "SpotPatch");
2854
2881
  }
2855
- return value;
2882
+ if (process.platform === "win32") {
2883
+ const localAppData = process.env.LOCALAPPDATA;
2884
+ return path6.join(
2885
+ localAppData !== void 0 && path6.isAbsolute(localAppData) ? localAppData : os.homedir(),
2886
+ "SpotPatch"
2887
+ );
2888
+ }
2889
+ const xdgConfig = process.env.XDG_CONFIG_HOME;
2890
+ return path6.join(
2891
+ xdgConfig !== void 0 && path6.isAbsolute(xdgConfig) ? xdgConfig : path6.join(os.homedir(), ".config"),
2892
+ "spotpatch"
2893
+ );
2856
2894
  }
2857
- function allowedArguments(arguments_, booleanOptions, valueOptions) {
2858
- const allowed = /* @__PURE__ */ new Set([...booleanOptions, ...valueOptions]);
2859
- const seen = /* @__PURE__ */ new Set();
2860
- for (let index = 0; index < arguments_.length; index += 1) {
2861
- const argument = arguments_[index];
2862
- if (argument === void 0 || !allowed.has(argument) || seen.has(argument)) {
2863
- throw new SpotPatchError10(ERROR_CODES10.INVALID_REQUEST);
2895
+ function permissionsArePrivate(mode) {
2896
+ return process.platform === "win32" || (mode & 63) === 0;
2897
+ }
2898
+ function isOwnedByCurrentUser(uid) {
2899
+ const currentUid = process.getuid?.();
2900
+ return currentUid === void 0 || uid === currentUid;
2901
+ }
2902
+ async function ensurePrivateDirectory(directory) {
2903
+ await mkdir2(directory, { recursive: true, mode: 448 });
2904
+ const metadata = await lstat3(directory);
2905
+ if (!metadata.isDirectory() || metadata.isSymbolicLink() || !permissionsArePrivate(metadata.mode) || !isOwnedByCurrentUser(metadata.uid)) {
2906
+ throw new Error("SpotPatch private storage directory is not private.");
2907
+ }
2908
+ }
2909
+ async function resolvePrivateConfigBase(configuredBase) {
2910
+ const configBase = path6.resolve(configuredBase ?? defaultConfigBase());
2911
+ await ensurePrivateDirectory(configBase);
2912
+ return realpath5(configBase);
2913
+ }
2914
+ async function readPrivateJson(filePath, maximumBytes) {
2915
+ const metadata = await lstat3(filePath).catch((error) => {
2916
+ if (typeof error === "object" && error !== null && "code" in error && error.code === "ENOENT") {
2917
+ return void 0;
2864
2918
  }
2865
- seen.add(argument);
2866
- if (valueOptions.includes(argument)) {
2867
- const value = arguments_[index + 1];
2868
- if (value === void 0 || value.startsWith("--")) {
2869
- throw new SpotPatchError10(ERROR_CODES10.INVALID_REQUEST);
2870
- }
2871
- index += 1;
2919
+ throw error;
2920
+ });
2921
+ if (metadata === void 0) return void 0;
2922
+ if (!metadata.isFile() || metadata.isSymbolicLink() || !permissionsArePrivate(metadata.mode) || !isOwnedByCurrentUser(metadata.uid) || metadata.size > maximumBytes) {
2923
+ throw new Error("SpotPatch private storage file is not private or bounded.");
2924
+ }
2925
+ return JSON.parse(await readFile(filePath, "utf8"));
2926
+ }
2927
+ async function writePrivateJsonAtomic(filePath, temporaryPrefix, value) {
2928
+ const directory = path6.dirname(filePath);
2929
+ await ensurePrivateDirectory(directory);
2930
+ const temporaryPath = path6.join(
2931
+ directory,
2932
+ `.${temporaryPrefix}-${randomBytes3(12).toString("hex")}.tmp`
2933
+ );
2934
+ try {
2935
+ const handle = await open3(temporaryPath, "wx", 384);
2936
+ try {
2937
+ await handle.writeFile(`${JSON.stringify(value)}
2938
+ `, "utf8");
2939
+ await handle.sync();
2940
+ } finally {
2941
+ await handle.close();
2872
2942
  }
2943
+ await rename2(temporaryPath, filePath);
2944
+ } catch (error) {
2945
+ await rm(temporaryPath, { force: true }).catch(() => void 0);
2946
+ throw error;
2873
2947
  }
2874
2948
  }
2875
- function exitCode(error) {
2876
- if (error instanceof CodexAdapterError) {
2877
- if (error.code === CODEX_ADAPTER_ERROR_CODES.EXECUTABLE_UNTRUSTED) return 7;
2878
- if (error.code === CODEX_ADAPTER_ERROR_CODES.MCP_NOT_READY || error.code === CODEX_ADAPTER_ERROR_CODES.PROTOCOL || error.code === CODEX_ADAPTER_ERROR_CODES.UNSUPPORTED_VERSION) {
2879
- return 6;
2949
+
2950
+ // src/active/codex/managed-runtime.ts
2951
+ var AUTH_FILE_NAME = "auth.json";
2952
+ var MAXIMUM_AUTH_FILE_BYTES = 1024 * 1024;
2953
+ var RUNTIME_KEY_PATTERN = /^[a-f0-9]{64}$/u;
2954
+ function isMissingPathError(error) {
2955
+ return typeof error === "object" && error !== null && "code" in error && (error.code === "ENOENT" || error.code === "ENOTDIR");
2956
+ }
2957
+ function permissionsArePrivate2(mode) {
2958
+ return process.platform === "win32" || (mode & 63) === 0;
2959
+ }
2960
+ function isOwnedByCurrentUser2(uid) {
2961
+ const currentUid = process.getuid?.();
2962
+ return currentUid === void 0 || uid === currentUid;
2963
+ }
2964
+ function validateRuntimeKey(runtimeKey) {
2965
+ if (!RUNTIME_KEY_PATTERN.test(runtimeKey)) {
2966
+ throw new TypeError("The managed Codex runtime key is invalid.");
2967
+ }
2968
+ }
2969
+ function isWithin2(root, candidate) {
2970
+ const relative = path7.relative(root, candidate);
2971
+ return relative === "" || !relative.startsWith(`..${path7.sep}`) && relative !== "..";
2972
+ }
2973
+ async function canonicalProspectivePath(candidate) {
2974
+ let existing = path7.resolve(candidate);
2975
+ const missingSegments = [];
2976
+ for (; ; ) {
2977
+ const metadata = await lstat4(existing).catch((error) => {
2978
+ if (isMissingPathError(error)) return void 0;
2979
+ throw error;
2980
+ });
2981
+ if (metadata !== void 0) {
2982
+ return path7.join(await realpath6(existing), ...missingSegments.reverse());
2880
2983
  }
2881
- if (error.code === CODEX_ADAPTER_ERROR_CODES.BUSY || error.code === CODEX_ADAPTER_ERROR_CODES.CLOSED || error.code === CODEX_ADAPTER_ERROR_CODES.EXECUTABLE_NOT_FOUND || error.code === CODEX_ADAPTER_ERROR_CODES.PROCESS_EXITED || error.code === CODEX_ADAPTER_ERROR_CODES.REQUEST_TIMEOUT) {
2882
- return 8;
2984
+ const parent = path7.dirname(existing);
2985
+ if (parent === existing) throw new Error("No existing path ancestor was found.");
2986
+ missingSegments.push(path7.basename(existing));
2987
+ existing = parent;
2988
+ }
2989
+ }
2990
+ async function resolveSourceAuthFile(environment) {
2991
+ const configuredHome = environment.CODEX_HOME;
2992
+ const sourceHome = configuredHome ?? path7.join(os2.homedir(), ".codex");
2993
+ if (!path7.isAbsolute(sourceHome)) {
2994
+ throw new Error("CODEX_HOME must be absolute for managed isolation.");
2995
+ }
2996
+ const authFile = path7.join(sourceHome, AUTH_FILE_NAME);
2997
+ const metadata = await lstat4(authFile).catch((error) => {
2998
+ if (isMissingPathError(error)) return void 0;
2999
+ throw error;
3000
+ });
3001
+ if (metadata === void 0) return void 0;
3002
+ if (!metadata.isFile() || metadata.isSymbolicLink() || metadata.size > MAXIMUM_AUTH_FILE_BYTES || !permissionsArePrivate2(metadata.mode) || !isOwnedByCurrentUser2(metadata.uid)) {
3003
+ throw new Error("The Codex authentication file is not private and bounded.");
3004
+ }
3005
+ return realpath6(authFile);
3006
+ }
3007
+ async function replaceAuthLink(runtimeHome, sourceAuthFile) {
3008
+ const authLink = path7.join(runtimeHome, AUTH_FILE_NAME);
3009
+ const existing = await lstat4(authLink).catch((error) => {
3010
+ if (isMissingPathError(error)) return void 0;
3011
+ throw error;
3012
+ });
3013
+ if (sourceAuthFile === void 0) {
3014
+ if (existing === void 0) return;
3015
+ if (!existing.isSymbolicLink() || !isOwnedByCurrentUser2(existing.uid)) {
3016
+ throw new Error("The managed Codex authentication entry is not owned.");
2883
3017
  }
2884
- return 1;
3018
+ await rm2(authLink);
3019
+ return;
2885
3020
  }
2886
- if (!(error instanceof SpotPatchError10)) return 1;
2887
- if (error.code === ERROR_CODES10.INVALID_REQUEST) return 2;
2888
- if (error.code === ERROR_CODES10.SESSION_NOT_FOUND) return 3;
2889
- if (error.code === ERROR_CODES10.HANDOFF_NOT_FOUND || error.code === ERROR_CODES10.HANDOFF_EXPIRED) {
2890
- return 4;
3021
+ if (existing !== void 0) {
3022
+ if (!existing.isSymbolicLink() || !isOwnedByCurrentUser2(existing.uid)) {
3023
+ throw new Error("The managed Codex authentication entry is not owned.");
3024
+ }
3025
+ if (await readlink(authLink) === sourceAuthFile) return;
2891
3026
  }
2892
- if (error.code === ERROR_CODES10.SESSION_AMBIGUOUS) return 5;
2893
- if (error.code === ERROR_CODES10.BRIDGE_PROTOCOL_MISMATCH) return 6;
2894
- if (error.code === ERROR_CODES10.BRIDGE_UNAUTHORIZED) return 7;
2895
- if (error.code === ERROR_CODES10.SESSION_CLOSED || error.code === ERROR_CODES10.EXTERNAL_HANDOFF_UNAVAILABLE) {
2896
- return 8;
3027
+ const temporaryLink = path7.join(
3028
+ runtimeHome,
3029
+ `.auth-${randomBytes4(12).toString("hex")}.tmp`
3030
+ );
3031
+ try {
3032
+ await symlink(sourceAuthFile, temporaryLink, "file");
3033
+ await rename3(temporaryLink, authLink);
3034
+ } finally {
3035
+ await rm2(temporaryLink, { force: true }).catch(() => void 0);
2897
3036
  }
2898
- return 1;
2899
3037
  }
2900
- function writeJson(stdout, command, data) {
2901
- stdout.write(`${JSON.stringify({ schemaVersion: 1, command, data })}
2902
- `);
3038
+ async function resolveRuntimeHome(options) {
3039
+ validateRuntimeKey(options.runtimeKey);
3040
+ if (options.runtimeBase !== void 0 && options.excludedRoot !== void 0 && isWithin2(
3041
+ await realpath6(options.excludedRoot),
3042
+ await canonicalProspectivePath(options.runtimeBase)
3043
+ )) {
3044
+ throw new Error("The managed Codex runtime cannot be stored in the project.");
3045
+ }
3046
+ const configBase = await resolvePrivateConfigBase(options.runtimeBase);
3047
+ const configuredRuntimeRoot = path7.join(
3048
+ configBase,
3049
+ "external-agent-runtime",
3050
+ "codex"
3051
+ );
3052
+ if (options.excludedRoot !== void 0 && isWithin2(await realpath6(options.excludedRoot), configuredRuntimeRoot)) {
3053
+ throw new Error("The managed Codex runtime cannot be stored in the project.");
3054
+ }
3055
+ await ensurePrivateDirectory(configuredRuntimeRoot);
3056
+ const runtimeRoot = await realpath6(configuredRuntimeRoot);
3057
+ const runtimeHome = path7.join(runtimeRoot, options.runtimeKey);
3058
+ await ensurePrivateDirectory(runtimeHome);
3059
+ return realpath6(runtimeHome);
2903
3060
  }
2904
- function usage(stderr) {
2905
- stderr.write(
2906
- "Usage: spotpatch-bridge <sessions|current|wait|ack|mcp|channel|connect|setup> [options]\n"
3061
+ async function prepareManagedCodexRuntimeHome(options) {
3062
+ const sourceAuthFile = await resolveSourceAuthFile(
3063
+ options.environment ?? process.env
3064
+ );
3065
+ const runtimeHome = await resolveRuntimeHome(options);
3066
+ await replaceAuthLink(runtimeHome, sourceAuthFile);
3067
+ return runtimeHome;
3068
+ }
3069
+ async function removeManagedCodexRuntimeHome(options) {
3070
+ validateRuntimeKey(options.runtimeKey);
3071
+ const configBase = await resolvePrivateConfigBase(options.runtimeBase);
3072
+ const configuredRuntimeRoot = path7.join(
3073
+ configBase,
3074
+ "external-agent-runtime",
3075
+ "codex"
3076
+ );
3077
+ const runtimeRootMetadata = await lstat4(configuredRuntimeRoot).catch(
3078
+ (error) => {
3079
+ if (isMissingPathError(error)) return void 0;
3080
+ throw error;
3081
+ }
2907
3082
  );
3083
+ if (runtimeRootMetadata === void 0) return;
3084
+ if (!runtimeRootMetadata.isDirectory() || runtimeRootMetadata.isSymbolicLink() || !permissionsArePrivate2(runtimeRootMetadata.mode) || !isOwnedByCurrentUser2(runtimeRootMetadata.uid)) {
3085
+ throw new Error("The managed Codex runtime root is not private.");
3086
+ }
3087
+ const runtimeRoot = await realpath6(configuredRuntimeRoot);
3088
+ const runtimeHome = path7.join(runtimeRoot, options.runtimeKey);
3089
+ const runtimeMetadata = await lstat4(runtimeHome).catch((error) => {
3090
+ if (isMissingPathError(error)) return void 0;
3091
+ throw error;
3092
+ });
3093
+ if (runtimeMetadata === void 0) return;
3094
+ if (!runtimeMetadata.isDirectory() || runtimeMetadata.isSymbolicLink() || !permissionsArePrivate2(runtimeMetadata.mode) || !isOwnedByCurrentUser2(runtimeMetadata.uid)) {
3095
+ throw new Error("The managed Codex runtime is not owned.");
3096
+ }
3097
+ await rm2(runtimeHome, { recursive: true, force: true });
3098
+ }
3099
+
3100
+ // src/active/codex/managed-adapter.ts
3101
+ var CLIENT_NAME2 = "spotpatch";
3102
+ var CLIENT_TITLE2 = "SpotPatch";
3103
+ var MANAGED_PERMISSION_PROFILE = "spotpatch-managed";
3104
+ var DEFAULT_PROCESS_SHUTDOWN_TIMEOUT_MS2 = 2e3;
3105
+ var MAXIMUM_MODEL_PAGES = 8;
3106
+ var MAXIMUM_MCP_STATUS_PAGES2 = 8;
3107
+ var MANAGED_SHELL_ENVIRONMENT_NAMES = Object.freeze([
3108
+ "LANG",
3109
+ "LC_ALL",
3110
+ "NODE_ENV",
3111
+ "NO_COLOR",
3112
+ "PATH",
3113
+ "PATHEXT",
3114
+ "SSL_CERT_DIR",
3115
+ "SSL_CERT_FILE",
3116
+ "SYSTEMROOT",
3117
+ "TEMP",
3118
+ "TMP",
3119
+ "TMPDIR"
3120
+ ]);
3121
+ var MANAGED_SHELL_ENVIRONMENT_FILTERS = Object.freeze(
3122
+ Object.fromEntries(
3123
+ MANAGED_SHELL_ENVIRONMENT_NAMES.map((name) => [name, "include"])
3124
+ )
3125
+ );
3126
+ var MANAGED_SHELL_ENVIRONMENT_POLICY = Object.freeze({
3127
+ inherit: "all",
3128
+ ignore_default_excludes: false,
3129
+ filters: MANAGED_SHELL_ENVIRONMENT_FILTERS
3130
+ });
3131
+ var MANAGED_SHELL_ENVIRONMENT_POLICY_TOML = `{ inherit="all", ignore_default_excludes=false, filters={ ${MANAGED_SHELL_ENVIRONMENT_NAMES.map(
3132
+ (name) => `${name}="include"`
3133
+ ).join(", ")} } }`;
3134
+ var MANAGED_CODEX_CONFIG_OVERRIDES = Object.freeze([
3135
+ "agents.enabled=false",
3136
+ "features.apps=false",
3137
+ "features.hooks=false",
3138
+ "features.plugins=false",
3139
+ "features.remote_plugin=false",
3140
+ 'web_search="disabled"',
3141
+ "mcp_servers={}",
3142
+ `shell_environment_policy=${MANAGED_SHELL_ENVIRONMENT_POLICY_TOML}`
3143
+ ]);
3144
+ function isRecord3(value) {
3145
+ return typeof value === "object" && value !== null && !Array.isArray(value);
3146
+ }
3147
+ function hasOnlyKeys2(value, keys) {
3148
+ const actual = Object.keys(value);
3149
+ return actual.length === keys.length && keys.every((key) => key in value);
2908
3150
  }
2909
3151
  function abortError4() {
2910
- const error = new Error("The SpotPatch command was interrupted.");
3152
+ const error = new Error("The managed Codex operation was aborted.");
2911
3153
  error.name = "AbortError";
2912
3154
  return error;
2913
3155
  }
2914
- function abortableOperation(operation, signal) {
2915
- if (signal.aborted) return Promise.reject(abortError4());
2916
- return new Promise((resolve, reject) => {
2917
- const finish = () => {
2918
- signal.removeEventListener("abort", abort);
2919
- };
2920
- const abort = () => {
2921
- finish();
2922
- reject(abortError4());
2923
- };
2924
- signal.addEventListener("abort", abort, { once: true });
2925
- void operation.then(
2926
- (value) => {
2927
- finish();
2928
- resolve(value);
2929
- },
2930
- (error) => {
2931
- finish();
2932
- reject(
2933
- error instanceof Error ? error : new Error("The SpotPatch operation failed.", { cause: error })
2934
- );
2935
- }
2936
- );
2937
- });
3156
+ function managedFailureReason(error, fallback) {
3157
+ if (error instanceof CodexAdapterError) {
3158
+ if (error.code === CODEX_ADAPTER_ERROR_CODES.CONFIG_ISOLATION_UNSUPPORTED) {
3159
+ return "config-isolation";
3160
+ }
3161
+ if (error.code === CODEX_ADAPTER_ERROR_CODES.PROTOCOL || error.code === CODEX_ADAPTER_ERROR_CODES.REQUEST_FAILED) {
3162
+ return "protocol";
3163
+ }
3164
+ return fallback;
3165
+ }
3166
+ if (!(error instanceof SpotPatchError10)) return fallback;
3167
+ switch (error.code) {
3168
+ case ERROR_CODES10.AGENT_LIMIT_EXCEEDED:
3169
+ return "change-limit";
3170
+ case ERROR_CODES10.PATCH_REJECTED:
3171
+ case ERROR_CODES10.HANDOFF_VALIDATION_FAILED:
3172
+ return "scope";
3173
+ case ERROR_CODES10.VALIDATION_FAILED:
3174
+ return "validation";
3175
+ case ERROR_CODES10.APPLY_CONFLICT:
3176
+ return "workspace-conflict";
3177
+ case ERROR_CODES10.WORKTREE_CONFLICTED:
3178
+ case ERROR_CODES10.WORKTREE_DIRTY:
3179
+ case ERROR_CODES10.WORKTREE_LOCAL_CHANGES_TOO_LARGE:
3180
+ case ERROR_CODES10.WORKTREE_LOCAL_CHANGES_UNSUPPORTED:
3181
+ case ERROR_CODES10.WORKTREE_NOT_REPOSITORY:
3182
+ case ERROR_CODES10.WORKTREE_OPERATION_IN_PROGRESS:
3183
+ case ERROR_CODES10.WORKTREE_UNTRACKED_UNSUPPORTED:
3184
+ return "snapshot";
3185
+ default:
3186
+ return fallback;
3187
+ }
2938
3188
  }
2939
- function processSignalScope(onInterrupt) {
2940
- let interrupted = false;
2941
- const interrupt = () => {
2942
- if (interrupted) return;
2943
- interrupted = true;
2944
- onInterrupt();
3189
+ function throwIfAborted3(signal) {
3190
+ if (signal?.aborted === true) throw abortError4();
3191
+ }
3192
+ function signalProcessTree2(child, signal) {
3193
+ if (process.platform !== "win32" && child.pid !== void 0) {
3194
+ try {
3195
+ process.kill(-child.pid, signal);
3196
+ return;
3197
+ } catch {
3198
+ }
3199
+ }
3200
+ child.kill(signal);
3201
+ }
3202
+ function managedEnvironment(codexHome) {
3203
+ const names = [
3204
+ "CODEX_ACCESS_TOKEN",
3205
+ "CODEX_API_KEY",
3206
+ "OPENAI_API_KEY",
3207
+ "PATH",
3208
+ "PATHEXT",
3209
+ "SYSTEMROOT",
3210
+ "TMPDIR",
3211
+ "TMP",
3212
+ "TEMP",
3213
+ "LANG",
3214
+ "LC_ALL",
3215
+ "SSL_CERT_FILE",
3216
+ "SSL_CERT_DIR",
3217
+ "HTTP_PROXY",
3218
+ "HTTPS_PROXY",
3219
+ "ALL_PROXY",
3220
+ "http_proxy",
3221
+ "https_proxy",
3222
+ "all_proxy"
3223
+ ];
3224
+ const environment = {
3225
+ CODEX_HOME: codexHome,
3226
+ HOME: codexHome,
3227
+ NO_COLOR: "1",
3228
+ NODE_ENV: process.env.NODE_ENV ?? "development",
3229
+ USERPROFILE: codexHome
2945
3230
  };
2946
- process.once("SIGINT", interrupt);
2947
- process.once("SIGTERM", interrupt);
3231
+ for (const name of names) {
3232
+ const value = process.env[name];
3233
+ if (value !== void 0) environment[name] = value;
3234
+ }
3235
+ const loopback = "localhost,127.0.0.1,::1";
3236
+ const existing = process.env.NO_PROXY ?? process.env.no_proxy;
3237
+ environment.NO_PROXY = existing === void 0 ? loopback : `${loopback},${existing}`;
3238
+ environment.no_proxy = environment.NO_PROXY;
3239
+ return environment;
3240
+ }
3241
+ function managedThreadConfig() {
2948
3242
  return Object.freeze({
2949
- interrupted: () => interrupted,
2950
- remove() {
2951
- process.removeListener("SIGINT", interrupt);
2952
- process.removeListener("SIGTERM", interrupt);
2953
- }
3243
+ agents: Object.freeze({ enabled: false }),
3244
+ default_permissions: MANAGED_PERMISSION_PROFILE,
3245
+ features: Object.freeze({
3246
+ apps: false,
3247
+ hooks: false,
3248
+ plugins: false,
3249
+ remote_plugin: false
3250
+ }),
3251
+ mcp_servers: Object.freeze({}),
3252
+ permissions: Object.freeze({
3253
+ [MANAGED_PERMISSION_PROFILE]: Object.freeze({
3254
+ filesystem: Object.freeze({
3255
+ ":root": "deny",
3256
+ ":minimal": "read",
3257
+ ":workspace_roots": Object.freeze({ ".": "write" })
3258
+ }),
3259
+ network: Object.freeze({ enabled: false })
3260
+ })
3261
+ }),
3262
+ shell_environment_policy: MANAGED_SHELL_ENVIRONMENT_POLICY,
3263
+ web_search: "disabled"
2954
3264
  });
2955
3265
  }
2956
- async function runClaudeChannel(cwd, sessionId) {
2957
- let close;
2958
- const signals = processSignalScope(() => {
2959
- void close?.();
3266
+ function verifyInitializeResponse2(value, codexHome) {
3267
+ if (!isRecord3(value) || typeof value.userAgent !== "string" || value.codexHome !== codexHome || typeof value.platformFamily !== "string" || typeof value.platformOs !== "string") {
3268
+ throw new CodexAdapterError(CODEX_ADAPTER_ERROR_CODES.PROTOCOL);
3269
+ }
3270
+ }
3271
+ function parseAuthReadiness(value) {
3272
+ if (!isRecord3(value) || !hasOnlyKeys2(value, ["account", "requiresOpenaiAuth"]) || typeof value.requiresOpenaiAuth !== "boolean" || value.account !== null && !isRecord3(value.account)) {
3273
+ throw new CodexAdapterError(CODEX_ADAPTER_ERROR_CODES.PROTOCOL);
3274
+ }
3275
+ if (value.account !== null) return "authenticated";
3276
+ return value.requiresOpenaiAuth ? "signed-out" : "auth-not-required";
3277
+ }
3278
+ function parseModelPage(value) {
3279
+ if (!isRecord3(value) || !Array.isArray(value.data) || value.nextCursor !== null && typeof value.nextCursor !== "string") {
3280
+ throw new CodexAdapterError(CODEX_ADAPTER_ERROR_CODES.PROTOCOL);
3281
+ }
3282
+ const models = value.data.map((item) => {
3283
+ if (!isRecord3(item) || typeof item.model !== "string" || item.model.length === 0 || typeof item.isDefault !== "boolean") {
3284
+ throw new CodexAdapterError(CODEX_ADAPTER_ERROR_CODES.PROTOCOL);
3285
+ }
3286
+ return Object.freeze({ model: item.model, isDefault: item.isDefault });
2960
3287
  });
2961
- try {
2962
- const exactSessionId = await resolveExactProjectSessionId(cwd, sessionId);
2963
- const handle = await serveClaudeChannelMcp({ cwd, sessionId: exactSessionId });
2964
- close = handle.close;
2965
- if (signals.interrupted()) await handle.close();
2966
- await handle.done;
2967
- return signals.interrupted() ? 130 : 0;
2968
- } finally {
2969
- signals.remove();
2970
- await close?.().catch(() => void 0);
3288
+ return Object.freeze({ models: Object.freeze(models), nextCursor: value.nextCursor });
3289
+ }
3290
+ function verifyConfigRequirements(value) {
3291
+ if (!isRecord3(value) || !hasOnlyKeys2(value, ["requirements"])) {
3292
+ throw new CodexAdapterError(CODEX_ADAPTER_ERROR_CODES.PROTOCOL);
3293
+ }
3294
+ if (value.requirements === null) return;
3295
+ if (!isRecord3(value.requirements)) {
3296
+ throw new CodexAdapterError(CODEX_ADAPTER_ERROR_CODES.PROTOCOL);
3297
+ }
3298
+ const approvals = value.requirements.allowedApprovalPolicies;
3299
+ const sandboxes = value.requirements.allowedSandboxModes;
3300
+ const permissionProfiles = value.requirements.allowedPermissionProfiles;
3301
+ if (Array.isArray(approvals) && !approvals.includes("never") || permissionProfiles !== null && (!isRecord3(permissionProfiles) || permissionProfiles[MANAGED_PERMISSION_PROFILE] !== true) || permissionProfiles === null && Array.isArray(sandboxes) && !sandboxes.includes("workspaceWrite")) {
3302
+ throw new CodexAdapterError(CODEX_ADAPTER_ERROR_CODES.PROTOCOL);
2971
3303
  }
2972
3304
  }
2973
- function writeCodexConnectorEvent(event, stderr) {
2974
- if (event.type === "ready") {
2975
- stderr.write(
2976
- "[spotpatch:bridge] Codex connected and ready for SpotPatch requests.\n"
2977
- );
2978
- return;
3305
+ function parseThreadStartResponse2(value, root) {
3306
+ if (!isRecord3(value) || !isRecord3(value.thread) || typeof value.thread.id !== "string" || value.thread.ephemeral !== false || value.thread.cwd !== root || value.cwd !== root || value.approvalPolicy !== "never" || !Array.isArray(value.runtimeWorkspaceRoots) || value.runtimeWorkspaceRoots.length !== 1 || value.runtimeWorkspaceRoots[0] !== root || !isRecord3(value.activePermissionProfile) || value.activePermissionProfile.id !== MANAGED_PERMISSION_PROFILE) {
3307
+ throw new CodexAdapterError(CODEX_ADAPTER_ERROR_CODES.PROTOCOL);
2979
3308
  }
2980
- const revision = String(event.revision);
2981
- switch (event.phase) {
2982
- case "dispatching":
2983
- stderr.write(
2984
- `[spotpatch:bridge] SpotPatch is preparing revision ${revision} for Codex.
2985
- `
2986
- );
2987
- return;
2988
- case "working":
2989
- stderr.write(`[spotpatch:bridge] Codex started revision ${revision}.
2990
- `);
2991
- return;
2992
- case "completed":
2993
- stderr.write(
2994
- `[spotpatch:bridge] Codex turn ended for revision ${revision}; review the workspace. Ready for the next request.
2995
- `
2996
- );
2997
- return;
2998
- case "failed":
2999
- stderr.write(
3000
- `[spotpatch:bridge] Revision ${revision} failed before a verified Codex turn completed; review the connector output and workspace. Ready for the next request.
3001
- `
3002
- );
3003
- return;
3004
- case "delivery-unknown":
3005
- stderr.write(
3006
- `[spotpatch:bridge] Codex delivery for revision ${revision} is unknown; the connector will stop.
3007
- `
3008
- );
3009
- return;
3010
- case "dispatched":
3011
- stderr.write(`[spotpatch:bridge] Codex accepted revision ${revision}.
3012
- `);
3013
- return;
3309
+ return value.thread.id;
3310
+ }
3311
+ function parseTurn2(value) {
3312
+ if (!isRecord3(value) || typeof value.id !== "string") {
3313
+ throw new CodexAdapterError(CODEX_ADAPTER_ERROR_CODES.PROTOCOL);
3314
+ }
3315
+ if (value.status !== "inProgress" && value.status !== "completed" && value.status !== "failed" && value.status !== "interrupted") {
3316
+ throw new CodexAdapterError(CODEX_ADAPTER_ERROR_CODES.PROTOCOL);
3014
3317
  }
3318
+ return Object.freeze({ id: value.id, status: value.status });
3015
3319
  }
3016
- async function runCodexConnector(adapterKind, cwd, stderr, sessionId) {
3017
- const pumpController = new AbortController();
3018
- const startupController = new AbortController();
3320
+ function parseTurnEvent2(method, params) {
3321
+ if (method !== "turn/started" && method !== "turn/completed") return void 0;
3322
+ if (!isRecord3(params) || typeof params.threadId !== "string") {
3323
+ throw new CodexAdapterError(CODEX_ADAPTER_ERROR_CODES.PROTOCOL);
3324
+ }
3325
+ const turn = parseTurn2(params.turn);
3326
+ if (method === "turn/started" && turn.status !== "inProgress" || method === "turn/completed" && turn.status === "inProgress") {
3327
+ throw new CodexAdapterError(CODEX_ADAPTER_ERROR_CODES.PROTOCOL);
3328
+ }
3329
+ return Object.freeze({
3330
+ kind: method === "turn/started" ? "started" : "completed",
3331
+ status: turn.status,
3332
+ threadId: params.threadId,
3333
+ turnId: turn.id
3334
+ });
3335
+ }
3336
+ async function canonicalRoot(root) {
3337
+ const canonical = await realpath7(root);
3338
+ if (!(await stat3(canonical)).isDirectory()) {
3339
+ throw new CodexAdapterError(CODEX_ADAPTER_ERROR_CODES.EXECUTABLE_UNTRUSTED);
3340
+ }
3341
+ return canonical;
3342
+ }
3343
+ var ManagedCodexAppServerAdapter = class _ManagedCodexAppServerAdapter {
3344
+ kind = "codex-app-server";
3345
+ #child;
3346
+ #client;
3347
+ #cleanupJournal;
3348
+ #execution;
3349
+ #onEvent;
3350
+ #processShutdownTimeoutMs;
3351
+ #terminalTimeoutMs;
3352
+ #active;
3353
+ #closed = false;
3354
+ #closePromise;
3355
+ #fatalError;
3356
+ constructor(child, client, options) {
3357
+ this.#child = child;
3358
+ this.#client = client;
3359
+ this.#cleanupJournal = options.cleanupJournal;
3360
+ this.#execution = options.execution;
3361
+ this.#onEvent = options.onEvent;
3362
+ this.#processShutdownTimeoutMs = options.processShutdownTimeoutMs ?? DEFAULT_PROCESS_SHUTDOWN_TIMEOUT_MS2;
3363
+ this.#terminalTimeoutMs = options.terminalTimeoutMs ?? EXTERNAL_HANDOFF_LIMITS9.activeDispatchTimeoutMs;
3364
+ }
3365
+ static async connect(options) {
3366
+ throwIfAborted3(options.signal);
3367
+ const projectRoot = await canonicalRoot(options.projectRoot);
3368
+ const executable = await resolveCodexExecutable(projectRoot, {
3369
+ ...options.pathValue === void 0 ? {} : { pathValue: options.pathValue }
3370
+ });
3371
+ let codexHome;
3372
+ try {
3373
+ codexHome = await prepareManagedCodexRuntimeHome({
3374
+ excludedRoot: projectRoot,
3375
+ ...options.privateRuntimeBase === void 0 ? {} : { runtimeBase: options.privateRuntimeBase },
3376
+ runtimeKey: options.runtimeKey
3377
+ });
3378
+ } catch (error) {
3379
+ throw new CodexAdapterError(
3380
+ CODEX_ADAPTER_ERROR_CODES.CONFIG_ISOLATION_UNSUPPORTED,
3381
+ error
3382
+ );
3383
+ }
3384
+ const args = MANAGED_CODEX_CONFIG_OVERRIDES.flatMap((value) => ["-c", value]);
3385
+ args.push("app-server");
3386
+ const child = spawn3(executable.path, args, {
3387
+ cwd: path8.dirname(executable.path),
3388
+ detached: process.platform !== "win32",
3389
+ env: managedEnvironment(codexHome),
3390
+ shell: false,
3391
+ stdio: ["pipe", "pipe", "pipe"],
3392
+ windowsHide: true
3393
+ });
3394
+ const state = {};
3395
+ let earlyFatal;
3396
+ const client = new CodexJsonlClient(child, {
3397
+ ...options.maximumLineBytes === void 0 ? {} : { maximumLineBytes: options.maximumLineBytes },
3398
+ ...options.maximumStderrBytes === void 0 ? {} : { maximumStderrBytes: options.maximumStderrBytes },
3399
+ ...options.requestTimeoutMs === void 0 ? {} : { requestTimeoutMs: options.requestTimeoutMs },
3400
+ onFatal(error) {
3401
+ if (state.adapter === void 0) earlyFatal = error;
3402
+ else state.adapter.#handleFatal(error);
3403
+ },
3404
+ onNotification(method, params) {
3405
+ if (state.adapter !== void 0) {
3406
+ state.adapter.#handleNotification(method, params);
3407
+ }
3408
+ }
3409
+ });
3410
+ const adapter = new _ManagedCodexAppServerAdapter(child, client, options);
3411
+ state.adapter = adapter;
3412
+ if (earlyFatal !== void 0) adapter.#handleFatal(earlyFatal);
3413
+ const abort = () => {
3414
+ void adapter.close().catch(() => void 0);
3415
+ };
3416
+ options.signal.addEventListener("abort", abort, { once: true });
3417
+ try {
3418
+ const initialized = await adapter.#request("initialize", {
3419
+ clientInfo: {
3420
+ name: CLIENT_NAME2,
3421
+ title: CLIENT_TITLE2,
3422
+ version: package_default.version
3423
+ },
3424
+ capabilities: { experimentalApi: true, requestAttestation: false }
3425
+ });
3426
+ verifyInitializeResponse2(initialized, codexHome);
3427
+ client.notify("initialized");
3428
+ const account = parseAuthReadiness(
3429
+ await adapter.#request("account/read", { refreshToken: false })
3430
+ );
3431
+ if (account === "signed-out") {
3432
+ throw new CodexAdapterError(CODEX_ADAPTER_ERROR_CODES.AUTH_REQUIRED);
3433
+ }
3434
+ const requestedModel = await adapter.#readDefaultModel();
3435
+ verifyConfigRequirements(await adapter.#request("configRequirements/read", {}));
3436
+ await adapter.#recoverThreadCleanup();
3437
+ throwIfAborted3(options.signal);
3438
+ return Object.freeze({
3439
+ adapter,
3440
+ authReadiness: account,
3441
+ requestedModel,
3442
+ effectiveModel: requestedModel
3443
+ });
3444
+ } catch (error) {
3445
+ await adapter.close();
3446
+ throwIfAborted3(options.signal);
3447
+ throw error;
3448
+ } finally {
3449
+ options.signal.removeEventListener("abort", abort);
3450
+ }
3451
+ }
3452
+ diagnostics() {
3453
+ return this.#client.diagnostics();
3454
+ }
3455
+ async deliver(handoff, lifecycle, signal) {
3456
+ if (this.#active !== void 0) {
3457
+ throw new CodexAdapterError(CODEX_ADAPTER_ERROR_CODES.BUSY);
3458
+ }
3459
+ if (this.#closed) {
3460
+ throw this.#fatalError ?? new CodexAdapterError(CODEX_ADAPTER_ERROR_CODES.CLOSED);
3461
+ }
3462
+ this.#onEvent({ type: "phase", revision: handoff.revision, phase: "preparing" });
3463
+ let task;
3464
+ try {
3465
+ task = await this.#execution.prepare(
3466
+ { annotation: handoff.annotation, revision: handoff.revision },
3467
+ signal
3468
+ );
3469
+ } catch (error) {
3470
+ this.#onEvent({
3471
+ type: "failure",
3472
+ revision: handoff.revision,
3473
+ reason: managedFailureReason(error, "snapshot")
3474
+ });
3475
+ await lifecycle.report("failed");
3476
+ return;
3477
+ }
3478
+ let threadId;
3479
+ try {
3480
+ const thread = await this.#request("thread/start", {
3481
+ cwd: task.workspaceRoot,
3482
+ runtimeWorkspaceRoots: [task.workspaceRoot],
3483
+ approvalPolicy: "never",
3484
+ permissions: MANAGED_PERMISSION_PROFILE,
3485
+ config: managedThreadConfig(),
3486
+ ephemeral: false
3487
+ });
3488
+ threadId = parseThreadStartResponse2(thread, task.workspaceRoot);
3489
+ try {
3490
+ await this.#cleanupJournal.add(threadId);
3491
+ } catch (error) {
3492
+ await this.#deleteThread(threadId).catch(() => void 0);
3493
+ throw new CodexAdapterError(
3494
+ CODEX_ADAPTER_ERROR_CODES.THREAD_CLEANUP_INCOMPLETE,
3495
+ error
3496
+ );
3497
+ }
3498
+ await this.#assertNoHooks(task.workspaceRoot);
3499
+ await this.#assertNoMcpServers(threadId);
3500
+ await this.#runTurn(handoff, task, threadId, lifecycle, signal);
3501
+ } catch (error) {
3502
+ if (signal.aborted) throw abortError4();
3503
+ if (error instanceof ActiveDeliveryUnknownError) throw error;
3504
+ this.#onEvent({
3505
+ type: "failure",
3506
+ revision: handoff.revision,
3507
+ reason: managedFailureReason(error, "apply")
3508
+ });
3509
+ await lifecycle.report("failed").catch(() => void 0);
3510
+ } finally {
3511
+ if (threadId !== void 0 && this.#threadCleanupAvailable()) {
3512
+ try {
3513
+ await this.#deleteThread(threadId);
3514
+ await this.#cleanupJournal.remove(threadId);
3515
+ } catch {
3516
+ this.#onEvent({
3517
+ type: "cleanup-warning",
3518
+ revision: handoff.revision
3519
+ });
3520
+ }
3521
+ }
3522
+ }
3523
+ }
3524
+ close() {
3525
+ this.#closePromise ??= this.#closeResources();
3526
+ return this.#closePromise;
3527
+ }
3528
+ async #readDefaultModel() {
3529
+ let cursor = null;
3530
+ const seen = /* @__PURE__ */ new Set();
3531
+ let first;
3532
+ for (let page = 0; page < MAXIMUM_MODEL_PAGES; page += 1) {
3533
+ const value = parseModelPage(
3534
+ await this.#request("model/list", {
3535
+ cursor,
3536
+ limit: 100,
3537
+ includeHidden: false
3538
+ })
3539
+ );
3540
+ first ??= value.models[0]?.model;
3541
+ const selected = value.models.find((model) => model.isDefault)?.model;
3542
+ if (selected !== void 0) return selected;
3543
+ if (value.nextCursor === null || seen.has(value.nextCursor)) break;
3544
+ seen.add(value.nextCursor);
3545
+ cursor = value.nextCursor;
3546
+ }
3547
+ if (first === void 0) {
3548
+ throw new CodexAdapterError(CODEX_ADAPTER_ERROR_CODES.MODEL_UNAVAILABLE);
3549
+ }
3550
+ return first;
3551
+ }
3552
+ async #assertNoMcpServers(threadId) {
3553
+ let cursor = null;
3554
+ const seen = /* @__PURE__ */ new Set();
3555
+ for (let page = 0; page < MAXIMUM_MCP_STATUS_PAGES2; page += 1) {
3556
+ const value = await this.#request("mcpServerStatus/list", {
3557
+ cursor,
3558
+ limit: 100,
3559
+ detail: "toolsAndAuthOnly",
3560
+ threadId
3561
+ });
3562
+ if (!isRecord3(value) || !Array.isArray(value.data) || value.nextCursor !== null && value.nextCursor !== void 0 && typeof value.nextCursor !== "string") {
3563
+ throw new CodexAdapterError(CODEX_ADAPTER_ERROR_CODES.PROTOCOL);
3564
+ }
3565
+ if (value.data.length > 0) {
3566
+ throw new CodexAdapterError(
3567
+ CODEX_ADAPTER_ERROR_CODES.CONFIG_ISOLATION_UNSUPPORTED
3568
+ );
3569
+ }
3570
+ const nextCursor = value.nextCursor ?? null;
3571
+ if (nextCursor === null || seen.has(nextCursor)) return;
3572
+ seen.add(nextCursor);
3573
+ cursor = nextCursor;
3574
+ }
3575
+ throw new CodexAdapterError(CODEX_ADAPTER_ERROR_CODES.PROTOCOL);
3576
+ }
3577
+ async #assertNoHooks(workspaceRoot) {
3578
+ const value = await this.#request("hooks/list", { cwds: [workspaceRoot] });
3579
+ if (!isRecord3(value) || !Array.isArray(value.data) || value.data.length !== 1) {
3580
+ throw new CodexAdapterError(CODEX_ADAPTER_ERROR_CODES.PROTOCOL);
3581
+ }
3582
+ const [entry] = value.data;
3583
+ if (!isRecord3(entry) || entry.cwd !== workspaceRoot || !Array.isArray(entry.hooks) || !Array.isArray(entry.warnings) || !Array.isArray(entry.errors)) {
3584
+ throw new CodexAdapterError(CODEX_ADAPTER_ERROR_CODES.PROTOCOL);
3585
+ }
3586
+ if (entry.hooks.length > 0 || entry.warnings.length > 0 || entry.errors.length > 0) {
3587
+ throw new CodexAdapterError(
3588
+ CODEX_ADAPTER_ERROR_CODES.CONFIG_ISOLATION_UNSUPPORTED
3589
+ );
3590
+ }
3591
+ }
3592
+ async #recoverThreadCleanup() {
3593
+ try {
3594
+ for (const entry of await this.#cleanupJournal.list()) {
3595
+ await this.#deleteThread(entry.threadId);
3596
+ await this.#cleanupJournal.remove(entry.threadId);
3597
+ }
3598
+ } catch (error) {
3599
+ throw new CodexAdapterError(
3600
+ CODEX_ADAPTER_ERROR_CODES.THREAD_CLEANUP_INCOMPLETE,
3601
+ error
3602
+ );
3603
+ }
3604
+ }
3605
+ async #runTurn(handoff, task, threadId, lifecycle, signal) {
3606
+ let resolveTerminal;
3607
+ let rejectTerminal;
3608
+ const terminal = new Promise((resolve, reject) => {
3609
+ resolveTerminal = resolve;
3610
+ rejectTerminal = reject;
3611
+ });
3612
+ const active = {
3613
+ events: [],
3614
+ handoff,
3615
+ lifecycle,
3616
+ reject: (error) => rejectTerminal?.(error),
3617
+ resolve: () => resolveTerminal?.(),
3618
+ signal,
3619
+ task,
3620
+ terminal,
3621
+ threadId,
3622
+ finalized: false,
3623
+ processing: Promise.resolve(),
3624
+ started: false,
3625
+ timeout: void 0,
3626
+ terminalEvent: void 0,
3627
+ turnId: void 0,
3628
+ written: false
3629
+ };
3630
+ this.#active = active;
3631
+ const abort = () => {
3632
+ void this.#interruptAndFail(active, threadId);
3633
+ };
3634
+ signal.addEventListener("abort", abort, { once: true });
3635
+ try {
3636
+ const value = await this.#client.request(
3637
+ "turn/start",
3638
+ {
3639
+ threadId,
3640
+ input: [{ type: "text", text: task.prompt, text_elements: [] }],
3641
+ cwd: task.workspaceRoot,
3642
+ approvalPolicy: "never"
3643
+ },
3644
+ () => {
3645
+ active.written = true;
3646
+ }
3647
+ );
3648
+ if (!isRecord3(value) || !hasOnlyKeys2(value, ["turn"])) {
3649
+ throw new CodexAdapterError(CODEX_ADAPTER_ERROR_CODES.PROTOCOL);
3650
+ }
3651
+ const turn = parseTurn2(value.turn);
3652
+ if (turn.status !== "inProgress") {
3653
+ throw new CodexAdapterError(CODEX_ADAPTER_ERROR_CODES.PROTOCOL);
3654
+ }
3655
+ active.turnId = turn.id;
3656
+ await lifecycle.report("dispatched");
3657
+ active.timeout = setTimeout(() => {
3658
+ void this.#finishUnknown(active);
3659
+ }, this.#terminalTimeoutMs);
3660
+ active.timeout.unref();
3661
+ this.#schedule(active);
3662
+ await terminal;
3663
+ } catch (error) {
3664
+ const explicitlyRejected = error instanceof CodexAdapterError && error.code === CODEX_ADAPTER_ERROR_CODES.REQUEST_FAILED;
3665
+ if (!active.finalized) {
3666
+ if (explicitlyRejected) {
3667
+ this.#onEvent({
3668
+ type: "failure",
3669
+ revision: handoff.revision,
3670
+ reason: "protocol"
3671
+ });
3672
+ await this.#finishFailed(active);
3673
+ } else if (active.written) {
3674
+ await this.#finishUnknown(active);
3675
+ } else {
3676
+ await this.#finishFailed(active);
3677
+ }
3678
+ }
3679
+ await terminal;
3680
+ if (error instanceof ActiveDeliveryUnknownError) throw error;
3681
+ } finally {
3682
+ signal.removeEventListener("abort", abort);
3683
+ this.#cleanup(active);
3684
+ }
3685
+ }
3686
+ #handleNotification(method, params) {
3687
+ let event;
3688
+ try {
3689
+ event = parseTurnEvent2(method, params);
3690
+ } catch {
3691
+ const active2 = this.#active;
3692
+ if (active2 !== void 0) void this.#finishUnknown(active2);
3693
+ this.#client.close();
3694
+ return;
3695
+ }
3696
+ if (event === void 0) return;
3697
+ const active = this.#active;
3698
+ if (active === void 0 || active.finalized || event.threadId !== active.threadId) {
3699
+ return;
3700
+ }
3701
+ active.events.push(event);
3702
+ this.#schedule(active);
3703
+ }
3704
+ #schedule(active) {
3705
+ if (active.turnId === void 0 || active.finalized) return;
3706
+ active.processing = active.processing.then(async () => {
3707
+ while (active.events.length > 0 && !active.finalized) {
3708
+ const event = active.events.shift();
3709
+ if (event?.threadId !== active.threadId || event.turnId !== active.turnId) {
3710
+ continue;
3711
+ }
3712
+ if (event.kind === "started") {
3713
+ if (!active.started) {
3714
+ active.started = true;
3715
+ this.#onEvent({
3716
+ type: "phase",
3717
+ revision: active.handoff.revision,
3718
+ phase: "running"
3719
+ });
3720
+ await active.lifecycle.report("working");
3721
+ }
3722
+ const terminalEvent = active.terminalEvent;
3723
+ active.terminalEvent = void 0;
3724
+ if (terminalEvent !== void 0) {
3725
+ if (terminalEvent.status === "completed") {
3726
+ await this.#finishCompleted(active);
3727
+ } else {
3728
+ await this.#finishFailed(active);
3729
+ }
3730
+ }
3731
+ continue;
3732
+ }
3733
+ if (!active.started) {
3734
+ active.terminalEvent = event;
3735
+ continue;
3736
+ }
3737
+ if (event.status === "completed") await this.#finishCompleted(active);
3738
+ else await this.#finishFailed(active);
3739
+ }
3740
+ }).catch(async () => this.#finishUnknown(active));
3741
+ }
3742
+ async #finishCompleted(active) {
3743
+ if (active.finalized) return;
3744
+ active.finalized = true;
3745
+ try {
3746
+ this.#onEvent({
3747
+ type: "phase",
3748
+ revision: active.handoff.revision,
3749
+ phase: "auditing"
3750
+ });
3751
+ const result = await this.#execution.auditAndApply(
3752
+ active.task,
3753
+ active.signal,
3754
+ (phase) => {
3755
+ this.#onEvent({
3756
+ type: "phase",
3757
+ revision: active.handoff.revision,
3758
+ phase
3759
+ });
3760
+ }
3761
+ );
3762
+ this.#onEvent({ type: "result", result });
3763
+ await active.lifecycle.report("completed");
3764
+ active.resolve();
3765
+ } catch (error) {
3766
+ this.#onEvent({
3767
+ type: "failure",
3768
+ revision: active.handoff.revision,
3769
+ reason: managedFailureReason(error, "apply")
3770
+ });
3771
+ active.finalized = false;
3772
+ await this.#finishFailed(active);
3773
+ }
3774
+ }
3775
+ async #finishFailed(active) {
3776
+ if (active.finalized) return;
3777
+ active.finalized = true;
3778
+ try {
3779
+ await active.lifecycle.report("failed");
3780
+ } finally {
3781
+ active.resolve();
3782
+ }
3783
+ }
3784
+ async #finishUnknown(active) {
3785
+ if (active.finalized) return;
3786
+ active.finalized = true;
3787
+ await active.lifecycle.report("delivery-unknown").catch(() => void 0);
3788
+ active.reject(new ActiveDeliveryUnknownError());
3789
+ }
3790
+ async #interruptAndFail(active, threadId) {
3791
+ if (active.turnId !== void 0 && !this.#closed) {
3792
+ await this.#client.request("turn/interrupt", { threadId, turnId: active.turnId }).catch(() => void 0);
3793
+ }
3794
+ await this.#finishFailed(active);
3795
+ }
3796
+ async #deleteThread(threadId) {
3797
+ const value = await this.#request("thread/delete", { threadId });
3798
+ if (!isRecord3(value) || Object.keys(value).length !== 0) {
3799
+ throw new CodexAdapterError(CODEX_ADAPTER_ERROR_CODES.PROTOCOL);
3800
+ }
3801
+ }
3802
+ async #request(method, params) {
3803
+ this.#throwIfFatal();
3804
+ const value = await this.#client.request(method, params).catch((error) => {
3805
+ this.#throwIfFatal();
3806
+ throw error;
3807
+ });
3808
+ this.#throwIfFatal();
3809
+ return value;
3810
+ }
3811
+ #throwIfFatal() {
3812
+ if (this.#fatalError !== void 0) throw this.#fatalError;
3813
+ }
3814
+ #threadCleanupAvailable() {
3815
+ return !this.#closed;
3816
+ }
3817
+ #handleFatal(error) {
3818
+ this.#fatalError = error;
3819
+ this.#closed = true;
3820
+ if (this.#active !== void 0) void this.#finishUnknown(this.#active);
3821
+ }
3822
+ #cleanup(active) {
3823
+ if (active.timeout !== void 0) clearTimeout(active.timeout);
3824
+ if (this.#active === active) this.#active = void 0;
3825
+ }
3826
+ async #closeResources() {
3827
+ this.#closed = true;
3828
+ if (this.#active !== void 0) await this.#finishUnknown(this.#active);
3829
+ this.#client.close();
3830
+ signalProcessTree2(this.#child, "SIGTERM");
3831
+ await new Promise((resolve) => {
3832
+ let settled = false;
3833
+ const finish = () => {
3834
+ if (settled) return;
3835
+ settled = true;
3836
+ clearTimeout(timeout);
3837
+ this.#child.removeListener("exit", finish);
3838
+ signalProcessTree2(this.#child, "SIGKILL");
3839
+ resolve();
3840
+ };
3841
+ const timeout = setTimeout(finish, this.#processShutdownTimeoutMs);
3842
+ timeout.unref();
3843
+ this.#child.once("exit", finish);
3844
+ if (this.#child.exitCode !== null || this.#child.signalCode !== null) finish();
3845
+ });
3846
+ }
3847
+ };
3848
+ async function connectManagedCodexAppServer(options) {
3849
+ return ManagedCodexAppServerAdapter.connect(options);
3850
+ }
3851
+
3852
+ // src/cli-runner.ts
3853
+ function optionValue(arguments_, name) {
3854
+ const index = arguments_.indexOf(name);
3855
+ if (index === -1) return void 0;
3856
+ const value = arguments_[index + 1];
3857
+ if (value === void 0 || value.startsWith("--")) {
3858
+ throw new SpotPatchError11(ERROR_CODES11.INVALID_REQUEST);
3859
+ }
3860
+ return value;
3861
+ }
3862
+ function allowedArguments(arguments_, booleanOptions, valueOptions) {
3863
+ const allowed = /* @__PURE__ */ new Set([...booleanOptions, ...valueOptions]);
3864
+ const seen = /* @__PURE__ */ new Set();
3865
+ for (let index = 0; index < arguments_.length; index += 1) {
3866
+ const argument = arguments_[index];
3867
+ if (argument === void 0 || !allowed.has(argument) || seen.has(argument)) {
3868
+ throw new SpotPatchError11(ERROR_CODES11.INVALID_REQUEST);
3869
+ }
3870
+ seen.add(argument);
3871
+ if (valueOptions.includes(argument)) {
3872
+ const value = arguments_[index + 1];
3873
+ if (value === void 0 || value.startsWith("--")) {
3874
+ throw new SpotPatchError11(ERROR_CODES11.INVALID_REQUEST);
3875
+ }
3876
+ index += 1;
3877
+ }
3878
+ }
3879
+ }
3880
+ function exitCode(error) {
3881
+ if (error instanceof CodexAdapterError) {
3882
+ if (error.code === CODEX_ADAPTER_ERROR_CODES.EXECUTABLE_UNTRUSTED) return 7;
3883
+ if (error.code === CODEX_ADAPTER_ERROR_CODES.MCP_NOT_READY || error.code === CODEX_ADAPTER_ERROR_CODES.PROTOCOL || error.code === CODEX_ADAPTER_ERROR_CODES.UNSUPPORTED_VERSION) {
3884
+ return 6;
3885
+ }
3886
+ if (error.code === CODEX_ADAPTER_ERROR_CODES.BUSY || error.code === CODEX_ADAPTER_ERROR_CODES.CLOSED || error.code === CODEX_ADAPTER_ERROR_CODES.EXECUTABLE_NOT_FOUND || error.code === CODEX_ADAPTER_ERROR_CODES.PROCESS_EXITED || error.code === CODEX_ADAPTER_ERROR_CODES.REQUEST_TIMEOUT) {
3887
+ return 8;
3888
+ }
3889
+ return 1;
3890
+ }
3891
+ if (!(error instanceof SpotPatchError11)) return 1;
3892
+ if (error.code === ERROR_CODES11.INVALID_REQUEST) return 2;
3893
+ if (error.code === ERROR_CODES11.SESSION_NOT_FOUND) return 3;
3894
+ if (error.code === ERROR_CODES11.HANDOFF_NOT_FOUND || error.code === ERROR_CODES11.HANDOFF_EXPIRED) {
3895
+ return 4;
3896
+ }
3897
+ if (error.code === ERROR_CODES11.SESSION_AMBIGUOUS) return 5;
3898
+ if (error.code === ERROR_CODES11.BRIDGE_PROTOCOL_MISMATCH) return 6;
3899
+ if (error.code === ERROR_CODES11.BRIDGE_UNAUTHORIZED) return 7;
3900
+ if (error.code === ERROR_CODES11.SESSION_CLOSED || error.code === ERROR_CODES11.EXTERNAL_HANDOFF_UNAVAILABLE) {
3901
+ return 8;
3902
+ }
3903
+ return 1;
3904
+ }
3905
+ function writeJson(stdout, command, data) {
3906
+ stdout.write(`${JSON.stringify({ schemaVersion: 1, command, data })}
3907
+ `);
3908
+ }
3909
+ function usage(stderr) {
3910
+ stderr.write(
3911
+ "Usage: spotpatch-bridge <sessions|current|wait|ack|mcp|channel|connect|setup> [options]\n"
3912
+ );
3913
+ }
3914
+ function abortError5() {
3915
+ const error = new Error("The SpotPatch command was interrupted.");
3916
+ error.name = "AbortError";
3917
+ return error;
3918
+ }
3919
+ function abortableOperation(operation, signal) {
3920
+ if (signal.aborted) return Promise.reject(abortError5());
3921
+ return new Promise((resolve, reject) => {
3922
+ const finish = () => {
3923
+ signal.removeEventListener("abort", abort);
3924
+ };
3925
+ const abort = () => {
3926
+ finish();
3927
+ reject(abortError5());
3928
+ };
3929
+ signal.addEventListener("abort", abort, { once: true });
3930
+ void operation.then(
3931
+ (value) => {
3932
+ finish();
3933
+ resolve(value);
3934
+ },
3935
+ (error) => {
3936
+ finish();
3937
+ reject(
3938
+ error instanceof Error ? error : new Error("The SpotPatch operation failed.", { cause: error })
3939
+ );
3940
+ }
3941
+ );
3942
+ });
3943
+ }
3944
+ function processSignalScope(onInterrupt) {
3945
+ let interrupted = false;
3946
+ const interrupt = () => {
3947
+ if (interrupted) return;
3948
+ interrupted = true;
3949
+ onInterrupt();
3950
+ };
3951
+ process.once("SIGINT", interrupt);
3952
+ process.once("SIGTERM", interrupt);
3953
+ return Object.freeze({
3954
+ interrupted: () => interrupted,
3955
+ remove() {
3956
+ process.removeListener("SIGINT", interrupt);
3957
+ process.removeListener("SIGTERM", interrupt);
3958
+ }
3959
+ });
3960
+ }
3961
+ async function runClaudeChannel(cwd, sessionId) {
3962
+ let close;
3963
+ const signals = processSignalScope(() => {
3964
+ void close?.();
3965
+ });
3966
+ try {
3967
+ const exactSessionId = await resolveExactProjectSessionId(cwd, sessionId);
3968
+ const handle = await serveClaudeChannelMcp({ cwd, sessionId: exactSessionId });
3969
+ close = handle.close;
3970
+ if (signals.interrupted()) await handle.close();
3971
+ await handle.done;
3972
+ return signals.interrupted() ? 130 : 0;
3973
+ } finally {
3974
+ signals.remove();
3975
+ await close?.().catch(() => void 0);
3976
+ }
3977
+ }
3978
+ function writeCodexConnectorEvent(event, stderr) {
3979
+ if (event.type === "ready") {
3980
+ stderr.write(
3981
+ "[spotpatch:bridge] Codex connected and ready for SpotPatch requests.\n"
3982
+ );
3983
+ return;
3984
+ }
3985
+ const revision = String(event.revision);
3986
+ switch (event.phase) {
3987
+ case "dispatching":
3988
+ stderr.write(
3989
+ `[spotpatch:bridge] SpotPatch is preparing revision ${revision} for Codex.
3990
+ `
3991
+ );
3992
+ return;
3993
+ case "working":
3994
+ stderr.write(`[spotpatch:bridge] Codex started revision ${revision}.
3995
+ `);
3996
+ return;
3997
+ case "completed":
3998
+ stderr.write(
3999
+ `[spotpatch:bridge] Codex turn ended for revision ${revision}; review the workspace. Ready for the next request.
4000
+ `
4001
+ );
4002
+ return;
4003
+ case "failed":
4004
+ stderr.write(
4005
+ `[spotpatch:bridge] Revision ${revision} failed before a verified Codex turn completed; review the connector output and workspace. Ready for the next request.
4006
+ `
4007
+ );
4008
+ return;
4009
+ case "delivery-unknown":
4010
+ stderr.write(
4011
+ `[spotpatch:bridge] Codex delivery for revision ${revision} is unknown; the connector will stop.
4012
+ `
4013
+ );
4014
+ return;
4015
+ case "dispatched":
4016
+ stderr.write(`[spotpatch:bridge] Codex accepted revision ${revision}.
4017
+ `);
4018
+ return;
4019
+ }
4020
+ }
4021
+ async function runCodexConnector(adapterKind, cwd, stderr, sessionId) {
4022
+ const pumpController = new AbortController();
4023
+ const startupController = new AbortController();
3019
4024
  let fatalError;
3020
4025
  const signals = processSignalScope(() => {
3021
4026
  startupController.abort("cli-interrupted");
@@ -3082,7 +4087,7 @@ async function runSpotPatchBridgeCli(arguments_, options = {}) {
3082
4087
  }
3083
4088
  if (command === "channel") {
3084
4089
  if (rest[0] !== "claude") {
3085
- throw new SpotPatchError10(ERROR_CODES10.INVALID_REQUEST);
4090
+ throw new SpotPatchError11(ERROR_CODES11.INVALID_REQUEST);
3086
4091
  }
3087
4092
  const channelArguments = rest.slice(1);
3088
4093
  allowedArguments(channelArguments, [], ["--session"]);
@@ -3090,12 +4095,12 @@ async function runSpotPatchBridgeCli(arguments_, options = {}) {
3090
4095
  }
3091
4096
  if (command === "connect") {
3092
4097
  if (rest[0] !== "codex") {
3093
- throw new SpotPatchError10(ERROR_CODES10.INVALID_REQUEST);
4098
+ throw new SpotPatchError11(ERROR_CODES11.INVALID_REQUEST);
3094
4099
  }
3095
4100
  const connectorArguments2 = rest.slice(1);
3096
4101
  allowedArguments(connectorArguments2, ["--allow-workspace-write"], ["--session"]);
3097
4102
  if (!connectorArguments2.includes("--allow-workspace-write")) {
3098
- throw new SpotPatchError10(ERROR_CODES10.INVALID_REQUEST);
4103
+ throw new SpotPatchError11(ERROR_CODES11.INVALID_REQUEST);
3099
4104
  }
3100
4105
  return await runCodexConnector(
3101
4106
  adapter,
@@ -3110,12 +4115,12 @@ async function runSpotPatchBridgeCli(arguments_, options = {}) {
3110
4115
  const mode = optionValue(rest, "--mode") ?? "inbox";
3111
4116
  const scope = optionValue(rest, "--scope") ?? "project";
3112
4117
  if (scope !== "project" || mode !== "inbox" && mode !== "active" || mode === "active" && client2 !== "claude" || client2 !== "claude" && client2 !== "cursor" && client2 !== "codex") {
3113
- throw new SpotPatchError10(ERROR_CODES10.INVALID_REQUEST);
4118
+ throw new SpotPatchError11(ERROR_CODES11.INVALID_REQUEST);
3114
4119
  }
3115
4120
  const plan = createBridgeSetupPlan(client2, adapter, cwd, mode);
3116
4121
  const write = rest.includes("--write");
3117
4122
  const result = write ? await applyBridgeSetupPlan(plan) : "dry-run";
3118
- const displayPath = path6.relative(cwd, plan.path).split(path6.sep).join("/");
4123
+ const displayPath = path9.relative(cwd, plan.path).split(path9.sep).join("/");
3119
4124
  const backup = result === "updated" ? `Backup: ${displayPath}.spotpatch.bak
3120
4125
  ` : "";
3121
4126
  stdout.write(
@@ -3156,8 +4161,8 @@ ${backup}${plan.content}`
3156
4161
  allowedArguments(rest, ["--json"], ["--session", "--after", "--timeout"]);
3157
4162
  const timeoutText = optionValue(rest, "--timeout");
3158
4163
  const timeout = timeoutText === void 0 ? void 0 : Number(timeoutText);
3159
- if (timeout !== void 0 && (!Number.isSafeInteger(timeout) || timeout <= 0 || timeout > EXTERNAL_HANDOFF_LIMITS9.maximumWaitMs)) {
3160
- throw new SpotPatchError10(ERROR_CODES10.INVALID_REQUEST);
4164
+ if (timeout !== void 0 && (!Number.isSafeInteger(timeout) || timeout <= 0 || timeout > EXTERNAL_HANDOFF_LIMITS10.maximumWaitMs)) {
4165
+ throw new SpotPatchError11(ERROR_CODES11.INVALID_REQUEST);
3161
4166
  }
3162
4167
  const controller = new AbortController();
3163
4168
  const abort = () => {
@@ -3187,7 +4192,7 @@ ${backup}${plan.content}`
3187
4192
  if (command === "ack") {
3188
4193
  allowedArguments(rest, ["--json"], ["--session", "--cursor"]);
3189
4194
  const cursor = optionValue(rest, "--cursor");
3190
- if (cursor === void 0) throw new SpotPatchError10(ERROR_CODES10.INVALID_REQUEST);
4195
+ if (cursor === void 0) throw new SpotPatchError11(ERROR_CODES11.INVALID_REQUEST);
3191
4196
  const summary = await client.ack(cursor, optionValue(rest, "--session"));
3192
4197
  const result = { outcome: "acknowledged", summary };
3193
4198
  if (json) writeJson(stdout, command, result);
@@ -3201,10 +4206,10 @@ ${backup}${plan.content}`
3201
4206
  usage(stderr);
3202
4207
  return 2;
3203
4208
  } catch (error) {
3204
- const code = error instanceof SpotPatchError10 || error instanceof CodexAdapterError ? error.code : ERROR_CODES10.INTERNAL_ERROR;
4209
+ const code = error instanceof SpotPatchError11 || error instanceof CodexAdapterError ? error.code : ERROR_CODES11.INTERNAL_ERROR;
3205
4210
  stderr.write(`[spotpatch:bridge] ${code}
3206
4211
  `);
3207
- if (command === "connect" && (code === ERROR_CODES10.SESSION_NOT_FOUND || code === ERROR_CODES10.SESSION_CLOSED)) {
4212
+ if (command === "connect" && (code === ERROR_CODES11.SESSION_NOT_FOUND || code === ERROR_CODES11.SESSION_CLOSED)) {
3208
4213
  stderr.write(
3209
4214
  "[spotpatch:bridge] The SpotPatch development session ended or changed. Keep the dev server running, then rerun the same connect command.\n"
3210
4215
  );
@@ -3212,9 +4217,724 @@ ${backup}${plan.content}`
3212
4217
  return exitCode(error);
3213
4218
  }
3214
4219
  }
4220
+
4221
+ // src/supervisor/supervisor.ts
4222
+ import { createInterface } from "readline/promises";
4223
+ import {
4224
+ ERROR_CODES as ERROR_CODES12,
4225
+ EXTERNAL_AGENT_CONTROL_SCHEMA_VERSION,
4226
+ EXTERNAL_AGENT_MANAGED_PROFILE as EXTERNAL_AGENT_MANAGED_PROFILE2,
4227
+ SpotPatchError as SpotPatchError12,
4228
+ externalAgentControlStatusSchema,
4229
+ externalAgentManagedResultSchema
4230
+ } from "@spotpatch/shared";
4231
+ import {
4232
+ createManagedExecutionRunner
4233
+ } from "@spotpatch/agent";
4234
+
4235
+ // src/supervisor/grant-store.ts
4236
+ import { rm as rm3 } from "fs/promises";
4237
+ import path10 from "path";
4238
+ import { EXTERNAL_AGENT_MANAGED_PROFILE } from "@spotpatch/shared";
4239
+ import { computeExternalHandoffProjectKey as computeExternalHandoffProjectKey2 } from "@spotpatch/shared/external-agent-node";
4240
+ import { z as z4 } from "zod";
4241
+ var GRANT_SCHEMA_VERSION = 1;
4242
+ var GRANT_POLICY_VERSION = 1;
4243
+ var grantSchema = z4.strictObject({
4244
+ schemaVersion: z4.literal(GRANT_SCHEMA_VERSION),
4245
+ projectKey: z4.string().regex(/^[a-f0-9]{64}$/u),
4246
+ adapterKind: z4.literal("codex"),
4247
+ profile: z4.literal(EXTERNAL_AGENT_MANAGED_PROFILE),
4248
+ policyVersion: z4.literal(GRANT_POLICY_VERSION),
4249
+ createdAt: z4.iso.datetime(),
4250
+ lastUsedAt: z4.iso.datetime()
4251
+ });
4252
+ async function readSecureGrant(filePath) {
4253
+ return readPrivateJson(filePath, 4096);
4254
+ }
4255
+ async function writeAtomicGrant(filePath, value) {
4256
+ await writePrivateJsonAtomic(filePath, "grant", value);
4257
+ }
4258
+ async function createManagedGrantStore(options) {
4259
+ const projectKey = await computeExternalHandoffProjectKey2(options.root);
4260
+ const canonicalBase = await resolvePrivateConfigBase(options.configBase);
4261
+ const directory = path10.join(canonicalBase, "external-agent-grants");
4262
+ await ensurePrivateDirectory(directory);
4263
+ const filePath = path10.join(directory, `${projectKey}.json`);
4264
+ const now = options.now ?? (() => /* @__PURE__ */ new Date());
4265
+ const record2 = (createdAt) => Object.freeze({
4266
+ schemaVersion: GRANT_SCHEMA_VERSION,
4267
+ projectKey,
4268
+ adapterKind: "codex",
4269
+ profile: EXTERNAL_AGENT_MANAGED_PROFILE,
4270
+ policyVersion: GRANT_POLICY_VERSION,
4271
+ createdAt,
4272
+ lastUsedAt: now().toISOString()
4273
+ });
4274
+ return Object.freeze({
4275
+ projectKey,
4276
+ async read() {
4277
+ try {
4278
+ const value = await readSecureGrant(filePath);
4279
+ if (value === void 0) return "missing";
4280
+ const parsed = grantSchema.safeParse(value);
4281
+ return parsed.success && parsed.data.projectKey === projectKey ? "valid" : "invalid";
4282
+ } catch {
4283
+ return "invalid";
4284
+ }
4285
+ },
4286
+ async grant() {
4287
+ const timestamp = now().toISOString();
4288
+ await writeAtomicGrant(filePath, record2(timestamp));
4289
+ },
4290
+ async touch() {
4291
+ const value = await readSecureGrant(filePath);
4292
+ const parsed = grantSchema.safeParse(value);
4293
+ if (!parsed.success || parsed.data.projectKey !== projectKey) {
4294
+ throw new Error("Managed grant is invalid.");
4295
+ }
4296
+ await writeAtomicGrant(filePath, record2(parsed.data.createdAt));
4297
+ },
4298
+ async revoke() {
4299
+ await rm3(filePath, { force: true });
4300
+ }
4301
+ });
4302
+ }
4303
+
4304
+ // src/supervisor/thread-cleanup-journal.ts
4305
+ import { rm as rm4 } from "fs/promises";
4306
+ import path11 from "path";
4307
+ import { computeExternalHandoffProjectKey as computeExternalHandoffProjectKey3 } from "@spotpatch/shared/external-agent-node";
4308
+ import { z as z5 } from "zod";
4309
+ var CLEANUP_JOURNAL_SCHEMA_VERSION = 1;
4310
+ var MAXIMUM_THREAD_RECORDS = 32;
4311
+ var MAXIMUM_JOURNAL_BYTES = 16 * 1024;
4312
+ function hasOnlyPrintableCharacters(value) {
4313
+ for (const character of value) {
4314
+ const code = character.codePointAt(0) ?? 0;
4315
+ if (code < 32 || code === 127) return false;
4316
+ }
4317
+ return true;
4318
+ }
4319
+ var threadIdSchema = z5.string().min(1).max(256).refine(hasOnlyPrintableCharacters);
4320
+ var journalSchema = z5.strictObject({
4321
+ schemaVersion: z5.literal(CLEANUP_JOURNAL_SCHEMA_VERSION),
4322
+ projectKey: z5.string().regex(/^[a-f0-9]{64}$/u),
4323
+ threads: z5.array(
4324
+ z5.strictObject({
4325
+ threadId: threadIdSchema,
4326
+ createdAt: z5.iso.datetime()
4327
+ })
4328
+ ).max(MAXIMUM_THREAD_RECORDS)
4329
+ });
4330
+ async function createManagedThreadCleanupJournal(options) {
4331
+ const projectKey = await computeExternalHandoffProjectKey3(options.root);
4332
+ const canonicalBase = await resolvePrivateConfigBase(options.configBase);
4333
+ const directory = path11.join(canonicalBase, "external-agent-cleanup");
4334
+ await ensurePrivateDirectory(directory);
4335
+ const filePath = path11.join(directory, `${projectKey}.json`);
4336
+ const now = options.now ?? (() => /* @__PURE__ */ new Date());
4337
+ let operationTail = Promise.resolve();
4338
+ const readEntries = async () => {
4339
+ const value = await readPrivateJson(filePath, MAXIMUM_JOURNAL_BYTES);
4340
+ if (value === void 0) return Object.freeze([]);
4341
+ const parsed = journalSchema.safeParse(value);
4342
+ if (!parsed.success || parsed.data.projectKey !== projectKey) {
4343
+ throw new Error("Managed thread cleanup journal is invalid.");
4344
+ }
4345
+ return Object.freeze(
4346
+ parsed.data.threads.map((entry) => Object.freeze({ ...entry }))
4347
+ );
4348
+ };
4349
+ const writeEntries = async (entries) => {
4350
+ if (entries.length === 0) {
4351
+ await rm4(filePath, { force: true });
4352
+ return;
4353
+ }
4354
+ await writePrivateJsonAtomic(filePath, "cleanup", {
4355
+ schemaVersion: CLEANUP_JOURNAL_SCHEMA_VERSION,
4356
+ projectKey,
4357
+ threads: entries
4358
+ });
4359
+ };
4360
+ const serialize = (operation) => {
4361
+ const result = operationTail.then(operation, operation);
4362
+ operationTail = result.then(
4363
+ () => void 0,
4364
+ () => void 0
4365
+ );
4366
+ return result;
4367
+ };
4368
+ return Object.freeze({
4369
+ list: () => serialize(readEntries),
4370
+ add(threadId) {
4371
+ return serialize(async () => {
4372
+ const parsedThreadId = threadIdSchema.parse(threadId);
4373
+ const entries = await readEntries();
4374
+ if (entries.some((entry) => entry.threadId === parsedThreadId)) return;
4375
+ if (entries.length >= MAXIMUM_THREAD_RECORDS) {
4376
+ throw new Error("Managed thread cleanup journal is full.");
4377
+ }
4378
+ await writeEntries([
4379
+ ...entries,
4380
+ Object.freeze({
4381
+ threadId: parsedThreadId,
4382
+ createdAt: now().toISOString()
4383
+ })
4384
+ ]);
4385
+ });
4386
+ },
4387
+ remove(threadId) {
4388
+ return serialize(async () => {
4389
+ const parsedThreadId = threadIdSchema.parse(threadId);
4390
+ const entries = await readEntries();
4391
+ await writeEntries(
4392
+ entries.filter((entry) => entry.threadId !== parsedThreadId)
4393
+ );
4394
+ });
4395
+ }
4396
+ });
4397
+ }
4398
+
4399
+ // src/supervisor/supervisor.ts
4400
+ var MAXIMUM_IDEMPOTENCY_RECORDS = 64;
4401
+ var MAXIMUM_RESULT_RECORDS = 16;
4402
+ function managedError(code, stage, recoverability, action) {
4403
+ return Object.freeze({ code, stage, recoverability, action });
4404
+ }
4405
+ function classifyConnectionError(error) {
4406
+ if (error instanceof SpotPatchError12) {
4407
+ if (error.code === ERROR_CODES12.WORKTREE_NOT_REPOSITORY) {
4408
+ return managedError(
4409
+ "MANAGED_GIT_REQUIRED",
4410
+ "snapshot",
4411
+ "reconfigure",
4412
+ "use-inbox"
4413
+ );
4414
+ }
4415
+ if (error.code === ERROR_CODES12.WORKTREE_DIRTY || error.code === ERROR_CODES12.WORKTREE_CONFLICTED || error.code === ERROR_CODES12.WORKTREE_OPERATION_IN_PROGRESS) {
4416
+ return managedError(
4417
+ "MANAGED_SNAPSHOT_FAILED",
4418
+ "snapshot",
4419
+ "user-action",
4420
+ "review-workspace-conflict"
4421
+ );
4422
+ }
4423
+ }
4424
+ if (typeof error === "object" && error !== null && "code" in error && typeof error.code === "string") {
4425
+ if (error.code === "CODEX_EXECUTABLE_NOT_FOUND") {
4426
+ return managedError(
4427
+ "AGENT_BINARY_NOT_FOUND",
4428
+ "binary",
4429
+ "user-action",
4430
+ "install-agent"
4431
+ );
4432
+ }
4433
+ if (error.code === "CODEX_EXECUTABLE_UNTRUSTED") {
4434
+ return managedError(
4435
+ "AGENT_BINARY_UNTRUSTED",
4436
+ "binary",
4437
+ "reconfigure",
4438
+ "use-inbox"
4439
+ );
4440
+ }
4441
+ if (error.code === "CODEX_UNSUPPORTED_VERSION") {
4442
+ return managedError(
4443
+ "AGENT_VERSION_UNSUPPORTED",
4444
+ "protocol",
4445
+ "user-action",
4446
+ "use-supported-version"
4447
+ );
4448
+ }
4449
+ if (error.code === "CODEX_AUTH_REQUIRED") {
4450
+ return managedError("AGENT_AUTH_REQUIRED", "auth", "user-action", "sign-in");
4451
+ }
4452
+ if (error.code === "CODEX_MODEL_UNAVAILABLE") {
4453
+ return managedError(
4454
+ "AGENT_MODEL_UNAVAILABLE",
4455
+ "model",
4456
+ "user-action",
4457
+ "choose-available-model"
4458
+ );
4459
+ }
4460
+ if (error.code === "CODEX_CONFIG_ISOLATION_UNSUPPORTED") {
4461
+ return managedError(
4462
+ "CODEX_CONFIG_ISOLATION_UNSUPPORTED",
4463
+ "protocol",
4464
+ "reconfigure",
4465
+ "use-inbox"
4466
+ );
4467
+ }
4468
+ if (error.code === "CODEX_APP_SERVER_PROTOCOL_ERROR") {
4469
+ return managedError(
4470
+ "AGENT_PROTOCOL_INCOMPATIBLE",
4471
+ "protocol",
4472
+ "reconfigure",
4473
+ "use-supported-version"
4474
+ );
4475
+ }
4476
+ if (error.code === "CODEX_THREAD_CLEANUP_INCOMPLETE") {
4477
+ return managedError(
4478
+ "MANAGED_CLEANUP_INCOMPLETE",
4479
+ "cleanup",
4480
+ "user-action",
4481
+ "inspect-cleanup-warning"
4482
+ );
4483
+ }
4484
+ }
4485
+ return managedError("APP_SERVER_HANDSHAKE_FAILED", "handshake", "retry", "retry");
4486
+ }
4487
+ function managedExecutionError(reason) {
4488
+ switch (reason) {
4489
+ case "config-isolation":
4490
+ return managedError(
4491
+ "CODEX_CONFIG_ISOLATION_UNSUPPORTED",
4492
+ "protocol",
4493
+ "reconfigure",
4494
+ "use-inbox"
4495
+ );
4496
+ case "protocol":
4497
+ return managedError(
4498
+ "AGENT_PROTOCOL_INCOMPATIBLE",
4499
+ "protocol",
4500
+ "reconfigure",
4501
+ "use-supported-version"
4502
+ );
4503
+ case "snapshot":
4504
+ return managedError(
4505
+ "MANAGED_SNAPSHOT_FAILED",
4506
+ "snapshot",
4507
+ "user-action",
4508
+ "review-workspace-conflict"
4509
+ );
4510
+ case "scope":
4511
+ return managedError(
4512
+ "MANAGED_SCOPE_VIOLATION",
4513
+ "audit",
4514
+ "reconfigure",
4515
+ "use-inbox"
4516
+ );
4517
+ case "change-limit":
4518
+ return managedError(
4519
+ "MANAGED_CHANGE_LIMIT_EXCEEDED",
4520
+ "audit",
4521
+ "reconfigure",
4522
+ "use-inbox"
4523
+ );
4524
+ case "validation":
4525
+ return managedError(
4526
+ "MANAGED_VALIDATION_FAILED",
4527
+ "validation",
4528
+ "user-action",
4529
+ "review-candidate-diff"
4530
+ );
4531
+ case "workspace-conflict":
4532
+ return managedError(
4533
+ "MANAGED_WORKSPACE_CONFLICT",
4534
+ "apply",
4535
+ "user-action",
4536
+ "review-workspace-conflict"
4537
+ );
4538
+ case "apply":
4539
+ return managedError(
4540
+ "MANAGED_APPLY_FAILED",
4541
+ "apply",
4542
+ "user-action",
4543
+ "review-candidate-diff"
4544
+ );
4545
+ }
4546
+ }
4547
+ async function defaultTerminalConfirmation(projectLabel) {
4548
+ if (!process.stdin.isTTY || !process.stdout.isTTY) return false;
4549
+ const reader = createInterface({ input: process.stdin, output: process.stdout });
4550
+ try {
4551
+ process.stdout.write(
4552
+ [
4553
+ "\nSpotPatch managed Agent access request",
4554
+ `Project: ${projectLabel}`,
4555
+ "Adapter: Codex",
4556
+ `Profile: ${EXTERNAL_AGENT_MANAGED_PROFILE2}`,
4557
+ "Codex may write only an independent temporary snapshot. SpotPatch audits, validates, and applies eligible changes.",
4558
+ "You can revoke this grant from the SpotPatch panel."
4559
+ ].join("\n") + "\n"
4560
+ );
4561
+ const answer = (await reader.question('Type "yes" to grant access: ')).trim().toLowerCase();
4562
+ return answer === "yes";
4563
+ } finally {
4564
+ reader.close();
4565
+ }
4566
+ }
4567
+ function requestFingerprint(value) {
4568
+ return JSON.stringify(value);
4569
+ }
4570
+ function taskStatus(revision, phase, current) {
4571
+ return Object.freeze({
4572
+ revision,
4573
+ deliveryStatus: current?.deliveryStatus ?? "queued",
4574
+ executionStatus: current?.executionStatus ?? "not-observable",
4575
+ managedPhase: phase,
4576
+ ...current?.validationOutcome === void 0 ? {} : { validationOutcome: current.validationOutcome },
4577
+ files: [...current?.files ?? []],
4578
+ checks: [...current?.checks ?? []],
4579
+ timings: current?.timings ?? Object.freeze({}),
4580
+ ...current?.resultExpiresAt === void 0 ? {} : { resultExpiresAt: current.resultExpiresAt }
4581
+ });
4582
+ }
4583
+ async function createExternalAgentSupervisor(options) {
4584
+ const now = options.now ?? (() => /* @__PURE__ */ new Date());
4585
+ const grantStore = await createManagedGrantStore({
4586
+ root: options.root,
4587
+ ...options.configBase === void 0 ? {} : { configBase: options.configBase },
4588
+ now
4589
+ });
4590
+ const cleanupJournal = await createManagedThreadCleanupJournal({
4591
+ root: options.root,
4592
+ ...options.configBase === void 0 ? {} : { configBase: options.configBase },
4593
+ now
4594
+ });
4595
+ const listeners = /* @__PURE__ */ new Set();
4596
+ const idempotency = /* @__PURE__ */ new Map();
4597
+ const results = /* @__PURE__ */ new Map();
4598
+ let connection;
4599
+ let disposed = false;
4600
+ let operationTail = Promise.resolve();
4601
+ let status = externalAgentControlStatusSchema.parse({
4602
+ schemaVersion: EXTERNAL_AGENT_CONTROL_SCHEMA_VERSION,
4603
+ sequence: 0,
4604
+ mode: "inbox",
4605
+ adapter: {
4606
+ kind: "codex",
4607
+ maturity: "experimental",
4608
+ availability: "unavailable"
4609
+ },
4610
+ connectionState: "disconnected",
4611
+ authReadiness: "unknown",
4612
+ grantState: await grantStore.read(),
4613
+ updatedAt: now().toISOString()
4614
+ });
4615
+ const publish = (update) => {
4616
+ status = externalAgentControlStatusSchema.parse({
4617
+ ...status,
4618
+ ...update,
4619
+ schemaVersion: EXTERNAL_AGENT_CONTROL_SCHEMA_VERSION,
4620
+ sequence: status.sequence + 1,
4621
+ updatedAt: now().toISOString()
4622
+ });
4623
+ for (const listener of listeners) {
4624
+ try {
4625
+ listener(status);
4626
+ } catch {
4627
+ }
4628
+ }
4629
+ return status;
4630
+ };
4631
+ const rememberResult = (result) => {
4632
+ const managedResult = externalAgentManagedResultSchema.parse({
4633
+ revision: result.revision,
4634
+ diff: result.diff,
4635
+ files: result.files,
4636
+ checks: result.checks,
4637
+ timings: result.timings,
4638
+ validationOutcome: result.validationOutcome,
4639
+ expiresAt: result.expiresAt
4640
+ });
4641
+ results.set(result.revision, managedResult);
4642
+ while (results.size > MAXIMUM_RESULT_RECORDS) {
4643
+ const oldest = results.keys().next().value;
4644
+ if (oldest === void 0) break;
4645
+ results.delete(oldest);
4646
+ }
4647
+ publish({
4648
+ task: Object.freeze({
4649
+ revision: result.revision,
4650
+ deliveryStatus: "accepted",
4651
+ executionStatus: "terminal-succeeded",
4652
+ managedPhase: result.applied ? "completed" : "review-required",
4653
+ validationOutcome: result.validationOutcome,
4654
+ files: [...result.files],
4655
+ checks: [...result.checks],
4656
+ timings: result.timings,
4657
+ resultExpiresAt: result.expiresAt
4658
+ })
4659
+ });
4660
+ };
4661
+ const stopConnection = async () => {
4662
+ const active = connection;
4663
+ connection = void 0;
4664
+ if (active === void 0) return;
4665
+ active.controller.abort("supervisor-disconnect");
4666
+ await active.pump.close().catch(() => void 0);
4667
+ await active.run.catch(() => void 0);
4668
+ await active.execution.dispose().catch(() => void 0);
4669
+ };
4670
+ const connectInternal = async (allowPrompt) => {
4671
+ if (disposed) throw new SpotPatchError12(ERROR_CODES12.SESSION_CLOSED);
4672
+ if (connection !== void 0) return status;
4673
+ if (process.platform === "win32") {
4674
+ return publish({
4675
+ mode: "inbox",
4676
+ connectionState: "degraded",
4677
+ error: managedError(
4678
+ "MANAGED_PLATFORM_UNSUPPORTED",
4679
+ "integration",
4680
+ "none",
4681
+ "use-inbox"
4682
+ )
4683
+ });
4684
+ }
4685
+ publish({ connectionState: "diagnosing", error: void 0 });
4686
+ let grantState = await grantStore.read();
4687
+ if (grantState === "invalid") {
4688
+ return publish({
4689
+ grantState,
4690
+ connectionState: "error",
4691
+ error: managedError(
4692
+ "MANAGED_GRANT_INVALID",
4693
+ "integration",
4694
+ "user-action",
4695
+ "confirm-managed-access"
4696
+ )
4697
+ });
4698
+ }
4699
+ if (grantState === "missing") {
4700
+ publish({ grantState, connectionState: "awaiting-consent", mode: "inbox" });
4701
+ if (!allowPrompt) return status;
4702
+ const confirmed = await (options.confirmManagedAccess ?? defaultTerminalConfirmation)(options.projectLabel ?? grantStore.projectKey.slice(0, 12));
4703
+ if (!confirmed) return status;
4704
+ await grantStore.grant();
4705
+ grantState = "valid";
4706
+ publish({ grantState });
4707
+ }
4708
+ publish({ connectionState: "connecting", mode: "inbox", error: void 0 });
4709
+ const controller = new AbortController();
4710
+ const execution = createManagedExecutionRunner({
4711
+ root: options.root,
4712
+ ...options.checks === void 0 ? {} : { checks: options.checks },
4713
+ ...options.limits === void 0 ? {} : { limits: options.limits }
4714
+ });
4715
+ try {
4716
+ const adapterConnection = await (options.connectManagedAdapter ?? connectManagedCodexAppServer)({
4717
+ bridgeAdapter: options.bridgeAdapter,
4718
+ execution,
4719
+ cleanupJournal,
4720
+ onEvent(event) {
4721
+ if (event.type === "cleanup-warning") {
4722
+ const current2 = status.task?.revision === event.revision ? status.task : void 0;
4723
+ publish({
4724
+ mode: "inbox",
4725
+ connectionState: "degraded",
4726
+ task: taskStatus(event.revision, "cleanup-warning", current2),
4727
+ error: managedError(
4728
+ "MANAGED_CLEANUP_INCOMPLETE",
4729
+ "cleanup",
4730
+ "user-action",
4731
+ "inspect-cleanup-warning"
4732
+ )
4733
+ });
4734
+ controller.abort("managed-cleanup-incomplete");
4735
+ return;
4736
+ }
4737
+ if (event.type === "failure") {
4738
+ const current2 = status.task?.revision === event.revision ? status.task : void 0;
4739
+ const fatal = event.reason === "config-isolation" || event.reason === "protocol";
4740
+ publish({
4741
+ ...fatal ? { mode: "inbox", connectionState: "degraded" } : {},
4742
+ task: taskStatus(event.revision, "failed", current2),
4743
+ error: managedExecutionError(event.reason)
4744
+ });
4745
+ if (fatal) controller.abort("managed-config-isolation-unsupported");
4746
+ return;
4747
+ }
4748
+ if (event.type === "result") {
4749
+ rememberResult(event.result);
4750
+ return;
4751
+ }
4752
+ const current = status.task?.revision === event.revision ? status.task : void 0;
4753
+ publish({
4754
+ task: taskStatus(event.revision, event.phase, current)
4755
+ });
4756
+ },
4757
+ projectRoot: options.root,
4758
+ ...options.configBase === void 0 ? {} : { privateRuntimeBase: options.configBase },
4759
+ runtimeKey: grantStore.projectKey,
4760
+ sessionId: options.sessionId,
4761
+ signal: controller.signal
4762
+ });
4763
+ await grantStore.touch();
4764
+ const pump = createActiveEventPump({
4765
+ adapter: adapterConnection.adapter,
4766
+ client: createSpotPatchBridgeClient(options.root),
4767
+ sessionId: options.sessionId,
4768
+ onEvent(event) {
4769
+ if (event.type === "ready") {
4770
+ publish({ connectionState: "ready", mode: "managed", error: void 0 });
4771
+ return;
4772
+ }
4773
+ const current = status.task?.revision === event.revision ? status.task : void 0;
4774
+ const task = taskStatus(
4775
+ event.revision,
4776
+ event.phase === "working" ? "running" : event.phase === "completed" ? current?.managedPhase ?? "completed" : event.phase === "failed" || event.phase === "delivery-unknown" ? "failed" : current?.managedPhase ?? "preparing",
4777
+ current
4778
+ );
4779
+ publish({
4780
+ connectionState: event.phase === "completed" || event.phase === "failed" ? "ready" : "busy",
4781
+ task: Object.freeze({
4782
+ ...task,
4783
+ deliveryStatus: event.phase === "dispatching" ? "dispatching" : event.phase === "delivery-unknown" ? "unknown" : "accepted",
4784
+ executionStatus: event.phase === "working" ? "started" : event.phase === "completed" ? "terminal-succeeded" : event.phase === "failed" ? "terminal-failed" : event.phase === "delivery-unknown" ? "unknown" : task.executionStatus
4785
+ })
4786
+ });
4787
+ }
4788
+ });
4789
+ const run = pump.run(controller.signal).catch((error) => {
4790
+ if (!controller.signal.aborted && !disposed) {
4791
+ publish({
4792
+ mode: "inbox",
4793
+ connectionState: "degraded",
4794
+ error: classifyConnectionError(error)
4795
+ });
4796
+ }
4797
+ }).finally(async () => {
4798
+ if (connection?.controller === controller) connection = void 0;
4799
+ await execution.dispose().catch(() => void 0);
4800
+ });
4801
+ const activeConnection = {
4802
+ controller,
4803
+ execution,
4804
+ pump,
4805
+ run
4806
+ };
4807
+ connection = activeConnection;
4808
+ publish({
4809
+ adapter: {
4810
+ kind: "codex",
4811
+ maturity: "experimental",
4812
+ availability: "available"
4813
+ },
4814
+ authReadiness: adapterConnection.authReadiness,
4815
+ ...adapterConnection.requestedModel === void 0 ? {} : { requestedModel: adapterConnection.requestedModel },
4816
+ ...adapterConnection.effectiveModel === void 0 ? {} : { effectiveModel: adapterConnection.effectiveModel }
4817
+ });
4818
+ return status;
4819
+ } catch (error) {
4820
+ controller.abort("supervisor-connect-failed");
4821
+ await execution.dispose().catch(() => void 0);
4822
+ const classified = classifyConnectionError(error);
4823
+ return publish({
4824
+ mode: "inbox",
4825
+ connectionState: "degraded",
4826
+ ...classified.code === "AGENT_AUTH_REQUIRED" ? { authReadiness: "signed-out" } : {},
4827
+ error: classified
4828
+ });
4829
+ }
4830
+ };
4831
+ const serialize = (operation) => {
4832
+ const result = operationTail.then(operation, operation);
4833
+ operationTail = result.then(
4834
+ () => void 0,
4835
+ () => void 0
4836
+ );
4837
+ return result;
4838
+ };
4839
+ const idempotent = (requestId, fingerprint, operation) => {
4840
+ const prior = idempotency.get(requestId);
4841
+ if (prior !== void 0) {
4842
+ if (prior.fingerprint !== fingerprint) {
4843
+ return Promise.reject(new SpotPatchError12(ERROR_CODES12.INVALID_REQUEST));
4844
+ }
4845
+ return prior.result;
4846
+ }
4847
+ const result = serialize(operation);
4848
+ idempotency.set(requestId, { fingerprint, result });
4849
+ while (idempotency.size > MAXIMUM_IDEMPOTENCY_RECORDS) {
4850
+ const oldest = idempotency.keys().next().value;
4851
+ if (oldest === void 0) break;
4852
+ idempotency.delete(oldest);
4853
+ }
4854
+ return result;
4855
+ };
4856
+ const supervisor = Object.freeze({
4857
+ getStatus: () => status,
4858
+ connect(request, signal) {
4859
+ return idempotent(request.requestId, requestFingerprint(request), async () => {
4860
+ if (signal.aborted) throw new SpotPatchError12(ERROR_CODES12.AGENT_CANCELLED);
4861
+ return connectInternal(true);
4862
+ });
4863
+ },
4864
+ disconnect(request) {
4865
+ return idempotent(request.requestId, requestFingerprint(request), async () => {
4866
+ publish({ connectionState: "disconnecting" });
4867
+ await stopConnection();
4868
+ if (request.revokeGrant) {
4869
+ await removeManagedCodexRuntimeHome({
4870
+ ...options.configBase === void 0 ? {} : { runtimeBase: options.configBase },
4871
+ runtimeKey: grantStore.projectKey
4872
+ });
4873
+ await grantStore.revoke();
4874
+ }
4875
+ return publish({
4876
+ mode: "inbox",
4877
+ connectionState: "disconnected",
4878
+ grantState: await grantStore.read(),
4879
+ authReadiness: "unknown",
4880
+ requestedModel: void 0,
4881
+ effectiveModel: void 0,
4882
+ error: void 0
4883
+ });
4884
+ });
4885
+ },
4886
+ cancel(request) {
4887
+ return idempotent(request.requestId, requestFingerprint(request), async () => {
4888
+ if (status.task?.revision !== request.revision) {
4889
+ throw new SpotPatchError12(ERROR_CODES12.HANDOFF_NOT_FOUND);
4890
+ }
4891
+ await stopConnection();
4892
+ return publish({
4893
+ mode: "inbox",
4894
+ connectionState: "disconnected",
4895
+ task: Object.freeze({
4896
+ ...taskStatus(request.revision, "cancelled", status.task),
4897
+ executionStatus: "interrupted"
4898
+ })
4899
+ });
4900
+ });
4901
+ },
4902
+ getResult(revision) {
4903
+ const result = results.get(revision);
4904
+ if (result === void 0) return void 0;
4905
+ if (Date.parse(result.expiresAt) <= now().getTime()) {
4906
+ results.delete(revision);
4907
+ return void 0;
4908
+ }
4909
+ return result;
4910
+ },
4911
+ subscribe(listener) {
4912
+ if (disposed) return () => void 0;
4913
+ listeners.add(listener);
4914
+ return () => listeners.delete(listener);
4915
+ },
4916
+ async dispose() {
4917
+ if (disposed) return;
4918
+ disposed = true;
4919
+ listeners.clear();
4920
+ await serialize(stopConnection);
4921
+ idempotency.clear();
4922
+ results.clear();
4923
+ }
4924
+ });
4925
+ if (status.grantState === "valid") {
4926
+ void serialize(async () => {
4927
+ await connectInternal(false);
4928
+ });
4929
+ }
4930
+ return supervisor;
4931
+ }
3215
4932
  export {
4933
+ ManagedCodexAppServerAdapter,
3216
4934
  applyBridgeSetupPlan,
4935
+ connectManagedCodexAppServer,
3217
4936
  createBridgeSetupPlan,
4937
+ createExternalAgentSupervisor,
3218
4938
  createSpotPatchBridgeClient,
3219
4939
  createSpotPatchMcpServer,
3220
4940
  runSpotPatchBridgeCli,