@jskit-ai/assistant-core 0.2.7 → 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.
- 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
|
@@ -0,0 +1,426 @@
|
|
|
1
|
+
import { fileURLToPath } from "node:url";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import WebSocket from "ws";
|
|
4
|
+
import { setTimeout as delay } from "node:timers/promises";
|
|
5
|
+
import { normalizePositiveInteger } from "@jskit-ai/kernel/shared/support/normalize";
|
|
6
|
+
import { CodexAppServerJsonRpcClient, socketPathFromCodexAppServerEndpoint } from "./codexClient.js";
|
|
7
|
+
import { normalizeNullableText } from "./normalize.js";
|
|
8
|
+
import {
|
|
9
|
+
CODEX_APP_SERVER_DEFAULT_ENVIRONMENT, CODEX_APP_SERVER_ENDPOINT_STATUS, CODEX_APP_SERVER_EXECUTION_MODES,
|
|
10
|
+
CODEX_APP_SERVER_LIVENESS_TIMEOUT_MS, CODEX_APP_SERVER_METADATA_SCHEMA_VERSION, CODEX_APP_SERVER_PROCESS_IDENTITY_STATUS,
|
|
11
|
+
CODEX_APP_SERVER_PROCESS_STATE, CODEX_APP_SERVER_PROVIDER_ID, CODEX_APP_SERVER_READY_TIMEOUT_MS,
|
|
12
|
+
CODEX_APP_SERVER_RUNTIME_STATUS, CODEX_APP_SERVER_TRANSPORT, codexAppServerExecutionMode,
|
|
13
|
+
codexAppServerHelperWorkspaceDir, codexAppServerIsHelper, codexAppServerLivenessTimeoutMs,
|
|
14
|
+
codexAppServerTerminalEnvHash, codexHistoryAdapterUrlIsValid, ensureWritablePrivateDirectory, fileExists,
|
|
15
|
+
normalizeCodexAppServerMetadata, readCodexAppServerMetadata, writeCodexAppServerMetadata
|
|
16
|
+
} from "./codexProcess.js";
|
|
17
|
+
import { createCodexAppServerRuntimeStop } from "./codexRuntimeStop.js";
|
|
18
|
+
import { createCodexAppServerProcessStart } from "./codexRuntimeStart.js";
|
|
19
|
+
import { createConversationProcessIdentity } from "./processIdentity.js";
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* The host's process facility. Managed hosts add prepare, stopOwned and authoritative to the local one.
|
|
23
|
+
* @typedef {object} CodexRuntimeExecution
|
|
24
|
+
* @property {(request: object) => Promise<object>} run
|
|
25
|
+
* @property {(executionId: string, options: object) => Promise<object>} stop
|
|
26
|
+
* @property {(owner: object, options: object) => Promise<object>} [stopOwned]
|
|
27
|
+
* @property {() => Promise<void>} [prepare]
|
|
28
|
+
* @property {boolean} [authoritative]
|
|
29
|
+
*/
|
|
30
|
+
|
|
31
|
+
/** @typedef {ReturnType<typeof createConversationProcessIdentity>} CodexProcessIdentity */
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* The host's account facility for one native runtime.
|
|
35
|
+
* @typedef {object} CodexRuntimeCredentials
|
|
36
|
+
* @property {(options: object) => Promise<string>} identity
|
|
37
|
+
* @property {(options: object) => Promise<string>} generation
|
|
38
|
+
* @property {(captured: string) => Promise<void>} assertCurrent
|
|
39
|
+
* @property {(output: string) => void} observeFailure
|
|
40
|
+
*/
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* What the process start reads from the runtime configuration.
|
|
44
|
+
* @typedef {object} CodexRuntimeSettings
|
|
45
|
+
* @property {string | undefined} commandHookCommand
|
|
46
|
+
* @property {string} errorPrefix
|
|
47
|
+
* @property {string} modelCatalogEnvironmentName
|
|
48
|
+
* @property {string} processLabel
|
|
49
|
+
* @property {string} processPath
|
|
50
|
+
* @property {string} runtimeTokenEnvironmentName
|
|
51
|
+
* @property {string} commandHashEnvironmentName
|
|
52
|
+
* @property {string} socketPathHelp
|
|
53
|
+
* @property {string | null} umask
|
|
54
|
+
*/
|
|
55
|
+
|
|
56
|
+
// One preparation at a time per runtime directory, across every runtime in this
|
|
57
|
+
// process: a second start would race the first for the same socket and lock.
|
|
58
|
+
const codexAppServerRuntimePreparations = new Map();
|
|
59
|
+
|
|
60
|
+
// The process identity a runtime caller's own liveness check reports for the process group.
|
|
61
|
+
function codexAppServerLivenessIdentity(pid, alive) {
|
|
62
|
+
return {
|
|
63
|
+
processGroupIds: alive ? [pid] : [],
|
|
64
|
+
status: alive ? CODEX_APP_SERVER_PROCESS_IDENTITY_STATUS.EXACT : CODEX_APP_SERVER_PROCESS_IDENTITY_STATUS.ABSENT
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* The production shared runtime: one process owner per stable runtime directory.
|
|
70
|
+
* Execution and credential facilities retain host policy; threads do not own this process.
|
|
71
|
+
* @param {object} dependencies
|
|
72
|
+
* @param {CodexRuntimeExecution} dependencies.execution
|
|
73
|
+
* @param {CodexRuntimeCredentials} dependencies.credentials
|
|
74
|
+
* @param {object} [dependencies.configuration]
|
|
75
|
+
*/
|
|
76
|
+
export function createCodexAppServerRuntime({ execution, credentials, configuration = {} }) {
|
|
77
|
+
const {
|
|
78
|
+
clientInfo = { name: "jskit", title: "JSKIT", version: "0.1.0" },
|
|
79
|
+
commandHookCommand,
|
|
80
|
+
errorPrefix = "",
|
|
81
|
+
modelCatalogEnvironmentName = CODEX_APP_SERVER_DEFAULT_ENVIRONMENT.modelCatalogSource,
|
|
82
|
+
processLabel = "jskit-codex-app-server",
|
|
83
|
+
processPath = fileURLToPath(new URL("./codexProcessLeader.js", import.meta.url)),
|
|
84
|
+
runtimeTokenEnvironmentName = CODEX_APP_SERVER_DEFAULT_ENVIRONMENT.runtimeToken,
|
|
85
|
+
commandHashEnvironmentName = CODEX_APP_SERVER_DEFAULT_ENVIRONMENT.commandHash,
|
|
86
|
+
socketPathHelp = "Configure a shorter host runtime directory.",
|
|
87
|
+
umask = "0007"
|
|
88
|
+
} = configuration;
|
|
89
|
+
const codexProcessIdentity = createConversationProcessIdentity({
|
|
90
|
+
runtimeTokenEnvironmentName, commandHashEnvironmentName,
|
|
91
|
+
label: "Codex app-server",
|
|
92
|
+
unavailableCode: `${errorPrefix}codex_app_server_process_identity_unavailable`
|
|
93
|
+
});
|
|
94
|
+
const runtimeStop = createCodexAppServerRuntimeStop({ execution, codexProcessIdentity, errorPrefix });
|
|
95
|
+
const processStart = createCodexAppServerProcessStart({
|
|
96
|
+
execution,
|
|
97
|
+
credentials,
|
|
98
|
+
codexProcessIdentity,
|
|
99
|
+
runtimeStop,
|
|
100
|
+
codexAppServerEndpointStatus,
|
|
101
|
+
waitForCodexAppServer,
|
|
102
|
+
settings: {
|
|
103
|
+
commandHookCommand,
|
|
104
|
+
errorPrefix,
|
|
105
|
+
modelCatalogEnvironmentName,
|
|
106
|
+
processLabel,
|
|
107
|
+
processPath,
|
|
108
|
+
runtimeTokenEnvironmentName,
|
|
109
|
+
commandHashEnvironmentName,
|
|
110
|
+
socketPathHelp,
|
|
111
|
+
umask
|
|
112
|
+
}
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
function codexAppServerMetadataIsWellFormed(metadata = {}, options = {}) {
|
|
116
|
+
const expectedAttachmentHostRoot = options.compatibility.attachmentHostRoot;
|
|
117
|
+
const expectedAccountIdentitySignature = normalizeNullableText(options.accountIdentitySignature);
|
|
118
|
+
const expectedExecutionMode = codexAppServerExecutionMode(options);
|
|
119
|
+
const expectedToolHomeSource = codexAppServerIsHelper(options) ? "" : normalizeNullableText(options.toolHomeSource);
|
|
120
|
+
const expectedTerminalEnvHash = codexAppServerTerminalEnvHash(codexAppServerIsHelper(options) ? {} : options.terminalEnv);
|
|
121
|
+
const expectedRuntimesHash = options.compatibility.runtimesHash;
|
|
122
|
+
const expectedExecutionContextHash = options.compatibility.executionContextHash;
|
|
123
|
+
const expectedHelperProcessCwd = expectedExecutionMode === CODEX_APP_SERVER_EXECUTION_MODES.HELPER
|
|
124
|
+
? codexAppServerHelperWorkspaceDir(metadata.runtimeDir)
|
|
125
|
+
: "";
|
|
126
|
+
return Boolean(
|
|
127
|
+
metadata.schemaVersion === CODEX_APP_SERVER_METADATA_SCHEMA_VERSION &&
|
|
128
|
+
metadata.accountIdentitySignature === expectedAccountIdentitySignature &&
|
|
129
|
+
metadata.attachmentHostRoot === expectedAttachmentHostRoot &&
|
|
130
|
+
metadata.authStateSignature &&
|
|
131
|
+
metadata.executionId &&
|
|
132
|
+
metadata.executionContextHash === expectedExecutionContextHash &&
|
|
133
|
+
metadata.executionMode === expectedExecutionMode &&
|
|
134
|
+
metadata.processCwd &&
|
|
135
|
+
runtimeStop.processMetadataIsIdentifiable(metadata) &&
|
|
136
|
+
metadata.processState === CODEX_APP_SERVER_PROCESS_STATE.RUNNING &&
|
|
137
|
+
(expectedExecutionMode === CODEX_APP_SERVER_EXECUTION_MODES.HELPER ||
|
|
138
|
+
codexHistoryAdapterUrlIsValid(metadata.historyAdapterBaseUrl, metadata.processIdentity.runtimeToken)) &&
|
|
139
|
+
(!expectedHelperProcessCwd || metadata.processCwd === expectedHelperProcessCwd) &&
|
|
140
|
+
metadata.provider === CODEX_APP_SERVER_PROVIDER_ID &&
|
|
141
|
+
metadata.runtimesHash === expectedRuntimesHash &&
|
|
142
|
+
metadata.terminalEnvHash === expectedTerminalEnvHash &&
|
|
143
|
+
metadata.toolHomeSource === expectedToolHomeSource &&
|
|
144
|
+
metadata.transport === CODEX_APP_SERVER_TRANSPORT.UNIX &&
|
|
145
|
+
metadata.endpoint &&
|
|
146
|
+
metadata.socketPath
|
|
147
|
+
);
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
async function codexAppServerEndpointStatus(endpoint = "", {
|
|
151
|
+
timeoutMs = CODEX_APP_SERVER_LIVENESS_TIMEOUT_MS,
|
|
152
|
+
WebSocketImpl = WebSocket
|
|
153
|
+
} = {}) {
|
|
154
|
+
const normalizedEndpoint = normalizeNullableText(endpoint);
|
|
155
|
+
const socketPath = socketPathFromCodexAppServerEndpoint(normalizedEndpoint);
|
|
156
|
+
if (!socketPath || !await fileExists(socketPath)) {
|
|
157
|
+
return {
|
|
158
|
+
status: CODEX_APP_SERVER_ENDPOINT_STATUS.MISSING
|
|
159
|
+
};
|
|
160
|
+
}
|
|
161
|
+
const normalizedTimeoutMs = normalizePositiveInteger(timeoutMs, { fallback: CODEX_APP_SERVER_LIVENESS_TIMEOUT_MS });
|
|
162
|
+
return {
|
|
163
|
+
status: await probeCodexAppServer(normalizedEndpoint, normalizedTimeoutMs, WebSocketImpl)
|
|
164
|
+
};
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
// One bounded connect-and-initialize handshake: responsive, timeout or unreachable.
|
|
168
|
+
async function probeCodexAppServer(endpoint, timeoutMs, WebSocketImpl) {
|
|
169
|
+
const client = new CodexAppServerJsonRpcClient({
|
|
170
|
+
endpoint,
|
|
171
|
+
requestTimeoutMs: timeoutMs,
|
|
172
|
+
WebSocketImpl
|
|
173
|
+
});
|
|
174
|
+
let timeout = null;
|
|
175
|
+
const probe = (async () => {
|
|
176
|
+
await client.connect();
|
|
177
|
+
await client.initialize({ clientInfo });
|
|
178
|
+
return CODEX_APP_SERVER_ENDPOINT_STATUS.RESPONSIVE;
|
|
179
|
+
})();
|
|
180
|
+
probe.catch(() => null);
|
|
181
|
+
try {
|
|
182
|
+
return await Promise.race([
|
|
183
|
+
probe,
|
|
184
|
+
new Promise((resolve) => {
|
|
185
|
+
timeout = setTimeout(() => resolve(CODEX_APP_SERVER_ENDPOINT_STATUS.TIMEOUT), timeoutMs);
|
|
186
|
+
timeout.unref();
|
|
187
|
+
})
|
|
188
|
+
]);
|
|
189
|
+
} catch {
|
|
190
|
+
return CODEX_APP_SERVER_ENDPOINT_STATUS.UNREACHABLE;
|
|
191
|
+
} finally {
|
|
192
|
+
clearTimeout(timeout);
|
|
193
|
+
client.close();
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
async function codexAppServerMetadataIsLive(metadata = {}, options = {}) {
|
|
198
|
+
return (await codexAppServerRuntimeStatus(metadata, options)).status === CODEX_APP_SERVER_RUNTIME_STATUS.LIVE;
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
async function codexAppServerRuntimeStatus(metadata = {}, options = {}) {
|
|
202
|
+
const normalized = normalizeCodexAppServerMetadata(metadata);
|
|
203
|
+
if (
|
|
204
|
+
runtimeStop.processMetadataIsIdentifiable(normalized) &&
|
|
205
|
+
normalized.processState === CODEX_APP_SERVER_PROCESS_STATE.STOPPED &&
|
|
206
|
+
normalized.processExitVerifiedAt
|
|
207
|
+
) {
|
|
208
|
+
return {
|
|
209
|
+
metadata: normalized,
|
|
210
|
+
replace: true,
|
|
211
|
+
reusable: false,
|
|
212
|
+
status: CODEX_APP_SERVER_RUNTIME_STATUS.EXITED
|
|
213
|
+
};
|
|
214
|
+
}
|
|
215
|
+
if (!codexAppServerMetadataIsWellFormed(normalized, options)) {
|
|
216
|
+
return {
|
|
217
|
+
metadata: normalized,
|
|
218
|
+
replace: true,
|
|
219
|
+
reusable: false,
|
|
220
|
+
status: CODEX_APP_SERVER_RUNTIME_STATUS.INCOMPATIBLE
|
|
221
|
+
};
|
|
222
|
+
}
|
|
223
|
+
const authStateSignature = await credentials.generation(options);
|
|
224
|
+
if (normalized.authStateSignature !== authStateSignature) {
|
|
225
|
+
return {
|
|
226
|
+
metadata: normalized,
|
|
227
|
+
replace: true,
|
|
228
|
+
reusable: false,
|
|
229
|
+
status: CODEX_APP_SERVER_RUNTIME_STATUS.INCOMPATIBLE
|
|
230
|
+
};
|
|
231
|
+
}
|
|
232
|
+
// A runtime caller may check its process group's liveness itself; otherwise the process identity is inspected.
|
|
233
|
+
const processIdentity = options.processGroupIsAlive
|
|
234
|
+
? codexAppServerLivenessIdentity(normalized.pid, options.processGroupIsAlive(normalized.pid))
|
|
235
|
+
: await runtimeStop.inspectProcessIdentity(normalized, options);
|
|
236
|
+
if (
|
|
237
|
+
processIdentity.status === CODEX_APP_SERVER_PROCESS_IDENTITY_STATUS.ABSENT ||
|
|
238
|
+
processIdentity.status === CODEX_APP_SERVER_PROCESS_IDENTITY_STATUS.MISMATCH
|
|
239
|
+
) {
|
|
240
|
+
return {
|
|
241
|
+
metadata: normalized,
|
|
242
|
+
replace: true,
|
|
243
|
+
reusable: false,
|
|
244
|
+
status: CODEX_APP_SERVER_RUNTIME_STATUS.EXITED
|
|
245
|
+
};
|
|
246
|
+
}
|
|
247
|
+
if (processIdentity.status !== CODEX_APP_SERVER_PROCESS_IDENTITY_STATUS.EXACT) {
|
|
248
|
+
return {
|
|
249
|
+
metadata: normalized,
|
|
250
|
+
replace: false,
|
|
251
|
+
reusable: true,
|
|
252
|
+
status: CODEX_APP_SERVER_RUNTIME_STATUS.SUSPECT
|
|
253
|
+
};
|
|
254
|
+
}
|
|
255
|
+
const endpoint = await codexAppServerEndpointStatus(normalized.endpoint, {
|
|
256
|
+
timeoutMs: codexAppServerLivenessTimeoutMs(options),
|
|
257
|
+
WebSocketImpl: options.WebSocketImpl
|
|
258
|
+
});
|
|
259
|
+
if (endpoint.status === CODEX_APP_SERVER_ENDPOINT_STATUS.RESPONSIVE) {
|
|
260
|
+
return {
|
|
261
|
+
metadata: normalized,
|
|
262
|
+
replace: false,
|
|
263
|
+
reusable: true,
|
|
264
|
+
status: CODEX_APP_SERVER_RUNTIME_STATUS.LIVE
|
|
265
|
+
};
|
|
266
|
+
}
|
|
267
|
+
if (endpoint.status === CODEX_APP_SERVER_ENDPOINT_STATUS.MISSING) {
|
|
268
|
+
return {
|
|
269
|
+
metadata: normalized,
|
|
270
|
+
replace: true,
|
|
271
|
+
reusable: false,
|
|
272
|
+
status: CODEX_APP_SERVER_RUNTIME_STATUS.MISSING
|
|
273
|
+
};
|
|
274
|
+
}
|
|
275
|
+
if (endpoint.status === CODEX_APP_SERVER_ENDPOINT_STATUS.TIMEOUT) {
|
|
276
|
+
return {
|
|
277
|
+
metadata: normalized,
|
|
278
|
+
replace: false,
|
|
279
|
+
reusable: true,
|
|
280
|
+
status: CODEX_APP_SERVER_RUNTIME_STATUS.SUSPECT
|
|
281
|
+
};
|
|
282
|
+
}
|
|
283
|
+
return {
|
|
284
|
+
metadata: normalized,
|
|
285
|
+
replace: true,
|
|
286
|
+
reusable: false,
|
|
287
|
+
status: CODEX_APP_SERVER_RUNTIME_STATUS.MISSING
|
|
288
|
+
};
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
async function waitForCodexAppServer(endpoint = "", {
|
|
292
|
+
timeoutMs = CODEX_APP_SERVER_READY_TIMEOUT_MS,
|
|
293
|
+
WebSocketImpl = WebSocket
|
|
294
|
+
} = {}) {
|
|
295
|
+
const socketPath = socketPathFromCodexAppServerEndpoint(endpoint);
|
|
296
|
+
if (!socketPath) {
|
|
297
|
+
return false;
|
|
298
|
+
}
|
|
299
|
+
const startedAt = Date.now();
|
|
300
|
+
while (!await fileExists(socketPath) && Date.now() - startedAt <= timeoutMs) {
|
|
301
|
+
await delay(100);
|
|
302
|
+
}
|
|
303
|
+
const remainingMs = Math.max(1, timeoutMs - (Date.now() - startedAt));
|
|
304
|
+
if (!await fileExists(socketPath)) {
|
|
305
|
+
return false;
|
|
306
|
+
}
|
|
307
|
+
return await probeCodexAppServer(endpoint, remainingMs, WebSocketImpl) === CODEX_APP_SERVER_ENDPOINT_STATUS.RESPONSIVE;
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
async function ensureCodexAppServerRuntime(options = {}) {
|
|
311
|
+
const runtimeDir = path.resolve(options.runtimeDir);
|
|
312
|
+
while (codexAppServerRuntimePreparations.has(runtimeDir)) {
|
|
313
|
+
await codexAppServerRuntimePreparations.get(runtimeDir);
|
|
314
|
+
}
|
|
315
|
+
// Each waiter rechecks its own auth and runtime configuration after startup.
|
|
316
|
+
const operation = prepareCodexAppServerRuntime({ ...options, runtimeDir });
|
|
317
|
+
codexAppServerRuntimePreparations.set(runtimeDir, operation);
|
|
318
|
+
try {
|
|
319
|
+
return await operation;
|
|
320
|
+
} finally {
|
|
321
|
+
codexAppServerRuntimePreparations.delete(runtimeDir);
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
async function prepareCodexAppServerRuntime(options) {
|
|
326
|
+
const { runtimeDir } = options;
|
|
327
|
+
const [accountIdentitySignature, authStateSignature] = await Promise.all([
|
|
328
|
+
credentials.identity(options),
|
|
329
|
+
credentials.generation(options)
|
|
330
|
+
]);
|
|
331
|
+
const runtimeOptions = {
|
|
332
|
+
...options,
|
|
333
|
+
accountIdentitySignature,
|
|
334
|
+
authStateSignature
|
|
335
|
+
};
|
|
336
|
+
const { runtimeMetadataWriter = writeCodexAppServerMetadata } = options;
|
|
337
|
+
await ensureWritablePrivateDirectory(runtimeDir);
|
|
338
|
+
|
|
339
|
+
const existing = await readCodexAppServerMetadata(runtimeDir);
|
|
340
|
+
const existingStatus = existing ? await codexAppServerRuntimeStatus(existing, runtimeOptions) : null;
|
|
341
|
+
if (existingStatus?.reusable) {
|
|
342
|
+
return {
|
|
343
|
+
...existingStatus.metadata,
|
|
344
|
+
reused: true,
|
|
345
|
+
runtimeStatus: existingStatus.status
|
|
346
|
+
};
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
const releaseLock = await runtimeStop.acquireRuntimeLock(runtimeDir, runtimeOptions);
|
|
350
|
+
try {
|
|
351
|
+
let socketOwnerDrained = false;
|
|
352
|
+
const afterLock = await readCodexAppServerMetadata(runtimeDir);
|
|
353
|
+
const afterLockStatus = afterLock ? await codexAppServerRuntimeStatus(afterLock, runtimeOptions) : null;
|
|
354
|
+
if (afterLockStatus?.reusable) {
|
|
355
|
+
return {
|
|
356
|
+
...afterLockStatus.metadata,
|
|
357
|
+
reused: true,
|
|
358
|
+
runtimeStatus: afterLockStatus.status
|
|
359
|
+
};
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
if (afterLock && (!afterLockStatus || afterLockStatus.replace !== false)) {
|
|
363
|
+
const stopped = await runtimeStop.stopProcess(runtimeDir, runtimeOptions);
|
|
364
|
+
if (stopped.processExitVerified !== true) {
|
|
365
|
+
const error = new Error(
|
|
366
|
+
`${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.`
|
|
367
|
+
);
|
|
368
|
+
error.code = `${errorPrefix}codex_app_server_process_identity_unverified`;
|
|
369
|
+
error.cleanupRequired = true;
|
|
370
|
+
error.retryable = false;
|
|
371
|
+
throw error;
|
|
372
|
+
}
|
|
373
|
+
socketOwnerDrained = true;
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
const started = await processStart.start({
|
|
377
|
+
...runtimeOptions,
|
|
378
|
+
runtimeDir,
|
|
379
|
+
socketOwnerDrained
|
|
380
|
+
});
|
|
381
|
+
try {
|
|
382
|
+
await credentials.assertCurrent(started.authStateSignature, runtimeOptions);
|
|
383
|
+
await runtimeMetadataWriter(runtimeDir, started);
|
|
384
|
+
await credentials.assertCurrent(started.authStateSignature, runtimeOptions);
|
|
385
|
+
} catch (error) {
|
|
386
|
+
const helper = codexAppServerIsHelper(runtimeOptions);
|
|
387
|
+
const cleanup = await runtimeStop.cleanupFailedStart(runtimeDir, {
|
|
388
|
+
helper,
|
|
389
|
+
executionId: started.executionId,
|
|
390
|
+
pid: started.pid,
|
|
391
|
+
processIdentity: started.processIdentity
|
|
392
|
+
}, runtimeOptions);
|
|
393
|
+
const failure = new Error(
|
|
394
|
+
helper
|
|
395
|
+
? "Codex isolated helper runtime metadata could not be recorded."
|
|
396
|
+
: "Codex app-server runtime could not be published safely.",
|
|
397
|
+
{ cause: error }
|
|
398
|
+
);
|
|
399
|
+
failure.code = cleanup.cleanupFailed
|
|
400
|
+
? (helper
|
|
401
|
+
? `${errorPrefix}codex_helper_runtime_cleanup_required`
|
|
402
|
+
: `${errorPrefix}codex_app_server_cleanup_required`)
|
|
403
|
+
: (error?.code || (helper
|
|
404
|
+
? `${errorPrefix}codex_helper_runtime_metadata_failed`
|
|
405
|
+
: `${errorPrefix}codex_app_server_metadata_failed`));
|
|
406
|
+
failure.cleanupRequired = cleanup.cleanupFailed;
|
|
407
|
+
failure.retryable = false;
|
|
408
|
+
throw failure;
|
|
409
|
+
}
|
|
410
|
+
return {
|
|
411
|
+
...started,
|
|
412
|
+
reused: false
|
|
413
|
+
};
|
|
414
|
+
} finally {
|
|
415
|
+
await releaseLock();
|
|
416
|
+
}
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
return Object.freeze({
|
|
420
|
+
ensure: ensureCodexAppServerRuntime,
|
|
421
|
+
start: processStart.start,
|
|
422
|
+
stop: runtimeStop.stopRuntime,
|
|
423
|
+
isLive: codexAppServerMetadataIsLive,
|
|
424
|
+
cleanupFailedStart: runtimeStop.cleanupFailedStart
|
|
425
|
+
});
|
|
426
|
+
}
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
import path from "node:path";
|
|
2
|
+
import { stat } from "node:fs/promises";
|
|
3
|
+
import {
|
|
4
|
+
CODEX_APP_SERVER_PROCESS_STATE,
|
|
5
|
+
readCodexAppServerMetadata,
|
|
6
|
+
removeCodexAppServerRuntimeDir
|
|
7
|
+
} from "./codexProcess.js";
|
|
8
|
+
import { normalizeText } from "@jskit-ai/kernel/shared/support/normalize";
|
|
9
|
+
|
|
10
|
+
// Every provider owner in this process shares one inventory: one native
|
|
11
|
+
// runtime may serve several owners, so recovery and sharing always inspect the
|
|
12
|
+
// complete inventory by native runtime identity. Caller-local provider keys are
|
|
13
|
+
// grouped by owner without duplicating it.
|
|
14
|
+
export const codexAppServerProviders = new Map();
|
|
15
|
+
export const codexAppServerOwnedRuntimes = new Map();
|
|
16
|
+
const codexAppServerRuntimeLifecycles = new Map();
|
|
17
|
+
// Runtimes an account change left running a turn, by native runtime identity.
|
|
18
|
+
export const codexAppServerStaleRuntimes = new Map();
|
|
19
|
+
|
|
20
|
+
export function codexAppServerOwnedRuntimeKey(providerKey = "", providerOptions) {
|
|
21
|
+
const runtimeDir = normalizeText(providerOptions?.runtimeDir);
|
|
22
|
+
return runtimeDir
|
|
23
|
+
? `runtime:${path.resolve(runtimeDir)}`
|
|
24
|
+
: `provider:${normalizeText(providerKey)}`;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export function withRuntimeLifecycle(runtimeKey, operation) {
|
|
28
|
+
const previous = codexAppServerRuntimeLifecycles.get(runtimeKey) || Promise.resolve();
|
|
29
|
+
const run = previous.catch(() => null).then(operation);
|
|
30
|
+
const tail = run.catch(() => null).finally(() => {
|
|
31
|
+
if (codexAppServerRuntimeLifecycles.get(runtimeKey) === tail) {
|
|
32
|
+
codexAppServerRuntimeLifecycles.delete(runtimeKey);
|
|
33
|
+
}
|
|
34
|
+
});
|
|
35
|
+
codexAppServerRuntimeLifecycles.set(runtimeKey, tail);
|
|
36
|
+
return run;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// A session recorded a new runtime directory (a new sign-in gets its own), so
|
|
40
|
+
// its previous one is no longer used. It goes once its runtime has verifiably
|
|
41
|
+
// stopped; a stale runtime still finishing its turn drops it when it stops.
|
|
42
|
+
// Helper cleanup treats an absent directory as retired, which is also what it
|
|
43
|
+
// does with a previous sign-in's runtime it finds stopped.
|
|
44
|
+
export function releaseSupersededCodexRuntime(runtimeDir = "") {
|
|
45
|
+
const runtimeKey = codexAppServerOwnedRuntimeKey("", { runtimeDir });
|
|
46
|
+
return withRuntimeLifecycle(runtimeKey, async () => {
|
|
47
|
+
const stale = codexAppServerStaleRuntimes.get(runtimeKey);
|
|
48
|
+
if (stale) {
|
|
49
|
+
stale.superseded = true;
|
|
50
|
+
return { deferred: true, removed: false };
|
|
51
|
+
}
|
|
52
|
+
if (codexAppServerOwnedRuntimes.has(runtimeKey) || runtimePeers(runtimeKey).length) {
|
|
53
|
+
return { removed: false };
|
|
54
|
+
}
|
|
55
|
+
const metadata = await readCodexAppServerMetadata(runtimeDir);
|
|
56
|
+
if (metadata?.processState !== CODEX_APP_SERVER_PROCESS_STATE.STOPPED || !metadata.processExitVerifiedAt) {
|
|
57
|
+
return { removed: false };
|
|
58
|
+
}
|
|
59
|
+
return { removed: await removeCodexAppServerRuntimeDir(runtimeDir) };
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export function codexAppServerRuntimeStopWasVerified(result) {
|
|
64
|
+
return result?.stopped === true ||
|
|
65
|
+
result?.processExitVerified === true ||
|
|
66
|
+
result?.runtimeDirRemoved === true;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
export async function directoryExists(filePath = "") {
|
|
70
|
+
try {
|
|
71
|
+
return (await stat(filePath)).isDirectory();
|
|
72
|
+
} catch (error) {
|
|
73
|
+
if (error?.code === "ENOENT" || error?.code === "ENOTDIR") {
|
|
74
|
+
return false;
|
|
75
|
+
}
|
|
76
|
+
throw error;
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
export function* providerRecords() {
|
|
81
|
+
for (const records of codexAppServerProviders.values()) yield* records.values();
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// Every owner's provider records on one native runtime.
|
|
85
|
+
export function runtimePeers(runtimeKey) {
|
|
86
|
+
return [...providerRecords()].filter(record =>
|
|
87
|
+
codexAppServerOwnedRuntimeKey(record.providerKey, record.providerOptions) === runtimeKey);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
export function nativeRequest(client, method, params) {
|
|
91
|
+
return client.request(method, params, { signal: AbortSignal.timeout(5_000) });
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
// The loaded threads of one native runtime that have a running turn, read from
|
|
95
|
+
// the runtime itself so Temporary, Main and native terminal turns all count.
|
|
96
|
+
export async function runningNativeThreads(peers) {
|
|
97
|
+
const client = peers.find(record => record.provider.client?.isOpen())?.provider.client;
|
|
98
|
+
if (!client) return [];
|
|
99
|
+
const running = [];
|
|
100
|
+
const seen = new Set();
|
|
101
|
+
let cursor;
|
|
102
|
+
do {
|
|
103
|
+
const page = await nativeRequest(client, "thread/loaded/list", { limit: 100, ...(cursor ? { cursor } : {}) });
|
|
104
|
+
for (const threadId of Array.isArray(page?.data) ? page.data : []) {
|
|
105
|
+
const { thread } = await nativeRequest(client, "thread/read", { threadId, includeTurns: false });
|
|
106
|
+
if ((typeof thread?.status === "string" ? thread.status : thread?.status?.type) === "active") running.push(threadId);
|
|
107
|
+
}
|
|
108
|
+
cursor = page?.nextCursor;
|
|
109
|
+
if (cursor && seen.has(cursor)) throw new Error("Codex's loaded-thread inventory did not finish.");
|
|
110
|
+
if (cursor) seen.add(cursor);
|
|
111
|
+
} while (cursor);
|
|
112
|
+
return running;
|
|
113
|
+
}
|
|
@@ -1,9 +1,29 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { normalizeText } from "@jskit-ai/kernel/shared/support/normalize";
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
3
|
+
/** @typedef {ReturnType<typeof import("./codexHelperThreads.js").createCodexHelperThreadLifecycle>} CodexHelperThreadLifecycle */
|
|
4
|
+
/** @typedef {ReturnType<typeof import("./codexNotificationObserver.js").createCodexNotificationObserver>} CodexNotificationObserver */
|
|
5
|
+
/** @typedef {ReturnType<typeof createCodexRuntimeLifecycle>} CodexRuntimeLifecycle */
|
|
6
|
+
/** @typedef {ReturnType<typeof import("./codexTurn.js").createCodexAppServerRunOwner>} CodexRunOwner */
|
|
7
|
+
/** @typedef {(event: string, details: object) => void} CodexDebugLog */
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Shutdown, wellbeing timers and managed-session records for shared Codex
|
|
11
|
+
* runtimes. It works on the run/provider owners' collections; it owns no servers.
|
|
12
|
+
* `pendingWork` names the run owner's in-flight task collections that shutdown
|
|
13
|
+
* drains; `observer` owns the event subscriptions a released provider ends.
|
|
14
|
+
* @param {object} dependencies
|
|
15
|
+
* @param {Record<string, Map<unknown, Promise<unknown>> | Set<Promise<unknown>>>} dependencies.pendingWork
|
|
16
|
+
* @param {CodexNotificationObserver} dependencies.observer
|
|
17
|
+
* @param {(providerKey: string, managed: object) => Promise<unknown>} dependencies.checkManagedConnection
|
|
18
|
+
* @param {import("./codexProviderOwner.js").CodexProviderSessions} dependencies.providerSessions
|
|
19
|
+
* @param {CodexHelperThreadLifecycle | null} dependencies.helperLifecycle
|
|
20
|
+
* @param {() => unknown} dependencies.captureContext
|
|
21
|
+
* @param {<T>(context: unknown, operation: () => T) => T} dependencies.runInContext
|
|
22
|
+
* @param {((sessionId: string, options: { threadId: string }) => Promise<unknown>) | null} [dependencies.continueGoalAfterAccountChange]
|
|
23
|
+
* @param {CodexDebugLog} dependencies.debugLog
|
|
24
|
+
* @param {(error: unknown) => unknown} dependencies.debugError
|
|
25
|
+
* @param {{ message: string, code: string, retryable: boolean } | null} [dependencies.closingError]
|
|
26
|
+
*/
|
|
7
27
|
export function createCodexRuntimeLifecycle({
|
|
8
28
|
pendingWork,
|
|
9
29
|
observer,
|
|
@@ -12,7 +32,7 @@ export function createCodexRuntimeLifecycle({
|
|
|
12
32
|
helperLifecycle,
|
|
13
33
|
captureContext,
|
|
14
34
|
runInContext,
|
|
15
|
-
continueGoalAfterAccountChange
|
|
35
|
+
continueGoalAfterAccountChange,
|
|
16
36
|
debugLog,
|
|
17
37
|
debugError,
|
|
18
38
|
closingError = null
|
|
@@ -268,7 +288,7 @@ export function createCodexRuntimeLifecycle({
|
|
|
268
288
|
}
|
|
269
289
|
});
|
|
270
290
|
}, providerSessions.wellbeingMs);
|
|
271
|
-
timer.unref
|
|
291
|
+
timer.unref();
|
|
272
292
|
codexAppServerWellbeingTimers.set(normalizedProviderKey, timer);
|
|
273
293
|
}
|
|
274
294
|
|
|
@@ -296,7 +316,7 @@ export function createCodexRuntimeLifecycle({
|
|
|
296
316
|
}
|
|
297
317
|
|
|
298
318
|
function invalidateRuntimes(input = {}, preparation) {
|
|
299
|
-
const serverShutdown = normalizeText(input
|
|
319
|
+
const serverShutdown = normalizeText(input.reason) === "server-shutdown";
|
|
300
320
|
if (serverShutdown) {
|
|
301
321
|
beginCodexAppServerShutdown();
|
|
302
322
|
}
|
|
@@ -328,18 +348,24 @@ export function createCodexRuntimeLifecycle({
|
|
|
328
348
|
})();
|
|
329
349
|
}
|
|
330
350
|
|
|
331
|
-
return
|
|
351
|
+
function nextReconcileGeneration() { return ++codexAppServerThreadReconcileGeneration; }
|
|
352
|
+
|
|
353
|
+
const managedSession = providerKey => providerSessions.managed.get(providerKey);
|
|
354
|
+
|
|
355
|
+
const managedThreadId = providerKey => providerSessions.managed.get(providerKey)?.threadId;
|
|
356
|
+
|
|
357
|
+
return Object.freeze({
|
|
332
358
|
get closing() { return codexAppServerServerClosing; },
|
|
333
359
|
assertOpen,
|
|
334
360
|
get reconcileGeneration() { return codexAppServerThreadReconcileGeneration; },
|
|
335
|
-
nextReconcileGeneration
|
|
361
|
+
nextReconcileGeneration,
|
|
336
362
|
reconcileTasks: codexAppServerReconcileTasks,
|
|
337
363
|
sessionClosures: codexAppServerSessionClosures,
|
|
338
364
|
invalidateRuntimes,
|
|
339
365
|
resources: codexAppServerProviderResources,
|
|
340
366
|
retireProvider: retireAndCloseCodexAppServerProvider,
|
|
341
367
|
rememberManagedSession: rememberCodexAppServerManagedSession,
|
|
342
|
-
managedSession
|
|
343
|
-
managedThreadId
|
|
344
|
-
};
|
|
368
|
+
managedSession,
|
|
369
|
+
managedThreadId
|
|
370
|
+
});
|
|
345
371
|
}
|