@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/cli.js +15 -5
- package/dist/cli.js.map +1 -1
- package/dist/index.cjs +1878 -168
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +110 -2
- package/dist/index.d.ts +110 -2
- package/dist/index.js +1884 -164
- package/dist/index.js.map +1 -1
- package/package.json +4 -3
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
|
|
556
|
-
var
|
|
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.
|
|
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
|
|
1945
|
-
if (!(await (0, import_promises3.stat)(
|
|
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(
|
|
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
|
|
1956
|
-
if (
|
|
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
|
|
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/
|
|
2834
|
-
|
|
2835
|
-
|
|
2836
|
-
|
|
2837
|
-
|
|
2838
|
-
|
|
2839
|
-
|
|
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
|
-
|
|
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
|
|
2844
|
-
|
|
2845
|
-
|
|
2846
|
-
|
|
2847
|
-
|
|
2848
|
-
|
|
2849
|
-
|
|
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
|
-
|
|
2852
|
-
|
|
2853
|
-
|
|
2854
|
-
|
|
2855
|
-
|
|
2856
|
-
|
|
2857
|
-
|
|
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
|
-
|
|
2862
|
-
|
|
2863
|
-
|
|
2864
|
-
|
|
2865
|
-
|
|
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
|
-
|
|
2868
|
-
|
|
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
|
-
|
|
3003
|
+
await (0, import_promises6.rm)(authLink);
|
|
3004
|
+
return;
|
|
2871
3005
|
}
|
|
2872
|
-
if (
|
|
2873
|
-
|
|
2874
|
-
|
|
2875
|
-
|
|
2876
|
-
return
|
|
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
|
-
|
|
2879
|
-
|
|
2880
|
-
|
|
2881
|
-
|
|
2882
|
-
|
|
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
|
|
2887
|
-
|
|
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
|
|
2891
|
-
|
|
2892
|
-
|
|
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
|
|
3137
|
+
const error = new Error("The managed Codex operation was aborted.");
|
|
2897
3138
|
error.name = "AbortError";
|
|
2898
3139
|
return error;
|
|
2899
3140
|
}
|
|
2900
|
-
function
|
|
2901
|
-
if (
|
|
2902
|
-
|
|
2903
|
-
|
|
2904
|
-
|
|
2905
|
-
|
|
2906
|
-
|
|
2907
|
-
|
|
2908
|
-
|
|
2909
|
-
|
|
2910
|
-
|
|
2911
|
-
|
|
2912
|
-
|
|
2913
|
-
|
|
2914
|
-
|
|
2915
|
-
|
|
2916
|
-
|
|
2917
|
-
|
|
2918
|
-
|
|
2919
|
-
|
|
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
|
|
2926
|
-
|
|
2927
|
-
|
|
2928
|
-
|
|
2929
|
-
|
|
2930
|
-
|
|
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
|
-
|
|
2933
|
-
|
|
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
|
-
|
|
2936
|
-
|
|
2937
|
-
|
|
2938
|
-
|
|
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
|
-
|
|
2943
|
-
|
|
2944
|
-
|
|
2945
|
-
|
|
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
|
-
|
|
2948
|
-
|
|
2949
|
-
|
|
2950
|
-
|
|
2951
|
-
|
|
2952
|
-
|
|
2953
|
-
|
|
2954
|
-
|
|
2955
|
-
|
|
2956
|
-
|
|
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
|
|
2960
|
-
if (
|
|
2961
|
-
|
|
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
|
-
|
|
2967
|
-
|
|
2968
|
-
|
|
2969
|
-
|
|
2970
|
-
|
|
2971
|
-
|
|
2972
|
-
|
|
2973
|
-
|
|
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
|
-
|
|
3003
|
-
|
|
3004
|
-
|
|
3005
|
-
|
|
3006
|
-
|
|
3007
|
-
|
|
3008
|
-
|
|
3009
|
-
|
|
3010
|
-
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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 =
|
|
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 >
|
|
3146
|
-
throw new
|
|
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
|
|
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
|
|
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 ===
|
|
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,
|