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 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
- - file: `~/.codex/auth.json`
84
- - format includes `tokens.id_token`, `tokens.access_token`, `tokens.refresh_token`, `tokens.account_id`
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` (Codex auth)
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,10 @@
1
+ #!/usr/bin/env node
2
+ import {
3
+ AppServerProcess
4
+ } from "./chunk-6GXXZQID.js";
5
+ import "./chunk-6WY7NPCL.js";
6
+ import "./chunk-4FTBKGMO.js";
7
+ import "./chunk-TMERKNQV.js";
8
+ export {
9
+ AppServerProcess
10
+ };
@@ -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
+ };