@jskit-ai/assistant-core 0.2.6 → 0.2.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (214) hide show
  1. package/examples/conversation/package.json +5 -5
  2. package/package.json +18 -7
  3. package/src/client/browser.js +21 -0
  4. package/src/client/conversation/AssistantAttachmentQueue.vue +25 -108
  5. package/src/client/conversation/AssistantComposerActions.vue +1 -1
  6. package/src/client/conversation/AssistantConversationElement.vue +14 -52
  7. package/src/client/conversation/AssistantGoalControl.vue +18 -12
  8. package/src/client/conversation/AssistantPromptInput.vue +6 -91
  9. package/src/client/conversation/AssistantTranscript.css +505 -0
  10. package/src/client/conversation/AssistantTranscript.vue +24 -1197
  11. package/src/client/conversation/attachmentFiles.js +158 -0
  12. package/src/client/conversation/attachmentQueueItems.js +121 -0
  13. package/src/client/conversation/attachmentUploadRows.js +265 -0
  14. package/src/client/conversation/conversationDisplay.js +54 -0
  15. package/src/client/conversation/messageDelivery.js +13 -9
  16. package/src/client/conversation/retainedConversation.js +14 -3
  17. package/src/client/conversation/submitText.js +13 -2
  18. package/src/client/conversation/transcriptEntries.js +225 -0
  19. package/src/client/conversation/useAssistantAttachments.js +86 -388
  20. package/src/client/conversation/useAssistantQuestions.js +16 -3
  21. package/src/client/conversation/useAssistantSuggestions.js +16 -2
  22. package/src/client/conversation/useScrollToBottom.js +17 -19
  23. package/src/client/conversation/useTextareaAutoGrow.js +115 -0
  24. package/src/client/conversation/useTranscriptScrollFollow.js +525 -0
  25. package/src/client/lib/assistantApi.js +192 -161
  26. package/src/client/types.js +110 -0
  27. package/src/server/conversation/attachments.js +21 -6
  28. package/src/server/conversation/boundedToolLoop.js +1 -0
  29. package/src/server/conversation/claudeClient.js +71 -20
  30. package/src/server/conversation/claudeCommandRun.js +191 -0
  31. package/src/server/conversation/claudeHistory.js +7 -0
  32. package/src/server/conversation/claudeNativeTurn.js +708 -0
  33. package/src/server/conversation/claudeProcess.js +186 -112
  34. package/src/server/conversation/claudeProcessStart.js +214 -0
  35. package/src/server/conversation/claudePromptDelivery.js +183 -0
  36. package/src/server/conversation/claudeSessionCleanup.js +248 -0
  37. package/src/server/conversation/claudeTools.js +15 -3
  38. package/src/server/conversation/claudeTurn.js +212 -1388
  39. package/src/server/conversation/codexAccountReader.js +168 -0
  40. package/src/server/conversation/codexAuthPreflight.js +5 -4
  41. package/src/server/conversation/codexClient.js +18 -6
  42. package/src/server/conversation/codexConfiguration.js +8 -844
  43. package/src/server/conversation/codexConversationStops.js +377 -0
  44. package/src/server/conversation/codexConversationStore.js +119 -85
  45. package/src/server/conversation/codexConversationTurns.js +378 -0
  46. package/src/server/conversation/codexConversations.js +134 -866
  47. package/src/server/conversation/codexDetachedConversation.js +203 -0
  48. package/src/server/conversation/codexDetachedTurn.js +145 -104
  49. package/src/server/conversation/codexEvents.js +5 -200
  50. package/src/server/conversation/codexGoalCommands.js +14 -4
  51. package/src/server/conversation/codexHelperExecution.js +57 -57
  52. package/src/server/conversation/codexHelperThreadLedger.js +30 -288
  53. package/src/server/conversation/codexHelperThreadRecord.js +304 -0
  54. package/src/server/conversation/codexHelperThreadRecovery.js +54 -76
  55. package/src/server/conversation/codexHelperThreads.js +94 -93
  56. package/src/server/conversation/codexHistoryAdapter.js +13 -8
  57. package/src/server/conversation/codexIsolation.js +672 -0
  58. package/src/server/conversation/codexLiveItems.js +379 -0
  59. package/src/server/conversation/codexMessageCommands.js +68 -286
  60. package/src/server/conversation/codexMessageDelivery.js +46 -37
  61. package/src/server/conversation/codexModelCatalog.js +86 -7
  62. package/src/server/conversation/codexNotificationObserver.js +81 -88
  63. package/src/server/conversation/codexNotificationQueue.js +21 -10
  64. package/src/server/conversation/codexObservationControl.js +105 -89
  65. package/src/server/conversation/codexOutput.js +84 -419
  66. package/src/server/conversation/codexProcess.js +29 -1053
  67. package/src/server/conversation/codexProcessLeader.js +1 -0
  68. package/src/server/conversation/codexPromptDispatch.js +241 -0
  69. package/src/server/conversation/codexProvider.js +147 -1807
  70. package/src/server/conversation/codexProviderAuth.js +348 -0
  71. package/src/server/conversation/codexProviderInvalidation.js +371 -0
  72. package/src/server/conversation/codexProviderInventories.js +437 -0
  73. package/src/server/conversation/codexProviderOwner.js +149 -957
  74. package/src/server/conversation/codexProviderRelease.js +465 -0
  75. package/src/server/conversation/codexProviderSelection.js +26 -15
  76. package/src/server/conversation/codexProviderThreads.js +698 -0
  77. package/src/server/conversation/codexProviderTurns.js +453 -0
  78. package/src/server/conversation/codexReasoning.js +32 -23
  79. package/src/server/conversation/codexRenewalCommands.js +18 -6
  80. package/src/server/conversation/codexRenewalThreads.js +7 -22
  81. package/src/server/conversation/codexResultSettlement.js +70 -88
  82. package/src/server/conversation/codexRunJournal.js +91 -97
  83. package/src/server/conversation/codexRuntime.js +426 -0
  84. package/src/server/conversation/codexRuntimeInventory.js +113 -0
  85. package/src/server/conversation/codexRuntimeLifecycle.js +39 -13
  86. package/src/server/conversation/codexRuntimeStart.js +431 -0
  87. package/src/server/conversation/codexRuntimeStop.js +324 -0
  88. package/src/server/conversation/codexSessionCleanup.js +25 -10
  89. package/src/server/conversation/codexSessionReconciliation.js +21 -7
  90. package/src/server/conversation/codexThreadReadiness.js +49 -52
  91. package/src/server/conversation/codexThreadSnapshot.js +210 -0
  92. package/src/server/conversation/codexThreadStatus.js +48 -62
  93. package/src/server/conversation/codexTurn.js +171 -55
  94. package/src/server/conversation/codexTurnActivity.js +210 -0
  95. package/src/server/conversation/codexTurnControl.js +44 -48
  96. package/src/server/conversation/codexTurnEndings.js +610 -0
  97. package/src/server/conversation/codexTurnRecovery.js +97 -848
  98. package/src/server/conversation/codexTurnState.js +138 -111
  99. package/src/server/conversation/completedResponses.js +227 -0
  100. package/src/server/conversation/configuration.js +2 -2
  101. package/src/server/conversation/continuity.js +43 -6
  102. package/src/server/conversation/conversationDelivery.js +394 -0
  103. package/src/server/conversation/conversationEntries.js +365 -0
  104. package/src/server/conversation/conversationEvents.js +192 -0
  105. package/src/server/conversation/conversationGoals.js +187 -0
  106. package/src/server/conversation/conversationHandles.js +237 -0
  107. package/src/server/conversation/conversationReads.js +206 -0
  108. package/src/server/conversation/conversationRefusals.js +58 -0
  109. package/src/server/conversation/conversationReplacement.js +254 -0
  110. package/src/server/conversation/conversationSending.js +273 -0
  111. package/src/server/conversation/conversationStopping.js +121 -0
  112. package/src/server/conversation/conversationTurnDelivery.js +413 -0
  113. package/src/server/conversation/conversationTurns.js +511 -0
  114. package/src/server/conversation/deliveryTiming.js +25 -3
  115. package/src/server/conversation/fileStorage.js +11 -6
  116. package/src/server/conversation/hookBridge.js +14 -3
  117. package/src/server/conversation/index.js +1 -1
  118. package/src/server/conversation/localExecution.js +82 -625
  119. package/src/server/conversation/nativeCommandCallbacks.js +10 -0
  120. package/src/server/conversation/nativeConversationLifecycle.js +379 -0
  121. package/src/server/conversation/nativeHistoryExport.js +27 -18
  122. package/src/server/conversation/normalize.js +9 -0
  123. package/src/server/conversation/openCodeClient.js +295 -366
  124. package/src/server/conversation/openCodeNativeHistory.js +141 -0
  125. package/src/server/conversation/openCodeNativeSession.js +79 -0
  126. package/src/server/conversation/openCodeProcess.js +109 -53
  127. package/src/server/conversation/openCodeProcessOwner.js +565 -0
  128. package/src/server/conversation/openCodeRuntime.js +111 -1814
  129. package/src/server/conversation/openCodeSessionTurns.js +547 -0
  130. package/src/server/conversation/openCodeTemporaryConversations.js +394 -0
  131. package/src/server/conversation/openCodeTools.js +28 -14
  132. package/src/server/conversation/openCodeTurn.js +80 -74
  133. package/src/server/conversation/openCodeTurnMonitor.js +388 -0
  134. package/src/server/conversation/processIdentity.js +582 -0
  135. package/src/server/conversation/provider.js +21 -6
  136. package/src/server/conversation/providers/api.js +268 -107
  137. package/src/server/conversation/providers/claude.js +29 -6
  138. package/src/server/conversation/providers/claudeDriver.js +325 -250
  139. package/src/server/conversation/providers/codex.js +69 -33
  140. package/src/server/conversation/providers/codexDriver.js +503 -1081
  141. package/src/server/conversation/providers/codexDriverConnection.js +331 -0
  142. package/src/server/conversation/providers/codexDriverTurns.js +464 -0
  143. package/src/server/conversation/providers/opencode.js +30 -4
  144. package/src/server/conversation/providers/opencodeDriver.js +321 -659
  145. package/src/server/conversation/providers/opencodeStandalone.js +475 -0
  146. package/src/server/conversation/providers/scoped.js +64 -19
  147. package/src/server/conversation/runtime.js +171 -2863
  148. package/src/server/conversation/storage.js +91 -50
  149. package/src/server/conversation/streams.js +15 -2
  150. package/src/server/conversation/structuredOutput.js +7 -11
  151. package/src/server/conversation/systemPrompt.js +13 -1
  152. package/src/server/conversation/tools.js +47 -15
  153. package/src/server/conversation/transcript.js +49 -20
  154. package/src/server/hosts/claudeTurn.js +2 -4
  155. package/src/server/hosts/codexConfiguration.js +6 -8
  156. package/src/server/hosts/codexEvents.js +4 -2
  157. package/src/server/hosts/codexProcess.js +1 -1
  158. package/src/server/hosts/codexProvider.js +11 -9
  159. package/src/server/hosts/codexTurn.js +3 -3
  160. package/src/server/hosts/openCodeClient.js +4 -2
  161. package/src/server/index.js +0 -2
  162. package/src/server/lib/aiConnectionClient.js +35 -25
  163. package/src/server/lib/ndjson.js +1 -0
  164. package/src/server/lib/providers/anthropicClient.js +15 -6
  165. package/src/server/lib/providers/common.js +10 -6
  166. package/src/server/lib/providers/openAiCompatibleClient.js +40 -23
  167. package/src/server/lib/serviceToolCatalog.js +29 -283
  168. package/src/server/lib/serviceToolEntries.js +270 -0
  169. package/src/server/types.js +78 -0
  170. package/src/shared/assistantResource.js +0 -23
  171. package/src/shared/conversation/answerChoiceSugar.js +2 -14
  172. package/src/shared/conversation/messageInputSugar.js +18 -0
  173. package/src/shared/conversation/numberedQuestionSugar.js +6 -16
  174. package/src/shared/conversation/pagination.js +1 -1
  175. package/src/shared/conversation/questions.js +1 -0
  176. package/src/shared/conversation/streaming.js +1 -0
  177. package/src/shared/index.js +0 -1
  178. package/src/testing/conversationStorageContract.js +20 -0
  179. package/test/aiConnectionClient.test.js +42 -1
  180. package/test/apiConversationDriver.test.js +217 -38
  181. package/test/assistantBoundedToolLoop.test.js +1 -1
  182. package/test/claudeConversationSession.test.js +15 -11
  183. package/test/claudeInstructionResume.test.js +1 -1
  184. package/test/claudeProcess.test.js +9 -9
  185. package/test/claudeTurn.test.js +21 -8
  186. package/test/codexAccountChange.test.js +5 -3
  187. package/test/codexConversationSession.test.js +22 -21
  188. package/test/codexEvents.test.js +3 -1
  189. package/test/codexHelperDeletion.test.js +3 -7
  190. package/test/codexIsolation.test.js +6 -6
  191. package/test/codexModelCatalog.test.js +6 -7
  192. package/test/codexProcess.test.js +3 -3
  193. package/test/codexThreadSelection.test.js +4 -1
  194. package/test/codexTurn.test.js +29 -20
  195. package/test/conversationApiFacilities.test.js +11 -9
  196. package/test/conversationAttachments.test.js +3 -3
  197. package/test/conversationConfiguration.test.js +4 -4
  198. package/test/conversationNativeStorage.test.js +11 -10
  199. package/test/conversationReplacement.test.js +13 -13
  200. package/test/conversationRuntime.test.js +9 -5
  201. package/test/conversationSession.test.js +78 -22
  202. package/test/conversationTools.test.js +47 -26
  203. package/test/fileConversationStorage.test.js +10 -3
  204. package/test/fixtures/codexHelperThreadLedger.js +3 -3
  205. package/test/frameworkActions.test.js +33 -18
  206. package/test/localConversationExecution.test.js +4 -4
  207. package/test/localExecution.test.js +1 -1
  208. package/test/openCodeConnectionChange.test.js +3 -2
  209. package/test/openCodeRuntime.test.js +8 -7
  210. package/test/openCodeTools.test.js +1 -1
  211. package/test/opencodeConversationSession.test.js +4 -3
  212. package/test/serviceToolCatalog.test.js +3 -8
  213. package/src/server/lib/assistantCompletion.js +0 -190
  214. package/src/server/lib/assistantToolLoop.js +0 -259
@@ -0,0 +1,331 @@
1
+ import { CODEX_APP_SERVER_DESKTOP_BUS_ENV_NAMES, codexCommandHookDefinition } from "../codexProcess.js";
2
+ import { CodexAppServerAgentProvider } from "../codexProvider.js";
3
+ import {
4
+ assertCodexApplicationToolSchemaIdentity,
5
+ codexApplicationToolConfiguration,
6
+ ensureCodexAppServerThread
7
+ } from "../codexProviderThreads.js";
8
+ import { codexProviderConfiguration } from "../codexConfiguration.js";
9
+ import { codexToolFreeConfiguration } from "../codexIsolation.js";
10
+ import { codexCommandHookCommand } from "../commandWrapper.js";
11
+ import { validateConnectionModel } from "../configuration.js";
12
+ import { jsonHash as hash } from "../normalize.js";
13
+ import { nativeAiProvider, nativeAiModel } from "../../../shared/nativeProviders.js";
14
+
15
+ /**
16
+ * The native side of one driver conversation: its observation, the provider it acquires and stops, the account it
17
+ * selects and the thread settings it prepares. It reads and updates the conversation's session record.
18
+ * @param {object} dependencies
19
+ * @param {import("./codexDriver.js").CodexDriverSession} dependencies.session
20
+ * @param {object | undefined} dependencies.supplied
21
+ * @param {string} dependencies.sessionId
22
+ * @param {string} dependencies.providerKey
23
+ * @param {import("../codexProviderOwner.js").CodexAppServerProviderOwner} dependencies.providerOwner
24
+ * @param {import("../codexRuntimeLifecycle.js").CodexRunOwner} dependencies.owner
25
+ * @param {object} dependencies.runtime
26
+ * @param {((error: Error) => void) | undefined} dependencies.reportFailure
27
+ * @param {(update: (binding: object) => object) => Promise<void>} dependencies.updateBinding
28
+ * @param {(goal: object | null) => Promise<object | null>} dependencies.recordGoal
29
+ * @param {(client: object, signal: AbortSignal) => Promise<string>} dependencies.accountIdentity
30
+ * @param {import("./codexDriver.js").CodexConversationConnections} dependencies.connections
31
+ * @param {Record<string, string>} dependencies.env
32
+ * @param {object} dependencies.host
33
+ * @param {object} dependencies.limits
34
+ * @param {(selected: object) => Promise<object>} dependencies.nativeHost
35
+ * @param {boolean} dependencies.nativeTools
36
+ */
37
+ export function createCodexDriverConnection({
38
+ session,
39
+ supplied,
40
+ sessionId,
41
+ providerKey,
42
+ providerOwner,
43
+ owner,
44
+ runtime,
45
+ reportFailure,
46
+ updateBinding,
47
+ recordGoal,
48
+ accountIdentity,
49
+ connections,
50
+ env,
51
+ host,
52
+ limits,
53
+ nativeHost,
54
+ nativeTools
55
+ }) {
56
+ const observation = supplied ? null : owner.createObservation(sessionId, {
57
+ target(error) {
58
+ if (session.observationTarget?.error !== error) session.observationTarget = { error, threadId: session.binding.threadId,
59
+ message: "Codex observation was lost. Work is stopped; use Resume or Send to continue.",
60
+ pendingMessage: "Codex observation was lost. A stop is not yet confirmed." };
61
+ return session.observationTarget;
62
+ },
63
+ retireProvider: () => providerOwner.closeProvider(providerKey)
64
+ });
65
+
66
+ async function recordObservationBarrier({ provider, error }) {
67
+ session.observationError = error;
68
+ // Keep admission unavailable in memory even if its durable write fails.
69
+ await reportFailure(error);
70
+ await updateBinding({ observationLoss: {
71
+ message: error.message, providerId: session.nativeProviderId,
72
+ runtime: provider.runtime || provider.runtimeStopOwner || session.binding.observationLoss?.runtime,
73
+ stopped: false
74
+ } });
75
+ }
76
+
77
+ async function completeObservation({ provider, error, sharedStop, threadIds }) {
78
+ if (threadIds && !threadIds.includes(session.binding.threadId)) return;
79
+ await updateBinding({ observationLoss: { ...session.binding.observationLoss, stopped: true },
80
+ ...(sharedStop ? { executionId: "" } : {}) });
81
+ await observation.complete({ provider, error, sharedStop, threadIds });
82
+ session.observationError = null;
83
+ await reportFailure(error, { recovered: true });
84
+ session.current?.completion.reject(error);
85
+ }
86
+
87
+ function acquireProvider(facilities, providerId) {
88
+ session.nativeProviderId = providerId;
89
+ const providerOptions = {
90
+ ...facilities.options, runtimeDir: facilities.parameters.runtimeDir, modelProviderId: providerId,
91
+ threadEnv: env,
92
+ readInstructions: () => session.preparedThreadSettings?.developerInstructions,
93
+ prepareThreadEnvironment: environment => session.preparedThreadSettings?.config.shell_environment_policy.set || environment,
94
+ prepareThreadResumeParams(threadId, params) {
95
+ if (threadId !== session.binding.threadId || !session.preparedThreadSettings) {
96
+ throw new Error("Codex requires current authorized settings before resuming this conversation.");
97
+ }
98
+ return { ...session.preparedThreadSettings, ...params,
99
+ config: { ...session.preparedThreadSettings.config, ...params.config } };
100
+ },
101
+ async beforeResumeThread(threadId) {
102
+ owner.assertThreadCanResume(await owner.readAgentRunForSession(runtime.store, sessionId), threadId);
103
+ },
104
+ requestTimeoutMs: limits.admissionTimeoutMs || 30_000
105
+ };
106
+ return providerOwner.withLifecycle(() => providerOwner.createProvider({
107
+ providerKey, providerOptions, preserveProcessExitProof: true,
108
+ resources: {
109
+ release(provider) {
110
+ if (session.native === provider) session.native = null;
111
+ const subscriptionPrefix = `${providerKey}:`;
112
+ for (const key of [...owner.eventSubscriptions.keys()]) {
113
+ if (key.startsWith(subscriptionPrefix)) owner.unsubscribeEventSubscription(key);
114
+ }
115
+ },
116
+ invalidation: {
117
+ async prepare(input) {
118
+ if (session.native !== input.provider) return false;
119
+ await recordObservationBarrier(input);
120
+ await observation.barrier(input);
121
+ },
122
+ async complete({ provider, error, preparationError, stopped }) {
123
+ try {
124
+ if (preparationError) throw preparationError;
125
+ if (stopped.status !== "fulfilled") throw stopped.reason;
126
+ await completeObservation({ provider, error, sharedStop: true });
127
+ } catch (failure) {
128
+ error.cleanupFailed = true;
129
+ error.cause = failure;
130
+ session.observationError = error;
131
+ await reportFailure(error);
132
+ session.current?.completion.reject(error);
133
+ throw failure;
134
+ }
135
+ }
136
+ }
137
+ },
138
+ create: onObservationLost => new CodexAppServerAgentProvider({ ...providerOptions, onObservationLost }, facilities),
139
+ observation: {
140
+ async prepare(input) {
141
+ if (input.threadIds && !input.threadIds.includes(session.binding.threadId)) return new Map();
142
+ session.observationError = input.error;
143
+ await reportFailure(input.error);
144
+ const threads = await observation.prepare(input);
145
+ await recordObservationBarrier(input);
146
+ return threads;
147
+ },
148
+ async barrier(input) {
149
+ if (input.threadIds && !input.threadIds.includes(session.binding.threadId)) return;
150
+ await observation.barrier(input);
151
+ await recordObservationBarrier(input);
152
+ },
153
+ complete: completeObservation
154
+ }
155
+ }), providerKey, providerOptions);
156
+ }
157
+
158
+ async function stop() {
159
+ if (session.stopping) return session.stopping;
160
+ session.stopping = (async () => {
161
+ if (!session.native && session.observationError) {
162
+ // Recover the exact saved owner through the production disk lock
163
+ // and metadata path. Cleanup must not start or resume a thread.
164
+ const saved = session.binding.observationLoss;
165
+ const facilities = await nativeHost({ providerId: saved.providerId,
166
+ accountIdentity: session.binding.connectionIdentities?.[saved.providerId] });
167
+ session.native = await acquireProvider(facilities, saved.providerId);
168
+ session.native.runtimeStopOwner = saved.runtime;
169
+ session.native.observationFailure = session.observationError;
170
+ }
171
+ if (session.native?.observationFailure) {
172
+ await providerOwner.recover(providerKey, session.native.observationFailure);
173
+ return;
174
+ }
175
+ if (session.native && session.binding.threadId) {
176
+ const provider = session.native;
177
+ try {
178
+ const { goal } = await provider.stopThreadForObservationLoss(session.binding.threadId, session.current?.turnId);
179
+ await recordGoal(goal || null);
180
+ } catch (error) {
181
+ await provider.failObservation(error);
182
+ }
183
+ }
184
+ })().catch(error => { error.cleanupFailed = true; throw error; }).finally(() => { session.stopping = null; });
185
+ return session.stopping;
186
+ }
187
+
188
+ async function selection(configuration, context, signal, requireBound = false, receiptBinding) {
189
+ if (!configuration.integrationId) {
190
+ if (requireBound && !receiptBinding?.accountIdentity) throw new Error("The completed Codex response has no saved native account fingerprint.");
191
+ if (requireBound && session.binding.accountIdentity !== receiptBinding.accountIdentity) {
192
+ throw new Error("The completed Codex response belongs to a different native account binding.");
193
+ }
194
+ return { model: configuration.model, providerId: "openai" };
195
+ }
196
+ const connection = await connections.resolve({ context, integrationId: configuration.integrationId });
197
+ signal.throwIfAborted();
198
+ const provider = nativeAiProvider(connection.providerId);
199
+ const model = nativeAiModel(connection.model, connection.providerId);
200
+ if (!provider || !model) throw new Error("This exact provider/model is not supported by the Codex integration. Choose a supported model without renaming it.");
201
+ validateConnectionModel(configuration, connection);
202
+ if (configuration.effort && !model.variants.includes(configuration.effort)) throw new Error("The selected Codex model does not support this reasoning effort.");
203
+ const identity = hash([provider.id, provider.baseUrl, connection.apiKey]);
204
+ const previous = session.binding.connectionIdentities?.[provider.id];
205
+ const expected = receiptBinding?.connectionIdentities?.[provider.id];
206
+ if (requireBound && !expected) throw new Error("The completed Codex response has no saved provider account fingerprint.");
207
+ if (requireBound && (previous !== expected || identity !== expected)) {
208
+ throw new Error("The completed Codex response belongs to a different provider account binding.");
209
+ }
210
+ if (previous && previous !== identity) throw new Error("This conversation belongs to another Codex provider account. Restore that connection or start a new conversation.");
211
+ if (!previous) await updateBinding({ connectionIdentities: { ...session.binding.connectionIdentities, [provider.id]: identity } });
212
+ return { model: model.id, providerId: provider.id, accountIdentity: identity, config: codexProviderConfiguration(provider, connection.apiKey) };
213
+ }
214
+
215
+ async function connect(signal, selected) {
216
+ if (session.observationError) throw session.observationError;
217
+ if (session.native && session.nativeProviderId !== selected.providerId) {
218
+ await stop();
219
+ providerOwner.closeProvider(providerKey);
220
+ }
221
+ if (!session.native) {
222
+ const facilities = await nativeHost(selected);
223
+ signal.throwIfAborted();
224
+ const provider = await acquireProvider(facilities, selected.providerId);
225
+ // Account startup has other waiters. Cancellation releases this
226
+ // observer; the runtime owner settles the shared startup.
227
+ const connected = providerOwner.acquireRuntime({ providerKey, provider,
228
+ providerOptions: provider.options, operation: () => provider.connect() });
229
+ const cancelled = Promise.withResolvers();
230
+ const abort = () => cancelled.reject(signal.reason);
231
+ signal.addEventListener("abort", abort, { once: true });
232
+ if (signal.aborted) abort();
233
+ try {
234
+ await Promise.race([connected, cancelled.promise]);
235
+ signal.throwIfAborted();
236
+ // Execution identity is a reference for recovery, not ownership of
237
+ // the account service by this one conversation.
238
+ await updateBinding({ executionId: provider.runtime.executionId });
239
+ session.native = provider;
240
+ session.nativeProviderId = selected.providerId;
241
+ } catch (error) {
242
+ void connected.then(() => providerOwner.closeProvider(providerKey), () => providerOwner.closeProvider(providerKey));
243
+ throw error;
244
+ } finally {
245
+ signal.removeEventListener("abort", abort);
246
+ }
247
+ }
248
+ signal.throwIfAborted();
249
+ if (selected.providerId !== "openai") return;
250
+ const identity = await accountIdentity(session.native.client, signal);
251
+ if (session.binding.accountIdentity && session.binding.accountIdentity !== identity) {
252
+ providerOwner.closeProvider(providerKey);
253
+ throw new Error("This conversation belongs to another Codex account. Restore that account or start a new conversation.");
254
+ }
255
+ if (!session.binding.accountIdentity) await updateBinding({ accountIdentity: identity });
256
+ }
257
+
258
+ async function prepareConfiguration(configuration, context, signal, tools) {
259
+ const { dynamicTools, toolSchemaIdentity } = codexApplicationToolConfiguration(tools);
260
+ assertCodexApplicationToolSchemaIdentity(session.binding.threadId, session.binding.toolSchemaIdentity, toolSchemaIdentity);
261
+ const selected = await selection(configuration, context, signal);
262
+ await connect(signal, selected);
263
+ const [configResult, hookResult] = await Promise.all([
264
+ session.native.client.request("config/read", { cwd: session.binding.workdir, includeLayers: false }, { signal }),
265
+ session.native.client.request("hooks/list", { cwds: [session.binding.workdir] }, { signal })
266
+ ]);
267
+ const toolConfiguration = codexToolFreeConfiguration({ configResult, hookResult, workdir: session.binding.workdir });
268
+ // Native control verification runs the host's shell even when model
269
+ // shell tools are disabled. Its login profile needs the standard tools;
270
+ // do not copy credentials or the rest of the host environment here.
271
+ toolConfiguration.shell_environment_policy.set.PATH = env.PATH || "/usr/bin:/bin";
272
+ // A goal can be set while an ordinary turn is already running. Codex
273
+ // must have its native scheduler enabled before that turn starts.
274
+ toolConfiguration.features.goals = true;
275
+ if (host.commandWrapper) {
276
+ const hookCommand = codexCommandHookCommand(host.commandWrapper);
277
+ const owned = hookResult.data[0].hooks.filter(hook => hook.source === "sessionFlags" &&
278
+ hook.eventName === "preToolUse" && hook.handlerType === "command" && hook.command === hookCommand);
279
+ if (owned.length !== 1 || typeof owned[0].currentHash !== "string" || !owned[0].currentHash) {
280
+ throw new Error("Codex did not install the host's required command wrapper.");
281
+ }
282
+ toolConfiguration.features.hooks = true;
283
+ toolConfiguration.hooks.PreToolUse = codexCommandHookDefinition(hookCommand);
284
+ toolConfiguration.hooks.state[owned[0].key] = { enabled: true, trusted_hash: owned[0].currentHash };
285
+ }
286
+ if (nativeTools) {
287
+ Object.assign(toolConfiguration.features, { shell_tool: true, unified_exec: true, view_image: true });
288
+ toolConfiguration.include_environment_context = true;
289
+ toolConfiguration.include_permissions_instructions = true;
290
+ toolConfiguration.tools.update_plan.enabled = true;
291
+ toolConfiguration.shell_environment_policy.set = Object.fromEntries(Object.entries(env).filter(([name, value]) =>
292
+ typeof value === "string" && !CODEX_APP_SERVER_DESKTOP_BUS_ENV_NAMES.has(name)));
293
+ toolConfiguration.web_search = selected.config?.web_search || "disabled";
294
+ }
295
+ const params = { developerInstructions: configuration.systemPrompt,
296
+ cwd: session.binding.workdir, modelProvider: selected.providerId, ...(selected.model ? { model: selected.model } : {}),
297
+ dynamicTools, ...(!nativeTools ? { environments: [], runtimeWorkspaceRoots: [], selectedCapabilityRoots: [] } : {}),
298
+ approvalPolicy: "never", sandbox: nativeTools ? "danger-full-access" : "read-only", allowProviderModelFallback: false,
299
+ config: { ...selected.config, ...toolConfiguration }
300
+ };
301
+ // Cold control recovery uses these authorized settings;
302
+ // it must not resume with ambient defaults or an old instruction prompt.
303
+ session.preparedThreadSettings = params;
304
+ return { selected, params, toolSchemaIdentity };
305
+ }
306
+
307
+ async function prepare(configuration, context, signal, tools) {
308
+ const { selected, params, toolSchemaIdentity } = await prepareConfiguration(configuration, context, signal, tools);
309
+ const retainedThreadId = session.binding.threadId;
310
+ const prepared = await ensureCodexAppServerThread({
311
+ provider: session.native, workdir: session.binding.workdir,
312
+ observeThread: threadId => owner.subscribeEvents(sessionId, session.native, threadId, { providerKey }),
313
+ settings: async () => ({ threadSettings: params, threadStartSettings: params }),
314
+ identity: { read: () => retainedThreadId,
315
+ async write({ threadId }) { await updateBinding({ threadId, toolSchemaIdentity }); } }
316
+ });
317
+ if (!retainedThreadId && prepared.thread.raw?.historyMode !== "paginated") {
318
+ throw new Error("Codex did not create the required paginated history. Update Codex before starting this conversation.");
319
+ }
320
+ owner.subscribeEvents(sessionId, session.native, prepared.threadId, { providerKey });
321
+ return { ...selected, resumeOptions: params };
322
+ }
323
+
324
+ return Object.freeze({
325
+ stop,
326
+ selection,
327
+ connect,
328
+ prepareConfiguration,
329
+ prepare
330
+ });
331
+ }