@jskit-ai/assistant-core 0.2.7 → 0.2.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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
@@ -0,0 +1,378 @@
1
+ import { codexAppServerTurnStatusIsSuccessfulComplete, codexAppServerTurnStatusIsProviderFailure, codexAppServerThreadStatus, codexAppServerErrorMessage, codexAppServerSteerFailure, codexAppServerInterruptFailure, codexAppServerConversationTurnIsActive } from "./codexTurnState.js";
2
+ import { CODEX_APP_SERVER_DETACHED_TURN_TIMEOUT_MS, createCodexAppServerDetachedTurnWatcher } from "./codexDetachedTurn.js";
3
+ import { assertCodexAppServerHelperAccountIdentity, readCodexAppServerAccountIdentity, sendPreparedCodexAppServerHelperTurn } from "./codexHelperExecution.js";
4
+ import { codexAppServerProjectHookTrustConfig } from "./codexConfiguration.js";
5
+ import { codexAppServerThreadError } from "./codexThreadSnapshot.js";
6
+ import { codexAppServerDeliveredTurn, codexLocalImageInput, sendCodexAppServerPrompt } from "./codexProviderTurns.js";
7
+ import { normalizeText } from "@jskit-ai/kernel/shared/support/normalize";
8
+
9
+ /** A conversation turn's listener when the application supplies none. */
10
+ export function ignoreCodexConversationEvent() {}
11
+
12
+ function notifyNoConversationPromptSending() {}
13
+
14
+ // What a read reports for a conversation this server tracks without a persistent native history.
15
+ export function codexAppServerEphemeralConversationSnapshot(state = {}) {
16
+ return {
17
+ conversationId: normalizeText(state.conversationId),
18
+ error: normalizeText(state.error),
19
+ message: normalizeText(state.message),
20
+ ok: true,
21
+ outcome: state.outcome || null,
22
+ progressUpdates: Array.isArray(state.progressUpdates)
23
+ ? state.progressUpdates.map((update = {}) => ({
24
+ id: normalizeText(update.id),
25
+ text: normalizeText(update.text)
26
+ })).filter((update) => update.id && update.text)
27
+ : [],
28
+ rawText: normalizeText(state.rawText),
29
+ messageId: normalizeText(state.messageId),
30
+ runId: normalizeText(state.runId),
31
+ status: normalizeText(state.status) || "ready",
32
+ turnMetadata: state.turnMetadata || null
33
+ };
34
+ }
35
+
36
+ // Thread settings for a conversation: a Helper's come from its isolation inspection, others from the
37
+ // application's settings with the project's hook trust when it asks for them.
38
+ export async function prepareCodexAppServerConversationThread(provider, preparation, helperIsolation) {
39
+ if (preparation.inspection) {
40
+ const enforcement = await helperIsolation.inspect(provider, preparation.inspection);
41
+ return preparation.prepared(enforcement);
42
+ }
43
+ const config = preparation.projectHooks
44
+ ? await codexAppServerProjectHookTrustConfig(provider, preparation.workdir)
45
+ : null;
46
+ return preparation.settings(config);
47
+ }
48
+
49
+ /**
50
+ * Creating, starting, steering and waiting for turns on prepared native conversation threads. The conversation
51
+ * registry and its observation belong to the conversation commands, which pass them in.
52
+ * @param {object} dependencies
53
+ * @param {import("./codexConversations.js").CodexConversationRegistry} dependencies.codexAppServerConversations
54
+ * @param {import("./codexConversations.js").CodexConversationLookup} dependencies.codexAppServerConversation
55
+ * @param {import("./codexConversations.js").CodexConversationObserver} dependencies.observeCodexConversation
56
+ * @param {import("./codexConversations.js").CodexConversationStateUpdate} dependencies.holdWhileActive
57
+ * @param {import("./codexTurn.js").CodexSessionNamespace} dependencies.namespace
58
+ * @param {string} dependencies.errorPrefix
59
+ * @param {string} dependencies.helperOwnershipError
60
+ * @param {string} dependencies.steerFailedCode
61
+ * @param {string} dependencies.interruptFailedCode
62
+ */
63
+ export function createCodexConversationTurns({
64
+ codexAppServerConversations,
65
+ codexAppServerConversation,
66
+ observeCodexConversation,
67
+ holdWhileActive,
68
+ namespace,
69
+ errorPrefix,
70
+ helperOwnershipError,
71
+ steerFailedCode,
72
+ interruptFailedCode
73
+ }) {
74
+ async function createPreparedCodexAppServerConversation(sessionId, input, {
75
+ provider,
76
+ executionProfile,
77
+ helperIsolation,
78
+ threadPreparation
79
+ }) {
80
+ const prepareThread = () => prepareCodexAppServerConversationThread(provider, threadPreparation(), helperIsolation);
81
+ const thread = executionProfile
82
+ ? (await helperIsolation.start(provider, prepareThread)).thread
83
+ : await provider.startThread({
84
+ ...await prepareThread(),
85
+ ...(input.ephemeral === true ? { ephemeral: true } : {})
86
+ });
87
+ const conversationId = normalizeText(thread.id || thread.response?.thread?.id);
88
+ if (!conversationId) {
89
+ throw new Error("Codex app-server did not return a conversation id.");
90
+ }
91
+ if (input.persistent === true) observeCodexConversation(sessionId, conversationId, provider);
92
+ if (input.ephemeral === true) {
93
+ const sessionKey = namespace(sessionId);
94
+ const conversations = codexAppServerConversations.get(sessionKey) || new Map();
95
+ conversations.set(conversationId, {
96
+ conversationId,
97
+ executionProfile,
98
+ provider,
99
+ error: "",
100
+ message: "",
101
+ messageId: "",
102
+ nextProgressSequence: 0,
103
+ outcome: null,
104
+ progressUpdates: [],
105
+ rawText: "",
106
+ runId: "",
107
+ status: "ready",
108
+ turnMetadata: null,
109
+ watcher: null
110
+ });
111
+ codexAppServerConversations.set(sessionKey, conversations);
112
+ }
113
+ return {
114
+ conversationId,
115
+ ok: true,
116
+ status: "ready"
117
+ };
118
+ }
119
+
120
+ async function steerCodexAppServerConversationTurn(input, {
121
+ conversationId,
122
+ conversationState,
123
+ messageId,
124
+ prompt,
125
+ provider
126
+ }) {
127
+ const images = codexLocalImageInput(input.attachments || []);
128
+ const result = await provider.steerTurn(
129
+ conversationId,
130
+ conversationState.runId,
131
+ images.length ? [prompt, ...images] : prompt,
132
+ { clientUserMessageId: messageId }
133
+ );
134
+ const failure = codexAppServerSteerFailure(result, steerFailedCode);
135
+ if (failure) return { ...failure, ok: false };
136
+ conversationState.messageId = messageId;
137
+ return {
138
+ conversationId,
139
+ messageId,
140
+ ok: true,
141
+ runId: conversationState.runId,
142
+ status: "inProgress",
143
+ deliveryMode: "steer"
144
+ };
145
+ }
146
+
147
+ async function startPreparedCodexAppServerConversationTurn(sessionId, input, {
148
+ conversationId,
149
+ messageId,
150
+ prompt,
151
+ provider,
152
+ conversationState,
153
+ executionProfile,
154
+ actorMetadata,
155
+ onEvent,
156
+ projectResult,
157
+ helperIsolation,
158
+ threadPreparation,
159
+ prepareTurn,
160
+ authorized,
161
+ readOnly
162
+ }) {
163
+ if (conversationState && messageId && conversationState.messageId === messageId) {
164
+ return codexAppServerEphemeralConversationSnapshot(conversationState);
165
+ }
166
+ if (conversationState && codexAppServerConversationTurnIsActive(conversationState.status)) {
167
+ if (input.steer === true) {
168
+ return steerCodexAppServerConversationTurn(input, {
169
+ conversationId,
170
+ conversationState,
171
+ messageId,
172
+ prompt,
173
+ provider
174
+ });
175
+ }
176
+ return {
177
+ code: `${errorPrefix}temporary_conversation_turn_active`,
178
+ error: "Temporary AI is already working on this conversation.",
179
+ messageCode: "assistant.codex.temporaryConversationBusy",
180
+ messageParams: {},
181
+ ok: false
182
+ };
183
+ }
184
+ if (!conversationState || input.persistent === true) {
185
+ const prepareThread = () => prepareCodexAppServerConversationThread(provider, threadPreparation(), helperIsolation);
186
+ if (executionProfile) await helperIsolation.resume(provider, conversationId, prepareThread);
187
+ else await provider.resumeThread(conversationId, await prepareThread());
188
+ }
189
+ let admittedAccountIdentitySignature = "";
190
+ if (executionProfile) {
191
+ admittedAccountIdentitySignature = await readCodexAppServerAccountIdentity(provider, helperOwnershipError);
192
+ }
193
+ let watcher = null;
194
+ let waitForResult = null;
195
+ if (conversationState && !input.persistent) {
196
+ Object.assign(conversationState, {
197
+ error: "",
198
+ message: "",
199
+ messageId,
200
+ outcome: null,
201
+ progressUpdates: [],
202
+ rawText: "",
203
+ runId: "",
204
+ status: "starting",
205
+ turnMetadata: actorMetadata
206
+ });
207
+ watcher = createCodexAppServerDetachedTurnWatcher(provider, conversationId, {
208
+ includeThreadHistory: false,
209
+ timeoutMs: executionProfile?.limits.timeoutMs || 0,
210
+ onEvent(classification = {}) {
211
+ const current = codexAppServerConversation(sessionId, conversationId);
212
+ if (!current || (classification.turnId && current.runId && classification.turnId !== current.runId)) {
213
+ return;
214
+ }
215
+ onEvent(classification, current);
216
+ }
217
+ });
218
+ waitForResult = watcher.wait();
219
+ void waitForResult.catch(() => null);
220
+ conversationState.watcher = watcher;
221
+ }
222
+ let delivery = null;
223
+ if (input.persistent && conversationState) Object.assign(conversationState, { onEvent, turnOutcome: "" });
224
+ try {
225
+ const { onPromptSending = notifyNoConversationPromptSending } = input;
226
+ await onPromptSending({ threadId: conversationId });
227
+ if (executionProfile) {
228
+ const prepared = prepareTurn();
229
+ delivery = await sendPreparedCodexAppServerHelperTurn(provider, prepared, helperIsolation);
230
+ } else {
231
+ delivery = await sendCodexAppServerPrompt({
232
+ clientUserMessageId: input.messageId,
233
+ outputSchema: input.outputSchema,
234
+ prompt,
235
+ attachments: input.attachments,
236
+ provider,
237
+ readOnly,
238
+ threadId: conversationId
239
+ }, authorized);
240
+ }
241
+ } catch (error) {
242
+ if (input.persistent && conversationState) conversationState.onEvent = ignoreCodexConversationEvent;
243
+ watcher?.failNow(error);
244
+ await waitForResult?.catch(() => null);
245
+ if (conversationState) {
246
+ Object.assign(conversationState, {
247
+ error: codexAppServerErrorMessage(error, "Temporary AI message could not be sent."),
248
+ status: "failed",
249
+ watcher: null
250
+ });
251
+ holdWhileActive(conversationState);
252
+ }
253
+ throw error;
254
+ }
255
+ const { turnId: runId, status } = codexAppServerDeliveredTurn(delivery);
256
+ if (!runId) {
257
+ watcher?.failNow(new Error("Codex app-server accepted a conversation turn without returning its id."));
258
+ await waitForResult?.catch(() => null);
259
+ throw new Error("Codex app-server accepted a conversation turn without returning its id.");
260
+ }
261
+ if (conversationState && !input.persistent) {
262
+ let nativeCompleted = false;
263
+ Object.assign(conversationState, {
264
+ runId,
265
+ status: !executionProfile && codexAppServerTurnStatusIsSuccessfulComplete(status) ? status : "inProgress"
266
+ });
267
+ watcher.setTurnId(runId);
268
+ if (codexAppServerTurnStatusIsProviderFailure(status)) {
269
+ watcher.failNow(new Error(`Codex app-server turn ${status}.`));
270
+ } else if (codexAppServerTurnStatusIsSuccessfulComplete(status)) {
271
+ await watcher.completeNow(status);
272
+ }
273
+ conversationState.completion = waitForResult.then(async (result = {}) => {
274
+ const current = codexAppServerConversation(sessionId, conversationId);
275
+ if (!current || current.runId !== runId) {
276
+ return;
277
+ }
278
+ nativeCompleted = true;
279
+ if (!executionProfile) current.status = result.status || "completed";
280
+ if (executionProfile) {
281
+ const completingRunId = current.runId;
282
+ await assertCodexAppServerHelperAccountIdentity(provider, admittedAccountIdentitySignature, helperOwnershipError);
283
+ if (codexAppServerConversation(sessionId, conversationId) !== current ||
284
+ current.runId !== completingRunId || !codexAppServerConversationTurnIsActive(current.status)) {
285
+ throw new Error("The scoped helper turn is unavailable.");
286
+ }
287
+ }
288
+ const response = projectResult(result);
289
+ Object.assign(current, {
290
+ error: "",
291
+ ...response,
292
+ status: result.status || "completed",
293
+ watcher: null
294
+ });
295
+ return codexAppServerEphemeralConversationSnapshot(current);
296
+ }).catch(async (error) => {
297
+ const current = codexAppServerConversation(sessionId, conversationId);
298
+ if (!current || current.runId !== runId || current.status === "interrupted") {
299
+ throw error;
300
+ }
301
+ if (executionProfile && !nativeCompleted && codexAppServerConversationTurnIsActive(current.status)) {
302
+ try {
303
+ const interrupted = await provider.interruptTurn(conversationId, runId);
304
+ const failure = codexAppServerInterruptFailure(interrupted, interruptFailedCode);
305
+ if (failure) throw new Error(failure.error);
306
+ } catch {
307
+ current.error = "The helper deadline ended, but Stop could not be confirmed. Retry Stop before reusing this helper.";
308
+ current.watcher = null;
309
+ throw new Error(current.error);
310
+ }
311
+ }
312
+ Object.assign(current, {
313
+ error: codexAppServerErrorMessage(error, "Temporary AI turn failed."),
314
+ status: "failed",
315
+ watcher: null
316
+ });
317
+ throw error;
318
+ });
319
+ void conversationState.completion.catch(() => null);
320
+ }
321
+ if (input.persistent && conversationState) {
322
+ Object.assign(conversationState, { runId, status, messageId });
323
+ holdWhileActive(conversationState);
324
+ }
325
+ return { conversationId, messageId, ok: true, runId, status };
326
+ }
327
+
328
+ async function waitForPreparedCodexAppServerConversationTurn(sessionId, input, {
329
+ provider,
330
+ onEvent
331
+ }) {
332
+ const conversationId = normalizeText(input.conversationId);
333
+ const runId = normalizeText(input.runId);
334
+ const watcher = createCodexAppServerDetachedTurnWatcher(provider, conversationId, {
335
+ onEvent,
336
+ timeoutMs: Number(input.timeoutMs || 0) > 0
337
+ ? Number(input.timeoutMs)
338
+ : input.persistent === true ? 0 : CODEX_APP_SERVER_DETACHED_TURN_TIMEOUT_MS
339
+ });
340
+ const conversationState = codexAppServerConversation(sessionId, conversationId);
341
+ if (input.persistent && conversationState) conversationState.watcher = watcher;
342
+ const waitForResult = watcher.wait();
343
+ void waitForResult.catch(() => {});
344
+ watcher.setTurnId(runId);
345
+ try {
346
+ const current = await provider.readThread(conversationId);
347
+ const status = codexAppServerThreadStatus(current);
348
+ if (codexAppServerTurnStatusIsProviderFailure(status)) {
349
+ watcher.failNow(new Error(
350
+ codexAppServerThreadError(current) || `Codex app-server turn ${status}.`
351
+ ));
352
+ } else if (codexAppServerTurnStatusIsSuccessfulComplete(status)) {
353
+ await watcher.completeNow(status);
354
+ }
355
+ const result = await waitForResult;
356
+ return {
357
+ conversationId,
358
+ ok: true,
359
+ runId,
360
+ status: result.status || status || "completed",
361
+ rawText: result.text
362
+ };
363
+ } catch (error) {
364
+ watcher.failNow(error);
365
+ throw error;
366
+ } finally {
367
+ if (conversationState?.watcher === watcher) conversationState.watcher = null;
368
+ }
369
+ }
370
+
371
+ return Object.freeze({
372
+ createPreparedCodexAppServerConversation,
373
+ startPreparedCodexAppServerConversationTurn,
374
+ waitForPreparedCodexAppServerConversationTurn
375
+ });
376
+ }
377
+
378
+ /** @typedef {ReturnType<typeof createCodexConversationTurns>} CodexConversationTurns */