@jskit-ai/assistant-core 0.2.7 → 0.2.9
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/examples/conversation/package.json +5 -5
- package/package.json +18 -7
- package/src/client/browser.js +21 -0
- package/src/client/conversation/AssistantAttachmentQueue.vue +25 -108
- package/src/client/conversation/AssistantComposerActions.vue +1 -1
- package/src/client/conversation/AssistantConversationElement.vue +14 -52
- package/src/client/conversation/AssistantGoalControl.vue +13 -10
- package/src/client/conversation/AssistantPromptInput.vue +6 -91
- package/src/client/conversation/AssistantTranscript.css +505 -0
- package/src/client/conversation/AssistantTranscript.vue +24 -1197
- package/src/client/conversation/attachmentFiles.js +158 -0
- package/src/client/conversation/attachmentQueueItems.js +121 -0
- package/src/client/conversation/attachmentUploadRows.js +265 -0
- package/src/client/conversation/conversationDisplay.js +54 -0
- package/src/client/conversation/messageDelivery.js +13 -9
- package/src/client/conversation/retainedConversation.js +14 -3
- package/src/client/conversation/submitText.js +13 -2
- package/src/client/conversation/transcriptEntries.js +225 -0
- package/src/client/conversation/useAssistantAttachments.js +86 -388
- package/src/client/conversation/useAssistantQuestions.js +16 -3
- package/src/client/conversation/useAssistantSuggestions.js +16 -2
- package/src/client/conversation/useScrollToBottom.js +17 -19
- package/src/client/conversation/useTextareaAutoGrow.js +115 -0
- package/src/client/conversation/useTranscriptScrollFollow.js +525 -0
- package/src/client/lib/assistantApi.js +192 -161
- package/src/client/types.js +110 -0
- package/src/server/conversation/attachments.js +21 -6
- package/src/server/conversation/boundedToolLoop.js +1 -0
- package/src/server/conversation/claudeClient.js +71 -20
- package/src/server/conversation/claudeCommandRun.js +191 -0
- package/src/server/conversation/claudeHistory.js +7 -0
- package/src/server/conversation/claudeNativeTurn.js +708 -0
- package/src/server/conversation/claudeProcess.js +186 -112
- package/src/server/conversation/claudeProcessStart.js +214 -0
- package/src/server/conversation/claudePromptDelivery.js +183 -0
- package/src/server/conversation/claudeSessionCleanup.js +248 -0
- package/src/server/conversation/claudeTools.js +15 -3
- package/src/server/conversation/claudeTurn.js +212 -1388
- package/src/server/conversation/codexAccountReader.js +168 -0
- package/src/server/conversation/codexAuthPreflight.js +5 -4
- package/src/server/conversation/codexClient.js +18 -6
- package/src/server/conversation/codexConfiguration.js +8 -844
- package/src/server/conversation/codexConversationStops.js +377 -0
- package/src/server/conversation/codexConversationStore.js +119 -85
- package/src/server/conversation/codexConversationTurns.js +378 -0
- package/src/server/conversation/codexConversations.js +134 -866
- package/src/server/conversation/codexDetachedConversation.js +203 -0
- package/src/server/conversation/codexDetachedTurn.js +145 -104
- package/src/server/conversation/codexEvents.js +5 -200
- package/src/server/conversation/codexGoalCommands.js +14 -4
- package/src/server/conversation/codexHelperExecution.js +57 -57
- package/src/server/conversation/codexHelperThreadLedger.js +30 -288
- package/src/server/conversation/codexHelperThreadRecord.js +304 -0
- package/src/server/conversation/codexHelperThreadRecovery.js +54 -76
- package/src/server/conversation/codexHelperThreads.js +94 -93
- package/src/server/conversation/codexHistoryAdapter.js +13 -8
- package/src/server/conversation/codexIsolation.js +672 -0
- package/src/server/conversation/codexLiveItems.js +379 -0
- package/src/server/conversation/codexMessageCommands.js +68 -286
- package/src/server/conversation/codexMessageDelivery.js +46 -37
- package/src/server/conversation/codexModelCatalog.js +86 -7
- package/src/server/conversation/codexNotificationObserver.js +81 -88
- package/src/server/conversation/codexNotificationQueue.js +21 -10
- package/src/server/conversation/codexObservationControl.js +105 -89
- package/src/server/conversation/codexOutput.js +84 -419
- package/src/server/conversation/codexProcess.js +29 -1053
- package/src/server/conversation/codexProcessLeader.js +1 -0
- package/src/server/conversation/codexPromptDispatch.js +241 -0
- package/src/server/conversation/codexProvider.js +147 -1807
- package/src/server/conversation/codexProviderAuth.js +348 -0
- package/src/server/conversation/codexProviderInvalidation.js +371 -0
- package/src/server/conversation/codexProviderInventories.js +437 -0
- package/src/server/conversation/codexProviderOwner.js +149 -957
- package/src/server/conversation/codexProviderRelease.js +465 -0
- package/src/server/conversation/codexProviderSelection.js +26 -15
- package/src/server/conversation/codexProviderThreads.js +698 -0
- package/src/server/conversation/codexProviderTurns.js +453 -0
- package/src/server/conversation/codexReasoning.js +32 -23
- package/src/server/conversation/codexRenewalCommands.js +18 -6
- package/src/server/conversation/codexRenewalThreads.js +7 -22
- package/src/server/conversation/codexResultSettlement.js +70 -88
- package/src/server/conversation/codexRunJournal.js +91 -97
- package/src/server/conversation/codexRuntime.js +426 -0
- package/src/server/conversation/codexRuntimeInventory.js +113 -0
- package/src/server/conversation/codexRuntimeLifecycle.js +39 -13
- package/src/server/conversation/codexRuntimeStart.js +431 -0
- package/src/server/conversation/codexRuntimeStop.js +324 -0
- package/src/server/conversation/codexSessionCleanup.js +25 -10
- package/src/server/conversation/codexSessionReconciliation.js +21 -7
- package/src/server/conversation/codexThreadReadiness.js +49 -52
- package/src/server/conversation/codexThreadSnapshot.js +210 -0
- package/src/server/conversation/codexThreadStatus.js +48 -62
- package/src/server/conversation/codexTurn.js +171 -55
- package/src/server/conversation/codexTurnActivity.js +210 -0
- package/src/server/conversation/codexTurnControl.js +44 -48
- package/src/server/conversation/codexTurnEndings.js +610 -0
- package/src/server/conversation/codexTurnRecovery.js +97 -848
- package/src/server/conversation/codexTurnState.js +138 -111
- package/src/server/conversation/completedResponses.js +227 -0
- package/src/server/conversation/configuration.js +2 -2
- package/src/server/conversation/continuity.js +43 -6
- package/src/server/conversation/conversationDelivery.js +394 -0
- package/src/server/conversation/conversationEntries.js +365 -0
- package/src/server/conversation/conversationEvents.js +192 -0
- package/src/server/conversation/conversationGoals.js +187 -0
- package/src/server/conversation/conversationHandles.js +237 -0
- package/src/server/conversation/conversationReads.js +206 -0
- package/src/server/conversation/conversationRefusals.js +58 -0
- package/src/server/conversation/conversationReplacement.js +254 -0
- package/src/server/conversation/conversationSending.js +273 -0
- package/src/server/conversation/conversationStopping.js +121 -0
- package/src/server/conversation/conversationTurnDelivery.js +413 -0
- package/src/server/conversation/conversationTurns.js +511 -0
- package/src/server/conversation/deliveryTiming.js +25 -3
- package/src/server/conversation/fileStorage.js +11 -6
- package/src/server/conversation/hookBridge.js +14 -3
- package/src/server/conversation/index.js +1 -1
- package/src/server/conversation/localExecution.js +82 -625
- package/src/server/conversation/nativeCommandCallbacks.js +10 -0
- package/src/server/conversation/nativeConversationLifecycle.js +379 -0
- package/src/server/conversation/nativeHistoryExport.js +27 -18
- package/src/server/conversation/normalize.js +9 -0
- package/src/server/conversation/openCodeClient.js +295 -366
- package/src/server/conversation/openCodeNativeHistory.js +141 -0
- package/src/server/conversation/openCodeNativeSession.js +79 -0
- package/src/server/conversation/openCodeProcess.js +109 -53
- package/src/server/conversation/openCodeProcessOwner.js +565 -0
- package/src/server/conversation/openCodeRuntime.js +111 -1814
- package/src/server/conversation/openCodeSessionTurns.js +547 -0
- package/src/server/conversation/openCodeTemporaryConversations.js +394 -0
- package/src/server/conversation/openCodeTools.js +28 -14
- package/src/server/conversation/openCodeTurn.js +80 -74
- package/src/server/conversation/openCodeTurnMonitor.js +388 -0
- package/src/server/conversation/processIdentity.js +582 -0
- package/src/server/conversation/provider.js +21 -6
- package/src/server/conversation/providers/api.js +268 -107
- package/src/server/conversation/providers/claude.js +29 -6
- package/src/server/conversation/providers/claudeDriver.js +325 -250
- package/src/server/conversation/providers/codex.js +69 -33
- package/src/server/conversation/providers/codexDriver.js +503 -1081
- package/src/server/conversation/providers/codexDriverConnection.js +331 -0
- package/src/server/conversation/providers/codexDriverTurns.js +464 -0
- package/src/server/conversation/providers/opencode.js +30 -4
- package/src/server/conversation/providers/opencodeDriver.js +321 -659
- package/src/server/conversation/providers/opencodeStandalone.js +475 -0
- package/src/server/conversation/providers/scoped.js +64 -19
- package/src/server/conversation/runtime.js +171 -2863
- package/src/server/conversation/storage.js +91 -50
- package/src/server/conversation/streams.js +15 -2
- package/src/server/conversation/structuredOutput.js +7 -11
- package/src/server/conversation/systemPrompt.js +13 -1
- package/src/server/conversation/tools.js +47 -15
- package/src/server/conversation/transcript.js +49 -20
- package/src/server/hosts/claudeTurn.js +2 -4
- package/src/server/hosts/codexConfiguration.js +6 -8
- package/src/server/hosts/codexEvents.js +4 -2
- package/src/server/hosts/codexProcess.js +1 -1
- package/src/server/hosts/codexProvider.js +11 -9
- package/src/server/hosts/codexTurn.js +3 -3
- package/src/server/hosts/openCodeClient.js +4 -2
- package/src/server/index.js +0 -2
- package/src/server/lib/aiConnectionClient.js +35 -25
- package/src/server/lib/ndjson.js +1 -0
- package/src/server/lib/providers/anthropicClient.js +15 -6
- package/src/server/lib/providers/common.js +10 -6
- package/src/server/lib/providers/openAiCompatibleClient.js +40 -23
- package/src/server/lib/serviceToolCatalog.js +29 -283
- package/src/server/lib/serviceToolEntries.js +270 -0
- package/src/server/types.js +78 -0
- package/src/shared/assistantResource.js +0 -23
- package/src/shared/conversation/answerChoiceSugar.js +2 -14
- package/src/shared/conversation/messageInputSugar.js +18 -0
- package/src/shared/conversation/numberedQuestionSugar.js +6 -16
- package/src/shared/conversation/pagination.js +1 -1
- package/src/shared/conversation/questions.js +1 -0
- package/src/shared/conversation/streaming.js +1 -0
- package/src/shared/index.js +0 -1
- package/src/testing/conversationStorageContract.js +20 -0
- package/test/aiConnectionClient.test.js +42 -1
- package/test/apiConversationDriver.test.js +217 -38
- package/test/assistantBoundedToolLoop.test.js +1 -1
- package/test/claudeConversationSession.test.js +15 -11
- package/test/claudeInstructionResume.test.js +1 -1
- package/test/claudeProcess.test.js +9 -9
- package/test/claudeTurn.test.js +21 -8
- package/test/codexAccountChange.test.js +5 -3
- package/test/codexConversationSession.test.js +22 -21
- package/test/codexEvents.test.js +3 -1
- package/test/codexHelperDeletion.test.js +3 -7
- package/test/codexIsolation.test.js +6 -6
- package/test/codexModelCatalog.test.js +6 -7
- package/test/codexProcess.test.js +3 -3
- package/test/codexThreadSelection.test.js +4 -1
- package/test/codexTurn.test.js +29 -20
- package/test/conversationApiFacilities.test.js +11 -9
- package/test/conversationAttachments.test.js +3 -3
- package/test/conversationConfiguration.test.js +4 -4
- package/test/conversationNativeStorage.test.js +11 -10
- package/test/conversationReplacement.test.js +13 -13
- package/test/conversationRuntime.test.js +9 -5
- package/test/conversationSession.test.js +78 -22
- package/test/conversationTools.test.js +47 -26
- package/test/fileConversationStorage.test.js +10 -3
- package/test/fixtures/codexHelperThreadLedger.js +3 -3
- package/test/frameworkActions.test.js +33 -18
- package/test/localConversationExecution.test.js +4 -4
- package/test/localExecution.test.js +1 -1
- package/test/openCodeConnectionChange.test.js +3 -2
- package/test/openCodeRuntime.test.js +8 -7
- package/test/openCodeTools.test.js +1 -1
- package/test/opencodeConversationSession.test.js +4 -3
- package/test/serviceToolCatalog.test.js +3 -8
- package/src/server/lib/assistantCompletion.js +0 -190
- package/src/server/lib/assistantToolLoop.js +0 -259
|
@@ -1,36 +1,33 @@
|
|
|
1
|
-
import { fileURLToPath } from "node:url";
|
|
2
|
-
import WebSocket from "ws";
|
|
3
1
|
import { spawn } from "node:child_process";
|
|
4
2
|
import { chmod, mkdir, readFile, readdir, rename, rm, stat, writeFile } from "node:fs/promises";
|
|
5
3
|
import { createHash, randomUUID } from "node:crypto";
|
|
6
4
|
import os from "node:os";
|
|
7
5
|
import path from "node:path";
|
|
8
6
|
import { setTimeout as delay } from "node:timers/promises";
|
|
9
|
-
import {
|
|
10
|
-
import { createConversationProcessIdentity } from "./localExecution.js";
|
|
7
|
+
import { createConversationProcessIdentity } from "./processIdentity.js";
|
|
11
8
|
import { isRecord, normalizePositiveInteger } from "@jskit-ai/kernel/shared/support/normalize";
|
|
12
9
|
import { normalizeNullableText } from "./normalize.js";
|
|
13
|
-
import {
|
|
14
|
-
import { codexDisabledProcessFeatureArguments } from "./codexHomeStorage.js";
|
|
10
|
+
import { prepareCodexModelCatalog } from "./codexConfiguration.js";
|
|
15
11
|
import { startCodexHistoryAdapter } from "./codexHistoryAdapter.js";
|
|
16
12
|
|
|
17
13
|
const CODEX_APP_SERVER_METADATA_FILE = "runtime.json";
|
|
18
14
|
const CODEX_APP_SERVER_LOCK_DIR = "runtime.lock";
|
|
19
15
|
const CODEX_APP_SERVER_LOCK_TIMEOUT_MS = 10000;
|
|
20
16
|
const CODEX_APP_SERVER_LOCK_STALE_MS = 120000;
|
|
21
|
-
const CODEX_APP_SERVER_EXECUTION_MODES = Object.freeze({ HELPER: "helper", INTERACTIVE: "interactive" });
|
|
17
|
+
export const CODEX_APP_SERVER_EXECUTION_MODES = Object.freeze({ HELPER: "helper", INTERACTIVE: "interactive" });
|
|
22
18
|
const runtimeProcessIdentity = createConversationProcessIdentity();
|
|
23
19
|
|
|
24
|
-
// Run in the host-owned execution's leader, independently of subscribers.
|
|
20
|
+
// Run in the host-owned execution's leader, independently of subscribers. `env` is the leader's environment.
|
|
25
21
|
export async function runCodexAppServerProcess({
|
|
26
|
-
runtimeDir, command, args = [], runtimeToken: token, bundled = false, additionalModels = []
|
|
22
|
+
runtimeDir, command, args = [], runtimeToken: token, bundled = false, additionalModels = [], env
|
|
27
23
|
}) {
|
|
24
|
+
if (!env) throw new TypeError("runCodexAppServerProcess requires the application's environment, such as process.env.");
|
|
28
25
|
const descriptor = path.join(runtimeDir, "history-adapter.json");
|
|
29
26
|
const startup = new AbortController();
|
|
30
27
|
const abortStartup = () => startup.abort();
|
|
31
28
|
process.once("SIGTERM", abortStartup);
|
|
32
29
|
process.once("SIGINT", abortStartup);
|
|
33
|
-
const codexHome =
|
|
30
|
+
const codexHome = env.CODEX_HOME || path.join(os.homedir(), ".codex");
|
|
34
31
|
const catalogPath = await prepareCodexModelCatalog({
|
|
35
32
|
command,
|
|
36
33
|
runtimeDir,
|
|
@@ -237,7 +234,7 @@ export async function acquireCodexRuntimeLock(runtimeDir = "", {
|
|
|
237
234
|
|
|
238
235
|
export const CODEX_APP_SERVER_METADATA_SCHEMA_VERSION = 19;
|
|
239
236
|
|
|
240
|
-
const CODEX_APP_SERVER_PROVIDER_ID = "codex_app_server";
|
|
237
|
+
export const CODEX_APP_SERVER_PROVIDER_ID = "codex_app_server";
|
|
241
238
|
|
|
242
239
|
export const CODEX_APP_SERVER_TRANSPORT = Object.freeze({
|
|
243
240
|
UNIX: "unix"
|
|
@@ -249,9 +246,9 @@ const CODEX_APP_SERVER_LOG_FILE = "app-server.log";
|
|
|
249
246
|
|
|
250
247
|
const CODEX_APP_SERVER_SOCKET_FILE = "app-server.sock";
|
|
251
248
|
|
|
252
|
-
const CODEX_APP_SERVER_READY_TIMEOUT_MS = 60000;
|
|
249
|
+
export const CODEX_APP_SERVER_READY_TIMEOUT_MS = 60000;
|
|
253
250
|
|
|
254
|
-
const CODEX_APP_SERVER_LIVENESS_TIMEOUT_MS = 2000;
|
|
251
|
+
export const CODEX_APP_SERVER_LIVENESS_TIMEOUT_MS = 2000;
|
|
255
252
|
|
|
256
253
|
export const CODEX_APP_SERVER_DESKTOP_BUS_ENV_NAMES = new Set([
|
|
257
254
|
"DBUS_SESSION_BUS_ADDRESS",
|
|
@@ -263,16 +260,16 @@ const CODEX_APP_SERVER_HELPER_HOME_DIR = "codex-home";
|
|
|
263
260
|
|
|
264
261
|
const CODEX_APP_SERVER_HELPER_WORKSPACE_DIR = "workspace";
|
|
265
262
|
|
|
266
|
-
const CODEX_APP_SERVER_UNIX_SOCKET_PATH_MAX_BYTES = process.platform === "linux" ? 107 : 103;
|
|
263
|
+
export const CODEX_APP_SERVER_UNIX_SOCKET_PATH_MAX_BYTES = process.platform === "linux" ? 107 : 103;
|
|
267
264
|
|
|
268
|
-
const CODEX_APP_SERVER_ENDPOINT_STATUS = Object.freeze({
|
|
265
|
+
export const CODEX_APP_SERVER_ENDPOINT_STATUS = Object.freeze({
|
|
269
266
|
MISSING: "missing",
|
|
270
267
|
RESPONSIVE: "responsive",
|
|
271
268
|
TIMEOUT: "timeout",
|
|
272
269
|
UNREACHABLE: "unreachable"
|
|
273
270
|
});
|
|
274
271
|
|
|
275
|
-
const CODEX_APP_SERVER_RUNTIME_STATUS = Object.freeze({
|
|
272
|
+
export const CODEX_APP_SERVER_RUNTIME_STATUS = Object.freeze({
|
|
276
273
|
EXITED: "exited",
|
|
277
274
|
INCOMPATIBLE: "incompatible",
|
|
278
275
|
LIVE: "live",
|
|
@@ -285,7 +282,7 @@ export const CODEX_APP_SERVER_PROCESS_STATE = Object.freeze({
|
|
|
285
282
|
STOPPED: "stopped"
|
|
286
283
|
});
|
|
287
284
|
|
|
288
|
-
const CODEX_APP_SERVER_PROCESS_IDENTITY_STATUS = Object.freeze({
|
|
285
|
+
export const CODEX_APP_SERVER_PROCESS_IDENTITY_STATUS = Object.freeze({
|
|
289
286
|
ABSENT: "absent",
|
|
290
287
|
AMBIGUOUS: "ambiguous",
|
|
291
288
|
EXACT: "exact",
|
|
@@ -293,7 +290,7 @@ const CODEX_APP_SERVER_PROCESS_IDENTITY_STATUS = Object.freeze({
|
|
|
293
290
|
MISMATCH: "mismatch"
|
|
294
291
|
});
|
|
295
292
|
|
|
296
|
-
function stableHash(value) {
|
|
293
|
+
export function stableHash(value) {
|
|
297
294
|
return createHash("sha256")
|
|
298
295
|
.update(String(value || ""))
|
|
299
296
|
.digest("hex")
|
|
@@ -329,7 +326,7 @@ export function codexAppServerHelperWorkspaceDir(runtimeDir = "") {
|
|
|
329
326
|
return path.join(runtimeDir, CODEX_APP_SERVER_HELPER_WORKSPACE_DIR);
|
|
330
327
|
}
|
|
331
328
|
|
|
332
|
-
function codexAppServerHelperCommandBaseEnv(env
|
|
329
|
+
export function codexAppServerHelperCommandBaseEnv(env, codexHome = "") {
|
|
333
330
|
const source = isRecord(env) ? env : {};
|
|
334
331
|
const cleared = Object.fromEntries(Object.keys({
|
|
335
332
|
...process.env,
|
|
@@ -355,27 +352,27 @@ function codexAppServerHelperCommandBaseEnv(env = process.env, codexHome = "") {
|
|
|
355
352
|
};
|
|
356
353
|
}
|
|
357
354
|
|
|
358
|
-
function codexAppServerLogPath(runtimeDir = "") {
|
|
355
|
+
export function codexAppServerLogPath(runtimeDir = "") {
|
|
359
356
|
return path.join(runtimeDir, CODEX_APP_SERVER_LOG_FILE);
|
|
360
357
|
}
|
|
361
358
|
|
|
362
|
-
function codexAppServerSocketPath(runtimeDir = "") {
|
|
359
|
+
export function codexAppServerSocketPath(runtimeDir = "") {
|
|
363
360
|
return path.join(runtimeDir, CODEX_APP_SERVER_SOCKET_FILE);
|
|
364
361
|
}
|
|
365
362
|
|
|
366
|
-
function codexAppServerSocketPathBytes(socketPath = "") {
|
|
363
|
+
export function codexAppServerSocketPathBytes(socketPath = "") {
|
|
367
364
|
return Buffer.byteLength(String(socketPath || ""), "utf8");
|
|
368
365
|
}
|
|
369
366
|
|
|
370
|
-
function codexAppServerSocketPathTooLong(socketPath = "") {
|
|
367
|
+
export function codexAppServerSocketPathTooLong(socketPath = "") {
|
|
371
368
|
return codexAppServerSocketPathBytes(socketPath) > CODEX_APP_SERVER_UNIX_SOCKET_PATH_MAX_BYTES;
|
|
372
369
|
}
|
|
373
370
|
|
|
374
|
-
function codexAppServerUnixEndpoint(socketPath = "") {
|
|
371
|
+
export function codexAppServerUnixEndpoint(socketPath = "") {
|
|
375
372
|
return `unix://${socketPath}`;
|
|
376
373
|
}
|
|
377
374
|
|
|
378
|
-
function codexAppServerProjectTrustOverride(workdir = "") {
|
|
375
|
+
export function codexAppServerProjectTrustOverride(workdir = "") {
|
|
379
376
|
const normalizedWorkdir = normalizeNullableText(workdir);
|
|
380
377
|
if (!normalizedWorkdir) {
|
|
381
378
|
return "";
|
|
@@ -400,7 +397,7 @@ export function codexAppServerProcessCwd({
|
|
|
400
397
|
return normalizeNullableText(runtimeDir) ? path.resolve(runtimeDir) : "";
|
|
401
398
|
}
|
|
402
399
|
|
|
403
|
-
function codexAppServerRuntimeDirIsManaged(runtimeDir = "") {
|
|
400
|
+
export function codexAppServerRuntimeDirIsManaged(runtimeDir = "") {
|
|
404
401
|
const normalizedRuntimeDir = normalizeNullableText(runtimeDir);
|
|
405
402
|
if (!normalizedRuntimeDir) {
|
|
406
403
|
return false;
|
|
@@ -422,7 +419,7 @@ export async function removeCodexAppServerRuntimeDir(runtimeDir = "") {
|
|
|
422
419
|
return true;
|
|
423
420
|
}
|
|
424
421
|
|
|
425
|
-
async function removeCodexAppServerMetadataTemps(runtimeDir = "") {
|
|
422
|
+
export async function removeCodexAppServerMetadataTemps(runtimeDir = "") {
|
|
426
423
|
let names = [];
|
|
427
424
|
try {
|
|
428
425
|
names = await readdir(runtimeDir);
|
|
@@ -434,7 +431,7 @@ async function removeCodexAppServerMetadataTemps(runtimeDir = "") {
|
|
|
434
431
|
.map((name) => rm(path.join(runtimeDir, name), { force: true })));
|
|
435
432
|
}
|
|
436
433
|
|
|
437
|
-
function codexAppServerRuntimeCleanupCanSkip(error) {
|
|
434
|
+
export function codexAppServerRuntimeCleanupCanSkip(error) {
|
|
438
435
|
return ["EACCES", "EPERM", "ENOENT"].includes(String(error?.code || ""));
|
|
439
436
|
}
|
|
440
437
|
|
|
@@ -470,7 +467,7 @@ export function normalizeCodexAppServerTerminalEnv(terminalEnv = {}) {
|
|
|
470
467
|
}
|
|
471
468
|
|
|
472
469
|
export function codexAppServerCommandBaseEnv({
|
|
473
|
-
env
|
|
470
|
+
env,
|
|
474
471
|
terminalEnv = {}
|
|
475
472
|
} = {}) {
|
|
476
473
|
const baseEnv = {
|
|
@@ -499,7 +496,7 @@ export function codexAppServerTerminalEnvHash(terminalEnv = {}) {
|
|
|
499
496
|
.sort(([left], [right]) => left.localeCompare(right))));
|
|
500
497
|
}
|
|
501
498
|
|
|
502
|
-
function codexHistoryAdapterUrlIsValid(baseUrl, token) {
|
|
499
|
+
export function codexHistoryAdapterUrlIsValid(baseUrl, token) {
|
|
503
500
|
// Only this adapter URL shape serves thread-bound compaction recovery. Any
|
|
504
501
|
// other shape keeps its process identity readable but is retired through
|
|
505
502
|
// normal owned cleanup.
|
|
@@ -507,14 +504,14 @@ function codexHistoryAdapterUrlIsValid(baseUrl, token) {
|
|
|
507
504
|
return Boolean(match && Number(match[1]) <= 65535 && baseUrl.endsWith(`/${token}/v2`));
|
|
508
505
|
}
|
|
509
506
|
|
|
510
|
-
function codexAppServerLivenessTimeoutMs(options = {}) {
|
|
507
|
+
export function codexAppServerLivenessTimeoutMs(options = {}) {
|
|
511
508
|
return normalizePositiveInteger(
|
|
512
509
|
options.livenessTimeoutMs,
|
|
513
510
|
{ fallback: normalizePositiveInteger(options.timeoutMs, { fallback: CODEX_APP_SERVER_LIVENESS_TIMEOUT_MS }) }
|
|
514
511
|
);
|
|
515
512
|
}
|
|
516
513
|
|
|
517
|
-
function codexAppServerProcessCommandHash({
|
|
514
|
+
export function codexAppServerProcessCommandHash({
|
|
518
515
|
codexArgs = [],
|
|
519
516
|
codexCommand = "",
|
|
520
517
|
executionMode = "",
|
|
@@ -530,11 +527,6 @@ function codexAppServerProcessCommandHash({
|
|
|
530
527
|
}));
|
|
531
528
|
}
|
|
532
529
|
|
|
533
|
-
const codexAppServerRuntimePreparations = new Map();
|
|
534
|
-
|
|
535
|
-
/** The production shared runtime: one process owner per stable runtime directory.
|
|
536
|
-
* Execution and credential facilities retain host policy; threads do not own this process.
|
|
537
|
-
*/
|
|
538
530
|
/** Default environment names the runtime and its process leader share. */
|
|
539
531
|
export const CODEX_APP_SERVER_DEFAULT_ENVIRONMENT = Object.freeze({
|
|
540
532
|
runtimeToken: "JSKIT_EXECUTION_RUNTIME_TOKEN",
|
|
@@ -547,1019 +539,3 @@ export function codexCommandHookDefinition(command) {
|
|
|
547
539
|
return [{ matcher: "^Bash$", hooks: [{ type: "command", command, timeout: 30 }] }];
|
|
548
540
|
}
|
|
549
541
|
|
|
550
|
-
// Inline TOML for `-c` overrides: bare keys, JSON-quoted strings.
|
|
551
|
-
function tomlInlineValue(value) {
|
|
552
|
-
if (Array.isArray(value)) {
|
|
553
|
-
return `[${value.map(tomlInlineValue).join(",")}]`;
|
|
554
|
-
}
|
|
555
|
-
if (isRecord(value)) {
|
|
556
|
-
return `{${Object.entries(value).map(([key, item]) => `${key}=${tomlInlineValue(item)}`).join(",")}}`;
|
|
557
|
-
}
|
|
558
|
-
return JSON.stringify(value);
|
|
559
|
-
}
|
|
560
|
-
|
|
561
|
-
export function createCodexAppServerRuntime({ execution, credentials, configuration = {} }) {
|
|
562
|
-
const {
|
|
563
|
-
clientInfo = { name: "jskit", title: "JSKIT", version: "0.1.0" },
|
|
564
|
-
commandHookCommand,
|
|
565
|
-
errorPrefix = "",
|
|
566
|
-
modelCatalogEnvironmentName = CODEX_APP_SERVER_DEFAULT_ENVIRONMENT.modelCatalogSource,
|
|
567
|
-
processLabel = "jskit-codex-app-server",
|
|
568
|
-
processPath = fileURLToPath(new URL("./codexProcessLeader.js", import.meta.url)),
|
|
569
|
-
runtimeTokenEnvironmentName = CODEX_APP_SERVER_DEFAULT_ENVIRONMENT.runtimeToken,
|
|
570
|
-
commandHashEnvironmentName = CODEX_APP_SERVER_DEFAULT_ENVIRONMENT.commandHash,
|
|
571
|
-
socketPathHelp = "Configure a shorter host runtime directory.",
|
|
572
|
-
umask = "0007"
|
|
573
|
-
} = configuration;
|
|
574
|
-
const CODEX_APP_SERVER_PROCESS_PATH = processPath;
|
|
575
|
-
const CODEX_APP_SERVER_PROCESS_RUNTIME_TOKEN_ENV = runtimeTokenEnvironmentName;
|
|
576
|
-
const CODEX_APP_SERVER_PROCESS_COMMAND_HASH_ENV = commandHashEnvironmentName;
|
|
577
|
-
const codexProcessIdentity = createConversationProcessIdentity({
|
|
578
|
-
runtimeTokenEnvironmentName, commandHashEnvironmentName,
|
|
579
|
-
label: "Codex app-server",
|
|
580
|
-
unavailableCode: `${errorPrefix}codex_app_server_process_identity_unavailable`
|
|
581
|
-
});
|
|
582
|
-
const codexAppServerProcessIdentityIsWellFormed = codexProcessIdentity.isValid;
|
|
583
|
-
const captureCodexAppServerProcessIdentity = codexProcessIdentity.capture;
|
|
584
|
-
const currentCodexAccountIdentitySignature = credentials.identity;
|
|
585
|
-
const currentCodexAuthStateSignature = credentials.generation;
|
|
586
|
-
const assertCodexAuthGenerationCurrent = credentials.assertCurrent;
|
|
587
|
-
|
|
588
|
-
function codexAppServerSessionCommandHookConfig() {
|
|
589
|
-
return `hooks.PreToolUse=${tomlInlineValue(codexCommandHookDefinition(commandHookCommand))}`;
|
|
590
|
-
}
|
|
591
|
-
|
|
592
|
-
async function acquireRuntimeLock(runtimeDir = "", options = {}) {
|
|
593
|
-
await execution.prepare?.();
|
|
594
|
-
return acquireCodexRuntimeLock(runtimeDir, { ...options, busyCode: `${errorPrefix}codex_app_server_runtime_busy` });
|
|
595
|
-
}
|
|
596
|
-
|
|
597
|
-
// An empty or null umask inherits the host's own; hosts that share files set one.
|
|
598
|
-
const startupUmask = normalizeNullableText(umask);
|
|
599
|
-
if (startupUmask && !/^[0-7]{3,4}$/u.test(startupUmask)) {
|
|
600
|
-
throw new TypeError(`Codex app-server umask must be octal, not ${JSON.stringify(startupUmask)}.`);
|
|
601
|
-
}
|
|
602
|
-
const CODEX_APP_SERVER_UMASK_LINES = startupUmask ? [`umask ${startupUmask}`] : [];
|
|
603
|
-
|
|
604
|
-
const CODEX_APP_SERVER_MANAGED_SHELL = "/bin/sh";
|
|
605
|
-
|
|
606
|
-
const CODEX_APP_SERVER_MANAGED_STARTUP_SCRIPT = [
|
|
607
|
-
...CODEX_APP_SERVER_UMASK_LINES,
|
|
608
|
-
`unset ${[...CODEX_APP_SERVER_DESKTOP_BUS_ENV_NAMES].join(" ")}`,
|
|
609
|
-
'exec "$@"'
|
|
610
|
-
].join("\n");
|
|
611
|
-
|
|
612
|
-
const CODEX_APP_SERVER_HELPER_STARTUP_SCRIPT = [
|
|
613
|
-
...CODEX_APP_SERVER_UMASK_LINES,
|
|
614
|
-
"exec /usr/bin/env -i \\",
|
|
615
|
-
' HOME="$HOME" LOGNAME="$LOGNAME" USER="$USER" PATH="$PATH" \\',
|
|
616
|
-
' CODEX_HOME="$CODEX_HOME" LANG="$LANG" LC_ALL="$LC_ALL" LC_CTYPE="$LC_CTYPE" \\',
|
|
617
|
-
` ${CODEX_APP_SERVER_PROCESS_RUNTIME_TOKEN_ENV}="$${CODEX_APP_SERVER_PROCESS_RUNTIME_TOKEN_ENV}" ${CODEX_APP_SERVER_PROCESS_COMMAND_HASH_ENV}="$${CODEX_APP_SERVER_PROCESS_COMMAND_HASH_ENV}" \\`,
|
|
618
|
-
' SSL_CERT_DIR="$SSL_CERT_DIR" SSL_CERT_FILE="$SSL_CERT_FILE" TERM="$TERM" TMPDIR="$TMPDIR" TZ="$TZ" \\',
|
|
619
|
-
' "$@"'
|
|
620
|
-
].join("\n");
|
|
621
|
-
|
|
622
|
-
function codexAppServerProcessMetadataIsIdentifiable(metadata = {}, runtimeDir = "") {
|
|
623
|
-
const normalizedRuntimeDir = normalizeNullableText(runtimeDir);
|
|
624
|
-
return Boolean(
|
|
625
|
-
Number(metadata.schemaVersion) === CODEX_APP_SERVER_METADATA_SCHEMA_VERSION &&
|
|
626
|
-
metadata.provider === CODEX_APP_SERVER_PROVIDER_ID &&
|
|
627
|
-
Number.isSafeInteger(Number(metadata.pid)) && Number(metadata.pid) > 0 &&
|
|
628
|
-
(!normalizedRuntimeDir || metadata.runtimeDir === normalizedRuntimeDir) &&
|
|
629
|
-
codexAppServerProcessIdentityIsWellFormed(metadata.processIdentity) &&
|
|
630
|
-
[
|
|
631
|
-
CODEX_APP_SERVER_PROCESS_STATE.RUNNING,
|
|
632
|
-
CODEX_APP_SERVER_PROCESS_STATE.STOPPED
|
|
633
|
-
].includes(metadata.processState)
|
|
634
|
-
);
|
|
635
|
-
}
|
|
636
|
-
|
|
637
|
-
async function inspectCodexAppServerProcessIdentity(metadata = {}, options = {}) {
|
|
638
|
-
if (typeof options.processIdentityInspector === "function") {
|
|
639
|
-
return codexProcessIdentity.normalizeInspection(await options.processIdentityInspector(metadata));
|
|
640
|
-
}
|
|
641
|
-
if (!codexAppServerProcessMetadataIsIdentifiable(metadata)) {
|
|
642
|
-
return { processGroupIds: [], status: CODEX_APP_SERVER_PROCESS_IDENTITY_STATUS.INVALID };
|
|
643
|
-
}
|
|
644
|
-
return codexProcessIdentity.inspect(metadata);
|
|
645
|
-
}
|
|
646
|
-
|
|
647
|
-
async function stopCodexAppServerProcessGroup(metadata = {}, options = {}) {
|
|
648
|
-
if (!codexAppServerProcessMetadataIsIdentifiable(metadata)) {
|
|
649
|
-
return {
|
|
650
|
-
identityStatus: CODEX_APP_SERVER_PROCESS_IDENTITY_STATUS.INVALID,
|
|
651
|
-
processExitVerified: false,
|
|
652
|
-
stopped: false
|
|
653
|
-
};
|
|
654
|
-
}
|
|
655
|
-
return codexProcessIdentity.stop(metadata, {
|
|
656
|
-
...options,
|
|
657
|
-
processIdentityInspector: (current) => inspectCodexAppServerProcessIdentity(current, options)
|
|
658
|
-
});
|
|
659
|
-
}
|
|
660
|
-
|
|
661
|
-
function assertCodexAppServerSocketPathSupported(socketPath = "") {
|
|
662
|
-
if (!codexAppServerSocketPathTooLong(socketPath)) {
|
|
663
|
-
return;
|
|
664
|
-
}
|
|
665
|
-
throw new Error(
|
|
666
|
-
`Codex app-server Unix socket path is too long for this OS: ${socketPath} ` +
|
|
667
|
-
`(${codexAppServerSocketPathBytes(socketPath)} bytes, max ${CODEX_APP_SERVER_UNIX_SOCKET_PATH_MAX_BYTES}). ` +
|
|
668
|
-
socketPathHelp
|
|
669
|
-
);
|
|
670
|
-
}
|
|
671
|
-
|
|
672
|
-
async function stopOwnedCodexAppServerExecution(metadata = {}, options = {}) {
|
|
673
|
-
if (
|
|
674
|
-
metadata.processState === CODEX_APP_SERVER_PROCESS_STATE.STOPPED &&
|
|
675
|
-
metadata.processExitVerifiedAt
|
|
676
|
-
) {
|
|
677
|
-
return stopCodexAppServerProcessGroup(metadata, options);
|
|
678
|
-
}
|
|
679
|
-
const executionId = normalizeNullableText(metadata.executionId);
|
|
680
|
-
if (executionId) {
|
|
681
|
-
const stopExecution = typeof options.stopExecution === "function"
|
|
682
|
-
? options.stopExecution
|
|
683
|
-
: execution.stop;
|
|
684
|
-
let executionStop;
|
|
685
|
-
try {
|
|
686
|
-
executionStop = await stopExecution(executionId, {
|
|
687
|
-
allowMissingRecordScopeRecovery: true,
|
|
688
|
-
killTimeoutMs: options.killTimeoutMs,
|
|
689
|
-
reason: options.reason || "codex-app-server-stop",
|
|
690
|
-
termTimeoutMs: options.termTimeoutMs
|
|
691
|
-
});
|
|
692
|
-
} catch (error) {
|
|
693
|
-
return {
|
|
694
|
-
error: String(error?.message || error || "The owned execution could not be stopped."),
|
|
695
|
-
executionId,
|
|
696
|
-
processExitVerified: false,
|
|
697
|
-
scopeEmpty: false,
|
|
698
|
-
stopped: false
|
|
699
|
-
};
|
|
700
|
-
}
|
|
701
|
-
if (executionStop?.ok === true && executionStop?.scopeEmpty === true) {
|
|
702
|
-
return {
|
|
703
|
-
...executionStop,
|
|
704
|
-
executionId,
|
|
705
|
-
processExitVerified: true,
|
|
706
|
-
stopped: executionStop.stopped === true
|
|
707
|
-
};
|
|
708
|
-
}
|
|
709
|
-
if (typeof options.stopExecution === "function" || execution.authoritative) {
|
|
710
|
-
return {
|
|
711
|
-
...(executionStop && typeof executionStop === "object" ? executionStop : {}),
|
|
712
|
-
executionId,
|
|
713
|
-
processExitVerified: false,
|
|
714
|
-
scopeEmpty: false,
|
|
715
|
-
stopped: false
|
|
716
|
-
};
|
|
717
|
-
}
|
|
718
|
-
}
|
|
719
|
-
return stopCodexAppServerProcessGroup(metadata, options);
|
|
720
|
-
}
|
|
721
|
-
|
|
722
|
-
async function stopCodexAppServerProcess(runtimeDir = "", options = {}) {
|
|
723
|
-
const normalizedRuntimeDir = normalizeNullableText(runtimeDir);
|
|
724
|
-
if (!normalizedRuntimeDir) {
|
|
725
|
-
return {
|
|
726
|
-
processExitVerified: false,
|
|
727
|
-
stopped: false
|
|
728
|
-
};
|
|
729
|
-
}
|
|
730
|
-
const metadata = await readCodexAppServerMetadata(normalizedRuntimeDir);
|
|
731
|
-
if (!codexAppServerProcessMetadataIsIdentifiable(metadata, normalizedRuntimeDir)) {
|
|
732
|
-
return {
|
|
733
|
-
identityStatus: CODEX_APP_SERVER_PROCESS_IDENTITY_STATUS.INVALID,
|
|
734
|
-
processExitVerified: false,
|
|
735
|
-
stopped: false
|
|
736
|
-
};
|
|
737
|
-
}
|
|
738
|
-
return stopOwnedCodexAppServerExecution(metadata, options);
|
|
739
|
-
}
|
|
740
|
-
|
|
741
|
-
async function cleanupFailedCodexAppServerStart(runtimeDir = "", {
|
|
742
|
-
helper = false,
|
|
743
|
-
executionId = "",
|
|
744
|
-
neverStarted = false,
|
|
745
|
-
pid = null,
|
|
746
|
-
processIdentity = null
|
|
747
|
-
} = {}, options = {}) {
|
|
748
|
-
let processStop = {
|
|
749
|
-
processExitVerified: neverStarted === true,
|
|
750
|
-
scopeEmpty: neverStarted === true,
|
|
751
|
-
stopped: false
|
|
752
|
-
};
|
|
753
|
-
let processCleanupFailed = false;
|
|
754
|
-
if (!neverStarted) {
|
|
755
|
-
try {
|
|
756
|
-
processStop = await stopOwnedCodexAppServerExecution({
|
|
757
|
-
executionId: normalizeNullableText(executionId),
|
|
758
|
-
pid: Number(pid),
|
|
759
|
-
processIdentity,
|
|
760
|
-
processState: CODEX_APP_SERVER_PROCESS_STATE.RUNNING,
|
|
761
|
-
provider: CODEX_APP_SERVER_PROVIDER_ID,
|
|
762
|
-
runtimeDir: normalizeNullableText(runtimeDir),
|
|
763
|
-
schemaVersion: CODEX_APP_SERVER_METADATA_SCHEMA_VERSION
|
|
764
|
-
}, options);
|
|
765
|
-
} catch {
|
|
766
|
-
processCleanupFailed = true;
|
|
767
|
-
}
|
|
768
|
-
}
|
|
769
|
-
if (processCleanupFailed || processStop.processExitVerified !== true) {
|
|
770
|
-
return {
|
|
771
|
-
...processStop,
|
|
772
|
-
cleanupFailed: true
|
|
773
|
-
};
|
|
774
|
-
}
|
|
775
|
-
const helperRemovals = helper
|
|
776
|
-
? [
|
|
777
|
-
rm(codexAppServerHelperHomeDir(runtimeDir), { force: true, recursive: true }),
|
|
778
|
-
rm(codexAppServerHelperWorkspaceDir(runtimeDir), { force: true, recursive: true }),
|
|
779
|
-
rm(codexAppServerLogPath(runtimeDir), { force: true })
|
|
780
|
-
]
|
|
781
|
-
: [];
|
|
782
|
-
const removals = await Promise.allSettled([
|
|
783
|
-
...helperRemovals,
|
|
784
|
-
rm(codexAppServerSocketPath(runtimeDir), { force: true }),
|
|
785
|
-
rm(codexAppServerMetadataPath(runtimeDir), { force: true }),
|
|
786
|
-
removeCodexAppServerMetadataTemps(runtimeDir)
|
|
787
|
-
]);
|
|
788
|
-
return {
|
|
789
|
-
...processStop,
|
|
790
|
-
cleanupFailed: removals.some(({ status }) => status === "rejected")
|
|
791
|
-
};
|
|
792
|
-
}
|
|
793
|
-
|
|
794
|
-
async function stopCodexAppServerRuntime(options = {}) {
|
|
795
|
-
const runtimeDir = normalizeNullableText(options.runtimeDir);
|
|
796
|
-
const preserveProcessExitProof = options.preserveProcessExitProof === true;
|
|
797
|
-
if (!runtimeDir || !codexAppServerRuntimeDirIsManaged(runtimeDir)) {
|
|
798
|
-
return {
|
|
799
|
-
processExitVerified: false,
|
|
800
|
-
runtimeDirPreserved: false,
|
|
801
|
-
runtimeDirRemoved: false,
|
|
802
|
-
stopped: false
|
|
803
|
-
};
|
|
804
|
-
}
|
|
805
|
-
let releaseLock;
|
|
806
|
-
try {
|
|
807
|
-
releaseLock = await acquireRuntimeLock(runtimeDir, options);
|
|
808
|
-
} catch (error) {
|
|
809
|
-
if (!codexAppServerRuntimeCleanupCanSkip(error)) {
|
|
810
|
-
throw error;
|
|
811
|
-
}
|
|
812
|
-
return {
|
|
813
|
-
processExitVerified: false,
|
|
814
|
-
runtimeDirCleanupError: String(error?.message || error || ""),
|
|
815
|
-
runtimeDirCleanupSkipped: String(error?.code || "") !== "ENOENT",
|
|
816
|
-
runtimeDirPreserved: false,
|
|
817
|
-
runtimeDirRemoved: false,
|
|
818
|
-
stopped: false
|
|
819
|
-
};
|
|
820
|
-
}
|
|
821
|
-
let runtimeDirRemoved = false;
|
|
822
|
-
let runtimeDirCleanupSkipped = false;
|
|
823
|
-
let runtimeDirCleanupError = "";
|
|
824
|
-
let runtimeDirPreserved = false;
|
|
825
|
-
let processStop = {
|
|
826
|
-
processExitVerified: false,
|
|
827
|
-
stopped: false
|
|
828
|
-
};
|
|
829
|
-
try {
|
|
830
|
-
const existing = await readCodexAppServerMetadata(runtimeDir);
|
|
831
|
-
const expectedAccount = normalizeNullableText(options.expectedAccountIdentitySignature);
|
|
832
|
-
const ownedRuntime = options.ownedRuntime;
|
|
833
|
-
if (
|
|
834
|
-
ownedRuntime &&
|
|
835
|
-
codexAppServerProcessMetadataIsIdentifiable(ownedRuntime, runtimeDir) &&
|
|
836
|
-
(!expectedAccount || ownedRuntime.accountIdentitySignature === expectedAccount) &&
|
|
837
|
-
(!existing || codexAppServerRuntimeIdentity(existing) !== codexAppServerRuntimeIdentity(ownedRuntime))
|
|
838
|
-
) {
|
|
839
|
-
// Another owner may already have removed or replaced the shared runtime.
|
|
840
|
-
// Prove this provider's exact execution stopped without touching its successor.
|
|
841
|
-
return {
|
|
842
|
-
...await stopOwnedCodexAppServerExecution(ownedRuntime, options),
|
|
843
|
-
runtimeDirPreserved: Boolean(existing),
|
|
844
|
-
runtimeDirRemoved: false
|
|
845
|
-
};
|
|
846
|
-
}
|
|
847
|
-
if (!existing) {
|
|
848
|
-
// Changeover after a server restart may have no runtime.json left. The
|
|
849
|
-
// managed execution owner can still drain and prove its exact scope empty.
|
|
850
|
-
// A missing file alone is never process-exit proof, and account-specific
|
|
851
|
-
// cleanup must keep using its retained execution identity above.
|
|
852
|
-
const owned = options.verifyOwnerScope === true && !expectedAccount
|
|
853
|
-
? await execution.stopOwned({
|
|
854
|
-
kind: "assistant",
|
|
855
|
-
operationId: "codex-app-server",
|
|
856
|
-
ownerId: normalizeNullableText(options.ownerId) || stableHash(runtimeDir)
|
|
857
|
-
}, { reason: "codex-app-server-changeover" })
|
|
858
|
-
: null;
|
|
859
|
-
const ownerExitVerified = owned?.supported === true && owned?.ok === true && owned?.scopeEmpty === true;
|
|
860
|
-
return {
|
|
861
|
-
processExitVerified: ownerExitVerified,
|
|
862
|
-
runtimeDirPreserved: false,
|
|
863
|
-
runtimeDirRemoved: false,
|
|
864
|
-
stopped: ownerExitVerified && Number(owned?.closed) > 0
|
|
865
|
-
};
|
|
866
|
-
}
|
|
867
|
-
if (expectedAccount) {
|
|
868
|
-
// A replacement runtime is installed only after the previous process has
|
|
869
|
-
// drained under this same lock. Never stop that replacement for stale
|
|
870
|
-
// ephemeral-thread cleanup.
|
|
871
|
-
if (!codexAppServerProcessMetadataIsIdentifiable(existing, runtimeDir) ||
|
|
872
|
-
!/^sha256:[a-f0-9]{64}$/u.test(existing.accountIdentitySignature)) {
|
|
873
|
-
return { processExitVerified: false, runtimeDirRemoved: false, stopped: false };
|
|
874
|
-
}
|
|
875
|
-
if (existing.accountIdentitySignature !== expectedAccount) {
|
|
876
|
-
return { ownershipSuperseded: true, processExitVerified: false, runtimeDirRemoved: false, stopped: false };
|
|
877
|
-
}
|
|
878
|
-
}
|
|
879
|
-
processStop = await stopCodexAppServerProcess(runtimeDir, options);
|
|
880
|
-
if (processStop.processExitVerified === true && preserveProcessExitProof) {
|
|
881
|
-
const metadata = await readCodexAppServerMetadata(runtimeDir);
|
|
882
|
-
if (!codexAppServerProcessMetadataIsIdentifiable(metadata, runtimeDir)) {
|
|
883
|
-
return {
|
|
884
|
-
...processStop,
|
|
885
|
-
processExitVerified: false,
|
|
886
|
-
runtimeDirPreserved: false,
|
|
887
|
-
runtimeDirRemoved: false
|
|
888
|
-
};
|
|
889
|
-
}
|
|
890
|
-
await writeCodexAppServerMetadata(runtimeDir, {
|
|
891
|
-
...metadata,
|
|
892
|
-
processExitVerifiedAt: metadata.processExitVerifiedAt || new Date().toISOString(),
|
|
893
|
-
processState: CODEX_APP_SERVER_PROCESS_STATE.STOPPED
|
|
894
|
-
});
|
|
895
|
-
runtimeDirPreserved = true;
|
|
896
|
-
} else if (processStop.processExitVerified === true) {
|
|
897
|
-
try {
|
|
898
|
-
runtimeDirRemoved = await removeCodexAppServerRuntimeDir(runtimeDir);
|
|
899
|
-
} catch (error) {
|
|
900
|
-
if (!codexAppServerRuntimeCleanupCanSkip(error)) {
|
|
901
|
-
throw error;
|
|
902
|
-
}
|
|
903
|
-
runtimeDirCleanupSkipped = true;
|
|
904
|
-
runtimeDirCleanupError = String(error?.message || error || "");
|
|
905
|
-
}
|
|
906
|
-
}
|
|
907
|
-
} finally {
|
|
908
|
-
await releaseLock();
|
|
909
|
-
}
|
|
910
|
-
return {
|
|
911
|
-
...processStop,
|
|
912
|
-
runtimeDirCleanupError,
|
|
913
|
-
runtimeDirCleanupSkipped,
|
|
914
|
-
runtimeDirPreserved,
|
|
915
|
-
runtimeDirRemoved
|
|
916
|
-
};
|
|
917
|
-
}
|
|
918
|
-
|
|
919
|
-
function codexAppServerMetadataIsWellFormed(metadata = {}, options = {}) {
|
|
920
|
-
const expectedAttachmentHostRoot = options.compatibility.attachmentHostRoot;
|
|
921
|
-
const expectedAccountIdentitySignature = normalizeNullableText(options.accountIdentitySignature);
|
|
922
|
-
const expectedExecutionMode = codexAppServerExecutionMode(options);
|
|
923
|
-
const expectedToolHomeSource = codexAppServerIsHelper(options) ? "" : normalizeNullableText(options.toolHomeSource);
|
|
924
|
-
const expectedTerminalEnvHash = codexAppServerTerminalEnvHash(codexAppServerIsHelper(options) ? {} : options.terminalEnv);
|
|
925
|
-
const expectedRuntimesHash = options.compatibility.runtimesHash;
|
|
926
|
-
const expectedExecutionContextHash = options.compatibility.executionContextHash;
|
|
927
|
-
const expectedHelperProcessCwd = expectedExecutionMode === CODEX_APP_SERVER_EXECUTION_MODES.HELPER
|
|
928
|
-
? codexAppServerHelperWorkspaceDir(metadata.runtimeDir)
|
|
929
|
-
: "";
|
|
930
|
-
return Boolean(
|
|
931
|
-
metadata.schemaVersion === CODEX_APP_SERVER_METADATA_SCHEMA_VERSION &&
|
|
932
|
-
metadata.accountIdentitySignature === expectedAccountIdentitySignature &&
|
|
933
|
-
metadata.attachmentHostRoot === expectedAttachmentHostRoot &&
|
|
934
|
-
metadata.authStateSignature &&
|
|
935
|
-
metadata.executionId &&
|
|
936
|
-
metadata.executionContextHash === expectedExecutionContextHash &&
|
|
937
|
-
metadata.executionMode === expectedExecutionMode &&
|
|
938
|
-
metadata.processCwd &&
|
|
939
|
-
codexAppServerProcessMetadataIsIdentifiable(metadata) &&
|
|
940
|
-
metadata.processState === CODEX_APP_SERVER_PROCESS_STATE.RUNNING &&
|
|
941
|
-
(expectedExecutionMode === CODEX_APP_SERVER_EXECUTION_MODES.HELPER ||
|
|
942
|
-
codexHistoryAdapterUrlIsValid(metadata.historyAdapterBaseUrl, metadata.processIdentity.runtimeToken)) &&
|
|
943
|
-
(!expectedHelperProcessCwd || metadata.processCwd === expectedHelperProcessCwd) &&
|
|
944
|
-
metadata.provider === CODEX_APP_SERVER_PROVIDER_ID &&
|
|
945
|
-
metadata.runtimesHash === expectedRuntimesHash &&
|
|
946
|
-
metadata.terminalEnvHash === expectedTerminalEnvHash &&
|
|
947
|
-
metadata.toolHomeSource === expectedToolHomeSource &&
|
|
948
|
-
metadata.transport === CODEX_APP_SERVER_TRANSPORT.UNIX &&
|
|
949
|
-
metadata.endpoint &&
|
|
950
|
-
metadata.socketPath
|
|
951
|
-
);
|
|
952
|
-
}
|
|
953
|
-
|
|
954
|
-
async function codexAppServerEndpointStatus(endpoint = "", {
|
|
955
|
-
timeoutMs = CODEX_APP_SERVER_LIVENESS_TIMEOUT_MS,
|
|
956
|
-
WebSocketImpl = WebSocket
|
|
957
|
-
} = {}) {
|
|
958
|
-
const normalizedEndpoint = normalizeNullableText(endpoint);
|
|
959
|
-
const socketPath = socketPathFromCodexAppServerEndpoint(normalizedEndpoint);
|
|
960
|
-
if (!socketPath || !await fileExists(socketPath)) {
|
|
961
|
-
return {
|
|
962
|
-
status: CODEX_APP_SERVER_ENDPOINT_STATUS.MISSING
|
|
963
|
-
};
|
|
964
|
-
}
|
|
965
|
-
const normalizedTimeoutMs = normalizePositiveInteger(timeoutMs, { fallback: CODEX_APP_SERVER_LIVENESS_TIMEOUT_MS });
|
|
966
|
-
return {
|
|
967
|
-
status: await probeCodexAppServer(normalizedEndpoint, normalizedTimeoutMs, WebSocketImpl)
|
|
968
|
-
};
|
|
969
|
-
}
|
|
970
|
-
|
|
971
|
-
// One bounded connect-and-initialize handshake: responsive, timeout or unreachable.
|
|
972
|
-
async function probeCodexAppServer(endpoint, timeoutMs, WebSocketImpl) {
|
|
973
|
-
const client = new CodexAppServerJsonRpcClient({
|
|
974
|
-
endpoint,
|
|
975
|
-
requestTimeoutMs: timeoutMs,
|
|
976
|
-
WebSocketImpl
|
|
977
|
-
});
|
|
978
|
-
let timeout = null;
|
|
979
|
-
const probe = (async () => {
|
|
980
|
-
await client.connect();
|
|
981
|
-
await client.initialize({ clientInfo });
|
|
982
|
-
return CODEX_APP_SERVER_ENDPOINT_STATUS.RESPONSIVE;
|
|
983
|
-
})();
|
|
984
|
-
probe.catch(() => null);
|
|
985
|
-
try {
|
|
986
|
-
return await Promise.race([
|
|
987
|
-
probe,
|
|
988
|
-
new Promise((resolve) => {
|
|
989
|
-
timeout = setTimeout(() => resolve(CODEX_APP_SERVER_ENDPOINT_STATUS.TIMEOUT), timeoutMs);
|
|
990
|
-
timeout.unref?.();
|
|
991
|
-
})
|
|
992
|
-
]);
|
|
993
|
-
} catch {
|
|
994
|
-
return CODEX_APP_SERVER_ENDPOINT_STATUS.UNREACHABLE;
|
|
995
|
-
} finally {
|
|
996
|
-
clearTimeout(timeout);
|
|
997
|
-
client.close();
|
|
998
|
-
}
|
|
999
|
-
}
|
|
1000
|
-
|
|
1001
|
-
async function codexAppServerMetadataIsLive(metadata = {}, options = {}) {
|
|
1002
|
-
return (await codexAppServerRuntimeStatus(metadata, options)).status === CODEX_APP_SERVER_RUNTIME_STATUS.LIVE;
|
|
1003
|
-
}
|
|
1004
|
-
|
|
1005
|
-
async function codexAppServerRuntimeStatus(metadata = {}, options = {}) {
|
|
1006
|
-
const normalized = normalizeCodexAppServerMetadata(metadata);
|
|
1007
|
-
if (
|
|
1008
|
-
codexAppServerProcessMetadataIsIdentifiable(normalized) &&
|
|
1009
|
-
normalized.processState === CODEX_APP_SERVER_PROCESS_STATE.STOPPED &&
|
|
1010
|
-
normalized.processExitVerifiedAt
|
|
1011
|
-
) {
|
|
1012
|
-
return {
|
|
1013
|
-
metadata: normalized,
|
|
1014
|
-
replace: true,
|
|
1015
|
-
reusable: false,
|
|
1016
|
-
status: CODEX_APP_SERVER_RUNTIME_STATUS.EXITED
|
|
1017
|
-
};
|
|
1018
|
-
}
|
|
1019
|
-
if (!codexAppServerMetadataIsWellFormed(normalized, options)) {
|
|
1020
|
-
return {
|
|
1021
|
-
metadata: normalized,
|
|
1022
|
-
replace: true,
|
|
1023
|
-
reusable: false,
|
|
1024
|
-
status: CODEX_APP_SERVER_RUNTIME_STATUS.INCOMPATIBLE
|
|
1025
|
-
};
|
|
1026
|
-
}
|
|
1027
|
-
const authStateSignature = await currentCodexAuthStateSignature(options);
|
|
1028
|
-
if (normalized.authStateSignature !== authStateSignature) {
|
|
1029
|
-
return {
|
|
1030
|
-
metadata: normalized,
|
|
1031
|
-
replace: true,
|
|
1032
|
-
reusable: false,
|
|
1033
|
-
status: CODEX_APP_SERVER_RUNTIME_STATUS.INCOMPATIBLE
|
|
1034
|
-
};
|
|
1035
|
-
}
|
|
1036
|
-
let processIdentity = null;
|
|
1037
|
-
if (typeof options.processGroupIsAlive === "function") {
|
|
1038
|
-
const alive = options.processGroupIsAlive(normalized.pid);
|
|
1039
|
-
processIdentity = {
|
|
1040
|
-
processGroupIds: alive ? [normalized.pid] : [],
|
|
1041
|
-
status: alive
|
|
1042
|
-
? CODEX_APP_SERVER_PROCESS_IDENTITY_STATUS.EXACT
|
|
1043
|
-
: CODEX_APP_SERVER_PROCESS_IDENTITY_STATUS.ABSENT
|
|
1044
|
-
};
|
|
1045
|
-
} else {
|
|
1046
|
-
processIdentity = await inspectCodexAppServerProcessIdentity(normalized, options);
|
|
1047
|
-
}
|
|
1048
|
-
if (
|
|
1049
|
-
processIdentity.status === CODEX_APP_SERVER_PROCESS_IDENTITY_STATUS.ABSENT ||
|
|
1050
|
-
processIdentity.status === CODEX_APP_SERVER_PROCESS_IDENTITY_STATUS.MISMATCH
|
|
1051
|
-
) {
|
|
1052
|
-
return {
|
|
1053
|
-
metadata: normalized,
|
|
1054
|
-
replace: true,
|
|
1055
|
-
reusable: false,
|
|
1056
|
-
status: CODEX_APP_SERVER_RUNTIME_STATUS.EXITED
|
|
1057
|
-
};
|
|
1058
|
-
}
|
|
1059
|
-
if (processIdentity.status !== CODEX_APP_SERVER_PROCESS_IDENTITY_STATUS.EXACT) {
|
|
1060
|
-
return {
|
|
1061
|
-
metadata: normalized,
|
|
1062
|
-
replace: false,
|
|
1063
|
-
reusable: true,
|
|
1064
|
-
status: CODEX_APP_SERVER_RUNTIME_STATUS.SUSPECT
|
|
1065
|
-
};
|
|
1066
|
-
}
|
|
1067
|
-
const endpoint = await codexAppServerEndpointStatus(normalized.endpoint, {
|
|
1068
|
-
timeoutMs: codexAppServerLivenessTimeoutMs(options),
|
|
1069
|
-
WebSocketImpl: options.WebSocketImpl
|
|
1070
|
-
});
|
|
1071
|
-
if (endpoint.status === CODEX_APP_SERVER_ENDPOINT_STATUS.RESPONSIVE) {
|
|
1072
|
-
return {
|
|
1073
|
-
metadata: normalized,
|
|
1074
|
-
replace: false,
|
|
1075
|
-
reusable: true,
|
|
1076
|
-
status: CODEX_APP_SERVER_RUNTIME_STATUS.LIVE
|
|
1077
|
-
};
|
|
1078
|
-
}
|
|
1079
|
-
if (endpoint.status === CODEX_APP_SERVER_ENDPOINT_STATUS.MISSING) {
|
|
1080
|
-
return {
|
|
1081
|
-
metadata: normalized,
|
|
1082
|
-
replace: true,
|
|
1083
|
-
reusable: false,
|
|
1084
|
-
status: CODEX_APP_SERVER_RUNTIME_STATUS.MISSING
|
|
1085
|
-
};
|
|
1086
|
-
}
|
|
1087
|
-
if (endpoint.status === CODEX_APP_SERVER_ENDPOINT_STATUS.TIMEOUT) {
|
|
1088
|
-
return {
|
|
1089
|
-
metadata: normalized,
|
|
1090
|
-
replace: false,
|
|
1091
|
-
reusable: true,
|
|
1092
|
-
status: CODEX_APP_SERVER_RUNTIME_STATUS.SUSPECT
|
|
1093
|
-
};
|
|
1094
|
-
}
|
|
1095
|
-
return {
|
|
1096
|
-
metadata: normalized,
|
|
1097
|
-
replace: true,
|
|
1098
|
-
reusable: false,
|
|
1099
|
-
status: CODEX_APP_SERVER_RUNTIME_STATUS.MISSING
|
|
1100
|
-
};
|
|
1101
|
-
}
|
|
1102
|
-
|
|
1103
|
-
async function waitForCodexAppServer(endpoint = "", {
|
|
1104
|
-
timeoutMs = CODEX_APP_SERVER_READY_TIMEOUT_MS,
|
|
1105
|
-
WebSocketImpl = WebSocket
|
|
1106
|
-
} = {}) {
|
|
1107
|
-
const socketPath = socketPathFromCodexAppServerEndpoint(endpoint);
|
|
1108
|
-
if (!socketPath) {
|
|
1109
|
-
return false;
|
|
1110
|
-
}
|
|
1111
|
-
const startedAt = Date.now();
|
|
1112
|
-
while (!await fileExists(socketPath) && Date.now() - startedAt <= timeoutMs) {
|
|
1113
|
-
await delay(100);
|
|
1114
|
-
}
|
|
1115
|
-
const remainingMs = Math.max(1, timeoutMs - (Date.now() - startedAt));
|
|
1116
|
-
if (!await fileExists(socketPath)) {
|
|
1117
|
-
return false;
|
|
1118
|
-
}
|
|
1119
|
-
return await probeCodexAppServer(endpoint, remainingMs, WebSocketImpl) === CODEX_APP_SERVER_ENDPOINT_STATUS.RESPONSIVE;
|
|
1120
|
-
}
|
|
1121
|
-
|
|
1122
|
-
async function startCodexAppServerProcess({
|
|
1123
|
-
providerHome = false,
|
|
1124
|
-
accountIdentitySignature = "",
|
|
1125
|
-
authStateSignature = "",
|
|
1126
|
-
codexCommand = "codex",
|
|
1127
|
-
env = process.env,
|
|
1128
|
-
executionRoot = "",
|
|
1129
|
-
executionMode = CODEX_APP_SERVER_EXECUTION_MODES.INTERACTIVE,
|
|
1130
|
-
killTimeoutMs = 0,
|
|
1131
|
-
processIdentityInspector = null,
|
|
1132
|
-
readyTimeoutMs = CODEX_APP_SERVER_READY_TIMEOUT_MS,
|
|
1133
|
-
signalProcessGroup: signalProcessGroupOverride = null,
|
|
1134
|
-
stopExecution = null,
|
|
1135
|
-
terminalEnv = {},
|
|
1136
|
-
termTimeoutMs = 0,
|
|
1137
|
-
toolHomeSource = "",
|
|
1138
|
-
WebSocketImpl = WebSocket,
|
|
1139
|
-
workdir = "",
|
|
1140
|
-
socketOwnerDrained = false,
|
|
1141
|
-
runtimeDir,
|
|
1142
|
-
ownerId = "",
|
|
1143
|
-
compatibility
|
|
1144
|
-
} = {}) {
|
|
1145
|
-
const processLifecycleOptions = {
|
|
1146
|
-
killTimeoutMs,
|
|
1147
|
-
processIdentityInspector,
|
|
1148
|
-
signalProcessGroup: signalProcessGroupOverride,
|
|
1149
|
-
stopExecution,
|
|
1150
|
-
termTimeoutMs
|
|
1151
|
-
};
|
|
1152
|
-
await ensureWritablePrivateDirectory(runtimeDir);
|
|
1153
|
-
const helper = codexAppServerExecutionMode({ executionMode }) ===
|
|
1154
|
-
CODEX_APP_SERVER_EXECUTION_MODES.HELPER;
|
|
1155
|
-
const normalizedToolHomeSource = normalizeNullableText(toolHomeSource);
|
|
1156
|
-
if (normalizedToolHomeSource) {
|
|
1157
|
-
await assertExistingDirectory(normalizedToolHomeSource, "Codex credential home");
|
|
1158
|
-
}
|
|
1159
|
-
const resolvedAuthStateSignature = await currentCodexAuthStateSignature({ authStateSignature });
|
|
1160
|
-
await assertCodexAuthGenerationCurrent(resolvedAuthStateSignature);
|
|
1161
|
-
const socketPath = codexAppServerSocketPath(runtimeDir);
|
|
1162
|
-
assertCodexAppServerSocketPathSupported(socketPath);
|
|
1163
|
-
const endpoint = codexAppServerUnixEndpoint(socketPath);
|
|
1164
|
-
const logPath = codexAppServerLogPath(runtimeDir);
|
|
1165
|
-
const helperHome = helper ? codexAppServerHelperHomeDir(runtimeDir) : "";
|
|
1166
|
-
const helperWorkspace = helper ? codexAppServerHelperWorkspaceDir(runtimeDir) : "";
|
|
1167
|
-
if (helper) {
|
|
1168
|
-
await Promise.all([
|
|
1169
|
-
rm(helperHome, { force: true, recursive: true }),
|
|
1170
|
-
rm(helperWorkspace, { force: true, recursive: true })
|
|
1171
|
-
]);
|
|
1172
|
-
await Promise.all([
|
|
1173
|
-
ensureWritablePrivateDirectory(helperHome),
|
|
1174
|
-
ensureWritablePrivateDirectory(helperWorkspace)
|
|
1175
|
-
]);
|
|
1176
|
-
if (providerHome) {
|
|
1177
|
-
// The canonical home contains only our curated provider projection. Copy
|
|
1178
|
-
// it into the existing isolated helper home; never load OpenAI auth here.
|
|
1179
|
-
try {
|
|
1180
|
-
await copyCodexProviderHome(path.join(normalizedToolHomeSource, ".codex"), helperHome);
|
|
1181
|
-
} catch (cause) {
|
|
1182
|
-
const error = new Error("Codex isolated helper runtime could not copy the provider home.", { cause });
|
|
1183
|
-
error.code = `${errorPrefix}codex_helper_runtime_start_failed`;
|
|
1184
|
-
throw error;
|
|
1185
|
-
}
|
|
1186
|
-
}
|
|
1187
|
-
}
|
|
1188
|
-
const processCwd = helper
|
|
1189
|
-
? helperWorkspace
|
|
1190
|
-
: codexAppServerProcessCwd({
|
|
1191
|
-
executionRoot,
|
|
1192
|
-
runtimeDir,
|
|
1193
|
-
workdir
|
|
1194
|
-
});
|
|
1195
|
-
const projectTrustOverride = helper ? "" : codexAppServerProjectTrustOverride(workdir);
|
|
1196
|
-
const normalizedTerminalEnv = normalizeCodexAppServerTerminalEnv(helper ? {} : terminalEnv);
|
|
1197
|
-
const commandBaseEnv = helper
|
|
1198
|
-
? codexAppServerHelperCommandBaseEnv(env, helperHome)
|
|
1199
|
-
: codexAppServerCommandBaseEnv({
|
|
1200
|
-
env,
|
|
1201
|
-
terminalEnv: normalizedTerminalEnv
|
|
1202
|
-
});
|
|
1203
|
-
if (await fileExists(socketPath) && !socketOwnerDrained) {
|
|
1204
|
-
const endpointStatus = await codexAppServerEndpointStatus(endpoint, {
|
|
1205
|
-
timeoutMs: CODEX_APP_SERVER_LIVENESS_TIMEOUT_MS,
|
|
1206
|
-
WebSocketImpl
|
|
1207
|
-
});
|
|
1208
|
-
if (![CODEX_APP_SERVER_ENDPOINT_STATUS.MISSING, CODEX_APP_SERVER_ENDPOINT_STATUS.UNREACHABLE]
|
|
1209
|
-
.includes(endpointStatus.status)) {
|
|
1210
|
-
const error = new Error("The existing Codex app-server socket still has an unretired owner.");
|
|
1211
|
-
error.code = `${errorPrefix}codex_app_server_socket_owner_unverified`;
|
|
1212
|
-
error.retryable = false;
|
|
1213
|
-
throw error;
|
|
1214
|
-
}
|
|
1215
|
-
}
|
|
1216
|
-
await rm(socketPath, {
|
|
1217
|
-
force: true
|
|
1218
|
-
});
|
|
1219
|
-
const codexArgs = [
|
|
1220
|
-
// Keep every override after app-server: a trailing -c (including the model
|
|
1221
|
-
// catalogue) otherwise drops the earlier ones. See openai/codex#39012.
|
|
1222
|
-
"app-server",
|
|
1223
|
-
"--listen",
|
|
1224
|
-
endpoint,
|
|
1225
|
-
// App-server uses config overrides, not the top-level sandbox bypass flag.
|
|
1226
|
-
...(helper ? [] : [
|
|
1227
|
-
"-c",
|
|
1228
|
-
'approval_policy="never"',
|
|
1229
|
-
"-c",
|
|
1230
|
-
'sandbox_mode="danger-full-access"',
|
|
1231
|
-
"-c",
|
|
1232
|
-
`features.hooks=${Boolean(commandHookCommand)}`,
|
|
1233
|
-
...(commandHookCommand ? ["-c", codexAppServerSessionCommandHookConfig()] : [])
|
|
1234
|
-
]),
|
|
1235
|
-
"-c",
|
|
1236
|
-
"check_for_update_on_startup=false",
|
|
1237
|
-
// Plugin and app startup syncs fill the home with unused catalogues.
|
|
1238
|
-
...codexDisabledProcessFeatureArguments(),
|
|
1239
|
-
...(projectTrustOverride
|
|
1240
|
-
? [
|
|
1241
|
-
"-c",
|
|
1242
|
-
projectTrustOverride
|
|
1243
|
-
]
|
|
1244
|
-
: [])
|
|
1245
|
-
];
|
|
1246
|
-
const runtimeToken = randomUUID();
|
|
1247
|
-
const commandHash = codexAppServerProcessCommandHash({
|
|
1248
|
-
codexArgs,
|
|
1249
|
-
codexCommand,
|
|
1250
|
-
executionMode,
|
|
1251
|
-
processCwd,
|
|
1252
|
-
runtimeDir
|
|
1253
|
-
});
|
|
1254
|
-
const baseEnv = {
|
|
1255
|
-
...commandBaseEnv,
|
|
1256
|
-
// A curated provider home contains a provider-only static catalogue. Use
|
|
1257
|
-
// the binary's native definitions as the base for its shared runtime.
|
|
1258
|
-
[modelCatalogEnvironmentName]: providerHome ? "bundled" : "native",
|
|
1259
|
-
[CODEX_APP_SERVER_PROCESS_COMMAND_HASH_ENV]: commandHash,
|
|
1260
|
-
[CODEX_APP_SERVER_PROCESS_RUNTIME_TOKEN_ENV]: runtimeToken
|
|
1261
|
-
};
|
|
1262
|
-
const profileBinary = path.isAbsolute(codexCommand) ? await stat(codexCommand).catch(() => null) : null;
|
|
1263
|
-
const startResult = await execution.run({
|
|
1264
|
-
args: [
|
|
1265
|
-
"-c",
|
|
1266
|
-
helper ? CODEX_APP_SERVER_HELPER_STARTUP_SCRIPT : CODEX_APP_SERVER_MANAGED_STARTUP_SCRIPT,
|
|
1267
|
-
processLabel,
|
|
1268
|
-
...(helper ? [] : [process.execPath, CODEX_APP_SERVER_PROCESS_PATH, runtimeDir]),
|
|
1269
|
-
codexCommand,
|
|
1270
|
-
...codexArgs
|
|
1271
|
-
],
|
|
1272
|
-
baseEnv,
|
|
1273
|
-
command: CODEX_APP_SERVER_MANAGED_SHELL,
|
|
1274
|
-
credentialHome: codexAppServerCredentialHome(
|
|
1275
|
-
helper ? helperHome : normalizedToolHomeSource,
|
|
1276
|
-
baseEnv
|
|
1277
|
-
),
|
|
1278
|
-
cwd: processCwd || process.cwd(),
|
|
1279
|
-
execution: {
|
|
1280
|
-
kind: "assistant",
|
|
1281
|
-
label: "Codex assistant",
|
|
1282
|
-
lifecycle: "service",
|
|
1283
|
-
operationId: "codex-app-server",
|
|
1284
|
-
ownerId: normalizeNullableText(ownerId) || stableHash(runtimeDir)
|
|
1285
|
-
},
|
|
1286
|
-
processProfile: profileBinary?.isFile() ? {
|
|
1287
|
-
command: codexCommand, helper, executionMode,
|
|
1288
|
-
binary: [profileBinary.dev, profileBinary.ino, profileBinary.size, profileBinary.mtimeMs, profileBinary.ctimeMs],
|
|
1289
|
-
platform: process.platform, architecture: process.arch,
|
|
1290
|
-
startup: helper ? CODEX_APP_SERVER_HELPER_STARTUP_SCRIPT : CODEX_APP_SERVER_MANAGED_STARTUP_SCRIPT,
|
|
1291
|
-
historyAdapter: !helper
|
|
1292
|
-
} : null,
|
|
1293
|
-
logPath,
|
|
1294
|
-
inheritProcessEnv: false,
|
|
1295
|
-
mode: "detached",
|
|
1296
|
-
timeout: readyTimeoutMs
|
|
1297
|
-
});
|
|
1298
|
-
if (!startResult.ok) {
|
|
1299
|
-
const failedProcessIdentity = await captureCodexAppServerProcessIdentity({
|
|
1300
|
-
commandHash,
|
|
1301
|
-
pid: startResult.pid,
|
|
1302
|
-
reportedIdentity: startResult.processIdentity,
|
|
1303
|
-
runtimeToken
|
|
1304
|
-
}).catch(() => null);
|
|
1305
|
-
const cleanup = await cleanupFailedCodexAppServerStart(runtimeDir, {
|
|
1306
|
-
helper,
|
|
1307
|
-
executionId: startResult.execution?.id,
|
|
1308
|
-
neverStarted: !Number.isSafeInteger(Number(startResult.pid)),
|
|
1309
|
-
pid: startResult.pid,
|
|
1310
|
-
processIdentity: failedProcessIdentity
|
|
1311
|
-
}, processLifecycleOptions);
|
|
1312
|
-
const error = new Error(
|
|
1313
|
-
startResult.output || startResult.error ||
|
|
1314
|
-
(helper ? "Codex isolated helper runtime failed to start." : "Codex app-server failed to start.")
|
|
1315
|
-
);
|
|
1316
|
-
error.code = cleanup.cleanupFailed
|
|
1317
|
-
? (helper
|
|
1318
|
-
? `${errorPrefix}codex_helper_runtime_cleanup_required`
|
|
1319
|
-
: `${errorPrefix}codex_app_server_cleanup_required`)
|
|
1320
|
-
: (startResult.code || (helper
|
|
1321
|
-
? `${errorPrefix}codex_helper_runtime_start_failed`
|
|
1322
|
-
: `${errorPrefix}codex_app_server_start_failed`));
|
|
1323
|
-
error.cleanupRequired = cleanup.cleanupFailed;
|
|
1324
|
-
error.execution = startResult.execution;
|
|
1325
|
-
error.retryable = cleanup.cleanupFailed ? false : startResult.retryable === true;
|
|
1326
|
-
throw error;
|
|
1327
|
-
}
|
|
1328
|
-
|
|
1329
|
-
let processIdentity;
|
|
1330
|
-
try {
|
|
1331
|
-
processIdentity = await captureCodexAppServerProcessIdentity({
|
|
1332
|
-
commandHash,
|
|
1333
|
-
pid: startResult.pid,
|
|
1334
|
-
reportedIdentity: startResult.processIdentity,
|
|
1335
|
-
runtimeToken
|
|
1336
|
-
});
|
|
1337
|
-
} catch (cause) {
|
|
1338
|
-
const cleanup = await cleanupFailedCodexAppServerStart(runtimeDir, {
|
|
1339
|
-
helper,
|
|
1340
|
-
executionId: startResult.execution?.id,
|
|
1341
|
-
pid: startResult.pid,
|
|
1342
|
-
processIdentity: startResult.processIdentity
|
|
1343
|
-
}, processLifecycleOptions);
|
|
1344
|
-
const error = new Error("Codex app-server process ownership could not be recorded.", {
|
|
1345
|
-
cause
|
|
1346
|
-
});
|
|
1347
|
-
error.code = cleanup.cleanupFailed
|
|
1348
|
-
? `${errorPrefix}codex_app_server_cleanup_required`
|
|
1349
|
-
: `${errorPrefix}codex_app_server_process_identity_unavailable`;
|
|
1350
|
-
error.cleanupRequired = cleanup.cleanupFailed;
|
|
1351
|
-
throw error;
|
|
1352
|
-
}
|
|
1353
|
-
|
|
1354
|
-
const ready = await waitForCodexAppServer(endpoint, {
|
|
1355
|
-
timeoutMs: readyTimeoutMs,
|
|
1356
|
-
WebSocketImpl
|
|
1357
|
-
});
|
|
1358
|
-
if (!ready) {
|
|
1359
|
-
const logTail = await tailTextFile(logPath);
|
|
1360
|
-
const cleanup = await cleanupFailedCodexAppServerStart(runtimeDir, {
|
|
1361
|
-
helper,
|
|
1362
|
-
executionId: startResult.execution?.id,
|
|
1363
|
-
pid: startResult.pid,
|
|
1364
|
-
processIdentity
|
|
1365
|
-
}, processLifecycleOptions);
|
|
1366
|
-
if (!cleanup.cleanupFailed && !helper) {
|
|
1367
|
-
await credentials.observeFailure(logTail);
|
|
1368
|
-
}
|
|
1369
|
-
const error = new Error([
|
|
1370
|
-
helper
|
|
1371
|
-
? "Codex isolated helper runtime did not become ready."
|
|
1372
|
-
: `Codex app-server did not become ready at ${endpoint}.`,
|
|
1373
|
-
logTail ? `Recent log output:\n${logTail}` : ""
|
|
1374
|
-
].filter(Boolean).join("\n"));
|
|
1375
|
-
error.code = cleanup.cleanupFailed
|
|
1376
|
-
? (helper
|
|
1377
|
-
? `${errorPrefix}codex_helper_runtime_cleanup_required`
|
|
1378
|
-
: `${errorPrefix}codex_app_server_cleanup_required`)
|
|
1379
|
-
: (helper
|
|
1380
|
-
? `${errorPrefix}codex_helper_runtime_start_failed`
|
|
1381
|
-
: `${errorPrefix}codex_app_server_ready_timeout`);
|
|
1382
|
-
error.cleanupRequired = cleanup.cleanupFailed;
|
|
1383
|
-
error.retryable = false;
|
|
1384
|
-
throw error;
|
|
1385
|
-
}
|
|
1386
|
-
|
|
1387
|
-
let historyAdapterBaseUrl = "";
|
|
1388
|
-
try {
|
|
1389
|
-
if (!helper) {
|
|
1390
|
-
const descriptor = JSON.parse(await readFile(path.join(runtimeDir, "history-adapter.json"), "utf8"));
|
|
1391
|
-
if (descriptor.runtimeToken !== runtimeToken || !codexHistoryAdapterUrlIsValid(descriptor.baseUrl, runtimeToken)) {
|
|
1392
|
-
throw new Error("Codex history adapter did not confirm the current runtime.");
|
|
1393
|
-
}
|
|
1394
|
-
historyAdapterBaseUrl = descriptor.baseUrl;
|
|
1395
|
-
}
|
|
1396
|
-
await assertCodexAuthGenerationCurrent(resolvedAuthStateSignature);
|
|
1397
|
-
} catch (cause) {
|
|
1398
|
-
const cleanup = await cleanupFailedCodexAppServerStart(runtimeDir, {
|
|
1399
|
-
helper,
|
|
1400
|
-
executionId: startResult.execution?.id,
|
|
1401
|
-
pid: startResult.pid,
|
|
1402
|
-
processIdentity
|
|
1403
|
-
}, processLifecycleOptions);
|
|
1404
|
-
if (cleanup.cleanupFailed) {
|
|
1405
|
-
const error = new Error("Codex app-server authentication changed and its execution could not be drained.", {
|
|
1406
|
-
cause
|
|
1407
|
-
});
|
|
1408
|
-
error.code = `${errorPrefix}codex_app_server_cleanup_required`;
|
|
1409
|
-
error.cleanupRequired = true;
|
|
1410
|
-
error.retryable = false;
|
|
1411
|
-
throw error;
|
|
1412
|
-
}
|
|
1413
|
-
throw cause;
|
|
1414
|
-
}
|
|
1415
|
-
|
|
1416
|
-
return {
|
|
1417
|
-
accountIdentitySignature: normalizeNullableText(accountIdentitySignature),
|
|
1418
|
-
attachmentHostRoot: compatibility.attachmentHostRoot,
|
|
1419
|
-
authStateSignature: resolvedAuthStateSignature,
|
|
1420
|
-
endpoint,
|
|
1421
|
-
executionId: normalizeNullableText(startResult.execution?.id),
|
|
1422
|
-
executionMode: helper
|
|
1423
|
-
? CODEX_APP_SERVER_EXECUTION_MODES.HELPER
|
|
1424
|
-
: CODEX_APP_SERVER_EXECUTION_MODES.INTERACTIVE,
|
|
1425
|
-
executionContextHash: compatibility.executionContextHash,
|
|
1426
|
-
healthz: "",
|
|
1427
|
-
historyAdapterBaseUrl,
|
|
1428
|
-
logPath,
|
|
1429
|
-
pid: Number.isSafeInteger(Number(startResult.pid)) ? Number(startResult.pid) : null,
|
|
1430
|
-
processCwd,
|
|
1431
|
-
processIdentity,
|
|
1432
|
-
processState: CODEX_APP_SERVER_PROCESS_STATE.RUNNING,
|
|
1433
|
-
provider: CODEX_APP_SERVER_PROVIDER_ID,
|
|
1434
|
-
readyz: "",
|
|
1435
|
-
runtimeDir,
|
|
1436
|
-
runtimesHash: compatibility.runtimesHash,
|
|
1437
|
-
schemaVersion: CODEX_APP_SERVER_METADATA_SCHEMA_VERSION,
|
|
1438
|
-
socketPath,
|
|
1439
|
-
startedAt: new Date().toISOString(),
|
|
1440
|
-
terminalEnvHash: codexAppServerTerminalEnvHash(normalizedTerminalEnv),
|
|
1441
|
-
toolHomeSource: helper ? "" : normalizedToolHomeSource,
|
|
1442
|
-
transport: CODEX_APP_SERVER_TRANSPORT.UNIX
|
|
1443
|
-
};
|
|
1444
|
-
}
|
|
1445
|
-
|
|
1446
|
-
async function ensureCodexAppServerRuntime(options = {}) {
|
|
1447
|
-
const runtimeDir = path.resolve(options.runtimeDir);
|
|
1448
|
-
while (codexAppServerRuntimePreparations.has(runtimeDir)) {
|
|
1449
|
-
await codexAppServerRuntimePreparations.get(runtimeDir);
|
|
1450
|
-
}
|
|
1451
|
-
// Each waiter rechecks its own auth and runtime configuration after startup.
|
|
1452
|
-
const operation = prepareCodexAppServerRuntime({ ...options, runtimeDir });
|
|
1453
|
-
codexAppServerRuntimePreparations.set(runtimeDir, operation);
|
|
1454
|
-
try {
|
|
1455
|
-
return await operation;
|
|
1456
|
-
} finally {
|
|
1457
|
-
codexAppServerRuntimePreparations.delete(runtimeDir);
|
|
1458
|
-
}
|
|
1459
|
-
}
|
|
1460
|
-
|
|
1461
|
-
async function prepareCodexAppServerRuntime(options) {
|
|
1462
|
-
const { runtimeDir } = options;
|
|
1463
|
-
const [accountIdentitySignature, authStateSignature] = await Promise.all([
|
|
1464
|
-
currentCodexAccountIdentitySignature(options),
|
|
1465
|
-
currentCodexAuthStateSignature(options)
|
|
1466
|
-
]);
|
|
1467
|
-
const runtimeOptions = {
|
|
1468
|
-
...options,
|
|
1469
|
-
accountIdentitySignature,
|
|
1470
|
-
authStateSignature
|
|
1471
|
-
};
|
|
1472
|
-
const runtimeMetadataWriter = typeof options.runtimeMetadataWriter === "function"
|
|
1473
|
-
? options.runtimeMetadataWriter
|
|
1474
|
-
: writeCodexAppServerMetadata;
|
|
1475
|
-
await ensureWritablePrivateDirectory(runtimeDir);
|
|
1476
|
-
|
|
1477
|
-
const existing = await readCodexAppServerMetadata(runtimeDir);
|
|
1478
|
-
const existingStatus = existing ? await codexAppServerRuntimeStatus(existing, runtimeOptions) : null;
|
|
1479
|
-
if (existingStatus?.reusable) {
|
|
1480
|
-
return {
|
|
1481
|
-
...existingStatus.metadata,
|
|
1482
|
-
reused: true,
|
|
1483
|
-
runtimeStatus: existingStatus.status
|
|
1484
|
-
};
|
|
1485
|
-
}
|
|
1486
|
-
|
|
1487
|
-
const releaseLock = await acquireRuntimeLock(runtimeDir, runtimeOptions);
|
|
1488
|
-
try {
|
|
1489
|
-
let socketOwnerDrained = false;
|
|
1490
|
-
const afterLock = await readCodexAppServerMetadata(runtimeDir);
|
|
1491
|
-
const afterLockStatus = afterLock ? await codexAppServerRuntimeStatus(afterLock, runtimeOptions) : null;
|
|
1492
|
-
if (afterLockStatus?.reusable) {
|
|
1493
|
-
return {
|
|
1494
|
-
...afterLockStatus.metadata,
|
|
1495
|
-
reused: true,
|
|
1496
|
-
runtimeStatus: afterLockStatus.status
|
|
1497
|
-
};
|
|
1498
|
-
}
|
|
1499
|
-
|
|
1500
|
-
if (afterLock && (!afterLockStatus || afterLockStatus.replace !== false)) {
|
|
1501
|
-
const stopped = await stopCodexAppServerProcess(runtimeDir, runtimeOptions);
|
|
1502
|
-
if (stopped.processExitVerified !== true) {
|
|
1503
|
-
const error = new Error(
|
|
1504
|
-
`${clientInfo.title} found an earlier Codex app-server but could not prove that its owned execution was stopped. It refused to start a replacement because two assistant processes could damage the session or worsen resource pressure.`
|
|
1505
|
-
);
|
|
1506
|
-
error.code = `${errorPrefix}codex_app_server_process_identity_unverified`;
|
|
1507
|
-
error.cleanupRequired = true;
|
|
1508
|
-
error.retryable = false;
|
|
1509
|
-
throw error;
|
|
1510
|
-
}
|
|
1511
|
-
socketOwnerDrained = true;
|
|
1512
|
-
}
|
|
1513
|
-
|
|
1514
|
-
const started = await startCodexAppServerProcess({
|
|
1515
|
-
...runtimeOptions,
|
|
1516
|
-
runtimeDir,
|
|
1517
|
-
socketOwnerDrained
|
|
1518
|
-
});
|
|
1519
|
-
try {
|
|
1520
|
-
await assertCodexAuthGenerationCurrent(started.authStateSignature, runtimeOptions);
|
|
1521
|
-
await runtimeMetadataWriter(runtimeDir, started);
|
|
1522
|
-
await assertCodexAuthGenerationCurrent(started.authStateSignature, runtimeOptions);
|
|
1523
|
-
} catch (error) {
|
|
1524
|
-
const helper = codexAppServerIsHelper(runtimeOptions);
|
|
1525
|
-
const cleanup = await cleanupFailedCodexAppServerStart(runtimeDir, {
|
|
1526
|
-
helper,
|
|
1527
|
-
executionId: started.executionId,
|
|
1528
|
-
pid: started.pid,
|
|
1529
|
-
processIdentity: started.processIdentity
|
|
1530
|
-
}, runtimeOptions);
|
|
1531
|
-
const failure = new Error(
|
|
1532
|
-
helper
|
|
1533
|
-
? "Codex isolated helper runtime metadata could not be recorded."
|
|
1534
|
-
: "Codex app-server runtime could not be published safely.",
|
|
1535
|
-
{ cause: error }
|
|
1536
|
-
);
|
|
1537
|
-
failure.code = cleanup.cleanupFailed
|
|
1538
|
-
? (helper
|
|
1539
|
-
? `${errorPrefix}codex_helper_runtime_cleanup_required`
|
|
1540
|
-
: `${errorPrefix}codex_app_server_cleanup_required`)
|
|
1541
|
-
: (error?.code || (helper
|
|
1542
|
-
? `${errorPrefix}codex_helper_runtime_metadata_failed`
|
|
1543
|
-
: `${errorPrefix}codex_app_server_metadata_failed`));
|
|
1544
|
-
failure.cleanupRequired = cleanup.cleanupFailed;
|
|
1545
|
-
failure.retryable = false;
|
|
1546
|
-
throw failure;
|
|
1547
|
-
}
|
|
1548
|
-
return {
|
|
1549
|
-
...started,
|
|
1550
|
-
reused: false
|
|
1551
|
-
};
|
|
1552
|
-
} finally {
|
|
1553
|
-
await releaseLock();
|
|
1554
|
-
}
|
|
1555
|
-
}
|
|
1556
|
-
|
|
1557
|
-
return {
|
|
1558
|
-
ensure: ensureCodexAppServerRuntime,
|
|
1559
|
-
start: startCodexAppServerProcess,
|
|
1560
|
-
stop: stopCodexAppServerRuntime,
|
|
1561
|
-
isLive: codexAppServerMetadataIsLive,
|
|
1562
|
-
cleanupFailedStart: cleanupFailedCodexAppServerStart
|
|
1563
|
-
};
|
|
1564
|
-
}
|
|
1565
|
-
|