@jskit-ai/assistant-core 0.1.199 → 0.1.201

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 (164) hide show
  1. package/examples/conversation/README.md +8 -1
  2. package/examples/conversation/package.json +5 -5
  3. package/fixtures/responsive-assistant/App.vue +4 -1
  4. package/package.json +99 -36
  5. package/src/client/conversation/AssistantConversationElement.vue +27 -4
  6. package/src/client/conversation/AssistantConversationStatus.vue +24 -0
  7. package/src/client/conversation/AssistantGoalControl.vue +8 -5
  8. package/src/client/conversation/AssistantTranscript.vue +10 -3
  9. package/src/client/conversation/index.js +2 -0
  10. package/src/client/conversation/messageDelivery.js +115 -32
  11. package/src/client/conversation/retainedConversation.js +32 -0
  12. package/src/client/conversation/useAssistantQuestions.js +76 -0
  13. package/src/client/lib/assistantApi.js +72 -2
  14. package/src/server/conversation/agentRun.js +222 -0
  15. package/src/server/conversation/attachments.js +93 -0
  16. package/src/server/conversation/claudeClient.js +221 -0
  17. package/src/server/conversation/claudeHistory.js +256 -0
  18. package/src/server/conversation/claudeProcess.js +327 -0
  19. package/src/server/conversation/claudeTools.js +42 -0
  20. package/src/server/conversation/claudeTurn.js +1082 -0
  21. package/src/server/conversation/codexConfiguration.js +1183 -0
  22. package/src/server/conversation/codexConversations.js +1082 -0
  23. package/src/server/conversation/codexDetachedTurn.js +332 -0
  24. package/src/server/conversation/codexEvents.js +194 -1
  25. package/src/server/conversation/codexGoalCommands.js +124 -0
  26. package/src/server/conversation/codexHelperExecution.js +306 -0
  27. package/src/server/conversation/codexHelperThreadLedger.js +804 -0
  28. package/src/server/conversation/codexHelperThreadRecovery.js +395 -0
  29. package/src/server/conversation/codexHelperThreads.js +638 -0
  30. package/src/server/conversation/codexHistoryAdapter.js +369 -0
  31. package/src/server/conversation/codexMessageCommands.js +663 -0
  32. package/src/server/conversation/codexMessageDelivery.js +211 -0
  33. package/src/server/conversation/codexNativeHistoryExport.js +95 -0
  34. package/src/server/conversation/codexNotificationObserver.js +482 -0
  35. package/src/server/conversation/codexNotificationQueue.js +103 -0
  36. package/src/server/conversation/codexObservationControl.js +174 -0
  37. package/src/server/conversation/codexOutput.js +931 -0
  38. package/src/server/conversation/codexProcess.js +1559 -0
  39. package/src/server/conversation/codexProcessLeader.js +14 -0
  40. package/src/server/conversation/codexProvider.js +2710 -0
  41. package/src/server/conversation/codexProviderOwner.js +1058 -0
  42. package/src/server/conversation/codexProviderSelection.js +218 -0
  43. package/src/server/conversation/codexReasoning.js +314 -0
  44. package/src/server/conversation/codexRenewalCommands.js +290 -0
  45. package/src/server/conversation/codexResultSettlement.js +409 -0
  46. package/src/server/conversation/codexRunJournal.js +658 -0
  47. package/src/server/conversation/codexRuntimeLifecycle.js +288 -0
  48. package/src/server/conversation/codexSessionCleanup.js +96 -0
  49. package/src/server/conversation/codexSessionReconciliation.js +284 -0
  50. package/src/server/conversation/codexThreadReadiness.js +397 -0
  51. package/src/server/conversation/codexThreadStatus.js +354 -0
  52. package/src/server/conversation/codexTurn.js +701 -255
  53. package/src/server/conversation/codexTurnControl.js +317 -0
  54. package/src/server/conversation/codexTurnRecovery.js +990 -0
  55. package/src/server/conversation/codexTurnState.js +549 -0
  56. package/src/server/conversation/commandHook.js +18 -0
  57. package/src/server/conversation/commandWrapper.js +37 -0
  58. package/src/server/conversation/configuration.js +43 -0
  59. package/src/server/conversation/continuity.js +289 -0
  60. package/src/server/conversation/fileStorage.js +55 -0
  61. package/src/server/conversation/hookBridge.js +19 -0
  62. package/src/server/conversation/index.js +13 -0
  63. package/src/server/conversation/localExecution.js +665 -0
  64. package/src/server/conversation/memoryStorage.js +7 -72
  65. package/src/server/conversation/nativeHistoryExport.js +90 -0
  66. package/src/server/conversation/nativeStorage.js +23 -0
  67. package/src/server/conversation/openCodeClient.js +209 -19
  68. package/src/server/conversation/openCodePlugin.js +84 -0
  69. package/src/server/conversation/openCodeProcess.js +476 -0
  70. package/src/server/conversation/openCodeRuntime.js +1578 -0
  71. package/src/server/conversation/openCodeTools.js +60 -0
  72. package/src/server/conversation/openCodeTurn.js +557 -0
  73. package/src/server/conversation/provider.js +30 -0
  74. package/src/server/conversation/providers/api.js +136 -0
  75. package/src/server/conversation/providers/claude.js +63 -0
  76. package/src/server/conversation/providers/claudeDriver.js +425 -0
  77. package/src/server/conversation/providers/codex.js +248 -0
  78. package/src/server/conversation/providers/codexDriver.js +1078 -0
  79. package/src/server/conversation/providers/opencode.js +49 -0
  80. package/src/server/conversation/providers/opencodeDriver.js +650 -0
  81. package/src/server/conversation/providers/scoped.js +312 -0
  82. package/src/server/conversation/runtime.js +2220 -0
  83. package/src/server/conversation/runtimeStateUpgrade.js +125 -0
  84. package/src/server/conversation/storage.js +184 -0
  85. package/src/server/conversation/streams.js +7 -2
  86. package/src/server/conversation/structuredOutput.js +240 -0
  87. package/src/server/conversation/systemPrompt.js +54 -0
  88. package/src/server/conversation/tools.js +84 -0
  89. package/src/server/conversation/transcript.js +5 -4
  90. package/src/server/hosts/claudeHistory.js +5 -0
  91. package/src/server/hosts/claudeProcess.js +11 -0
  92. package/src/server/hosts/claudeTurn.js +4 -0
  93. package/src/server/hosts/codexConfiguration.js +20 -0
  94. package/src/server/hosts/codexEvents.js +13 -0
  95. package/src/server/hosts/codexProcess.js +12 -0
  96. package/src/server/hosts/codexProvider.js +23 -0
  97. package/src/server/hosts/codexTurn.js +20 -0
  98. package/src/server/hosts/nativeHistory.js +3 -0
  99. package/src/server/hosts/openCodeClient.js +3 -0
  100. package/src/server/hosts/openCodeProcess.js +23 -0
  101. package/src/server/hosts/openCodeTurn.js +7 -0
  102. package/src/server/index.js +2 -0
  103. package/src/server/lib/aiConnectionClient.js +13 -6
  104. package/src/server/lib/assistantCompletion.js +190 -0
  105. package/src/server/lib/assistantToolLoop.js +314 -0
  106. package/src/server/lib/serviceToolCatalog.js +25 -10
  107. package/src/shared/assistantResponseText.js +29 -0
  108. package/src/shared/conversation/attachments.js +19 -0
  109. package/src/shared/conversation/index.js +3 -0
  110. package/src/shared/conversation/pagination.js +78 -0
  111. package/src/shared/conversation/patches.js +150 -0
  112. package/src/shared/conversation/questions.js +48 -0
  113. package/src/shared/conversation/streaming.js +7 -4
  114. package/src/shared/index.js +2 -0
  115. package/src/shared/nativeProviders.js +45 -0
  116. package/src/testing/conversationStorageContract.js +31 -1
  117. package/src/testing/nativeCodex.js +11 -0
  118. package/src/testing/nativeOpenCode.js +3 -0
  119. package/test/aiConnectionClient.test.js +2 -0
  120. package/test/apiConversationDriver.test.js +73 -0
  121. package/test/assistantApiSurfaceHeader.test.js +85 -0
  122. package/test/assistantBoundedToolLoop.test.js +285 -0
  123. package/test/assistantScroll.browser.test.js +16 -0
  124. package/test/claudeClient.test.js +189 -0
  125. package/test/claudeConversationSession.test.js +1173 -0
  126. package/test/claudeInstructionResume.test.js +82 -0
  127. package/test/claudeProcess.test.js +204 -0
  128. package/test/claudeTurn.test.js +253 -0
  129. package/test/codexConversationSession.test.js +2122 -0
  130. package/test/codexGoal.test.js +37 -0
  131. package/test/codexHelperDeletion.test.js +163 -0
  132. package/test/codexHelperThreadLedger.test.js +494 -0
  133. package/test/codexHistoryAdapter.test.js +541 -0
  134. package/test/codexInteractiveArguments.test.js +30 -0
  135. package/test/codexIsolation.test.js +756 -0
  136. package/test/codexModelCatalog.test.js +362 -0
  137. package/test/codexNativeHistoryExport.test.js +81 -0
  138. package/test/codexThreadSelection.test.js +136 -0
  139. package/test/codexTurn.test.js +2158 -0
  140. package/test/conversation.test.js +110 -0
  141. package/test/conversationAgentRun.test.js +326 -0
  142. package/test/conversationApiFacilities.test.js +90 -0
  143. package/test/conversationAttachments.test.js +238 -0
  144. package/test/conversationCommandWrapper.test.js +48 -0
  145. package/test/conversationConfiguration.test.js +174 -0
  146. package/test/conversationNativeStorage.test.js +25 -0
  147. package/test/conversationReplacement.test.js +172 -0
  148. package/test/conversationRuntime.test.js +241 -0
  149. package/test/conversationRuntimeStateUpgrade.test.js +144 -0
  150. package/test/conversationSession.test.js +1080 -0
  151. package/test/conversationStreaming.test.js +68 -0
  152. package/test/conversationSystemPrompt.test.js +84 -0
  153. package/test/conversationTools.test.js +416 -0
  154. package/test/fileConversationStorage.test.js +73 -0
  155. package/test/fixtures/codexHelperThreadLedger.js +39 -0
  156. package/test/localConversationExecution.test.js +90 -0
  157. package/test/messageDelivery.test.js +234 -1
  158. package/test/nativeConversationRetirement.test.js +215 -0
  159. package/test/openCodeClient.test.js +515 -0
  160. package/test/openCodeProcess.test.js +177 -0
  161. package/test/openCodeRuntime.test.js +322 -0
  162. package/test/openCodeTools.test.js +287 -0
  163. package/test/opencodeConversationSession.test.js +789 -0
  164. package/test/serviceToolCatalog.test.js +24 -0
@@ -0,0 +1,290 @@
1
+ import { codexAppServerTurnStatusIsSuccessfulComplete, codexAppServerTurnStatusIsProviderFailure, normalizeCodexRunText, codexAppServerErrorMessage } from "./codexTurnState.js";
2
+ import { createCodexAppServerDetachedTurnWatcher, waitForCodexAppServerTurn } from "./codexDetachedTurn.js";
3
+ import { resumeExactCodexAppServerThread, startFreshCodexAppServerThread, sendCodexAppServerPrompt } from "./codexProvider.js";
4
+ import { codexAppServerProviderTurnId, codexAppServerProviderTurnError, inspectCodexAppServerRenewalThread } from "./codexEvents.js";
5
+
6
+ // Private composition of unchanged operations from the original run owner.
7
+ export function createCodexRenewalCommands({
8
+ claimCodexAppServerTurnStart,
9
+ codexAppServerNotificationQueue,
10
+ completeCodexAppServerTurn,
11
+ errorPrefix,
12
+ markCodexAppServerTurnActive,
13
+ markCodexAppServerTurnIdle,
14
+ observer,
15
+ selection,
16
+ turnAlreadyRunningError,
17
+ writeCodexAppServerUserMessageOwnership
18
+ }) {
19
+ async function runCodexAppServerRenewalHandover(sessionId, input = {}, context = {}) {
20
+ const { clientMessageId, operationId, timeoutMs } = input;
21
+ const { runtime, provider, errors } = context;
22
+ const resumed = await resumeExactCodexAppServerThread(context.threadPreparation);
23
+ const threadId = resumed.threadId;
24
+ const expectedTurnId = context.readExpectedTurnId(threadId);
25
+ const { targetTurn, failure: snapshotFailure } = inspectCodexAppServerRenewalThread(resumed.threadSnapshot, {
26
+ clientMessageId,
27
+ turnId: expectedTurnId
28
+ });
29
+ if (snapshotFailure === "turn_missing") {
30
+ throw errors.withIdentity(
31
+ errors.create("handover_turn_missing"),
32
+ { clientMessageId, operationId, threadId, turnId: expectedTurnId }
33
+ );
34
+ }
35
+ if (snapshotFailure === "history_missing") {
36
+ throw errors.withIdentity(
37
+ errors.create("handover_history_missing"),
38
+ { clientMessageId, operationId, threadId }
39
+ );
40
+ }
41
+
42
+ observer.subscribeEvents(sessionId, provider, threadId,
43
+ selection.codexAppServerSessionObserverOptions(sessionId, context.providerOptions));
44
+ selection.rememberCodexAppServerPreparedThread(sessionId, context.providerOptions, context, resumed);
45
+
46
+ let result = null;
47
+ let reconciled = Boolean(targetTurn);
48
+ let turnId = codexAppServerProviderTurnId(targetTurn || {});
49
+ if (targetTurn) {
50
+ try {
51
+ result = await waitForCodexAppServerTurn(
52
+ provider,
53
+ threadId,
54
+ targetTurn,
55
+ { timeoutMs, createError: errors.create }
56
+ );
57
+ } catch (error) {
58
+ throw errors.withIdentity(error, {
59
+ clientMessageId,
60
+ operationId,
61
+ threadId,
62
+ turnId
63
+ });
64
+ }
65
+ } else {
66
+ const claim = await claimCodexAppServerTurnStart(
67
+ runtime,
68
+ sessionId,
69
+ clientMessageId,
70
+ { inputSource: "session_renewal_handover" }
71
+ );
72
+ if (!claim?.claimed) {
73
+ return { response: claim?.response || {
74
+ code: `${errorPrefix}agent_turn_already_running`,
75
+ error: turnAlreadyRunningError,
76
+ ok: false
77
+ } };
78
+ }
79
+ // The exact old thread is already known before prompt delivery. Bind
80
+ // it to the durable claim now, so an immediate provider response can
81
+ // supply the turn id and complete the run even when its notifications
82
+ // arrive before sendTurn() returns.
83
+ await markCodexAppServerTurnActive(sessionId, {
84
+ status: "starting",
85
+ threadId
86
+ });
87
+ await context.writeMetadata({
88
+ clientMessageId,
89
+ operationId,
90
+ threadId
91
+ });
92
+ await writeCodexAppServerUserMessageOwnership(
93
+ runtime.store,
94
+ sessionId,
95
+ clientMessageId,
96
+ {
97
+ eventKind: "codex-app-server-renewal-handover-owned",
98
+ owned: true
99
+ }
100
+ );
101
+ const watcher = createCodexAppServerDetachedTurnWatcher(provider, threadId, {
102
+ timeoutMs
103
+ });
104
+ const pending = watcher.wait();
105
+ void pending.catch(() => null);
106
+ let delivery = null;
107
+ let status = "";
108
+ try {
109
+ delivery = await sendCodexAppServerPrompt({
110
+ clientUserMessageId: clientMessageId,
111
+ prompt: input.prompt,
112
+ provider,
113
+ threadId
114
+ }, context);
115
+ turnId = normalizeCodexRunText(delivery.turn?.id);
116
+ status = normalizeCodexRunText(delivery.turn?.status || delivery.turn?.raw?.status);
117
+ if (!turnId) {
118
+ throw errors.create("handover_identity_missing");
119
+ }
120
+ await context.writeMetadata({ turnId });
121
+ await markCodexAppServerTurnActive(sessionId, {
122
+ requireTrackedTurn: true,
123
+ status: "inProgress",
124
+ threadId,
125
+ turnId
126
+ });
127
+ watcher.setTurnId(turnId);
128
+ if (codexAppServerTurnStatusIsProviderFailure(status)) {
129
+ watcher.failAfterDetailGrace(errors.create(
130
+ "failed",
131
+ { status },
132
+ codexAppServerProviderTurnError(delivery.turn || {})
133
+ ));
134
+ } else if (codexAppServerTurnStatusIsSuccessfulComplete(status)) {
135
+ await watcher.completeNow(status);
136
+ }
137
+ result = await pending;
138
+ } catch (error) {
139
+ watcher.failNow(error);
140
+ await writeCodexAppServerUserMessageOwnership(
141
+ runtime.store,
142
+ sessionId,
143
+ clientMessageId,
144
+ {
145
+ eventKind: "codex-app-server-renewal-handover-released",
146
+ owned: false
147
+ }
148
+ );
149
+ await markCodexAppServerTurnIdle(sessionId, {
150
+ error: codexAppServerErrorMessage(error, "Codex handover generation failed."),
151
+ status: "failed",
152
+ threadId,
153
+ turnId
154
+ });
155
+ throw errors.withIdentity(error, {
156
+ clientMessageId,
157
+ operationId,
158
+ threadId,
159
+ turnId
160
+ });
161
+ }
162
+ }
163
+
164
+ await codexAppServerNotificationQueue.drain(sessionId);
165
+ if (codexAppServerTurnStatusIsSuccessfulComplete(result.status)) {
166
+ // A reconciled turn may already be complete before this process
167
+ // subscribes, so no completion notification is guaranteed. Route it
168
+ // through the ordinary finalizer as well; its turn identity guards
169
+ // make the live-notification case an idempotent no-op.
170
+ await completeCodexAppServerTurn(
171
+ sessionId,
172
+ threadId,
173
+ result.turnId || turnId,
174
+ {
175
+ provider,
176
+ status: result.status,
177
+ verifyInactive: false
178
+ }
179
+ );
180
+ }
181
+ return { result, reconciled, turnId, threadId };
182
+ }
183
+
184
+ async function runCodexAppServerRenewalSeed(input = {}, context = {}) {
185
+ const { clientMessageId, operationId, timeoutMs } = input;
186
+ const { provider, errors } = context;
187
+ const started = await startFreshCodexAppServerThread(context.threadPreparation);
188
+ const threadId = started.threadId;
189
+ const expectedTurnId = context.readExpectedTurnId(threadId);
190
+ const { targetTurn, failure: snapshotFailure } = inspectCodexAppServerRenewalThread(started.threadSnapshot, {
191
+ clientMessageId,
192
+ turnId: expectedTurnId,
193
+ requireFresh: true
194
+ });
195
+ if (snapshotFailure === "turn_missing") {
196
+ throw errors.withIdentity(
197
+ errors.create("seed_turn_missing"),
198
+ { clientMessageId, operationId, threadId, turnId: expectedTurnId }
199
+ );
200
+ }
201
+ if (snapshotFailure === "unrelated_history") {
202
+ throw errors.withIdentity(
203
+ errors.create("seed_unrelated_history"),
204
+ {
205
+ clientMessageId,
206
+ operationId,
207
+ threadId,
208
+ turnId: codexAppServerProviderTurnId(targetTurn || {})
209
+ }
210
+ );
211
+ }
212
+
213
+ let result = null;
214
+ let turnId = codexAppServerProviderTurnId(targetTurn || {});
215
+ const reconciled = Boolean(targetTurn);
216
+ if (targetTurn) {
217
+ try {
218
+ result = await waitForCodexAppServerTurn(provider, threadId, targetTurn, {
219
+ timeoutMs, createError: errors.create
220
+ });
221
+ } catch (error) {
222
+ throw errors.withIdentity(error, {
223
+ clientMessageId,
224
+ handoverPromptAccepted: true,
225
+ operationId,
226
+ threadId,
227
+ turnId
228
+ });
229
+ }
230
+ } else {
231
+ await context.writeMetadata({
232
+ clientMessageId,
233
+ operationId,
234
+ threadId
235
+ });
236
+ const watcher = createCodexAppServerDetachedTurnWatcher(provider, threadId, {
237
+ timeoutMs
238
+ });
239
+ const pending = watcher.wait();
240
+ void pending.catch(() => null);
241
+ let delivery = null;
242
+ let handoverPromptAccepted = false;
243
+ let status = "";
244
+ try {
245
+ delivery = await sendCodexAppServerPrompt({
246
+ clientUserMessageId: clientMessageId,
247
+ outputSchema: input.outputSchema,
248
+ prompt: input.prompt,
249
+ provider,
250
+ readOnly: true,
251
+ threadId
252
+ }, context);
253
+ turnId = normalizeCodexRunText(delivery.turn?.id);
254
+ status = normalizeCodexRunText(delivery.turn?.status || delivery.turn?.raw?.status);
255
+ if (!turnId) {
256
+ throw errors.create("seed_identity_missing");
257
+ }
258
+ handoverPromptAccepted = true;
259
+ await context.writeMetadata({ turnId });
260
+ watcher.setTurnId(turnId);
261
+ if (codexAppServerTurnStatusIsProviderFailure(status)) {
262
+ watcher.failAfterDetailGrace(errors.create(
263
+ "failed",
264
+ { status },
265
+ codexAppServerProviderTurnError(delivery.turn || {})
266
+ ));
267
+ } else if (codexAppServerTurnStatusIsSuccessfulComplete(status)) {
268
+ await watcher.completeNow(status);
269
+ }
270
+ result = await pending;
271
+ } catch (error) {
272
+ watcher.failNow(error);
273
+ throw errors.withIdentity(error, {
274
+ clientMessageId,
275
+ handoverPromptAccepted,
276
+ operationId,
277
+ threadId,
278
+ turnId
279
+ });
280
+ }
281
+ }
282
+ return { result, reconciled, turnId, threadId, freshThread: started.fresh };
283
+ }
284
+
285
+
286
+ return {
287
+ runCodexAppServerRenewalHandover,
288
+ runCodexAppServerRenewalSeed
289
+ };
290
+ }
@@ -0,0 +1,409 @@
1
+ import {
2
+ normalizeCodexRunText,
3
+ CODEX_APP_SERVER_AGENT_RUN_ID,
4
+ CODEX_APP_SERVER_RESULT_PROCESSED_EVENT,
5
+ codexAppServerTurnCanReceiveProviderCompletion,
6
+ codexAppServerProcessedResultEvent,
7
+ codexAppServerTurnAwaitsProviderIdentity,
8
+ codexAppServerTurnCanAdoptSuccessor,
9
+ codexAppServerAgentRun,
10
+ CODEX_APP_SERVER_RUN_STATE,
11
+ codexAppServerTurnKey
12
+ } from "./codexTurnState.js";
13
+
14
+ // Original result settlement and successor adoption for one retained run owner.
15
+ // Journal/output collections remain with their owners and are shared by reference.
16
+ export function createCodexResultSettlement({ journal, output, createRuntime, publish, debugLog, debugError, turnState }) {
17
+ const {
18
+ processedTurns: codexAppServerProcessedTurns,
19
+ completedTurns: codexAppServerCompletedTurns,
20
+ agentRunPatch,
21
+ markTurnIdle: markCodexAppServerTurnIdle,
22
+ turnResultWasProcessed: codexAppServerTurnResultWasProcessed,
23
+ turnWasCompleted: codexAppServerTurnWasCompleted,
24
+ runInputSource: codexAppServerRunInputSource,
25
+ clearFinalizingTimer: clearCodexAppServerFinalizingTimer,
26
+ agentRunRealtimePayload: codexAppServerAgentRunRealtimePayload,
27
+ clearActiveTimer: clearCodexAppServerActiveTimer
28
+ } = journal;
29
+ const {
30
+ finalizedTurns: codexAppServerFinalizedTurns,
31
+ resultFinalizationKey: codexAppServerResultFinalizationKey,
32
+ cleanupUntrackedTurn: cleanupCodexAppServerUntrackedTurn,
33
+ submitAssistantResult: submitCodexAppServerAssistantResult,
34
+ flushReasoningPersist: flushCodexAppServerReasoningPersist,
35
+ readFinalAssistantResult: readCodexAppServerFinalAssistantResult,
36
+ persistAssistantReply: persistCodexAppServerAssistantReply
37
+ } = output;
38
+ const codexAppServerResultFinalizations = new Map();
39
+
40
+ async function recordCodexAppServerProcessedResult(runtime, sessionId = "", threadId = "", turnId = "", result = {}) {
41
+ await runtime.store.writeAgentRunEvent(sessionId, CODEX_APP_SERVER_AGENT_RUN_ID, {
42
+ event: {
43
+ kind: CODEX_APP_SERVER_RESULT_PROCESSED_EVENT,
44
+ providerThreadId: normalizeCodexRunText(threadId),
45
+ providerTurnId: normalizeCodexRunText(turnId),
46
+ resultReason: normalizeCodexRunText(result.reason)
47
+ },
48
+ patch: {}
49
+ });
50
+ }
51
+
52
+ async function settleCodexAppServerProcessedTurn(sessionId = "", threadId = "", turnId = "", {
53
+ result = {
54
+ ok: true,
55
+ processed: true,
56
+ reason: "already_processed"
57
+ },
58
+ status = "completed"
59
+ } = {}) {
60
+ const normalizedSessionId = normalizeCodexRunText(sessionId);
61
+ const normalizedThreadId = normalizeCodexRunText(threadId);
62
+ const normalizedTurnId = normalizeCodexRunText(turnId);
63
+ const key = codexAppServerResultFinalizationKey(normalizedSessionId, normalizedThreadId, normalizedTurnId);
64
+ const runtime = await createRuntime();
65
+ const session = await runtime.getSession(normalizedSessionId);
66
+ const turn = turnState(session);
67
+ if (!codexAppServerTurnCanReceiveProviderCompletion(turn, normalizedThreadId, normalizedTurnId)) {
68
+ codexAppServerProcessedTurns.delete(key);
69
+ codexAppServerFinalizedTurns.add(key);
70
+ cleanupCodexAppServerUntrackedTurn(normalizedThreadId, normalizedTurnId);
71
+ debugLog("appServerAgentResult.processedSettlementStale", {
72
+ currentState: turn.state,
73
+ currentStatus: turn.status,
74
+ currentThreadId: turn.threadId,
75
+ currentTurnId: turn.turnId,
76
+ sessionId: normalizedSessionId,
77
+ threadId: normalizedThreadId,
78
+ turnId: normalizedTurnId
79
+ });
80
+ return {
81
+ ...result,
82
+ reason: "stale_turn_state"
83
+ };
84
+ }
85
+ await markCodexAppServerTurnIdle(normalizedSessionId, {
86
+ status,
87
+ threadId: normalizedThreadId,
88
+ turnId: normalizedTurnId
89
+ });
90
+ codexAppServerProcessedTurns.delete(key);
91
+ codexAppServerFinalizedTurns.add(key);
92
+ cleanupCodexAppServerUntrackedTurn(normalizedThreadId, normalizedTurnId);
93
+ return result;
94
+ }
95
+
96
+ async function finalizeCodexAppServerAssistantResult(sessionId = "", threadId = "", turnId = "", {
97
+ recoverFromProvider = false,
98
+ status = "completed"
99
+ } = {}) {
100
+ const normalizedSessionId = normalizeCodexRunText(sessionId);
101
+ const normalizedThreadId = normalizeCodexRunText(threadId);
102
+ const normalizedTurnId = normalizeCodexRunText(turnId);
103
+ const key = codexAppServerResultFinalizationKey(normalizedSessionId, normalizedThreadId, normalizedTurnId);
104
+ if (!normalizedSessionId || !normalizedThreadId || !normalizedTurnId) {
105
+ return {
106
+ ok: false,
107
+ processed: false,
108
+ reason: "missing_turn"
109
+ };
110
+ }
111
+ if (codexAppServerProcessedTurns.has(key)) {
112
+ return settleCodexAppServerProcessedTurn(
113
+ normalizedSessionId,
114
+ normalizedThreadId,
115
+ normalizedTurnId,
116
+ {
117
+ status
118
+ }
119
+ );
120
+ }
121
+ if (codexAppServerFinalizedTurns.has(key)) {
122
+ return {
123
+ ok: true,
124
+ processed: true,
125
+ reason: "already_finalized"
126
+ };
127
+ }
128
+ const existing = codexAppServerResultFinalizations.get(key);
129
+ if (existing) {
130
+ return existing;
131
+ }
132
+ const operation = (async () => {
133
+ const runtime = await createRuntime();
134
+ const session = await runtime.getSession(normalizedSessionId);
135
+ const turn = turnState(session);
136
+ const processedEvent = codexAppServerProcessedResultEvent(
137
+ session,
138
+ normalizedThreadId,
139
+ normalizedTurnId
140
+ );
141
+ if (processedEvent) {
142
+ codexAppServerProcessedTurns.add(key);
143
+ return settleCodexAppServerProcessedTurn(
144
+ normalizedSessionId,
145
+ normalizedThreadId,
146
+ normalizedTurnId,
147
+ {
148
+ result: {
149
+ ok: true,
150
+ processed: true,
151
+ reason: normalizeCodexRunText(processedEvent.resultReason) || "already_processed"
152
+ },
153
+ status
154
+ }
155
+ );
156
+ }
157
+ if (codexAppServerTurnAwaitsProviderIdentity(turn, normalizedThreadId, normalizedTurnId)) {
158
+ return {
159
+ ok: true,
160
+ processed: false,
161
+ reason: "turn_identity_pending"
162
+ };
163
+ }
164
+ if (!codexAppServerTurnCanReceiveProviderCompletion(turn, normalizedThreadId, normalizedTurnId)) {
165
+ codexAppServerFinalizedTurns.add(key);
166
+ cleanupCodexAppServerUntrackedTurn(normalizedThreadId, normalizedTurnId);
167
+ debugLog("appServerAgentResult.stale", {
168
+ currentState: turn.state,
169
+ currentStatus: turn.status,
170
+ currentThreadId: turn.threadId,
171
+ currentTurnId: turn.turnId,
172
+ sessionId: normalizedSessionId,
173
+ threadId: normalizedThreadId,
174
+ turnId: normalizedTurnId
175
+ });
176
+ return {
177
+ ok: true,
178
+ processed: false,
179
+ reason: "stale_turn_state"
180
+ };
181
+ }
182
+ const result = await submitCodexAppServerAssistantResult(
183
+ normalizedSessionId,
184
+ normalizedThreadId,
185
+ normalizedTurnId,
186
+ {
187
+ recoverFromProvider
188
+ }
189
+ );
190
+ if (result?.processed) {
191
+ codexAppServerProcessedTurns.add(key);
192
+ await recordCodexAppServerProcessedResult(
193
+ runtime,
194
+ normalizedSessionId,
195
+ normalizedThreadId,
196
+ normalizedTurnId,
197
+ result
198
+ );
199
+ return settleCodexAppServerProcessedTurn(
200
+ normalizedSessionId,
201
+ normalizedThreadId,
202
+ normalizedTurnId,
203
+ {
204
+ result,
205
+ status
206
+ }
207
+ );
208
+ }
209
+ return result;
210
+ })().finally(() => {
211
+ codexAppServerResultFinalizations.delete(key);
212
+ });
213
+ codexAppServerResultFinalizations.set(key, operation);
214
+ return operation;
215
+ }
216
+
217
+ async function recoverCodexAppServerFinalResponseBeforeOutcome(
218
+ sessionId = "",
219
+ threadId = "",
220
+ turnId = "",
221
+ status = ""
222
+ ) {
223
+ const result = await finalizeCodexAppServerAssistantResult(
224
+ sessionId,
225
+ threadId,
226
+ turnId,
227
+ {
228
+ recoverFromProvider: true,
229
+ status
230
+ }
231
+ );
232
+ if (result?.processed !== true) {
233
+ return result;
234
+ }
235
+ if (result.reason !== "already_finalized") {
236
+ return result;
237
+ }
238
+ const runtime = await createRuntime();
239
+ const session = await runtime.getSession(sessionId);
240
+ return codexAppServerTurnResultWasProcessed(session, threadId, turnId)
241
+ ? result
242
+ : {
243
+ ...result,
244
+ processed: false,
245
+ reason: "already_finalized_without_response"
246
+ };
247
+ }
248
+
249
+ async function adoptCodexAppServerSuccessorTurn(sessionId = "", input = {}) {
250
+ const normalizedSessionId = normalizeCodexRunText(sessionId);
251
+ const normalizedThreadId = normalizeCodexRunText(input.threadId);
252
+ const normalizedTurnId = normalizeCodexRunText(input.turnId);
253
+ const expectedPreviousTurnId = normalizeCodexRunText(input.previousTurnId);
254
+ if (!normalizedSessionId || !normalizedThreadId || !normalizedTurnId || !expectedPreviousTurnId) {
255
+ return {
256
+ ok: true,
257
+ processed: false,
258
+ reason: "missing_successor_identity"
259
+ };
260
+ }
261
+ const runtime = await createRuntime();
262
+ let previousTurnId = "";
263
+ let runPatch = null;
264
+ let outcome = {
265
+ ok: true,
266
+ processed: false,
267
+ reason: "turn_changed"
268
+ };
269
+ const updatedSession = await runtime.store.mutateSession(normalizedSessionId, async () => {
270
+ const currentSession = await runtime.getSession(normalizedSessionId);
271
+ const currentTurn = turnState(currentSession);
272
+ if (
273
+ normalizeCodexRunText(currentTurn.threadId) === normalizedThreadId &&
274
+ normalizeCodexRunText(currentTurn.turnId) === normalizedTurnId
275
+ ) {
276
+ outcome = {
277
+ ok: true,
278
+ processed: false,
279
+ reason: "already_current"
280
+ };
281
+ return currentSession;
282
+ }
283
+ if (codexAppServerTurnWasCompleted(currentSession, normalizedThreadId, normalizedTurnId)) {
284
+ outcome = {
285
+ ok: true,
286
+ processed: false,
287
+ reason: "completed_turn"
288
+ };
289
+ return currentSession;
290
+ }
291
+ if (
292
+ normalizeCodexRunText(currentTurn.turnId) !== expectedPreviousTurnId ||
293
+ !codexAppServerTurnCanAdoptSuccessor(currentTurn, normalizedThreadId, normalizedTurnId)
294
+ ) {
295
+ outcome = {
296
+ ok: true,
297
+ processed: false,
298
+ reason: "turn_changed"
299
+ };
300
+ return currentSession;
301
+ }
302
+ previousTurnId = normalizeCodexRunText(currentTurn.turnId);
303
+ const updatedAt = new Date().toISOString();
304
+ runPatch = agentRunPatch({
305
+ runState: CODEX_APP_SERVER_RUN_STATE.ACTIVE,
306
+ session: currentSession,
307
+ status: normalizeCodexRunText(input.status) || "inProgress",
308
+ threadId: normalizedThreadId,
309
+ turnId: normalizedTurnId,
310
+ updatedAt
311
+ });
312
+ const currentInputSource = codexAppServerRunInputSource(codexAppServerAgentRun(currentSession));
313
+ if (currentInputSource) {
314
+ runPatch.inputSource = currentInputSource;
315
+ }
316
+ runPatch.finishedAt = "";
317
+ await runtime.store.writeAgentRunEvent(normalizedSessionId, CODEX_APP_SERVER_AGENT_RUN_ID, {
318
+ event: {
319
+ kind: "codex-app-server-turn-continued",
320
+ message: "",
321
+ previousProviderTurnId: previousTurnId,
322
+ providerThreadId: normalizedThreadId,
323
+ providerTurnId: normalizedTurnId,
324
+ source: normalizeCodexRunText(input.source),
325
+ state: runPatch.state
326
+ },
327
+ patch: runPatch
328
+ });
329
+ outcome = {
330
+ ok: true,
331
+ previousTurnId,
332
+ processed: true,
333
+ reason: "successor_turn_adopted",
334
+ threadId: normalizedThreadId,
335
+ turnId: normalizedTurnId
336
+ };
337
+ return runtime.getSession(normalizedSessionId);
338
+ });
339
+ if (!outcome.processed) {
340
+ return outcome;
341
+ }
342
+
343
+ clearCodexAppServerFinalizingTimer(
344
+ normalizedSessionId,
345
+ normalizedThreadId,
346
+ previousTurnId
347
+ );
348
+ codexAppServerCompletedTurns.add(codexAppServerTurnKey(
349
+ normalizedThreadId,
350
+ previousTurnId
351
+ ));
352
+ const previousResultKey = codexAppServerResultFinalizationKey(
353
+ normalizedSessionId,
354
+ normalizedThreadId,
355
+ previousTurnId
356
+ );
357
+ codexAppServerProcessedTurns.delete(previousResultKey);
358
+ codexAppServerFinalizedTurns.add(previousResultKey);
359
+ try {
360
+ await flushCodexAppServerReasoningPersist(
361
+ normalizedSessionId,
362
+ normalizedThreadId,
363
+ previousTurnId
364
+ );
365
+ const previousAssistantResult = readCodexAppServerFinalAssistantResult(
366
+ normalizedSessionId,
367
+ normalizedThreadId,
368
+ previousTurnId
369
+ );
370
+ if (previousAssistantResult?.text) {
371
+ await persistCodexAppServerAssistantReply(
372
+ runtime,
373
+ normalizedSessionId,
374
+ previousAssistantResult
375
+ );
376
+ }
377
+ } catch (error) {
378
+ debugLog("appServerTurn.continuedOutput.error", {
379
+ error: debugError(error),
380
+ previousTurnId,
381
+ sessionId: normalizedSessionId,
382
+ threadId: normalizedThreadId,
383
+ turnId: normalizedTurnId
384
+ });
385
+ }
386
+ cleanupCodexAppServerUntrackedTurn(normalizedThreadId, previousTurnId);
387
+ await publish(normalizedSessionId, {
388
+ payload: codexAppServerAgentRunRealtimePayload(runPatch),
389
+ reason: "codex-app-server-turn-active",
390
+ session: updatedSession
391
+ });
392
+ debugLog("appServerTurn.continued", {
393
+ previousTurnId,
394
+ sessionId: normalizedSessionId,
395
+ source: normalizeCodexRunText(input.source),
396
+ threadId: normalizedThreadId,
397
+ turnId: normalizedTurnId
398
+ });
399
+ clearCodexAppServerActiveTimer(normalizedSessionId);
400
+ return outcome;
401
+ }
402
+
403
+ return {
404
+ resultFinalizations: codexAppServerResultFinalizations,
405
+ finalizeAssistantResult: finalizeCodexAppServerAssistantResult,
406
+ recoverFinalResponseBeforeOutcome: recoverCodexAppServerFinalResponseBeforeOutcome,
407
+ adoptSuccessorTurn: adoptCodexAppServerSuccessorTurn
408
+ };
409
+ }