@jskit-ai/assistant-core 0.2.6 → 0.2.8

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.
Files changed (214) hide show
  1. package/examples/conversation/package.json +5 -5
  2. package/package.json +18 -7
  3. package/src/client/browser.js +21 -0
  4. package/src/client/conversation/AssistantAttachmentQueue.vue +25 -108
  5. package/src/client/conversation/AssistantComposerActions.vue +1 -1
  6. package/src/client/conversation/AssistantConversationElement.vue +14 -52
  7. package/src/client/conversation/AssistantGoalControl.vue +18 -12
  8. package/src/client/conversation/AssistantPromptInput.vue +6 -91
  9. package/src/client/conversation/AssistantTranscript.css +505 -0
  10. package/src/client/conversation/AssistantTranscript.vue +24 -1197
  11. package/src/client/conversation/attachmentFiles.js +158 -0
  12. package/src/client/conversation/attachmentQueueItems.js +121 -0
  13. package/src/client/conversation/attachmentUploadRows.js +265 -0
  14. package/src/client/conversation/conversationDisplay.js +54 -0
  15. package/src/client/conversation/messageDelivery.js +13 -9
  16. package/src/client/conversation/retainedConversation.js +14 -3
  17. package/src/client/conversation/submitText.js +13 -2
  18. package/src/client/conversation/transcriptEntries.js +225 -0
  19. package/src/client/conversation/useAssistantAttachments.js +86 -388
  20. package/src/client/conversation/useAssistantQuestions.js +16 -3
  21. package/src/client/conversation/useAssistantSuggestions.js +16 -2
  22. package/src/client/conversation/useScrollToBottom.js +17 -19
  23. package/src/client/conversation/useTextareaAutoGrow.js +115 -0
  24. package/src/client/conversation/useTranscriptScrollFollow.js +525 -0
  25. package/src/client/lib/assistantApi.js +192 -161
  26. package/src/client/types.js +110 -0
  27. package/src/server/conversation/attachments.js +21 -6
  28. package/src/server/conversation/boundedToolLoop.js +1 -0
  29. package/src/server/conversation/claudeClient.js +71 -20
  30. package/src/server/conversation/claudeCommandRun.js +191 -0
  31. package/src/server/conversation/claudeHistory.js +7 -0
  32. package/src/server/conversation/claudeNativeTurn.js +708 -0
  33. package/src/server/conversation/claudeProcess.js +186 -112
  34. package/src/server/conversation/claudeProcessStart.js +214 -0
  35. package/src/server/conversation/claudePromptDelivery.js +183 -0
  36. package/src/server/conversation/claudeSessionCleanup.js +248 -0
  37. package/src/server/conversation/claudeTools.js +15 -3
  38. package/src/server/conversation/claudeTurn.js +212 -1388
  39. package/src/server/conversation/codexAccountReader.js +168 -0
  40. package/src/server/conversation/codexAuthPreflight.js +5 -4
  41. package/src/server/conversation/codexClient.js +18 -6
  42. package/src/server/conversation/codexConfiguration.js +8 -844
  43. package/src/server/conversation/codexConversationStops.js +377 -0
  44. package/src/server/conversation/codexConversationStore.js +119 -85
  45. package/src/server/conversation/codexConversationTurns.js +378 -0
  46. package/src/server/conversation/codexConversations.js +134 -866
  47. package/src/server/conversation/codexDetachedConversation.js +203 -0
  48. package/src/server/conversation/codexDetachedTurn.js +145 -104
  49. package/src/server/conversation/codexEvents.js +5 -200
  50. package/src/server/conversation/codexGoalCommands.js +14 -4
  51. package/src/server/conversation/codexHelperExecution.js +57 -57
  52. package/src/server/conversation/codexHelperThreadLedger.js +30 -288
  53. package/src/server/conversation/codexHelperThreadRecord.js +304 -0
  54. package/src/server/conversation/codexHelperThreadRecovery.js +54 -76
  55. package/src/server/conversation/codexHelperThreads.js +94 -93
  56. package/src/server/conversation/codexHistoryAdapter.js +13 -8
  57. package/src/server/conversation/codexIsolation.js +672 -0
  58. package/src/server/conversation/codexLiveItems.js +379 -0
  59. package/src/server/conversation/codexMessageCommands.js +68 -286
  60. package/src/server/conversation/codexMessageDelivery.js +46 -37
  61. package/src/server/conversation/codexModelCatalog.js +86 -7
  62. package/src/server/conversation/codexNotificationObserver.js +81 -88
  63. package/src/server/conversation/codexNotificationQueue.js +21 -10
  64. package/src/server/conversation/codexObservationControl.js +105 -89
  65. package/src/server/conversation/codexOutput.js +84 -419
  66. package/src/server/conversation/codexProcess.js +29 -1053
  67. package/src/server/conversation/codexProcessLeader.js +1 -0
  68. package/src/server/conversation/codexPromptDispatch.js +241 -0
  69. package/src/server/conversation/codexProvider.js +147 -1807
  70. package/src/server/conversation/codexProviderAuth.js +348 -0
  71. package/src/server/conversation/codexProviderInvalidation.js +371 -0
  72. package/src/server/conversation/codexProviderInventories.js +437 -0
  73. package/src/server/conversation/codexProviderOwner.js +149 -957
  74. package/src/server/conversation/codexProviderRelease.js +465 -0
  75. package/src/server/conversation/codexProviderSelection.js +26 -15
  76. package/src/server/conversation/codexProviderThreads.js +698 -0
  77. package/src/server/conversation/codexProviderTurns.js +453 -0
  78. package/src/server/conversation/codexReasoning.js +32 -23
  79. package/src/server/conversation/codexRenewalCommands.js +18 -6
  80. package/src/server/conversation/codexRenewalThreads.js +7 -22
  81. package/src/server/conversation/codexResultSettlement.js +70 -88
  82. package/src/server/conversation/codexRunJournal.js +91 -97
  83. package/src/server/conversation/codexRuntime.js +426 -0
  84. package/src/server/conversation/codexRuntimeInventory.js +113 -0
  85. package/src/server/conversation/codexRuntimeLifecycle.js +39 -13
  86. package/src/server/conversation/codexRuntimeStart.js +431 -0
  87. package/src/server/conversation/codexRuntimeStop.js +324 -0
  88. package/src/server/conversation/codexSessionCleanup.js +25 -10
  89. package/src/server/conversation/codexSessionReconciliation.js +21 -7
  90. package/src/server/conversation/codexThreadReadiness.js +49 -52
  91. package/src/server/conversation/codexThreadSnapshot.js +210 -0
  92. package/src/server/conversation/codexThreadStatus.js +48 -62
  93. package/src/server/conversation/codexTurn.js +171 -55
  94. package/src/server/conversation/codexTurnActivity.js +210 -0
  95. package/src/server/conversation/codexTurnControl.js +44 -48
  96. package/src/server/conversation/codexTurnEndings.js +610 -0
  97. package/src/server/conversation/codexTurnRecovery.js +97 -848
  98. package/src/server/conversation/codexTurnState.js +138 -111
  99. package/src/server/conversation/completedResponses.js +227 -0
  100. package/src/server/conversation/configuration.js +2 -2
  101. package/src/server/conversation/continuity.js +43 -6
  102. package/src/server/conversation/conversationDelivery.js +394 -0
  103. package/src/server/conversation/conversationEntries.js +365 -0
  104. package/src/server/conversation/conversationEvents.js +192 -0
  105. package/src/server/conversation/conversationGoals.js +187 -0
  106. package/src/server/conversation/conversationHandles.js +237 -0
  107. package/src/server/conversation/conversationReads.js +206 -0
  108. package/src/server/conversation/conversationRefusals.js +58 -0
  109. package/src/server/conversation/conversationReplacement.js +254 -0
  110. package/src/server/conversation/conversationSending.js +273 -0
  111. package/src/server/conversation/conversationStopping.js +121 -0
  112. package/src/server/conversation/conversationTurnDelivery.js +413 -0
  113. package/src/server/conversation/conversationTurns.js +511 -0
  114. package/src/server/conversation/deliveryTiming.js +25 -3
  115. package/src/server/conversation/fileStorage.js +11 -6
  116. package/src/server/conversation/hookBridge.js +14 -3
  117. package/src/server/conversation/index.js +1 -1
  118. package/src/server/conversation/localExecution.js +82 -625
  119. package/src/server/conversation/nativeCommandCallbacks.js +10 -0
  120. package/src/server/conversation/nativeConversationLifecycle.js +379 -0
  121. package/src/server/conversation/nativeHistoryExport.js +27 -18
  122. package/src/server/conversation/normalize.js +9 -0
  123. package/src/server/conversation/openCodeClient.js +295 -366
  124. package/src/server/conversation/openCodeNativeHistory.js +141 -0
  125. package/src/server/conversation/openCodeNativeSession.js +79 -0
  126. package/src/server/conversation/openCodeProcess.js +109 -53
  127. package/src/server/conversation/openCodeProcessOwner.js +565 -0
  128. package/src/server/conversation/openCodeRuntime.js +111 -1814
  129. package/src/server/conversation/openCodeSessionTurns.js +547 -0
  130. package/src/server/conversation/openCodeTemporaryConversations.js +394 -0
  131. package/src/server/conversation/openCodeTools.js +28 -14
  132. package/src/server/conversation/openCodeTurn.js +80 -74
  133. package/src/server/conversation/openCodeTurnMonitor.js +388 -0
  134. package/src/server/conversation/processIdentity.js +582 -0
  135. package/src/server/conversation/provider.js +21 -6
  136. package/src/server/conversation/providers/api.js +268 -107
  137. package/src/server/conversation/providers/claude.js +29 -6
  138. package/src/server/conversation/providers/claudeDriver.js +325 -250
  139. package/src/server/conversation/providers/codex.js +69 -33
  140. package/src/server/conversation/providers/codexDriver.js +503 -1081
  141. package/src/server/conversation/providers/codexDriverConnection.js +331 -0
  142. package/src/server/conversation/providers/codexDriverTurns.js +464 -0
  143. package/src/server/conversation/providers/opencode.js +30 -4
  144. package/src/server/conversation/providers/opencodeDriver.js +321 -659
  145. package/src/server/conversation/providers/opencodeStandalone.js +475 -0
  146. package/src/server/conversation/providers/scoped.js +64 -19
  147. package/src/server/conversation/runtime.js +171 -2863
  148. package/src/server/conversation/storage.js +91 -50
  149. package/src/server/conversation/streams.js +15 -2
  150. package/src/server/conversation/structuredOutput.js +7 -11
  151. package/src/server/conversation/systemPrompt.js +13 -1
  152. package/src/server/conversation/tools.js +47 -15
  153. package/src/server/conversation/transcript.js +49 -20
  154. package/src/server/hosts/claudeTurn.js +2 -4
  155. package/src/server/hosts/codexConfiguration.js +6 -8
  156. package/src/server/hosts/codexEvents.js +4 -2
  157. package/src/server/hosts/codexProcess.js +1 -1
  158. package/src/server/hosts/codexProvider.js +11 -9
  159. package/src/server/hosts/codexTurn.js +3 -3
  160. package/src/server/hosts/openCodeClient.js +4 -2
  161. package/src/server/index.js +0 -2
  162. package/src/server/lib/aiConnectionClient.js +35 -25
  163. package/src/server/lib/ndjson.js +1 -0
  164. package/src/server/lib/providers/anthropicClient.js +15 -6
  165. package/src/server/lib/providers/common.js +10 -6
  166. package/src/server/lib/providers/openAiCompatibleClient.js +40 -23
  167. package/src/server/lib/serviceToolCatalog.js +29 -283
  168. package/src/server/lib/serviceToolEntries.js +270 -0
  169. package/src/server/types.js +78 -0
  170. package/src/shared/assistantResource.js +0 -23
  171. package/src/shared/conversation/answerChoiceSugar.js +2 -14
  172. package/src/shared/conversation/messageInputSugar.js +18 -0
  173. package/src/shared/conversation/numberedQuestionSugar.js +6 -16
  174. package/src/shared/conversation/pagination.js +1 -1
  175. package/src/shared/conversation/questions.js +1 -0
  176. package/src/shared/conversation/streaming.js +1 -0
  177. package/src/shared/index.js +0 -1
  178. package/src/testing/conversationStorageContract.js +20 -0
  179. package/test/aiConnectionClient.test.js +42 -1
  180. package/test/apiConversationDriver.test.js +217 -38
  181. package/test/assistantBoundedToolLoop.test.js +1 -1
  182. package/test/claudeConversationSession.test.js +15 -11
  183. package/test/claudeInstructionResume.test.js +1 -1
  184. package/test/claudeProcess.test.js +9 -9
  185. package/test/claudeTurn.test.js +21 -8
  186. package/test/codexAccountChange.test.js +5 -3
  187. package/test/codexConversationSession.test.js +22 -21
  188. package/test/codexEvents.test.js +3 -1
  189. package/test/codexHelperDeletion.test.js +3 -7
  190. package/test/codexIsolation.test.js +6 -6
  191. package/test/codexModelCatalog.test.js +6 -7
  192. package/test/codexProcess.test.js +3 -3
  193. package/test/codexThreadSelection.test.js +4 -1
  194. package/test/codexTurn.test.js +29 -20
  195. package/test/conversationApiFacilities.test.js +11 -9
  196. package/test/conversationAttachments.test.js +3 -3
  197. package/test/conversationConfiguration.test.js +4 -4
  198. package/test/conversationNativeStorage.test.js +11 -10
  199. package/test/conversationReplacement.test.js +13 -13
  200. package/test/conversationRuntime.test.js +9 -5
  201. package/test/conversationSession.test.js +78 -22
  202. package/test/conversationTools.test.js +47 -26
  203. package/test/fileConversationStorage.test.js +10 -3
  204. package/test/fixtures/codexHelperThreadLedger.js +3 -3
  205. package/test/frameworkActions.test.js +33 -18
  206. package/test/localConversationExecution.test.js +4 -4
  207. package/test/localExecution.test.js +1 -1
  208. package/test/openCodeConnectionChange.test.js +3 -2
  209. package/test/openCodeRuntime.test.js +8 -7
  210. package/test/openCodeTools.test.js +1 -1
  211. package/test/opencodeConversationSession.test.js +4 -3
  212. package/test/serviceToolCatalog.test.js +3 -8
  213. package/src/server/lib/assistantCompletion.js +0 -190
  214. 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 { CodexAppServerJsonRpcClient, socketPathFromCodexAppServerEndpoint } from "./codexClient.js";
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 { copyCodexProviderHome, prepareCodexModelCatalog } from "./codexConfiguration.js";
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 = process.env.CODEX_HOME || path.join(os.homedir(), ".codex");
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 = process.env, codexHome = "") {
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 = process.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
-