replicas-engine 0.1.667 → 0.1.671
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/README.md +4 -4
- package/dist/src/app-server-process-S2SD6PQE.js +10 -0
- package/dist/src/chunk-4FTBKGMO.js +162 -0
- package/dist/src/chunk-6GXXZQID.js +448 -0
- package/dist/src/chunk-6WY7NPCL.js +407 -0
- package/dist/src/{chunk-P2Q47GLV.js → chunk-TMERKNQV.js} +200 -747
- package/dist/src/chunk-XQW4B35Y.js +108 -0
- package/dist/src/codex-token-manager-RX4N2W22.js +13 -0
- package/dist/src/engine-env-A2FZA66L.js +14 -0
- package/dist/src/headless-agent.js +49 -34
- package/dist/src/index.js +428 -833
- package/package.json +1 -1
- package/workspace-sdk/shared/routes/plugins.d.ts +29 -7
package/README.md
CHANGED
|
@@ -80,14 +80,14 @@ From `monolith/src/lib/sandbox-helpers.ts` + `monolith/src/lib/workspaces.ts`, t
|
|
|
80
80
|
- file: `~/.claude/.bedrock-credentials.json`
|
|
81
81
|
- env: `CLAUDE_CODE_USE_BEDROCK=1`, `AWS_ACCESS_KEY_ID`, `AWS_SECRET_ACCESS_KEY`, `AWS_REGION`
|
|
82
82
|
- Codex credentials (optional):
|
|
83
|
-
-
|
|
84
|
-
-
|
|
83
|
+
- OAuth access tokens are supplied through Codex ASP external authentication.
|
|
84
|
+
- Provisioning writes `~/.codex/auth.json` for older engines; current engines remove it before starting Codex.
|
|
85
85
|
|
|
86
86
|
Token refresh managers may later overwrite credential files in place:
|
|
87
87
|
|
|
88
88
|
- `~/.git-credentials`
|
|
89
89
|
- `~/.claude/.credentials.json`
|
|
90
|
-
- `~/.codex/auth.json`
|
|
90
|
+
- `~/.codex/auth.json` (removed by current engines before Codex starts)
|
|
91
91
|
- `~/.replicas/infisical-env.sh` and `~/workspaces/.infisical.json`
|
|
92
92
|
|
|
93
93
|
Engine persistence locations:
|
|
@@ -119,7 +119,7 @@ Credential files expected/used by provider CLIs:
|
|
|
119
119
|
- `~/.git-credentials` (git/gh auth)
|
|
120
120
|
- `~/.claude/.credentials.json` (Claude OAuth auth)
|
|
121
121
|
- `~/.claude/.bedrock-credentials.json` (Claude Bedrock config)
|
|
122
|
-
- `~/.codex/auth.json`
|
|
122
|
+
- Codex OAuth uses ASP external authentication; the engine removes legacy `~/.codex/auth.json` files.
|
|
123
123
|
|
|
124
124
|
## What the engine sends upstream
|
|
125
125
|
|
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
REPLICAS_RUNTIME_ENV_ALIASES,
|
|
4
|
+
agentCredentialSnapshotSchema,
|
|
5
|
+
isValidAgentProvider,
|
|
6
|
+
parsePosixEnvFile,
|
|
7
|
+
readReplicasRuntimeEnv
|
|
8
|
+
} from "./chunk-TMERKNQV.js";
|
|
9
|
+
|
|
10
|
+
// src/engine-env.ts
|
|
11
|
+
import { readFileSync as readFileSync2 } from "fs";
|
|
12
|
+
import { homedir as homedir2 } from "os";
|
|
13
|
+
import { join as join2 } from "path";
|
|
14
|
+
|
|
15
|
+
// src/runtime-env-loader.ts
|
|
16
|
+
import { readFileSync } from "fs";
|
|
17
|
+
import { homedir } from "os";
|
|
18
|
+
import { join } from "path";
|
|
19
|
+
function loadRuntimeEnvFile() {
|
|
20
|
+
let content;
|
|
21
|
+
try {
|
|
22
|
+
content = readFileSync(join(homedir(), ".replicas", "runtime-env.sh"), "utf-8");
|
|
23
|
+
} catch {
|
|
24
|
+
return;
|
|
25
|
+
}
|
|
26
|
+
for (const [key, value] of Object.entries(parsePosixEnvFile(content))) {
|
|
27
|
+
process.env[key] = value;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
// src/utils/type-guards.ts
|
|
32
|
+
function isRecord(value) {
|
|
33
|
+
return typeof value === "object" && value !== null;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// src/engine-env.ts
|
|
37
|
+
var SANDBOX_IMAGE_VERSION_FILE = "/usr/local/lib/replicas-sandbox-image-version";
|
|
38
|
+
function readEnv(name) {
|
|
39
|
+
const value = process.env[name]?.trim();
|
|
40
|
+
return value ? value : void 0;
|
|
41
|
+
}
|
|
42
|
+
function readSandboxImageVersion() {
|
|
43
|
+
const environmentVersion = readEnv("REPLICAS_SANDBOX_IMAGE_VERSION");
|
|
44
|
+
if (environmentVersion) return environmentVersion;
|
|
45
|
+
try {
|
|
46
|
+
return readFileSync2(SANDBOX_IMAGE_VERSION_FILE, "utf8").trim() || "development";
|
|
47
|
+
} catch {
|
|
48
|
+
return "development";
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
function parsePort(value) {
|
|
52
|
+
if (!value) {
|
|
53
|
+
return 3737;
|
|
54
|
+
}
|
|
55
|
+
const parsed = Number(value);
|
|
56
|
+
if (!Number.isInteger(parsed) || parsed <= 0) {
|
|
57
|
+
throw new Error("Invalid engine environment: REPLICAS_ENGINE_PORT must be a positive integer");
|
|
58
|
+
}
|
|
59
|
+
return parsed;
|
|
60
|
+
}
|
|
61
|
+
function requireDefined(value, name) {
|
|
62
|
+
if (value === void 0 || value === null) {
|
|
63
|
+
throw new Error(`Invalid engine environment: ${name} is required`);
|
|
64
|
+
}
|
|
65
|
+
return value;
|
|
66
|
+
}
|
|
67
|
+
function requireValidURL(value, name) {
|
|
68
|
+
try {
|
|
69
|
+
new URL(value);
|
|
70
|
+
return value;
|
|
71
|
+
} catch {
|
|
72
|
+
throw new Error(`Invalid engine environment: ${name} must be a valid URL`);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
function parseClaudeAuthMethod(value) {
|
|
76
|
+
if (value === "oauth" || value === "api_key" || value === "bedrock" || value === "foundry") {
|
|
77
|
+
return value;
|
|
78
|
+
}
|
|
79
|
+
return void 0;
|
|
80
|
+
}
|
|
81
|
+
function parseCodexAuthMethod(value) {
|
|
82
|
+
if (value === "oauth" || value === "api_key" || value === "foundry") {
|
|
83
|
+
return value;
|
|
84
|
+
}
|
|
85
|
+
return void 0;
|
|
86
|
+
}
|
|
87
|
+
function parseAgentCredentialSnapshots(value) {
|
|
88
|
+
if (!value) return {};
|
|
89
|
+
try {
|
|
90
|
+
const parsed = JSON.parse(value);
|
|
91
|
+
if (!isRecord(parsed)) return {};
|
|
92
|
+
const snapshots = {};
|
|
93
|
+
for (const [provider, snapshot] of Object.entries(parsed)) {
|
|
94
|
+
if (!isValidAgentProvider(provider)) continue;
|
|
95
|
+
const parsedSnapshot = agentCredentialSnapshotSchema.safeParse(snapshot);
|
|
96
|
+
if (parsedSnapshot.success) snapshots[provider] = parsedSnapshot.data;
|
|
97
|
+
}
|
|
98
|
+
return snapshots;
|
|
99
|
+
} catch {
|
|
100
|
+
return {};
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
var IS_WARMING_MODE = process.argv.includes("--warming");
|
|
104
|
+
function loadEngineEnv() {
|
|
105
|
+
loadRuntimeEnvFile();
|
|
106
|
+
const HOME_DIR = homedir2();
|
|
107
|
+
const env = {
|
|
108
|
+
// Defined: always available
|
|
109
|
+
REPLICAS_ENGINE_SECRET: requireDefined(readEnv("REPLICAS_ENGINE_SECRET"), "REPLICAS_ENGINE_SECRET"),
|
|
110
|
+
REPLICAS_ENGINE_PORT: parsePort(readEnv("REPLICAS_ENGINE_PORT")),
|
|
111
|
+
REPLICAS_MONOLITH_URL: requireValidURL(
|
|
112
|
+
requireDefined(readReplicasRuntimeEnv(readEnv, REPLICAS_RUNTIME_ENV_ALIASES.monolithUrl), "REPLICAS_MONOLITH_URL"),
|
|
113
|
+
"REPLICAS_MONOLITH_URL"
|
|
114
|
+
),
|
|
115
|
+
HOME_DIR,
|
|
116
|
+
WORKSPACE_ROOT: join2(HOME_DIR, "workspaces"),
|
|
117
|
+
REPLICAS_SANDBOX_IMAGE_VERSION: readSandboxImageVersion(),
|
|
118
|
+
// Runtime: may not be set during warming
|
|
119
|
+
REPLICAS_WORKSPACE_ID: readReplicasRuntimeEnv(readEnv, REPLICAS_RUNTIME_ENV_ALIASES.workspaceId),
|
|
120
|
+
REPLICAS_LINEAR_SESSION_ID: readReplicasRuntimeEnv(readEnv, REPLICAS_RUNTIME_ENV_ALIASES.linearSessionId),
|
|
121
|
+
REPLICAS_LINEAR_ACCESS_TOKEN: readReplicasRuntimeEnv(readEnv, REPLICAS_RUNTIME_ENV_ALIASES.linearAccessToken),
|
|
122
|
+
REPLICAS_SLACK_BOT_TOKEN: readReplicasRuntimeEnv(readEnv, REPLICAS_RUNTIME_ENV_ALIASES.slackBotToken),
|
|
123
|
+
REPLICAS_SLACK_CHANNEL_ID: readReplicasRuntimeEnv(readEnv, REPLICAS_RUNTIME_ENV_ALIASES.slackChannelId),
|
|
124
|
+
REPLICAS_SLACK_THREAD_TS: readReplicasRuntimeEnv(readEnv, REPLICAS_RUNTIME_ENV_ALIASES.slackThreadTs),
|
|
125
|
+
ANTHROPIC_API_KEY: readEnv("ANTHROPIC_API_KEY"),
|
|
126
|
+
OPENAI_API_KEY: readEnv("OPENAI_API_KEY"),
|
|
127
|
+
CURSOR_API_KEY: readEnv("CURSOR_API_KEY"),
|
|
128
|
+
AI_GATEWAY_API_KEY: readEnv("AI_GATEWAY_API_KEY"),
|
|
129
|
+
CLAUDE_CODE_USE_BEDROCK: readEnv("CLAUDE_CODE_USE_BEDROCK"),
|
|
130
|
+
AWS_ACCESS_KEY_ID: readEnv("AWS_ACCESS_KEY_ID"),
|
|
131
|
+
AWS_SECRET_ACCESS_KEY: readEnv("AWS_SECRET_ACCESS_KEY"),
|
|
132
|
+
AWS_REGION: readEnv("AWS_REGION"),
|
|
133
|
+
ANTHROPIC_SMALL_FAST_MODEL_AWS_REGION: readEnv("ANTHROPIC_SMALL_FAST_MODEL_AWS_REGION"),
|
|
134
|
+
REPLICAS_CLAUDE_AUTH_METHOD: parseClaudeAuthMethod(readEnv("REPLICAS_CLAUDE_AUTH_METHOD")),
|
|
135
|
+
REPLICAS_CODEX_AUTH_METHOD: parseCodexAuthMethod(readEnv("REPLICAS_CODEX_AUTH_METHOD")),
|
|
136
|
+
REPLICAS_AGENT_CREDENTIALS: parseAgentCredentialSnapshots(readEnv("REPLICAS_AGENT_CREDENTIALS")),
|
|
137
|
+
REPLICAS_ENV_SYSTEM_PROMPT: readEnv("REPLICAS_ENV_SYSTEM_PROMPT"),
|
|
138
|
+
REPLICAS_ENV_START_HOOK: readEnv("REPLICAS_ENV_START_HOOK"),
|
|
139
|
+
REPLICAS_DISABLE_AUTO_START_HOOKS: readEnv("REPLICAS_DISABLE_AUTO_START_HOOKS")?.toLowerCase() === "true",
|
|
140
|
+
REPLICAS_ENGINE_DEFER_INITIALIZATION: readEnv("REPLICAS_ENGINE_DEFER_INITIALIZATION")?.toLowerCase() === "true"
|
|
141
|
+
};
|
|
142
|
+
if (!IS_WARMING_MODE && !env.REPLICAS_WORKSPACE_ID) {
|
|
143
|
+
console.error("REPLICAS_WORKSPACE_ID is not set \u2014 this is required in normal (non-warming) mode");
|
|
144
|
+
}
|
|
145
|
+
return env;
|
|
146
|
+
}
|
|
147
|
+
var ENGINE_ENV = loadEngineEnv();
|
|
148
|
+
function setAgentCredentialSnapshot(provider, snapshot) {
|
|
149
|
+
ENGINE_ENV.REPLICAS_AGENT_CREDENTIALS = {
|
|
150
|
+
...ENGINE_ENV.REPLICAS_AGENT_CREDENTIALS,
|
|
151
|
+
[provider]: snapshot
|
|
152
|
+
};
|
|
153
|
+
process.env.REPLICAS_AGENT_CREDENTIALS = JSON.stringify(ENGINE_ENV.REPLICAS_AGENT_CREDENTIALS);
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
export {
|
|
157
|
+
isRecord,
|
|
158
|
+
IS_WARMING_MODE,
|
|
159
|
+
loadEngineEnv,
|
|
160
|
+
ENGINE_ENV,
|
|
161
|
+
setAgentCredentialSnapshot
|
|
162
|
+
};
|
|
@@ -0,0 +1,448 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
CodexAspAuthMethodChangedError
|
|
4
|
+
} from "./chunk-6WY7NPCL.js";
|
|
5
|
+
import {
|
|
6
|
+
HOOK_EXEC_MAX_BUFFER_BYTES,
|
|
7
|
+
isVersionBelow
|
|
8
|
+
} from "./chunk-TMERKNQV.js";
|
|
9
|
+
|
|
10
|
+
// src/managers/codex-asp/app-server-process.ts
|
|
11
|
+
import { spawn } from "child_process";
|
|
12
|
+
import { EventEmitter as EventEmitter2 } from "events";
|
|
13
|
+
|
|
14
|
+
// src/managers/codex-asp/asp-client.ts
|
|
15
|
+
import { EventEmitter } from "events";
|
|
16
|
+
var DEFAULT_REQUEST_TIMEOUT_MS = 12e4;
|
|
17
|
+
function hasOwn(record, key) {
|
|
18
|
+
return Object.prototype.hasOwnProperty.call(record, key);
|
|
19
|
+
}
|
|
20
|
+
var AspClient = class {
|
|
21
|
+
stdin;
|
|
22
|
+
stdout;
|
|
23
|
+
emitter = new EventEmitter();
|
|
24
|
+
pending = /* @__PURE__ */ new Map();
|
|
25
|
+
nextId = 1;
|
|
26
|
+
lineBuffer = "";
|
|
27
|
+
disposed = false;
|
|
28
|
+
get isDisposed() {
|
|
29
|
+
return this.disposed;
|
|
30
|
+
}
|
|
31
|
+
constructor(options) {
|
|
32
|
+
this.stdin = options.stdin;
|
|
33
|
+
this.stdout = options.stdout;
|
|
34
|
+
this.stdout.setEncoding("utf8");
|
|
35
|
+
this.stdout.on("data", this.handleStdoutData);
|
|
36
|
+
this.stdin.on("error", this.handleStdinError);
|
|
37
|
+
}
|
|
38
|
+
on(event, listener) {
|
|
39
|
+
this.emitter.on(event, listener);
|
|
40
|
+
}
|
|
41
|
+
off(event, listener) {
|
|
42
|
+
this.emitter.off(event, listener);
|
|
43
|
+
}
|
|
44
|
+
async request(method, params, opts) {
|
|
45
|
+
if (this.disposed) {
|
|
46
|
+
throw new Error(`Cannot send ${method}: ASP client disposed`);
|
|
47
|
+
}
|
|
48
|
+
const id = this.nextId;
|
|
49
|
+
this.nextId += 1;
|
|
50
|
+
const promise = new Promise((resolve, reject) => {
|
|
51
|
+
const timeoutMs = opts?.timeoutMs ?? DEFAULT_REQUEST_TIMEOUT_MS;
|
|
52
|
+
const timer = timeoutMs > 0 ? setTimeout(() => {
|
|
53
|
+
this.pending.delete(id);
|
|
54
|
+
reject(new Error(`ASP request timed out for ${method}`));
|
|
55
|
+
}, timeoutMs) : null;
|
|
56
|
+
this.pending.set(id, { resolve, reject, method, timer });
|
|
57
|
+
});
|
|
58
|
+
this.write({ method, id, params });
|
|
59
|
+
return promise;
|
|
60
|
+
}
|
|
61
|
+
notify(method, params) {
|
|
62
|
+
if (this.disposed) {
|
|
63
|
+
return;
|
|
64
|
+
}
|
|
65
|
+
try {
|
|
66
|
+
this.write(params === void 0 ? { method } : { method, params });
|
|
67
|
+
} catch (error) {
|
|
68
|
+
console.warn(`[AspClient] Failed to send notification ${method}:`, error);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
respond(id, result) {
|
|
72
|
+
if (this.disposed) {
|
|
73
|
+
return;
|
|
74
|
+
}
|
|
75
|
+
try {
|
|
76
|
+
this.write({ id, result });
|
|
77
|
+
} catch (error) {
|
|
78
|
+
console.warn(`[AspClient] Failed to send response ${String(id)}:`, error);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
reject(id, code, message, data) {
|
|
82
|
+
if (this.disposed) {
|
|
83
|
+
return;
|
|
84
|
+
}
|
|
85
|
+
try {
|
|
86
|
+
this.write({
|
|
87
|
+
id,
|
|
88
|
+
error: {
|
|
89
|
+
code,
|
|
90
|
+
message,
|
|
91
|
+
...data !== void 0 ? { data } : {}
|
|
92
|
+
}
|
|
93
|
+
});
|
|
94
|
+
} catch (error) {
|
|
95
|
+
console.warn(`[AspClient] Failed to send error response ${String(id)}:`, error);
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
dispose(reason = new Error("ASP client disposed")) {
|
|
99
|
+
if (this.disposed) {
|
|
100
|
+
return;
|
|
101
|
+
}
|
|
102
|
+
this.disposed = true;
|
|
103
|
+
this.stdout.off("data", this.handleStdoutData);
|
|
104
|
+
this.stdin.removeListener("error", this.handleStdinError);
|
|
105
|
+
for (const [id, pending] of this.pending) {
|
|
106
|
+
if (pending.timer) {
|
|
107
|
+
clearTimeout(pending.timer);
|
|
108
|
+
}
|
|
109
|
+
pending.reject(new Error(`${reason.message} while waiting for ${pending.method}`));
|
|
110
|
+
this.pending.delete(id);
|
|
111
|
+
}
|
|
112
|
+
this.lineBuffer = "";
|
|
113
|
+
this.emitter.emit("dispose", reason);
|
|
114
|
+
this.emitter.removeAllListeners();
|
|
115
|
+
}
|
|
116
|
+
handleStdoutData = (chunk) => {
|
|
117
|
+
this.lineBuffer += chunk.toString();
|
|
118
|
+
let newlineIndex = this.lineBuffer.indexOf("\n");
|
|
119
|
+
while (newlineIndex >= 0) {
|
|
120
|
+
const line = this.lineBuffer.slice(0, newlineIndex).trim();
|
|
121
|
+
this.lineBuffer = this.lineBuffer.slice(newlineIndex + 1);
|
|
122
|
+
if (line.length > 0) {
|
|
123
|
+
this.handleLine(line);
|
|
124
|
+
}
|
|
125
|
+
newlineIndex = this.lineBuffer.indexOf("\n");
|
|
126
|
+
}
|
|
127
|
+
};
|
|
128
|
+
handleStdinError = (error) => {
|
|
129
|
+
this.dispose(new Error(`ASP stdin error: ${error.message}`));
|
|
130
|
+
};
|
|
131
|
+
handleLine(line) {
|
|
132
|
+
let parsed;
|
|
133
|
+
try {
|
|
134
|
+
parsed = JSON.parse(line);
|
|
135
|
+
} catch (error) {
|
|
136
|
+
console.warn("[AspClient] Failed to parse ASP JSON line:", error);
|
|
137
|
+
return;
|
|
138
|
+
}
|
|
139
|
+
if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) {
|
|
140
|
+
console.warn("[AspClient] Ignoring non-object ASP message");
|
|
141
|
+
return;
|
|
142
|
+
}
|
|
143
|
+
const message = parsed;
|
|
144
|
+
const hasRequestId = typeof message.id === "number" || typeof message.id === "string";
|
|
145
|
+
if (hasRequestId && (hasOwn(message, "result") || hasOwn(message, "error"))) {
|
|
146
|
+
this.handleResponse(message);
|
|
147
|
+
return;
|
|
148
|
+
}
|
|
149
|
+
if (hasRequestId && typeof message.method === "string") {
|
|
150
|
+
this.emitter.emit("serverRequest", message);
|
|
151
|
+
return;
|
|
152
|
+
}
|
|
153
|
+
if (!hasOwn(message, "id") && typeof message.method === "string") {
|
|
154
|
+
this.emitter.emit("notification", message);
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
handleResponse(message) {
|
|
158
|
+
if (typeof message.id !== "number") {
|
|
159
|
+
console.warn("[AspClient] Ignoring response with non-numeric request id");
|
|
160
|
+
return;
|
|
161
|
+
}
|
|
162
|
+
const pending = this.pending.get(message.id);
|
|
163
|
+
if (!pending) {
|
|
164
|
+
console.warn(`[AspClient] Ignoring response for unknown request id ${message.id}`);
|
|
165
|
+
return;
|
|
166
|
+
}
|
|
167
|
+
this.pending.delete(message.id);
|
|
168
|
+
if (pending.timer) {
|
|
169
|
+
clearTimeout(pending.timer);
|
|
170
|
+
}
|
|
171
|
+
if (hasOwn(message, "error")) {
|
|
172
|
+
pending.reject(this.createRpcError(pending.method, message.error));
|
|
173
|
+
return;
|
|
174
|
+
}
|
|
175
|
+
pending.resolve(message.result);
|
|
176
|
+
}
|
|
177
|
+
createRpcError(method, error) {
|
|
178
|
+
if (typeof error !== "object" || error === null || Array.isArray(error)) {
|
|
179
|
+
return new Error(`ASP request failed for ${method}`);
|
|
180
|
+
}
|
|
181
|
+
const rpcError = error;
|
|
182
|
+
const code = typeof rpcError.code === "number" ? ` ${rpcError.code}` : "";
|
|
183
|
+
const message = typeof rpcError.message === "string" ? rpcError.message : "Unknown ASP error";
|
|
184
|
+
const data = hasOwn(rpcError, "data") ? ` data=${JSON.stringify(rpcError.data)}` : "";
|
|
185
|
+
return new Error(`ASP request failed for ${method}:${code} ${message}${data}`);
|
|
186
|
+
}
|
|
187
|
+
write(message) {
|
|
188
|
+
try {
|
|
189
|
+
this.stdin.write(`${JSON.stringify(message)}
|
|
190
|
+
`, (error) => {
|
|
191
|
+
if (error) {
|
|
192
|
+
this.dispose(new Error(`ASP write failed: ${error.message}`));
|
|
193
|
+
}
|
|
194
|
+
});
|
|
195
|
+
} catch (error) {
|
|
196
|
+
const writeError = error instanceof Error ? error : new Error("ASP write failed");
|
|
197
|
+
this.dispose(writeError);
|
|
198
|
+
throw writeError;
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
};
|
|
202
|
+
|
|
203
|
+
// src/utils/exec.ts
|
|
204
|
+
import { exec, execFile } from "child_process";
|
|
205
|
+
import { promisify } from "util";
|
|
206
|
+
var execAsync = promisify(exec);
|
|
207
|
+
var execFileAsync = promisify(execFile);
|
|
208
|
+
var SUBPROCESS_MAX_BUFFER = HOOK_EXEC_MAX_BUFFER_BYTES;
|
|
209
|
+
|
|
210
|
+
// src/managers/codex-asp/app-server-process.ts
|
|
211
|
+
var DEFAULT_CODEX_BINARY = "codex";
|
|
212
|
+
var DEFAULT_CODEX_ARGS = [
|
|
213
|
+
"app-server",
|
|
214
|
+
"--listen",
|
|
215
|
+
"stdio://",
|
|
216
|
+
"-c",
|
|
217
|
+
"features.memories=false",
|
|
218
|
+
"-c",
|
|
219
|
+
"memories.use_memories=false",
|
|
220
|
+
"-c",
|
|
221
|
+
"memories.generate_memories=false"
|
|
222
|
+
];
|
|
223
|
+
var MIN_CODEX_CLI_VERSION = "0.144.6";
|
|
224
|
+
var CODEX_UPGRADE_TIMEOUT_MS = 12e4;
|
|
225
|
+
var codexCliVersionEnsured = null;
|
|
226
|
+
var ENGINE_PACKAGE_VERSION = "0.1.671";
|
|
227
|
+
var INITIALIZE_METHOD = "initialize";
|
|
228
|
+
var INITIALIZED_NOTIFICATION = "initialized";
|
|
229
|
+
var ACCOUNT_LOGIN_START_METHOD = "account/login/start";
|
|
230
|
+
var AppServerProcess = class {
|
|
231
|
+
binary;
|
|
232
|
+
args;
|
|
233
|
+
env;
|
|
234
|
+
cwd;
|
|
235
|
+
chatgptAuthTokens;
|
|
236
|
+
refreshChatgptAuthTokens;
|
|
237
|
+
emitter = new EventEmitter2();
|
|
238
|
+
child = null;
|
|
239
|
+
client = null;
|
|
240
|
+
shuttingDown = false;
|
|
241
|
+
invalidating = false;
|
|
242
|
+
constructor(options) {
|
|
243
|
+
this.binary = options.binary ?? DEFAULT_CODEX_BINARY;
|
|
244
|
+
const baseArgs = options.args ?? (options.env.REPLICAS_CODEX_AUTH_METHOD === "foundry" ? [
|
|
245
|
+
...DEFAULT_CODEX_ARGS,
|
|
246
|
+
"-c",
|
|
247
|
+
`model=${JSON.stringify(options.env.CODEX_FOUNDRY_MODEL)}`,
|
|
248
|
+
"-c",
|
|
249
|
+
'model_provider="azure"',
|
|
250
|
+
"-c",
|
|
251
|
+
'model_providers.azure.name="Azure OpenAI"',
|
|
252
|
+
"-c",
|
|
253
|
+
`model_providers.azure.base_url=${JSON.stringify(options.env.CODEX_FOUNDRY_BASE_URL)}`,
|
|
254
|
+
"-c",
|
|
255
|
+
'model_providers.azure.env_key="AZURE_OPENAI_API_KEY"',
|
|
256
|
+
"-c",
|
|
257
|
+
'model_providers.azure.wire_api="responses"'
|
|
258
|
+
] : DEFAULT_CODEX_ARGS);
|
|
259
|
+
this.args = [...baseArgs, ...(options.configOverrides ?? []).flatMap((override) => ["-c", override])];
|
|
260
|
+
this.env = options.env;
|
|
261
|
+
this.cwd = options.cwd;
|
|
262
|
+
this.chatgptAuthTokens = options.chatgptAuthTokens;
|
|
263
|
+
this.refreshChatgptAuthTokens = options.refreshChatgptAuthTokens;
|
|
264
|
+
}
|
|
265
|
+
on(event, listener) {
|
|
266
|
+
this.emitter.on(event, listener);
|
|
267
|
+
}
|
|
268
|
+
async start() {
|
|
269
|
+
if (this.child && this.client) {
|
|
270
|
+
return { client: this.client };
|
|
271
|
+
}
|
|
272
|
+
this.shuttingDown = false;
|
|
273
|
+
this.invalidating = false;
|
|
274
|
+
await this.ensureMinCodexCliVersion();
|
|
275
|
+
const child = spawn(this.binary, this.args, {
|
|
276
|
+
cwd: this.cwd,
|
|
277
|
+
env: this.env,
|
|
278
|
+
stdio: ["pipe", "pipe", "pipe"]
|
|
279
|
+
});
|
|
280
|
+
this.child = child;
|
|
281
|
+
child.stderr.setEncoding("utf8");
|
|
282
|
+
child.stderr.on("data", (chunk) => {
|
|
283
|
+
for (const line of chunk.toString().split("\n")) {
|
|
284
|
+
if (line.trim().length > 0) {
|
|
285
|
+
console.error(`[codex-app-server] ${line}`);
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
});
|
|
289
|
+
child.on("exit", (code, signal) => {
|
|
290
|
+
this.client?.dispose();
|
|
291
|
+
this.client = null;
|
|
292
|
+
this.child = null;
|
|
293
|
+
if (!this.shuttingDown) {
|
|
294
|
+
if (!this.invalidating) {
|
|
295
|
+
console.warn(`[AppServerProcess] codex app-server exited unexpectedly code=${code ?? "null"} signal=${signal ?? "null"}`);
|
|
296
|
+
}
|
|
297
|
+
this.emitter.emit("exit", code, signal);
|
|
298
|
+
}
|
|
299
|
+
});
|
|
300
|
+
const client = new AspClient({ stdin: child.stdin, stdout: child.stdout });
|
|
301
|
+
this.client = client;
|
|
302
|
+
client.on("serverRequest", (serverRequest) => {
|
|
303
|
+
if (serverRequest.method !== "account/chatgptAuthTokens/refresh") return;
|
|
304
|
+
if (!this.refreshChatgptAuthTokens) {
|
|
305
|
+
client.reject(serverRequest.id, -32603, "Codex OAuth refresh is not configured");
|
|
306
|
+
return;
|
|
307
|
+
}
|
|
308
|
+
void this.refreshChatgptAuthTokens(serverRequest.params).then((tokens) => {
|
|
309
|
+
client.respond(serverRequest.id, tokens);
|
|
310
|
+
}).catch((error) => {
|
|
311
|
+
if (error instanceof CodexAspAuthMethodChangedError) {
|
|
312
|
+
this.invalidating = true;
|
|
313
|
+
client.dispose(error);
|
|
314
|
+
if (!child.killed) child.kill("SIGTERM");
|
|
315
|
+
return;
|
|
316
|
+
}
|
|
317
|
+
client.reject(
|
|
318
|
+
serverRequest.id,
|
|
319
|
+
-32603,
|
|
320
|
+
error instanceof Error ? error.message : "Failed to refresh Codex OAuth credentials"
|
|
321
|
+
);
|
|
322
|
+
});
|
|
323
|
+
});
|
|
324
|
+
let cleanupEarlyFailureHandlers = () => {
|
|
325
|
+
};
|
|
326
|
+
const earlyFailure = new Promise((_resolve, reject) => {
|
|
327
|
+
const onError = (error) => {
|
|
328
|
+
reject(error);
|
|
329
|
+
};
|
|
330
|
+
const onExit = (code, signal) => {
|
|
331
|
+
reject(new Error(`codex app-server exited before initialize completed code=${code ?? "null"} signal=${signal ?? "null"}`));
|
|
332
|
+
};
|
|
333
|
+
child.once("error", onError);
|
|
334
|
+
child.once("exit", onExit);
|
|
335
|
+
cleanupEarlyFailureHandlers = () => {
|
|
336
|
+
child.off("error", onError);
|
|
337
|
+
child.off("exit", onExit);
|
|
338
|
+
};
|
|
339
|
+
});
|
|
340
|
+
try {
|
|
341
|
+
const initializeParams = {
|
|
342
|
+
clientInfo: {
|
|
343
|
+
name: "replicas_engine",
|
|
344
|
+
title: "Replicas Engine",
|
|
345
|
+
version: ENGINE_PACKAGE_VERSION
|
|
346
|
+
},
|
|
347
|
+
capabilities: {
|
|
348
|
+
experimentalApi: true,
|
|
349
|
+
requestAttestation: false,
|
|
350
|
+
optOutNotificationMethods: null
|
|
351
|
+
}
|
|
352
|
+
};
|
|
353
|
+
await Promise.race([
|
|
354
|
+
client.request(INITIALIZE_METHOD, initializeParams),
|
|
355
|
+
earlyFailure
|
|
356
|
+
]);
|
|
357
|
+
cleanupEarlyFailureHandlers();
|
|
358
|
+
client.notify(INITIALIZED_NOTIFICATION);
|
|
359
|
+
await this.loginWithConfiguredCredentials(client);
|
|
360
|
+
return { client };
|
|
361
|
+
} catch (error) {
|
|
362
|
+
cleanupEarlyFailureHandlers();
|
|
363
|
+
client.dispose();
|
|
364
|
+
await this.killAfterFailedStart();
|
|
365
|
+
throw error;
|
|
366
|
+
}
|
|
367
|
+
}
|
|
368
|
+
async stop() {
|
|
369
|
+
const child = this.child;
|
|
370
|
+
this.shuttingDown = true;
|
|
371
|
+
this.client?.dispose(new Error("ASP process stopped"));
|
|
372
|
+
this.client = null;
|
|
373
|
+
this.child = null;
|
|
374
|
+
if (!child || child.killed) {
|
|
375
|
+
return;
|
|
376
|
+
}
|
|
377
|
+
await new Promise((resolve) => {
|
|
378
|
+
const timer = setTimeout(() => {
|
|
379
|
+
child.kill("SIGKILL");
|
|
380
|
+
}, 2e3);
|
|
381
|
+
child.once("exit", () => {
|
|
382
|
+
clearTimeout(timer);
|
|
383
|
+
resolve();
|
|
384
|
+
});
|
|
385
|
+
child.kill("SIGTERM");
|
|
386
|
+
});
|
|
387
|
+
}
|
|
388
|
+
ensureMinCodexCliVersion() {
|
|
389
|
+
codexCliVersionEnsured ??= this.runEnsureMinCodexCliVersion().catch((error) => {
|
|
390
|
+
codexCliVersionEnsured = null;
|
|
391
|
+
console.warn("[AppServerProcess] Failed to ensure minimum codex CLI version, continuing with installed binary:", error);
|
|
392
|
+
});
|
|
393
|
+
return codexCliVersionEnsured;
|
|
394
|
+
}
|
|
395
|
+
async runEnsureMinCodexCliVersion() {
|
|
396
|
+
const { stdout } = await execFileAsync(this.binary, ["--version"], { env: this.env });
|
|
397
|
+
const version = stdout.match(/(\d+\.\d+\.\d+)/)?.[1];
|
|
398
|
+
if (version && !isVersionBelow(version, MIN_CODEX_CLI_VERSION)) {
|
|
399
|
+
return;
|
|
400
|
+
}
|
|
401
|
+
console.warn(`[AppServerProcess] codex CLI ${version ?? "unknown"} is below ${MIN_CODEX_CLI_VERSION}; upgrading @openai/codex`);
|
|
402
|
+
await execFileAsync(
|
|
403
|
+
"npm",
|
|
404
|
+
["install", "-g", "--no-audit", "--no-fund", `@openai/codex@${MIN_CODEX_CLI_VERSION}`],
|
|
405
|
+
{ env: this.env, timeout: CODEX_UPGRADE_TIMEOUT_MS }
|
|
406
|
+
);
|
|
407
|
+
console.warn(`[AppServerProcess] upgraded codex CLI to ${MIN_CODEX_CLI_VERSION}`);
|
|
408
|
+
}
|
|
409
|
+
async loginWithConfiguredCredentials(client) {
|
|
410
|
+
if (this.env.REPLICAS_CODEX_AUTH_METHOD === "oauth") {
|
|
411
|
+
if (!this.chatgptAuthTokens) {
|
|
412
|
+
throw new Error("Codex OAuth credentials were not prepared before app-server startup");
|
|
413
|
+
}
|
|
414
|
+
const params2 = {
|
|
415
|
+
type: "chatgptAuthTokens",
|
|
416
|
+
...this.chatgptAuthTokens
|
|
417
|
+
};
|
|
418
|
+
await client.request(ACCOUNT_LOGIN_START_METHOD, params2);
|
|
419
|
+
return;
|
|
420
|
+
}
|
|
421
|
+
if (this.env.REPLICAS_CODEX_AUTH_METHOD !== "api_key" || !this.env.OPENAI_API_KEY) return;
|
|
422
|
+
const params = {
|
|
423
|
+
type: "apiKey",
|
|
424
|
+
apiKey: this.env.OPENAI_API_KEY
|
|
425
|
+
};
|
|
426
|
+
await client.request(ACCOUNT_LOGIN_START_METHOD, params);
|
|
427
|
+
}
|
|
428
|
+
async killAfterFailedStart() {
|
|
429
|
+
const child = this.child;
|
|
430
|
+
this.child = null;
|
|
431
|
+
this.client = null;
|
|
432
|
+
if (!child || child.killed) {
|
|
433
|
+
return;
|
|
434
|
+
}
|
|
435
|
+
this.shuttingDown = true;
|
|
436
|
+
child.kill("SIGKILL");
|
|
437
|
+
await new Promise((resolve) => {
|
|
438
|
+
child.once("exit", () => resolve());
|
|
439
|
+
});
|
|
440
|
+
}
|
|
441
|
+
};
|
|
442
|
+
|
|
443
|
+
export {
|
|
444
|
+
execAsync,
|
|
445
|
+
execFileAsync,
|
|
446
|
+
SUBPROCESS_MAX_BUFFER,
|
|
447
|
+
AppServerProcess
|
|
448
|
+
};
|