@jskit-ai/assistant-core 0.2.7 → 0.2.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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 +13 -10
  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,127 +1,77 @@
1
1
  import path from "node:path";
2
- import { stat } from "node:fs/promises";
3
2
  import { CodexAppServerAgentProvider } from "./codexProvider.js";
4
3
  import { codexAppServerProjectHookTrustConfig } from "./codexConfiguration.js";
4
+ import { createCodexRuntimeModelCatalog } from "./codexModelCatalog.js";
5
+ import { createCodexProviderInvalidation } from "./codexProviderInvalidation.js";
6
+ import { createCodexProviderRelease } from "./codexProviderRelease.js";
5
7
  import {
6
- CODEX_APP_SERVER_PROCESS_STATE,
7
- readCodexAppServerMetadata,
8
- removeCodexAppServerRuntimeDir
9
- } from "./codexProcess.js";
10
- import { normalizeCodexRunText as normalizeText } from "./codexTurnState.js";
11
-
12
- // Moved from Vibe64's codexTerminal provider owner. Caller-local provider keys
13
- // are grouped without duplicating the inventory. Recovery and sharing always
14
- // inspect the complete inventory by native runtime identity.
15
- const codexAppServerProviders = new Map();
16
- const codexAppServerOwnedRuntimes = new Map();
17
- const codexAppServerRuntimeLifecycles = new Map();
18
- // Runtimes an account change left running a turn, by native runtime identity.
19
- const codexAppServerStaleRuntimes = new Map();
20
-
21
- export function codexAppServerOwnedRuntimeKey(providerKey = "", providerOptions = {}) {
22
- const runtimeDir = normalizeText(providerOptions?.runtimeDir);
23
- return runtimeDir
24
- ? `runtime:${path.resolve(runtimeDir)}`
25
- : `provider:${normalizeText(providerKey)}`;
26
- }
27
-
28
- function withRuntimeLifecycle(runtimeKey, operation) {
29
- const previous = codexAppServerRuntimeLifecycles.get(runtimeKey) || Promise.resolve();
30
- const run = previous.catch(() => null).then(operation);
31
- const tail = run.catch(() => null).finally(() => {
32
- if (codexAppServerRuntimeLifecycles.get(runtimeKey) === tail) {
33
- codexAppServerRuntimeLifecycles.delete(runtimeKey);
34
- }
35
- });
36
- codexAppServerRuntimeLifecycles.set(runtimeKey, tail);
37
- return run;
38
- }
39
-
40
- // A session recorded a new runtime directory (a new sign-in gets its own), so
41
- // its previous one is no longer used. It goes once its runtime has verifiably
42
- // stopped; a stale runtime still finishing its turn drops it when it stops.
43
- // Helper cleanup treats an absent directory as retired, which is also what it
44
- // does with a previous sign-in's runtime it finds stopped.
45
- export function releaseSupersededCodexRuntime(runtimeDir = "") {
46
- const runtimeKey = codexAppServerOwnedRuntimeKey("", { runtimeDir });
47
- return withRuntimeLifecycle(runtimeKey, async () => {
48
- const stale = codexAppServerStaleRuntimes.get(runtimeKey);
49
- if (stale) {
50
- stale.superseded = true;
51
- return { deferred: true, removed: false };
52
- }
53
- if (codexAppServerOwnedRuntimes.has(runtimeKey) || runtimePeers(runtimeKey).length) {
54
- return { removed: false };
55
- }
56
- const metadata = await readCodexAppServerMetadata(runtimeDir);
57
- if (metadata?.processState !== CODEX_APP_SERVER_PROCESS_STATE.STOPPED || !metadata.processExitVerifiedAt) {
58
- return { removed: false };
59
- }
60
- return { removed: await removeCodexAppServerRuntimeDir(runtimeDir) };
61
- });
62
- }
63
-
64
- export function codexAppServerRuntimeStopWasVerified(result = {}) {
65
- return result?.stopped === true ||
66
- result?.processExitVerified === true ||
67
- result?.runtimeDirRemoved === true;
68
- }
69
-
70
- export async function directoryExists(filePath = "") {
71
- try {
72
- return (await stat(filePath)).isDirectory();
73
- } catch (error) {
74
- if (error?.code === "ENOENT" || error?.code === "ENOTDIR") {
75
- return false;
76
- }
77
- throw error;
78
- }
79
- }
80
-
81
- function* providerRecords() {
82
- for (const records of codexAppServerProviders.values()) yield* records.values();
83
- }
84
-
85
- // Every owner's provider records on one native runtime.
86
- function runtimePeers(runtimeKey) {
87
- return [...providerRecords()].filter(record =>
88
- codexAppServerOwnedRuntimeKey(record.providerKey, record.providerOptions) === runtimeKey);
89
- }
90
-
91
- function nativeRequest(client, method, params) {
92
- return client.request(method, params, { signal: AbortSignal.timeout(5_000) });
93
- }
94
-
95
- // The loaded threads of one native runtime that have a running turn, read from
96
- // the runtime itself so Temporary, Main and native terminal turns all count.
97
- async function runningNativeThreads(peers) {
98
- const client = peers.find(record => record.provider.client?.isOpen?.())?.provider.client;
99
- if (!client) return [];
100
- const running = [];
101
- const seen = new Set();
102
- let cursor;
103
- do {
104
- const page = await nativeRequest(client, "thread/loaded/list", { limit: 100, ...(cursor ? { cursor } : {}) });
105
- for (const threadId of Array.isArray(page?.data) ? page.data : []) {
106
- const { thread } = await nativeRequest(client, "thread/read", { threadId, includeTurns: false });
107
- if ((typeof thread?.status === "string" ? thread.status : thread?.status?.type) === "active") running.push(threadId);
108
- }
109
- cursor = page?.nextCursor;
110
- if (cursor && seen.has(cursor)) throw new Error("Codex's loaded-thread inventory did not finish.");
111
- if (cursor) seen.add(cursor);
112
- } while (cursor);
113
- return running;
8
+ codexAppServerOwnedRuntimeKey,
9
+ codexAppServerOwnedRuntimes,
10
+ codexAppServerProviders,
11
+ codexAppServerRuntimeStopWasVerified,
12
+ codexAppServerStaleRuntimes,
13
+ providerRecords,
14
+ runningNativeThreads,
15
+ runtimePeers,
16
+ withRuntimeLifecycle
17
+ } from "./codexRuntimeInventory.js";
18
+ import { normalizeText } from "@jskit-ai/kernel/shared/support/normalize";
19
+
20
+ /** @typedef {ReturnType<typeof createCodexAppServerProviderOwner>} CodexAppServerProviderOwner */
21
+
22
+ /**
23
+ * The provider sessions a run owner works through, supplied by its application: the provider owner, the managed
24
+ * sessions it keeps running, and the per-session contexts it prepares.
25
+ * @typedef {object} CodexProviderSessions
26
+ * @property {CodexAppServerProviderOwner} owner
27
+ * @property {Map<string, object>} managed
28
+ * @property {(sessionId: string, providerOptions: object) => object} context
29
+ * @property {(providerKey: string) => object} keyFields
30
+ * @property {() => void} assertOpen
31
+ * @property {object} [connectionPolicy]
32
+ * @property {(request: { sessionId: string, runtime: object, session: object }) => Promise<object | null>} [outputContext]
33
+ * @property {number} [wellbeingMs]
34
+ */
35
+
36
+ /** An owner outside an application lifecycle is always open. */
37
+ function ownerStaysOpen() {}
38
+
39
+ /** An owner without a recovery listener reports nothing. */
40
+ function ignoreRecoveryEvent() {}
41
+
42
+ /** An owner without a debug log records nothing. */
43
+ function ignoreDebugEvent() {}
44
+
45
+ /** A provider built without a native host description gets an empty one. */
46
+ function emptyNativeHost() {
47
+ return {};
114
48
  }
115
49
 
116
- /** Internal consumer view of the one native provider owner, not a server per view. */
50
+ /**
51
+ * Internal consumer view of the one native provider owner, not a server per view.
52
+ * @param {object} [dependencies]
53
+ * @param {string} [dependencies.runtimeRoot]
54
+ * @param {() => void} [dependencies.assertOpen]
55
+ * @param {(owner: object, event: string, error: unknown) => void} [dependencies.onRecoveryEvent]
56
+ * @param {(event: string, details: object) => void} [dependencies.debugLog]
57
+ * @param {(error: unknown) => unknown} [dependencies.debugError]
58
+ * @param {((parameters: object) => CodexAppServerAgentProvider) | null} [dependencies.providerFactory]
59
+ * @param {(parameters: object) => object} [dependencies.prepareNativeHost]
60
+ * @param {string} [dependencies.runtimeBusyCode]
61
+ * @param {string} [dependencies.exitUnverifiedCode]
62
+ * @param {string} [dependencies.requiredStopCode]
63
+ * @param {string} [dependencies.runtimeCloseError]
64
+ * @param {number} [dependencies.transientIdleMs]
65
+ */
117
66
  export function createCodexAppServerProviderOwner({
118
67
  runtimeRoot = "",
119
- assertOpen: assertApplicationOpen = () => {},
120
- onRecoveryEvent = () => {},
121
- debugLog = () => {},
68
+ assertOpen: assertApplicationOpen = ownerStaysOpen,
69
+ onRecoveryEvent = ignoreRecoveryEvent,
70
+ debugLog = ignoreDebugEvent,
122
71
  debugError = error => error,
72
+ // readable-check: allow dependency-default - input validation: an application passes null to choose the built-in provider
123
73
  providerFactory = null,
124
- prepareNativeHost = () => ({}),
74
+ prepareNativeHost = emptyNativeHost,
125
75
  runtimeBusyCode = "codex_app_server_runtime_busy",
126
76
  exitUnverifiedCode = "codex_runtime_exit_unverified",
127
77
  requiredStopCode = "codex_runtime_exit_unverified",
@@ -135,7 +85,6 @@ export function createCodexAppServerProviderOwner({
135
85
  let codexAppServerProviderLifecycle = Promise.resolve();
136
86
  let closing = false;
137
87
  let runtimeLifecycle = null;
138
- let codexAppServerChatModelCatalog = null;
139
88
  // Providers borrowed by work that is not a conversation, terminal or helper.
140
89
  const transientReleases = new Map();
141
90
 
@@ -157,6 +106,15 @@ export function createCodexAppServerProviderOwner({
157
106
  }
158
107
  };
159
108
 
109
+ const modelCatalog = createCodexRuntimeModelCatalog({ withLifecycle, assertOpen, acquireRuntime, stopOwnedRuntime, forgetOwnedRuntime });
110
+ const invalidation = createCodexProviderInvalidation({
111
+ inScope, withLifecycle, forgetModelCatalog: modelCatalog.forget, exitUnverifiedCode, debugLog, debugError
112
+ });
113
+ const sessionRelease = createCodexProviderRelease({
114
+ records, providers, providerOwners, ownedRuntimes, withLifecycle, runtimeIsShared, forgetOwnedRuntime, closeProvider,
115
+ requiredStopCode, exitUnverifiedCode, runtimeCloseError, debugLog, debugError
116
+ });
117
+
160
118
  function attachRuntimeLifecycle(lifecycle) {
161
119
  if (runtimeLifecycle && runtimeLifecycle !== lifecycle) {
162
120
  throw new TypeError("Codex provider owner already has a runtime lifecycle.");
@@ -207,7 +165,7 @@ export function createCodexAppServerProviderOwner({
207
165
  providerKey: normalizeText(providerKey),
208
166
  providerOptions,
209
167
  sessionKey: consumer?.owner.sessionKey || "",
210
- runtimeDir: normalizeText(providerOptions?.runtimeDir),
168
+ runtimeDir: normalizeText(providerOptions.runtimeDir),
211
169
  runtimeKey,
212
170
  scope: api,
213
171
  resources: consumer?.resources,
@@ -237,7 +195,17 @@ export function createCodexAppServerProviderOwner({
237
195
  }
238
196
  }
239
197
 
240
- function createProvider({ providerKey, providerOptions = {}, owner = {}, create, observation, resources, preserveProcessExitProof = false } = {}) {
198
+ /**
199
+ * @param {object} request
200
+ * @param {string} request.providerKey
201
+ * @param {object} [request.providerOptions]
202
+ * @param {object} [request.owner]
203
+ * @param {(onObservationLost: (error: unknown) => Promise<void>) => CodexAppServerAgentProvider} request.create
204
+ * @param {object} [request.observation]
205
+ * @param {object} [request.resources]
206
+ * @param {boolean} [request.preserveProcessExitProof]
207
+ */
208
+ function createProvider({ providerKey, providerOptions = {}, owner = {}, create, observation, resources, preserveProcessExitProof = false }) {
241
209
  assertOpen();
242
210
  if (!path.isAbsolute(providerOptions.runtimeDir || "")) throw new TypeError("Codex ownership requires an absolute native runtime directory.");
243
211
  const existing = records.get(providerKey);
@@ -255,6 +223,7 @@ export function createCodexAppServerProviderOwner({
255
223
  async function retireAndCloseProvider(providerKey = "", options = {}) {
256
224
  const record = records.get(normalizeText(providerKey));
257
225
  if (!record) return;
226
+ // readable-check: allow optional-call - optional operation: a driver's resources have no retire step, and a borrowed provider has no resources
258
227
  await record.resources?.retire?.(record.provider);
259
228
  closeProvider(normalizeText(providerKey), options);
260
229
  }
@@ -335,8 +304,8 @@ export function createCodexAppServerProviderOwner({
335
304
  for (const [threadId, binding] of provider.threadEnvironments || []) {
336
305
  const boundEnv = binding.params.config?.shell_environment_policy?.set || {};
337
306
  if (!Object.entries(boundEnv).some(([key, value]) => key.endsWith("_COMMAND_SOCKET") && value === event.socketPath)) continue;
338
- // Keep listener replacement and rejected command recovery on the
339
- // provider's original per-thread operation.
307
+ // Listener replacement and rejected command recovery stay on the
308
+ // provider's per-thread operation that is already running.
340
309
  if (provider.threadEnvironmentTasks?.has(threadId)) continue;
341
310
  void prepared.runInContext(() => provider.ensureThreadControls(threadId))
342
311
  .catch(error => provider.failObservation(error))
@@ -376,11 +345,11 @@ export function createCodexAppServerProviderOwner({
376
345
  if (transientReleases.get(providerKey) !== release || release.users > 0) return;
377
346
  transientReleases.delete(providerKey);
378
347
  if (closing) return;
379
- stopCachedProvider(providerKey).catch(error => debugLog("appServerRuntime.transientRelease.error", {
348
+ sessionRelease.stopCachedProvider(providerKey).catch(error => debugLog("appServerRuntime.transientRelease.error", {
380
349
  error: debugError(error), providerKey
381
350
  }));
382
351
  }, transientIdleMs);
383
- release.timer.unref?.();
352
+ release.timer.unref();
384
353
  };
385
354
  }
386
355
 
@@ -391,7 +360,7 @@ export function createCodexAppServerProviderOwner({
391
360
  async function borrowSession(context) {
392
361
  const providerKey = context.providerKey;
393
362
  let release = transientReleases.get(providerKey);
394
- if (!release && (records.has(providerKey) || context.runOwner?.runtimeLifecycle?.managedThreadId?.(providerKey))) {
363
+ if (!release && (records.has(providerKey) || context.runOwner.runtimeLifecycle.managedThreadId(providerKey))) {
395
364
  return { provider: await acquireSession(context), release() {} };
396
365
  }
397
366
  if (!release) transientReleases.set(providerKey, release = { users: 0, timer: null });
@@ -446,15 +415,7 @@ export function createCodexAppServerProviderOwner({
446
415
  try {
447
416
  context.assertAdmission();
448
417
  await acquireRuntime({
449
- operation: () => {
450
- if (typeof provider.ensureAvailable === "function") {
451
- return provider.ensureAvailable({ modelProviderId: providerOptions.routingModelProviderId });
452
- }
453
- if (typeof provider.listLoadedThreads === "function") {
454
- return provider.listLoadedThreads({ limit: 1 });
455
- }
456
- return provider.ensureRuntime?.();
457
- },
418
+ operation: () => provider.ensureAvailable({ modelProviderId: providerOptions.routingModelProviderId }),
458
419
  provider,
459
420
  providerKey,
460
421
  providerOptions
@@ -468,7 +429,7 @@ export function createCodexAppServerProviderOwner({
468
429
  }
469
430
  if (!context.closing && mainThreadId) await provider.failObservation(error);
470
431
  if (!context.closing && !provider.observationFailure) {
471
- await stopProvider(context.providerKey, providerOptions, {
432
+ await sessionRelease.stopProvider(context.providerKey, providerOptions, {
472
433
  get runtimeHost() { return context.runtimeHost; }
473
434
  });
474
435
  }
@@ -479,8 +440,9 @@ export function createCodexAppServerProviderOwner({
479
440
  function closeProvider(providerKey = "", { closeProvider = true } = {}) {
480
441
  const record = records.get(providerKey);
481
442
  if (!record) return;
443
+ // readable-check: allow optional-call - optional operation: a provider borrowed for a catalogue read has no resources
482
444
  record.resources?.release?.(record.provider);
483
- if (closeProvider) record.provider.close?.();
445
+ if (closeProvider) record.provider.close();
484
446
  records.delete(providerKey);
485
447
  if (!records.size) codexAppServerProviders.delete(api);
486
448
  if (record.provider.stale) {
@@ -501,8 +463,10 @@ export function createCodexAppServerProviderOwner({
501
463
  const provider = record.provider;
502
464
  const providerKey = normalizeText(record.providerKey);
503
465
  const resources = record.resources || records.get(providerKey)?.resources;
466
+ // readable-check: allow optional-call - optional operation: a driver's resources have no retire step, and a borrowed provider has no resources
504
467
  const retirement = Promise.resolve().then(() => resources?.retire?.(provider));
505
468
  const runtimeStop = Promise.resolve().then(async () => {
469
+ // readable-check: allow method-probe - safety invariant: a record without a provider that stops would leave its process running unverified
506
470
  if (typeof provider?.stopRuntime !== "function") {
507
471
  throw new Error("Codex app-server provider must implement stopRuntime().");
508
472
  }
@@ -524,7 +488,7 @@ export function createCodexAppServerProviderOwner({
524
488
  if (stopped.status === "fulfilled" && codexAppServerRuntimeStopWasVerified(stopped.value)) {
525
489
  forgetOwnedRuntime(provider);
526
490
  } else {
527
- provider?.close?.();
491
+ provider?.close();
528
492
  }
529
493
  if (retired.status === "fulfilled" && cached) {
530
494
  (record.scope || api).closeProvider(providerKey, { closeProvider: stopped.status !== "fulfilled" });
@@ -542,327 +506,6 @@ export function createCodexAppServerProviderOwner({
542
506
  };
543
507
  }
544
508
 
545
- async function stopCachedProviderUnlocked(providerKey = "", {
546
- preserveProcessExitProof = false,
547
- requireStopped = false
548
- } = {}) {
549
- const normalizedProviderKey = normalizeText(providerKey);
550
- const provider = providers.get(normalizedProviderKey);
551
- if (!provider) {
552
- closeProvider(normalizedProviderKey);
553
- return {
554
- providerKey: normalizedProviderKey,
555
- stopped: false
556
- };
557
- }
558
- await records.get(normalizedProviderKey)?.resources?.retire?.(provider);
559
- const sharedProcessRetained = runtimeIsShared(
560
- normalizedProviderKey, providerOwners.get(normalizedProviderKey)?.providerOptions
561
- );
562
- if (sharedProcessRetained) {
563
- closeProvider(normalizedProviderKey);
564
- return {
565
- providerKey: normalizedProviderKey,
566
- sessionDetached: true,
567
- sharedProcessRetained: true,
568
- stopped: false
569
- };
570
- }
571
- if (typeof provider.stopRuntime !== "function") {
572
- closeProvider(normalizedProviderKey);
573
- throw new Error("Codex app-server provider must implement stopRuntime().");
574
- }
575
- let providerStoppedRuntime = false;
576
- try {
577
- const stoppedRuntime = await provider.stopRuntime({
578
- preserveProcessExitProof: preserveProcessExitProof || requireStopped
579
- });
580
- providerStoppedRuntime = codexAppServerRuntimeStopWasVerified(stoppedRuntime);
581
- if (requireStopped && !providerStoppedRuntime) {
582
- const error = new Error("Codex app-server process exit could not be verified.");
583
- error.code = requiredStopCode;
584
- error.retryable = true;
585
- throw error;
586
- }
587
- if (providerStoppedRuntime) {
588
- forgetOwnedRuntime(provider);
589
- }
590
- return {
591
- ...(stoppedRuntime && typeof stoppedRuntime === "object" ? stoppedRuntime : {}),
592
- providerKey: normalizedProviderKey,
593
- stopped: providerStoppedRuntime
594
- };
595
- } finally {
596
- closeProvider(normalizedProviderKey, {
597
- closeProvider: !providerStoppedRuntime
598
- });
599
- }
600
- }
601
-
602
- function stopCachedProvider(providerKey = "", options = {}) {
603
- return withLifecycle(
604
- () => stopCachedProviderUnlocked(providerKey, options),
605
- normalizeText(providerKey)
606
- );
607
- }
608
-
609
- function runtimeExitUnverifiedError(providerKey = "") {
610
- const error = new Error("Codex app-server process exit could not be verified.");
611
- error.code = exitUnverifiedCode;
612
- error.providerKey = normalizeText(providerKey);
613
- error.retryable = true;
614
- return error;
615
- }
616
-
617
- async function stopPreparedRuntime(context) {
618
- const { runtime, parameters } = context.runtimeHost;
619
- return runtime.stop(parameters);
620
- }
621
-
622
- async function stopProvider(providerKey, providerOptions, context = {}) {
623
- const { preserveProcessExitProof = false, requireStopped = false } = context;
624
- const provider = providers.get(providerKey);
625
- if (!provider) {
626
- if (runtimeIsShared(providerKey, providerOptions)) {
627
- return {
628
- providerKey,
629
- sessionDetached: true,
630
- sharedProcessRetained: true,
631
- stopped: false
632
- };
633
- }
634
- const stoppedRuntime = await stopPreparedRuntime(context);
635
- const stopped = codexAppServerRuntimeStopWasVerified(stoppedRuntime);
636
- if (requireStopped && !stopped) {
637
- throw runtimeExitUnverifiedError(providerKey);
638
- }
639
- return {
640
- ...(Boolean(stoppedRuntime && typeof stoppedRuntime === "object" && !Array.isArray(stoppedRuntime)) ? stoppedRuntime : {}),
641
- providerKey,
642
- stopped
643
- };
644
- }
645
- return stopCachedProvider(providerKey, {
646
- preserveProcessExitProof,
647
- requireStopped
648
- });
649
- }
650
-
651
- async function stopThreadBeforeRelease(providerKey, provider, threadId) {
652
- try {
653
- await provider.stopThreadForObservationLoss(threadId, "");
654
- } catch (error) {
655
- // If other sessions retain the shared process, closing this
656
- // socket alone is not proof that its native goal/work stopped.
657
- const shared = runtimeIsShared(
658
- providerKey, providerOwners.get(providerKey)?.providerOptions
659
- );
660
- if (shared) throw error;
661
- // The caller's existing shutdown must instead prove process exit.
662
- }
663
- }
664
-
665
- function readModelCatalog({
666
- prepareProviderOptions,
667
- providerFactory,
668
- resources = null,
669
- signal = null,
670
- cacheMs = 30_000
671
- } = {}) {
672
- return withLifecycle(async () => {
673
- assertOpen();
674
- signal?.throwIfAborted();
675
- const providerOptions = await prepareProviderOptions();
676
- return withRuntimeLifecycle(codexAppServerOwnedRuntimeKey("", providerOptions), async () => {
677
- const existing = codexAppServerOwnedRuntimes.get(
678
- codexAppServerOwnedRuntimeKey("", providerOptions)
679
- );
680
- const provider = existing?.provider || providerFactory(providerOptions);
681
- let runtime = null;
682
- let catalog = null;
683
- let identity = "";
684
- try {
685
- assertOpen();
686
- identity = JSON.stringify(await provider.currentRuntimeInfo());
687
- signal?.throwIfAborted();
688
- if (codexAppServerChatModelCatalog?.identity === identity &&
689
- codexAppServerChatModelCatalog.expiresAt > Date.now()) {
690
- return codexAppServerChatModelCatalog.value;
691
- }
692
- if (!existing?.provider) {
693
- runtime = await acquireRuntime({
694
- operation: () => provider.ensureRuntime(),
695
- provider,
696
- providerOptions
697
- });
698
- }
699
- catalog = await provider.listModels({ includeHidden: false, limit: 100 }, { signal });
700
- } finally {
701
- if (!existing?.provider) {
702
- try {
703
- if ((runtime || provider.runtime)?.reused === false) {
704
- await stopOwnedRuntime({ provider, resources });
705
- } else {
706
- forgetOwnedRuntime(provider);
707
- }
708
- } finally {
709
- provider.close();
710
- }
711
- }
712
- }
713
- codexAppServerChatModelCatalog = {
714
- expiresAt: Date.now() + cacheMs,
715
- identity,
716
- value: catalog
717
- };
718
- return catalog;
719
- });
720
- });
721
- }
722
-
723
- async function invalidateProviderThreads(modelProviderId, includeOwned, toolHomeSource) {
724
- const groups = new Map();
725
- const inventory = [...providerRecords()];
726
- if (includeOwned) for (const record of codexAppServerOwnedRuntimes.values()) {
727
- if (!inventory.some(candidate => candidate.provider === record.provider)) inventory.push(record);
728
- }
729
- for (const record of inventory) {
730
- if (!inScope(record)) continue;
731
- const key = codexAppServerOwnedRuntimeKey(record.providerKey, record.providerOptions);
732
- if (!groups.has(key)) groups.set(key, []);
733
- groups.get(key).push(record);
734
- }
735
- const results = [], failed = [];
736
- for (const [runtimeKey, peers] of groups) {
737
- try {
738
- await withRuntimeLifecycle(runtimeKey, async () => {
739
- const available = peers.filter(record => record.provider.client?.isOpen());
740
- if (!available.length) {
741
- if (peers.some(record => record.provider.runtime || record.provider.runtimeStopOwner ||
742
- codexAppServerOwnedRuntimes.get(runtimeKey)?.provider === record.provider)) {
743
- throw new Error("Codex's existing control connection is unavailable. Restore it before changing this provider connection.");
744
- }
745
- return;
746
- }
747
- const request = (record, method, params) => {
748
- const client = record.provider.client;
749
- if (!client?.isOpen()) throw new Error("Codex's control connection closed during provider retirement. Restore it and retry.");
750
- return client.request(method, params, { signal: AbortSignal.timeout(5_000) });
751
- };
752
- const selectedHome = normalizeText(toolHomeSource);
753
- const dedicated = Boolean(selectedHome && peers.some(record =>
754
- normalizeText(record.providerOptions.toolHomeSource) === selectedHome));
755
- if (dedicated && peers.some(record => normalizeText(record.providerOptions.toolHomeSource) !== selectedHome)) {
756
- throw new Error("Codex's provider credential home has other native consumers. Close its native terminal/helper and retry; unrelated runtimes remain running.");
757
- }
758
- const inspector = available[0], candidates = new Set(), seen = new Set();
759
- let cursor;
760
- do {
761
- const page = await request(inspector, "thread/loaded/list", { limit: 100, ...(cursor ? { cursor } : {}) });
762
- if (!Array.isArray(page?.data)) throw new Error("Codex returned an invalid loaded-thread inventory. Restore its connection before retrying.");
763
- for (const id of page.data) {
764
- if (typeof id !== "string" || !id) throw new Error("Codex returned an invalid loaded-thread identity.");
765
- candidates.add(id);
766
- }
767
- cursor = page.nextCursor;
768
- if (cursor && (typeof cursor !== "string" || seen.has(cursor) || seen.size >= 100)) throw new Error("Codex's loaded-thread inventory did not finish.");
769
- if (cursor) seen.add(cursor);
770
- } while (cursor);
771
- for (const { provider } of peers) for (const [id, binding] of provider.threadEnvironments || []) {
772
- if (dedicated || binding.params?.modelProvider === modelProviderId) candidates.add(id);
773
- }
774
- const threadIds = [];
775
- for (const id of candidates) {
776
- const { thread } = await request(inspector, "thread/read", { threadId: id, includeTurns: false });
777
- if (!thread || thread.id !== id || typeof thread.modelProvider !== "string" || !thread.modelProvider ||
778
- !(typeof thread.status === "string" ? thread.status : thread.status?.type)) {
779
- throw new Error("Codex returned an untrustworthy thread/provider inventory. Restore its connection before changing credentials.");
780
- }
781
- if (dedicated && thread.modelProvider !== modelProviderId) {
782
- throw new Error("Codex's provider credential home retains another provider thread. Close its native terminal/helper and retry; unrelated threads remain running.");
783
- }
784
- if (thread.modelProvider === modelProviderId) threadIds.push(id);
785
- }
786
- if (!threadIds.length && !dedicated) return;
787
- const error = Object.assign(new Error("This Codex provider connection changed. Work is stopped; use Send or Resume after reconnecting."), {
788
- code: "codex_provider_connection_changed"
789
- });
790
- for (const { provider } of peers) for (const threadId of threadIds) provider.conversationRuntime?.invalidateThread(threadId);
791
- await Promise.allSettled(peers.flatMap(({ provider }) => threadIds
792
- .map(id => provider.threadEnvironmentTasks?.get(id)).filter(Boolean)));
793
- // The same observation journal retains selected stop ownership. Peers
794
- // with other threads must keep their admission and active work intact.
795
- for (const record of available) await record.observation?.prepare({ provider: record.provider, error, threadIds });
796
- for (const threadId of threadIds) {
797
- await inspector.provider.stopThreadForObservationLoss(threadId);
798
- for (const record of available) {
799
- const detached = await request(record, "thread/unsubscribe", { threadId });
800
- if (!["unsubscribed", "notSubscribed", "notLoaded"].includes(detached?.status)) throw new Error("Codex did not confirm selected-thread detachment.");
801
- }
802
- const { thread } = await request(inspector, "thread/read", { threadId, includeTurns: false });
803
- if ((typeof thread?.status === "string" ? thread.status : thread?.status?.type) !== "notLoaded") {
804
- throw new Error("Close the native Codex terminal using this provider, then retry changing its connection. Another subscriber retained the selected thread; the shared server and other conversations remain running.");
805
- }
806
- }
807
- if (dedicated) {
808
- // A curated default also lives in this exact isolated helper's
809
- // native config cache. Reuse verified retirement only after proving
810
- // every loaded/cached thread belongs to the selected provider.
811
- const owned = codexAppServerOwnedRuntimes.get(runtimeKey);
812
- if (!owned || !peers.some(record => record.provider === owned.provider)) {
813
- throw new Error("Codex's dedicated provider runtime ownership cannot be verified. Restore its control connection and retry.");
814
- }
815
- await owned.scope.stopOwnedRuntime(owned, { cached: false, preserveProcessExitProof: true, requireVerifiedExit: true });
816
- }
817
- for (const record of available) await record.observation?.complete({ provider: record.provider, error, threadIds, sharedStop: dedicated });
818
- if (dedicated) for (const record of peers) record.scope.closeProvider(record.providerKey);
819
- results.push({ runtimeKey, threadIds, stopped: true, sharedProcessRetained: !dedicated,
820
- ...(dedicated ? { dedicatedRuntimeRetired: true } : {}) });
821
- });
822
- } catch (error) {
823
- failed.push({ runtimeKey, error: error.message, retryable: true });
824
- }
825
- }
826
- return { ok: failed.length === 0, failed, results, providerCount: results.length, stopped: results.reduce((sum, result) => sum + result.threadIds.length, 0) };
827
- }
828
-
829
- // A runtime with a running turn keeps the credentials it started with until
830
- // that turn ends. Its active goals are paused so it starts no further turn;
831
- // retireStaleRuntime stops it once it is idle. Any other runtime returns
832
- // null and stops at once, like an idle one.
833
- async function deferBusyRuntime(runtimeKey, peers) {
834
- let running;
835
- try {
836
- running = await runningNativeThreads(peers);
837
- } catch (error) {
838
- // An unreadable runtime cannot show a running turn.
839
- debugLog("appServerRuntime.staleInspection.error", { error: debugError(error), runtimeKey });
840
- return null;
841
- }
842
- // Nor can one whose control connection closed while it was read.
843
- const client = peers.find(record => record.provider.client?.isOpen?.())?.provider.client;
844
- if (!running.length || !client) return null;
845
- const pausedGoals = [];
846
- for (const threadId of running) {
847
- try {
848
- const { goal } = await nativeRequest(client, "thread/goal/get", { threadId });
849
- if (goal?.status !== "active") continue;
850
- const result = await nativeRequest(client, "thread/goal/set", { threadId, status: "paused" });
851
- if (result?.goal?.status !== "paused") throw new Error("Codex did not confirm that the goal was paused.");
852
- pausedGoals.push(threadId);
853
- } catch (error) {
854
- debugLog("appServerRuntime.staleGoalPause.error", { error: debugError(error), runtimeKey, threadId });
855
- }
856
- }
857
- for (const peer of peers) peer.provider.markStale?.();
858
- const stale = {
859
- runtimeKey, threadIds: running, pausedGoals, retiring: null,
860
- providerKeys: peers.map(peer => peer.providerKey)
861
- };
862
- codexAppServerStaleRuntimes.set(runtimeKey, stale);
863
- return stale;
864
- }
865
-
866
509
  // Stops a stale runtime once no loaded thread is running, through the same
867
510
  // verified stop an idle runtime gets at the account change.
868
511
  function retireStaleRuntime(runtimeKey = "") {
@@ -873,7 +516,7 @@ export function createCodexAppServerProviderOwner({
873
516
  const peers = runtimePeers(runtimeKey);
874
517
  const running = await runningNativeThreads(peers).catch(() => []);
875
518
  // A Temporary turn or goal holding a borrowed provider also keeps it.
876
- if (running.length || peers.some(peer => peer.scope.isHeld?.(peer.providerKey))) {
519
+ if (running.length || peers.some(peer => peer.scope.isHeld(peer.providerKey))) {
877
520
  return { ...stale, busy: true, stopped: false };
878
521
  }
879
522
  const owned = codexAppServerOwnedRuntimes.get(runtimeKey);
@@ -897,206 +540,6 @@ export function createCodexAppServerProviderOwner({
897
540
  return retiring;
898
541
  }
899
542
 
900
- async function invalidateRuntimes({
901
- deferBusy = false,
902
- includeOwned = false,
903
- requireVerifiedExit = false,
904
- stopOwnedRuntimes = false,
905
- toolHomeSource = "",
906
- modelProviderId = ""
907
- } = {}) {
908
- if (modelProviderId) return withLifecycle(() => invalidateProviderThreads(modelProviderId, includeOwned, toolHomeSource));
909
- codexAppServerChatModelCatalog = null;
910
- const normalizedToolHomeSource = normalizeText(toolHomeSource);
911
- const ownedRecordsByProvider = new Map(
912
- [...codexAppServerOwnedRuntimes.values()].filter(inScope).map((record) => [record.provider, record])
913
- );
914
- const targets = [...providerRecords()]
915
- .filter(record => inScope(record) && (!normalizedToolHomeSource ||
916
- normalizeText(record.providerOptions.toolHomeSource) === normalizedToolHomeSource))
917
- .map(record => ({
918
- ...record,
919
- kind: "cached",
920
- record: ownedRecordsByProvider.get(record.provider) || null
921
- }));
922
- const targetedProviders = new Set(targets.map((target) => target.provider));
923
- if (includeOwned) {
924
- for (const record of codexAppServerOwnedRuntimes.values()) {
925
- if (!inScope(record) || targetedProviders.has(record.provider)) {
926
- continue;
927
- }
928
- const recordToolHomeSource = normalizeText(
929
- record.providerOptions?.toolHomeSource
930
- );
931
- if (
932
- normalizedToolHomeSource &&
933
- recordToolHomeSource !== normalizedToolHomeSource
934
- ) {
935
- continue;
936
- }
937
- targets.push({
938
- ...record,
939
- kind: "owned",
940
- provider: record.provider,
941
- providerKey: record.providerKey,
942
- record
943
- });
944
- targetedProviders.add(record.provider);
945
- }
946
- }
947
- targets.sort((left, right) => Number(Boolean(left.record)) - Number(Boolean(right.record)));
948
- const failed = [];
949
- const results = [];
950
- const deferred = [];
951
- if (deferBusy) {
952
- const groups = new Map();
953
- for (const target of targets) {
954
- const runtimeKey = codexAppServerOwnedRuntimeKey(target.providerKey, target.providerOptions);
955
- if (!groups.has(runtimeKey)) groups.set(runtimeKey, []);
956
- groups.get(runtimeKey).push(target);
957
- }
958
- for (const [runtimeKey, peers] of groups) {
959
- if (codexAppServerStaleRuntimes.has(runtimeKey)) {
960
- deferred.push(codexAppServerStaleRuntimes.get(runtimeKey));
961
- } else {
962
- const stale = await withRuntimeLifecycle(runtimeKey, () => deferBusyRuntime(runtimeKey, peers));
963
- if (!stale) continue;
964
- deferred.push(stale);
965
- }
966
- for (const peer of peers) {
967
- targets.splice(targets.indexOf(peer), 1);
968
- results.push({ providerKey: peer.providerKey, deferred: true, stopped: false });
969
- }
970
- }
971
- }
972
- for (const target of targets) {
973
- if (!target.resources?.invalidation) continue;
974
- const error = Object.assign(new Error("Codex's native runtime was invalidated."), {
975
- code: "codex_runtime_invalidated"
976
- });
977
- target.invalidation = { error };
978
- try {
979
- if (await target.resources.invalidation.prepare({ provider: target.provider, error }) === false) {
980
- delete target.invalidation;
981
- }
982
- } catch (preparationError) {
983
- target.invalidation.preparationError = preparationError;
984
- }
985
- }
986
- for (const target of targets) {
987
- const preserveProcessExitProof = target.preserveProcessExitProof === true;
988
- try {
989
- const result = stopOwnedRuntimes && target.record
990
- ? await target.scope.stopOwnedRuntime({
991
- ...(target.record || {}),
992
- provider: target.provider,
993
- providerKey: target.providerKey
994
- }, {
995
- cached: target.kind === "cached",
996
- preserveProcessExitProof,
997
- requireVerifiedExit
998
- })
999
- : await target.scope.stopCachedProvider(target.providerKey, {
1000
- preserveProcessExitProof,
1001
- requireStopped: stopOwnedRuntimes && target.record != null
1002
- });
1003
- if (
1004
- requireVerifiedExit &&
1005
- target.record &&
1006
- !codexAppServerRuntimeStopWasVerified(result)
1007
- ) {
1008
- throw Object.assign(new Error("Codex app-server process exit could not be verified."), {
1009
- code: exitUnverifiedCode, providerKey: target.providerKey, retryable: true
1010
- });
1011
- }
1012
- target.stopResult = result;
1013
- results.push(result);
1014
- } catch (error) {
1015
- target.stopError = error;
1016
- failed.push({
1017
- code: normalizeText(error?.code),
1018
- error: String(error?.message || "Codex app-server runtime invalidation failed."),
1019
- providerKey: target.providerKey,
1020
- retryable: error?.retryable === true
1021
- });
1022
- }
1023
- }
1024
- for (const target of targets) {
1025
- if (!target.invalidation) continue;
1026
- const runtimeKey = codexAppServerOwnedRuntimeKey(target.providerKey, target.providerOptions);
1027
- const peers = targets.filter(peer =>
1028
- codexAppServerOwnedRuntimeKey(peer.providerKey, peer.providerOptions) === runtimeKey);
1029
- const proof = peers.find(peer => codexAppServerRuntimeStopWasVerified(peer.stopResult));
1030
- const stopped = proof
1031
- ? { status: "fulfilled", value: proof.stopResult }
1032
- : { status: "rejected", reason: peers.find(peer => peer.stopError)?.stopError ||
1033
- Object.assign(new Error("Codex app-server process exit could not be verified."), {
1034
- code: exitUnverifiedCode, providerKey: target.providerKey, retryable: true
1035
- }) };
1036
- try {
1037
- await target.resources.invalidation.complete({
1038
- provider: target.provider, ...target.invalidation, stopped
1039
- });
1040
- if (target.invalidation.preparationError) throw target.invalidation.preparationError;
1041
- if (stopped.status === "rejected") throw stopped.reason;
1042
- } catch (error) {
1043
- failed.push({
1044
- code: normalizeText(error?.code),
1045
- error: String(error?.message || "Codex app-server invalidation could not be recorded."),
1046
- providerKey: target.providerKey,
1047
- retryable: true
1048
- });
1049
- }
1050
- }
1051
- const stopped = results.filter((result) => result.stopped).length;
1052
- return {
1053
- deferred: deferred.map(({ runtimeKey, providerKeys, threadIds, pausedGoals }) =>
1054
- ({ runtimeKey, providerKeys: [...providerKeys], threadIds: [...threadIds], pausedGoals: [...pausedGoals] })),
1055
- failed,
1056
- ok: failed.length === 0,
1057
- providerCount: targets.length + deferred.reduce((sum, stale) => sum + stale.providerKeys.length, 0),
1058
- results,
1059
- stopped
1060
- };
1061
- }
1062
-
1063
- async function suspendUnobservedProvider(record, error) {
1064
- const targets = [record];
1065
- let sharedStop = false;
1066
- try {
1067
- const threads = await record.observation.prepare({ provider: record.provider, error });
1068
- if (!threads.size) throw new Error("The unobserved Codex thread could not be identified.");
1069
- for (const [threadId, turnId] of threads) {
1070
- await record.provider.stopThreadForObservationLoss(threadId, turnId);
1071
- }
1072
- } catch {
1073
- sharedStop = true;
1074
- const runtimeKey = codexAppServerOwnedRuntimeKey(record.providerKey, record.providerOptions);
1075
- for (const peer of providerRecords()) {
1076
- if (peer !== record && codexAppServerOwnedRuntimeKey(peer.providerKey, peer.providerOptions) === runtimeKey) {
1077
- peer.provider.observationFailure = error;
1078
- targets.push(peer);
1079
- }
1080
- }
1081
- // Preserve the original ordering and failure policy: attempt every durable
1082
- // barrier before native stop; a storage failure cannot leave work running.
1083
- for (const target of targets) {
1084
- try {
1085
- await target.observation.barrier({ provider: target.provider, error });
1086
- } catch (failure) {
1087
- target.scope.reportRecoveryEvent(target, "persistenceFailed", failure);
1088
- }
1089
- }
1090
- const owned = codexAppServerOwnedRuntimes.get(runtimeKey) || record;
1091
- await owned.scope.stopOwnedRuntime(owned, { preserveProcessExitProof: true, requireVerifiedExit: true });
1092
- }
1093
- for (const target of targets) {
1094
- // Each adapter retains its original persistence/publication order. It can
1095
- // release this provider only after its stopped state is durable.
1096
- await target.observation.complete({ provider: target.provider, error, sharedStop });
1097
- }
1098
- }
1099
-
1100
543
  function reportRecoveryEvent(record, event, error) {
1101
544
  onRecoveryEvent(record.owner, event, error);
1102
545
  }
@@ -1107,7 +550,7 @@ export function createCodexAppServerProviderOwner({
1107
550
  assertOpen();
1108
551
  if (!record.observationStop || record.observationStop.error !== error) {
1109
552
  reportRecoveryEvent(record, "lost", error);
1110
- const stop = { error, promise: suspendUnobservedProvider(record, error) };
553
+ const stop = { error, promise: invalidation.suspendUnobservedProvider(record, error) };
1111
554
  record.observationStop = stop;
1112
555
  const tracked = stop.promise.catch(failure => {
1113
556
  reportRecoveryEvent(record, "stopFailed", failure);
@@ -1125,294 +568,6 @@ export function createCodexAppServerProviderOwner({
1125
568
  return record && record.provider === provider ? record.observationStop?.promise || null : null;
1126
569
  }
1127
570
 
1128
- async function stopProvidersForProject({
1129
- preserveProcessExitProof = false,
1130
- projectContextRoot = "",
1131
- reason = "",
1132
- } = {}, { managed, keyFields, helperThreads, sessionKeyPrefix = "" }) {
1133
- const normalizedProjectContextRoot = normalizeText(projectContextRoot);
1134
- if (!normalizedProjectContextRoot) {
1135
- return {
1136
- failed: [],
1137
- ok: true,
1138
- projectContextRoot: normalizedProjectContextRoot,
1139
- providerCount: 0,
1140
- reason: normalizeText(reason),
1141
- results: [],
1142
- stopped: 0
1143
- };
1144
- }
1145
- const helperProviders = new Set(
1146
- helperThreads.records({
1147
- projectContextRoot: normalizedProjectContextRoot
1148
- }).map((record) => record.provider)
1149
- );
1150
- // Main threads name their project; every other provider of the project's
1151
- // sessions (Temporary chats, reads) is found by its session key.
1152
- const providerKeys = [...providers.keys()]
1153
- .filter((providerKey) => {
1154
- const current = managed.get(providerKey);
1155
- return normalizeText(current?.projectContext?.targetRoot) === normalizedProjectContextRoot ||
1156
- helperProviders.has(providers.get(providerKey)) ||
1157
- Boolean(sessionKeyPrefix && normalizeText(providerOwners.get(providerKey)?.sessionKey).startsWith(sessionKeyPrefix));
1158
- });
1159
- const failed = [];
1160
- const results = [];
1161
- for (const providerKey of providerKeys) {
1162
- try {
1163
- const fields = keyFields(providerKey);
1164
- results.push(await stopCachedProvider(providerKey, {
1165
- preserveProcessExitProof: Boolean(
1166
- preserveProcessExitProof &&
1167
- fields.sessionId &&
1168
- fields.executionMode !== "helper"
1169
- )
1170
- }));
1171
- } catch (error) {
1172
- failed.push({
1173
- code: normalizeText(error?.code),
1174
- error: normalizeText(error?.error || error?.message || error) || runtimeCloseError,
1175
- providerKey,
1176
- retryable: error?.retryable === true
1177
- });
1178
- }
1179
- }
1180
- const stopped = results.filter((result) => result.stopped).length;
1181
- debugLog("appServerRuntime.closeProject.done", {
1182
- failedCount: failed.length,
1183
- providerCount: providerKeys.length,
1184
- projectContextRoot: normalizedProjectContextRoot,
1185
- reason: normalizeText(reason),
1186
- stopped
1187
- });
1188
- return {
1189
- failed,
1190
- ok: failed.length === 0,
1191
- projectContextRoot: normalizedProjectContextRoot,
1192
- providerCount: providerKeys.length,
1193
- reason: normalizeText(reason),
1194
- results,
1195
- stopped
1196
- };
1197
- }
1198
-
1199
- async function stopCachedProvidersForSession(sessionKey = "", options = {}) {
1200
- if (!sessionKey) {
1201
- return {
1202
- failed: [],
1203
- ok: true,
1204
- providerCount: 0,
1205
- results: [],
1206
- stopped: 0
1207
- };
1208
- }
1209
- const providerKeys = [...providers.keys()]
1210
- .filter((providerKey) => (
1211
- providerOwners.get(providerKey)?.sessionKey === sessionKey
1212
- ));
1213
- const failed = [];
1214
- const results = [];
1215
- for (const providerKey of providerKeys) {
1216
- try {
1217
- results.push(await stopCachedProvider(providerKey, options));
1218
- } catch (error) {
1219
- failed.push({
1220
- code: normalizeText(error?.code),
1221
- error: normalizeText(error?.error || error?.message || error) || runtimeCloseError,
1222
- providerKey,
1223
- retryable: error?.retryable === true
1224
- });
1225
- }
1226
- }
1227
- return {
1228
- failed,
1229
- ok: failed.length === 0,
1230
- providerCount: providerKeys.length,
1231
- results,
1232
- stopped: results.filter((result) => result.stopped).length
1233
- };
1234
- }
1235
-
1236
- function drainThreadEnvironmentTasks(sessionKey) {
1237
- return Promise.all([...providers.entries()]
1238
- .filter(([key]) => providerOwners.get(key)?.sessionKey === sessionKey)
1239
- .flatMap(([, provider]) => [...(provider.threadEnvironmentTasks?.values() || [])])
1240
- .map((task) => task.catch(() => null)));
1241
- }
1242
-
1243
- async function stopPersistedRuntime({ runtime, parameters }, runtimeDir) {
1244
- const result = await runtime.stop(parameters);
1245
-
1246
- return {
1247
- ...result,
1248
- runtimeDirExists: await directoryExists(runtimeDir),
1249
- verifiedStopped: result?.stopped === true ||
1250
- result?.processExitVerified === true ||
1251
- result?.runtimeDirRemoved === true
1252
- };
1253
- }
1254
-
1255
- function sessionExitProof(sessionKey, context) {
1256
- const { cachedProviders, persistedRuntime } = context;
1257
- const noRuntimeAcquired = cachedProviders.providerCount === 0 &&
1258
- !context.hasRuntime &&
1259
- !context.threadId &&
1260
- ![...ownedRuntimes.values()].some((record) => record.sessionKey === sessionKey);
1261
- const cachedRuntimeExitVerified = cachedProviders.providerCount > 0 &&
1262
- cachedProviders.results.length === cachedProviders.providerCount &&
1263
- cachedProviders.results.every((result) => (
1264
- result?.stopped === true ||
1265
- (result?.sessionDetached === true && result?.sharedProcessRetained === true)
1266
- ));
1267
- const persistedRuntimeExitVerified = persistedRuntime?.verifiedStopped === true ||
1268
- (
1269
- persistedRuntime?.sessionDetached === true &&
1270
- persistedRuntime?.sharedProcessRetained === true
1271
- );
1272
- return { noRuntimeAcquired, cachedRuntimeExitVerified, persistedRuntimeExitVerified };
1273
- }
1274
-
1275
- async function unsubscribeSessionThread(sessionKey, sessionId, session) {
1276
- if (!sessionId) {
1277
- return { ok: true, sessionId, status: "notSubscribed" };
1278
- }
1279
- const { providerOptions, workdir, threadId } = session.unsubscribeParameters;
1280
- if (!threadId) {
1281
- return { ok: true, providerOptions, sessionId, status: "notSubscribed" };
1282
- }
1283
- // The existing record carries the workdir captured in its provider key.
1284
- // Never prepare new controls while releasing a retained connection.
1285
- const provider = [...records.values()].find(record =>
1286
- record.owner.sessionKey === sessionKey && record.owner.workdir === normalizeText(workdir))?.provider;
1287
- if (!provider || typeof provider.unsubscribeThread !== "function") {
1288
- return { ok: true, providerOptions, sessionId, status: "notSubscribed" };
1289
- }
1290
- const result = await provider.unsubscribeThread(threadId);
1291
- debugLog("appServerThread.unsubscribe.done", {
1292
- sessionId,
1293
- status: normalizeText(result?.status),
1294
- threadId
1295
- });
1296
- return {
1297
- ok: true,
1298
- providerOptions,
1299
- result,
1300
- sessionId,
1301
- status: normalizeText(result?.status) || "unsubscribed",
1302
- threadId
1303
- };
1304
- }
1305
-
1306
- async function stopPersistedSessionRuntime(session) {
1307
- const host = session.persistedRuntimeHost;
1308
- if (!host) return { stopped: false };
1309
- return stopPersistedRuntime(host, host.runtimeDir);
1310
- }
1311
-
1312
- async function releasePersistedRuntimeProof(runtimeOptions, session) {
1313
- // Session closure already detached this participant. Its archived runtime
1314
- // path may now belong to the successor or another live conversation.
1315
- // Releasing the old proof must not stop or delete that shared process.
1316
- if (runtimeIsShared("", runtimeOptions)) {
1317
- return { sharedProcessRetained: true };
1318
- }
1319
- const existed = await directoryExists(runtimeOptions.runtimeDir);
1320
- if (!existed) return { existed };
1321
- const result = await stopPersistedSessionRuntime(session);
1322
- return { existed, result };
1323
- }
1324
-
1325
- function releasePreparedRuntimeProof(preparation) {
1326
- return withLifecycle(async () => {
1327
- const prepared = preparation.read();
1328
- if (Object.hasOwn(prepared, "value")) return prepared.value;
1329
- return withRuntimeLifecycle(codexAppServerOwnedRuntimeKey("", prepared.runtimeOptions), async () => {
1330
- const evidence = await releasePersistedRuntimeProof(prepared.runtimeOptions, prepared);
1331
- return prepared.complete(evidence);
1332
- });
1333
- });
1334
- }
1335
-
1336
- async function releaseSession(sessionKey, context) {
1337
- const { sessionId, runOwner, session } = context;
1338
- if (context.changeover) {
1339
- const threadId = session.threadId;
1340
- for (const [key, provider] of providers) {
1341
- if (!threadId || providerOwners.get(key)?.sessionKey !== sessionKey) continue;
1342
- await stopThreadBeforeRelease(key, provider, threadId);
1343
- }
1344
- }
1345
- let exitFailure = null;
1346
- try {
1347
- const unsubscribeResult = await unsubscribeSessionThread(sessionKey, sessionId, session);
1348
- session.providerOptions = unsubscribeResult?.providerOptions || session.providerOptions;
1349
- } catch (error) {
1350
- debugLog("appServerThread.unsubscribe.error", {
1351
- error: debugError(error),
1352
- sessionId
1353
- });
1354
- } finally {
1355
- await runOwner.notificationQueue.drain(sessionId);
1356
- const cachedProviders = await stopCachedProvidersForSession(sessionId ? sessionKey : "", {
1357
- preserveProcessExitProof: context.preserveProcessExitProof,
1358
- requireStopped: context.requireStopped
1359
- });
1360
- if (cachedProviders.ok === false) {
1361
- debugLog("appServerRuntime.closeSession.cached.error", {
1362
- failed: cachedProviders.failed,
1363
- sessionId
1364
- });
1365
- }
1366
- let persistedRuntime = null;
1367
- if (session.exists) {
1368
- persistedRuntime = cachedProviders.stopped > 0
1369
- ? {
1370
- stopped: true,
1371
- verifiedStopped: true
1372
- }
1373
- : runtimeIsShared("", session.providerOptions || {})
1374
- ? {
1375
- sessionDetached: true,
1376
- sharedProcessRetained: true,
1377
- stopped: false,
1378
- verifiedStopped: false
1379
- }
1380
- : session.hasRuntime
1381
- ? await stopPersistedSessionRuntime(session)
1382
- : {
1383
- stopped: false,
1384
- verifiedStopped: false
1385
- };
1386
- debugLog("appServerRuntime.closeSession.persisted.done", {
1387
- removed: persistedRuntime?.removed === true,
1388
- runtimeDirRemoved: persistedRuntime?.runtimeDirRemoved === true,
1389
- sessionId,
1390
- stopped: persistedRuntime?.removed === true || persistedRuntime?.runtimeDirRemoved === true
1391
- });
1392
- }
1393
- if (session.requiresExitProof) {
1394
- // A fresh session with no acquired native owner needs no process proof.
1395
- // Losing a cache or recorded runtime does not make it a fresh session.
1396
- const { noRuntimeAcquired, cachedRuntimeExitVerified, persistedRuntimeExitVerified } =
1397
- sessionExitProof(sessionKey, {
1398
- cachedProviders,
1399
- persistedRuntime,
1400
- get hasRuntime() { return session.hasRuntime; },
1401
- get threadId() { return session.threadId; }
1402
- });
1403
- if (cachedProviders.failed.length > 0 ||
1404
- (!noRuntimeAcquired && !cachedRuntimeExitVerified && !persistedRuntimeExitVerified)) {
1405
- exitFailure = {
1406
- cachedFailures: cachedProviders.failed,
1407
- cachedRuntimeExitVerified,
1408
- persistedRuntime: persistedRuntime || null
1409
- };
1410
- }
1411
- }
1412
- }
1413
- return { exitFailure };
1414
- }
1415
-
1416
571
  // A borrowed provider (a read, storage work or a Temporary chat) is not the
1417
572
  // session's conversation.
1418
573
  async function readPlanUsage(sessionKey) {
@@ -1437,21 +592,58 @@ export function createCodexAppServerProviderOwner({
1437
592
  return { status: "unavailable", goal: null };
1438
593
  }
1439
594
 
595
+ function isHeld(providerKey) {
596
+ return (transientReleases.get(normalizeText(providerKey))?.users || 0) > 0;
597
+ }
598
+
599
+ // The exported ways to reach a provider for real use claim it first.
600
+ function acquireClaimedRuntime(options) {
601
+ claimTransient(options?.providerKey);
602
+ return acquireRuntime(options);
603
+ }
604
+
605
+ function createClaimedProvider(options) {
606
+ claimTransient(options?.providerKey);
607
+ return createProvider(options);
608
+ }
609
+
610
+ function claimedProviderForSession(context) {
611
+ claimTransient(context.providerKey);
612
+ return providerForSession(context);
613
+ }
614
+
615
+ function beginShutdown() {
616
+ closing = true;
617
+ for (const release of transientReleases.values()) clearTimeout(release.timer);
618
+ transientReleases.clear();
619
+ }
620
+
1440
621
  const api = Object.freeze({
1441
622
  providers, providerOwners, ownedRuntimes, lifecycleTasks, runtimeAcquisitions,
1442
- isHeld: providerKey => (transientReleases.get(normalizeText(providerKey))?.users || 0) > 0,
623
+ isHeld,
1443
624
  withLifecycle, runtimeIsShared, rememberOwnedRuntime, forgetOwnedRuntime, attachRuntimeLifecycle,
1444
- acquireRuntime: options => { claimTransient(options?.providerKey); return acquireRuntime(options); },
1445
- createProvider: options => { claimTransient(options?.providerKey); return createProvider(options); },
1446
- providerForSession: context => { claimTransient(context.providerKey); return providerForSession(context); },
1447
- createNativeProvider, ensureSession, borrowSession, withTransientSession, holdProvider, retireAndCloseProvider, closeProvider, stopOwnedRuntime, stopCachedProvider, stopProvider, stopThreadBeforeRelease, readModelCatalog, invalidateRuntimes, recover,
1448
- reportRecoveryEvent, pendingRecovery, readSessionGoal, readPlanUsage, stopCachedProvidersForSession, stopProvidersForProject, drainThreadEnvironmentTasks, stopPersistedRuntime, sessionExitProof,
1449
- unsubscribeSessionThread, releasePersistedRuntimeProof, releasePreparedRuntimeProof, releaseSession, retireStaleRuntime,
1450
- beginShutdown() {
1451
- closing = true;
1452
- for (const release of transientReleases.values()) clearTimeout(release.timer);
1453
- transientReleases.clear();
1454
- }
625
+ acquireRuntime: acquireClaimedRuntime,
626
+ createProvider: createClaimedProvider,
627
+ providerForSession: claimedProviderForSession,
628
+ createNativeProvider, ensureSession, borrowSession, withTransientSession, holdProvider, retireAndCloseProvider, closeProvider,
629
+ stopOwnedRuntime,
630
+ stopCachedProvider: sessionRelease.stopCachedProvider,
631
+ stopProvider: sessionRelease.stopProvider,
632
+ stopThreadBeforeRelease: sessionRelease.stopThreadBeforeRelease,
633
+ readModelCatalog: modelCatalog.read,
634
+ invalidateRuntimes: invalidation.invalidateRuntimes,
635
+ recover, reportRecoveryEvent, pendingRecovery, readSessionGoal, readPlanUsage,
636
+ stopCachedProvidersForSession: sessionRelease.stopCachedProvidersForSession,
637
+ stopProvidersForProject: sessionRelease.stopProvidersForProject,
638
+ drainThreadEnvironmentTasks: sessionRelease.drainThreadEnvironmentTasks,
639
+ stopPersistedRuntime: sessionRelease.stopPersistedRuntime,
640
+ sessionExitProof: sessionRelease.sessionExitProof,
641
+ unsubscribeSessionThread: sessionRelease.unsubscribeSessionThread,
642
+ releasePersistedRuntimeProof: sessionRelease.releasePersistedRuntimeProof,
643
+ releasePreparedRuntimeProof: sessionRelease.releasePreparedRuntimeProof,
644
+ releaseSession: sessionRelease.releaseSession,
645
+ retireStaleRuntime,
646
+ beginShutdown
1455
647
  });
1456
648
  return api;
1457
649
  }