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