@jskit-ai/assistant-core 0.1.200 → 0.1.202

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,658 @@
1
+ import {
2
+ normalizeCodexRunText,
3
+ codexAppServerRunStateIsActive,
4
+ codexAppServerAgentRun,
5
+ codexAppServerPendingUserMessageClientIds,
6
+ codexAppServerProcessedResultEvent,
7
+ codexAppServerTurnCanReceiveProviderActivity,
8
+ codexAppServerTurnKey,
9
+ codexAppServerAgentRunPatch,
10
+ codexAppServerRunIdentityForPatch,
11
+ codexAppServerRunPatchIsStaleAfterTerminalState,
12
+ CODEX_APP_SERVER_AGENT_RUN_ID,
13
+ CODEX_AGENT_PROVIDER,
14
+ CODEX_APP_SERVER_RUN_STATE,
15
+ CODEX_APP_SERVER_ACTIVE_RUN_STATES,
16
+ codexAppServerRunInputSource
17
+ } from "./codexTurnState.js";
18
+ import crypto from "node:crypto";
19
+ import { isPlainObject } from "./normalize.js";
20
+
21
+ const CODEX_APP_SERVER_PROMPT_CLAIM_GRACE_MS = 15000;
22
+
23
+ // Original native run claims, guarded journal writes and status retirement.
24
+ // The containing run owner shares these exact Sets and Maps by reference.
25
+ export function createCodexRunJournal({
26
+ namespace,
27
+ normalizeRunState,
28
+ turnState,
29
+ createRuntime,
30
+ publish,
31
+ checkpoint,
32
+ debugLog,
33
+ output,
34
+ messageIdPrefix,
35
+ sessionIdRequiredError,
36
+ turnClaimsUnsupportedError,
37
+ turnAlreadyRunningError,
38
+ errorPrefix,
39
+ idlePublishPayload,
40
+ finalizingGraceMs,
41
+ admissionTaskFinished,
42
+ orphanedPromptMessage
43
+ }) {
44
+ const codexAppServerCompletedTurns = new Set();
45
+ const codexAppServerProcessedTurns = new Set();
46
+ const codexAppServerActiveTimers = new Map();
47
+ const codexAppServerFinalizingTimers = new Map();
48
+ const agentRunPatch = input => codexAppServerAgentRunPatch(input, normalizeRunState);
49
+ const runIdentityForPatch = (session, input) => codexAppServerRunIdentityForPatch(session, input, normalizeRunState);
50
+ const runPatchIsStaleAfterTerminalState = (turn, patch) => codexAppServerRunPatchIsStaleAfterTerminalState(turn, patch, normalizeRunState);
51
+ const {
52
+ resultFinalizationKey: codexAppServerResultFinalizationKey,
53
+ readAgentRunForSession: readCodexAppServerAgentRunForSession
54
+ } = output;
55
+
56
+ function codexAppServerTurnResultWasProcessed(session = {}, threadId = "", turnId = "") {
57
+ const normalizedThreadId = normalizeCodexRunText(threadId);
58
+ const normalizedTurnId = normalizeCodexRunText(turnId);
59
+ if (!normalizedThreadId || !normalizedTurnId) {
60
+ return false;
61
+ }
62
+ return codexAppServerProcessedTurns.has(codexAppServerResultFinalizationKey(
63
+ session.sessionId,
64
+ normalizedThreadId,
65
+ normalizedTurnId
66
+ )) || Boolean(codexAppServerProcessedResultEvent(session, normalizedThreadId, normalizedTurnId));
67
+ }
68
+
69
+ async function claimCodexAppServerTurnStart(runtime, sessionId = "", outerTurnId = "", {
70
+ inputSource = "chat"
71
+ } = {}) {
72
+ const normalizedSessionId = normalizeCodexRunText(sessionId);
73
+ const normalizedOuterTurnId = normalizeCodexRunText(outerTurnId);
74
+ let claimResult = null;
75
+ const mutationResult = await runtime.store.mutateSession(normalizedSessionId, async () => {
76
+ const currentSession = await runtime.getSession(normalizedSessionId);
77
+ const currentTurn = turnState(currentSession);
78
+ if (currentTurn.active) {
79
+ claimResult = {
80
+ claimed: false,
81
+ session: currentSession
82
+ };
83
+ return claimResult;
84
+ }
85
+ const updatedAt = new Date().toISOString();
86
+ const runPatch = agentRunPatch({
87
+ inputSource: normalizeCodexRunText(inputSource) || "chat",
88
+ outerTurnId: normalizedOuterTurnId,
89
+ runState: CODEX_APP_SERVER_RUN_STATE.STARTING,
90
+ session: currentSession,
91
+ status: "starting",
92
+ updatedAt
93
+ });
94
+ runPatch.providerGoalStatus = "";
95
+ runPatch.providerGoalThreadId = "";
96
+ runPatch.providerGoalUpdatedAt = "";
97
+ await runtime.store.writeAgentRunEvent(normalizedSessionId, CODEX_APP_SERVER_AGENT_RUN_ID, {
98
+ event: {
99
+ kind: "codex-app-server-turn-claimed",
100
+ message: "",
101
+ state: runPatch.state
102
+ },
103
+ patch: runPatch
104
+ });
105
+ claimResult = {
106
+ claimed: true,
107
+ session: await runtime.getSession(normalizedSessionId)
108
+ };
109
+ return claimResult;
110
+ });
111
+ return claimResult || mutationResult;
112
+ }
113
+
114
+ async function claimCodexAppServerMessageStart(runtime, sessionId = "", outerTurnId = "", {
115
+ inputSource = "chat"
116
+ } = {}) {
117
+ const normalizedSessionId = normalizeCodexRunText(sessionId);
118
+ const normalizedOuterTurnId = normalizeCodexRunText(outerTurnId) ||
119
+ `${messageIdPrefix}${crypto.randomUUID()}`;
120
+ if (!normalizedSessionId) {
121
+ return {
122
+ claimed: false,
123
+ response: { value: {
124
+ ok: false,
125
+ error: sessionIdRequiredError
126
+ } }
127
+ };
128
+ }
129
+ if (typeof runtime?.store?.mutateSession !== "function" || typeof runtime?.getSession !== "function") {
130
+ throw new Error(turnClaimsUnsupportedError);
131
+ }
132
+ const result = await claimCodexAppServerTurnStart(runtime, normalizedSessionId, normalizedOuterTurnId, { inputSource });
133
+ if (result?.claimed === false && result.session) {
134
+ const turn = turnState(result.session);
135
+ return { ...result, response: { value: {
136
+ ok: false,
137
+ code: `${errorPrefix}agent_turn_already_running`,
138
+ error: turnAlreadyRunningError,
139
+ operationOutcome: "agent_already_running",
140
+ refreshRecommended: true,
141
+ threadId: normalizeCodexRunText(turn.threadId),
142
+ turnId: normalizeCodexRunText(turn.turnId)
143
+ }, session: result.session } };
144
+ }
145
+ if (result?.claimed) {
146
+ await publish(normalizedSessionId, {
147
+ reason: "codex-app-server-turn-claimed",
148
+ session: result.session,
149
+ payload: codexAppServerAgentRunRealtimePayload(codexAppServerAgentRun(result.session))
150
+ });
151
+ }
152
+ return result;
153
+ }
154
+
155
+ async function writeCodexAppServerAgentRun(runtime, sessionId = "", {
156
+ error = "",
157
+ inputSource = "",
158
+ phase,
159
+ publishReason = "",
160
+ observedRun = null,
161
+ runState = CODEX_APP_SERVER_RUN_STATE.COMPLETED,
162
+ status = "",
163
+ threadId = "",
164
+ turnId = "",
165
+ updatedAt = ""
166
+ } = {}) {
167
+ const normalizedSessionId = normalizeCodexRunText(sessionId);
168
+ let runPatch = null;
169
+ let conversationStream = null;
170
+ let wrote = false;
171
+ let stale = null;
172
+ const updatedSession = await runtime.store.mutateSession(normalizedSessionId, async () => {
173
+ const currentSession = typeof runtime?.getSession === "function"
174
+ ? await runtime.getSession(normalizedSessionId).catch(() => null)
175
+ : null;
176
+ const identity = runIdentityForPatch(currentSession || {}, {
177
+ threadId,
178
+ turnId
179
+ });
180
+ runPatch = agentRunPatch({
181
+ error,
182
+ inputSource,
183
+ phase,
184
+ runState,
185
+ session: currentSession || {},
186
+ status,
187
+ threadId: identity.threadId,
188
+ turnId: identity.turnId,
189
+ updatedAt: normalizeCodexRunText(updatedAt) || new Date().toISOString()
190
+ });
191
+ const currentTurn = turnState(currentSession || {});
192
+ if (
193
+ codexAppServerRunStateIsActive(runPatch.state) &&
194
+ currentTurn.threadId === normalizeCodexRunText(runPatch.providerThreadId) &&
195
+ currentTurn.turnId === normalizeCodexRunText(runPatch.providerTurnId) &&
196
+ currentTurn.startedAt
197
+ ) {
198
+ runPatch.startedAt = currentTurn.startedAt;
199
+ }
200
+ // The observation is valid only for the durable run read before the RPC.
201
+ // A later Stop, Pause, completion or successor wins under this same lock.
202
+ const canRecoverStoppedTurn = observedRun &&
203
+ currentTurn.status !== "observation_lost" &&
204
+ (codexAppServerAgentRun(currentSession)?.events?.length || 0) === (observedRun.events?.length || 0) &&
205
+ !codexAppServerTurnResultWasProcessed(currentSession, runPatch.providerThreadId, runPatch.providerTurnId) &&
206
+ [CODEX_APP_SERVER_RUN_STATE.FAILED, CODEX_APP_SERVER_RUN_STATE.INTERRUPTED].includes(currentTurn.runState) &&
207
+ currentTurn.threadId === runPatch.providerThreadId &&
208
+ currentTurn.turnId === runPatch.providerTurnId &&
209
+ runPatch.state === CODEX_APP_SERVER_RUN_STATE.ACTIVE;
210
+ if (
211
+ (currentTurn.status === "observation_lost" && status !== "observation_lost") ||
212
+ (phase !== undefined && (!normalizeCodexRunText(turnId) ||
213
+ !codexAppServerTurnCanReceiveProviderActivity(currentTurn, threadId, turnId))) ||
214
+ (observedRun && !canRecoverStoppedTurn) ||
215
+ (!canRecoverStoppedTurn && runPatchIsStaleAfterTerminalState(currentTurn, runPatch))
216
+ ) {
217
+ stale = {
218
+ currentState: currentTurn.state,
219
+ currentStatus: currentTurn.status,
220
+ currentThreadId: currentTurn.threadId,
221
+ currentTurnId: currentTurn.turnId,
222
+ patchState: runPatch.state,
223
+ patchStatus: runPatch.providerStatus,
224
+ patchThreadId: runPatch.providerThreadId,
225
+ patchTurnId: runPatch.providerTurnId
226
+ };
227
+ debugLog("appServerAgentRun.staleTerminalPatch", {
228
+ ...stale,
229
+ publishReason,
230
+ sessionId: normalizedSessionId
231
+ });
232
+ return currentSession;
233
+ }
234
+ await runtime.store.writeAgentRunEvent(normalizedSessionId, CODEX_APP_SERVER_AGENT_RUN_ID, {
235
+ event: {
236
+ kind: publishReason || "codex-app-server-turn-state",
237
+ message: normalizeCodexRunText(error),
238
+ state: runPatch.state
239
+ },
240
+ patch: runPatch
241
+ });
242
+ if (!codexAppServerRunStateIsActive(runPatch.state)) {
243
+ conversationStream = runtime.store.clearConversationStream(normalizedSessionId);
244
+ }
245
+ wrote = true;
246
+ return runtime.getSession(normalizedSessionId);
247
+ });
248
+ return { wrote, stale, runPatch, conversationStream, updatedSession };
249
+ }
250
+
251
+ function clearCodexAppServerActiveTimer(sessionId = "") {
252
+ const normalizedSessionId = normalizeCodexRunText(sessionId);
253
+ const sessionKey = namespace(normalizedSessionId);
254
+ const timer = codexAppServerActiveTimers.get(sessionKey);
255
+ if (timer) {
256
+ clearTimeout(timer);
257
+ codexAppServerActiveTimers.delete(sessionKey);
258
+ }
259
+ }
260
+
261
+ function clearCodexAppServerFinalizingTimer(sessionId = "", threadId = "", turnId = "") {
262
+ const key = codexAppServerResultFinalizationKey(sessionId, threadId, turnId);
263
+ const timer = codexAppServerFinalizingTimers.get(key);
264
+ if (timer) {
265
+ clearTimeout(timer);
266
+ codexAppServerFinalizingTimers.delete(key);
267
+ }
268
+ }
269
+
270
+ function clearCodexAppServerSessionRecoveryTimers(sessionId = "") {
271
+ const normalizedSessionId = normalizeCodexRunText(sessionId);
272
+ clearCodexAppServerActiveTimer(normalizedSessionId);
273
+ const finalizingKeyPrefix = `${namespace(normalizedSessionId)}:`;
274
+ for (const [key, timer] of codexAppServerFinalizingTimers.entries()) {
275
+ if (key.startsWith(finalizingKeyPrefix)) {
276
+ clearTimeout(timer);
277
+ codexAppServerFinalizingTimers.delete(key);
278
+ }
279
+ }
280
+ }
281
+
282
+ function terminalCodexAppServerAgentRunState(status = "") {
283
+ const normalizedStatus = normalizeCodexRunText(status);
284
+ if (normalizedStatus === "interrupted") {
285
+ return CODEX_APP_SERVER_RUN_STATE.INTERRUPTED;
286
+ }
287
+ if (normalizedStatus === "failed") {
288
+ return CODEX_APP_SERVER_RUN_STATE.FAILED;
289
+ }
290
+ return CODEX_APP_SERVER_RUN_STATE.COMPLETED;
291
+ }
292
+
293
+ function codexAppServerAgentRunRealtimePayload(runPatch = {}) {
294
+ const runState = normalizeRunState(runPatch.state);
295
+ const active = codexAppServerRunStateIsActive(runState);
296
+ const state = runState === CODEX_APP_SERVER_RUN_STATE.FINALIZING
297
+ ? "finalizing"
298
+ : runState === CODEX_APP_SERVER_RUN_STATE.STARTING
299
+ ? "starting"
300
+ : active
301
+ ? "active"
302
+ : "idle";
303
+ const turn = {
304
+ active,
305
+ completedAt: normalizeCodexRunText(runPatch.finishedAt),
306
+ error: normalizeCodexRunText(runPatch.error),
307
+ inputSource: normalizeCodexRunText(runPatch.inputSource),
308
+ outerTurnId: normalizeCodexRunText(runPatch.outerTurnId),
309
+ phase: active && runPatch.providerStatus !== "observation_lost" ? normalizeCodexRunText(runPatch.providerPhase) : "",
310
+ runId: CODEX_APP_SERVER_AGENT_RUN_ID,
311
+ runState,
312
+ startedAt: normalizeCodexRunText(runPatch.startedAt),
313
+ state,
314
+ status: normalizeCodexRunText(runPatch.providerStatus || runState),
315
+ threadId: normalizeCodexRunText(runPatch.providerThreadId),
316
+ turnId: normalizeCodexRunText(runPatch.providerTurnId),
317
+ updatedAt: normalizeCodexRunText(runPatch.updatedAt)
318
+ };
319
+ return {
320
+ agentRun: {
321
+ active,
322
+ id: CODEX_APP_SERVER_AGENT_RUN_ID,
323
+ inputSource: turn.inputSource,
324
+ outerTurnId: turn.outerTurnId,
325
+ provider: CODEX_AGENT_PROVIDER,
326
+ providerInterface: "codex_app_server",
327
+ providerStatus: turn.status,
328
+ providerThreadId: turn.threadId,
329
+ providerTurnId: turn.turnId,
330
+ state: runState,
331
+ updatedAt: turn.updatedAt
332
+ },
333
+ agentSession: {
334
+ providerId: CODEX_AGENT_PROVIDER,
335
+ thread: {
336
+ id: turn.threadId
337
+ },
338
+ transportId: "codex_app_server",
339
+ turn: {
340
+ active: turn.active,
341
+ completedAt: turn.completedAt,
342
+ error: turn.error,
343
+ id: turn.turnId,
344
+ inputSource: turn.inputSource,
345
+ outerTurnId: turn.outerTurnId,
346
+ phase: turn.phase,
347
+ runState: turn.runState,
348
+ startedAt: turn.startedAt,
349
+ state: turn.state,
350
+ status: turn.status,
351
+ updatedAt: turn.updatedAt
352
+ }
353
+ }
354
+ };
355
+ }
356
+
357
+ async function publishCodexAppServerAgentRun(sessionId = "", {
358
+ error = "",
359
+ inputSource = "",
360
+ phase,
361
+ publishPayload = null,
362
+ publishReason = "",
363
+ observedRun = null,
364
+ runState = CODEX_APP_SERVER_RUN_STATE.COMPLETED,
365
+ status = "",
366
+ threadId = "",
367
+ turnId = "",
368
+ updatedAt = ""
369
+ } = {}) {
370
+ const normalizedSessionId = normalizeCodexRunText(sessionId);
371
+ if (!normalizedSessionId) {
372
+ return {
373
+ ok: false,
374
+ error: sessionIdRequiredError
375
+ };
376
+ }
377
+ const runtime = await createRuntime();
378
+ const { wrote, stale, runPatch, conversationStream, updatedSession } = await writeCodexAppServerAgentRun(runtime, normalizedSessionId, {
379
+ error, inputSource, phase, publishReason, observedRun, runState, status, threadId, turnId, updatedAt
380
+ });
381
+ if (!wrote) {
382
+ return {
383
+ ok: true,
384
+ processed: false,
385
+ reason: "stale_terminal_turn_state",
386
+ stale
387
+ };
388
+ }
389
+ await publish(normalizedSessionId, {
390
+ payload: {
391
+ ...codexAppServerAgentRunRealtimePayload(runPatch),
392
+ ...(conversationStream ? { conversationStream } : {}),
393
+ ...(isPlainObject(publishPayload) ? publishPayload : {})
394
+ },
395
+ reason: publishReason || "codex-app-server-turn-state",
396
+ session: updatedSession
397
+ });
398
+ return {
399
+ ok: true
400
+ };
401
+ }
402
+
403
+ async function markCodexAppServerTurnFinalizing(sessionId = "", input = {}) {
404
+ const result = await publishCodexAppServerAgentRun(sessionId, {
405
+ error: normalizeCodexRunText(input.error),
406
+ publishReason: "codex-app-server-turn-finalizing",
407
+ runState: CODEX_APP_SERVER_RUN_STATE.FINALIZING,
408
+ status: normalizeCodexRunText(input.status) || "completed",
409
+ threadId: normalizeCodexRunText(input.threadId),
410
+ turnId: normalizeCodexRunText(input.turnId)
411
+ });
412
+ clearCodexAppServerActiveTimer(sessionId);
413
+ return result;
414
+ }
415
+
416
+ async function markCodexAppServerTurnIdle(sessionId = "", input = {}) {
417
+ const status = normalizeCodexRunText(input.status) || "completed";
418
+ const result = await publishCodexAppServerAgentRun(sessionId, {
419
+ error: normalizeCodexRunText(input.error),
420
+ publishPayload: idlePublishPayload,
421
+ publishReason: "codex-app-server-turn-idle",
422
+ runState: terminalCodexAppServerAgentRunState(status),
423
+ status,
424
+ threadId: normalizeCodexRunText(input.threadId),
425
+ turnId: normalizeCodexRunText(input.turnId)
426
+ });
427
+ clearCodexAppServerActiveTimer(sessionId);
428
+ clearCodexAppServerFinalizingTimer(sessionId, input.threadId, input.turnId);
429
+ if (result?.processed === false) {
430
+ return result;
431
+ }
432
+ return {
433
+ ...result,
434
+ checkpoint: await checkpoint(sessionId, {
435
+ status,
436
+ turnOutcome: normalizeCodexRunText(input.turnOutcome),
437
+ threadId: normalizeCodexRunText(input.threadId),
438
+ turnId: normalizeCodexRunText(input.turnId)
439
+ })
440
+ };
441
+ }
442
+
443
+ async function currentCodexAppServerTurnId(sessionId = "", threadId = "") {
444
+ const normalizedSessionId = normalizeCodexRunText(sessionId);
445
+ const normalizedThreadId = normalizeCodexRunText(threadId);
446
+ if (!normalizedSessionId) {
447
+ return "";
448
+ }
449
+ const runtime = await createRuntime();
450
+ const session = await runtime.getSession(normalizedSessionId);
451
+ const turn = turnState(session);
452
+ if (!turn.active) {
453
+ return "";
454
+ }
455
+ if (normalizedThreadId && turn.threadId && turn.threadId !== normalizedThreadId) {
456
+ return "";
457
+ }
458
+ return turn.turnId;
459
+ }
460
+
461
+ async function resolveCodexAppServerTurnId(sessionId = "", threadId = "", turnId = "") {
462
+ return normalizeCodexRunText(turnId) || await currentCodexAppServerTurnId(sessionId, threadId);
463
+ }
464
+
465
+ function codexAppServerDateValueMs(value = "") {
466
+ const parsed = Date.parse(normalizeCodexRunText(value));
467
+ return Number.isFinite(parsed) ? parsed : 0;
468
+ }
469
+
470
+ function codexAppServerFinalizingExpired(turn = {}, nowMs = Date.now()) {
471
+ if (normalizeCodexRunText(turn.state) !== "finalizing") {
472
+ return false;
473
+ }
474
+ const referenceMs = codexAppServerDateValueMs(turn.completedAt) || codexAppServerDateValueMs(turn.updatedAt);
475
+ return Boolean(referenceMs && nowMs - referenceMs >= finalizingGraceMs);
476
+ }
477
+
478
+ function codexAppServerFinalizingRemainingMs(turn = {}, nowMs = Date.now()) {
479
+ const referenceMs = codexAppServerDateValueMs(turn.completedAt) || codexAppServerDateValueMs(turn.updatedAt);
480
+ if (!referenceMs) {
481
+ return finalizingGraceMs;
482
+ }
483
+ return Math.max(0, finalizingGraceMs - (nowMs - referenceMs));
484
+ }
485
+
486
+ function codexAppServerTurnWasCompleted(session = {}, threadId = "", turnId = "") {
487
+ const normalizedThreadId = normalizeCodexRunText(threadId);
488
+ const normalizedTurnId = normalizeCodexRunText(turnId);
489
+ if (!normalizedThreadId || !normalizedTurnId) {
490
+ return false;
491
+ }
492
+ return codexAppServerCompletedTurns.has(codexAppServerTurnKey(
493
+ normalizedThreadId,
494
+ normalizedTurnId
495
+ )) || codexAppServerTurnResultWasProcessed(session, normalizedThreadId, normalizedTurnId);
496
+ }
497
+
498
+ async function failOrphanedCodexAppServerPromptDelivery(runtime, session = {}, turn = {}) {
499
+ const sessionId = normalizeCodexRunText(session.sessionId);
500
+ if (!sessionId) {
501
+ return session;
502
+ }
503
+ const error = orphanedPromptMessage;
504
+ const updatedAt = new Date().toISOString();
505
+ const runPatch = agentRunPatch({
506
+ error,
507
+ inputSource: turn.inputSource,
508
+ runState: CODEX_APP_SERVER_RUN_STATE.FAILED,
509
+ session,
510
+ status: "delivery_failed",
511
+ threadId: turn.threadId,
512
+ turnId: turn.turnId,
513
+ updatedAt
514
+ });
515
+ delete runPatch.pendingUserMessageClientIds;
516
+ const updatedSession = await runtime.store.mutateSession(sessionId, async () => {
517
+ await runtime.store.writeAgentRunEvent(sessionId, CODEX_APP_SERVER_AGENT_RUN_ID, {
518
+ event: {
519
+ kind: "codex-prompt-delivery-abandoned",
520
+ message: error,
521
+ state: CODEX_APP_SERVER_RUN_STATE.FAILED
522
+ },
523
+ patch: runPatch
524
+ });
525
+ return runtime.getSession(sessionId);
526
+ });
527
+ await publish(sessionId, {
528
+ payload: codexAppServerAgentRunRealtimePayload(runPatch),
529
+ reason: "codex-prompt-delivery-abandoned",
530
+ session: updatedSession
531
+ });
532
+ debugLog("appServerPrompt.orphaned", {
533
+ sessionId,
534
+ threadId: turn.threadId
535
+ });
536
+ return runtime.getSession(sessionId);
537
+ }
538
+
539
+ async function writeCodexAppServerUserMessageOwnership(store, sessionId = "", clientId = "", {
540
+ eventKind = "codex-app-server-user-message-ownership-updated",
541
+ owned = false
542
+ } = {}) {
543
+ const normalizedSessionId = normalizeCodexRunText(sessionId);
544
+ const normalizedClientId = normalizeCodexRunText(clientId);
545
+ if (!normalizedSessionId || !normalizedClientId) {
546
+ return false;
547
+ }
548
+ let wasOwned = false;
549
+ await store.mutateSession(normalizedSessionId, async () => {
550
+ const run = await readCodexAppServerAgentRunForSession(store, normalizedSessionId);
551
+ const currentIds = codexAppServerPendingUserMessageClientIds(run);
552
+ wasOwned = currentIds.indexOf(normalizedClientId) >= 0;
553
+ if (wasOwned === owned) {
554
+ return;
555
+ }
556
+ const pendingUserMessageClientIds = owned
557
+ ? [...currentIds, normalizedClientId]
558
+ : currentIds.filter((id) => id !== normalizedClientId);
559
+ await store.writeAgentRunEvent(normalizedSessionId, CODEX_APP_SERVER_AGENT_RUN_ID, {
560
+ event: {
561
+ clientId: normalizedClientId,
562
+ kind: eventKind,
563
+ message: ""
564
+ },
565
+ patch: {
566
+ pendingUserMessageClientIds
567
+ }
568
+ });
569
+ });
570
+ return wasOwned;
571
+ }
572
+
573
+ function abandonedCodexAppServerPromptClaim(session = {}, {
574
+ nowMs = Date.now(),
575
+ promptDeliveryActive = false
576
+ } = {}) {
577
+ const run = codexAppServerAgentRun(session);
578
+ if (!run) {
579
+ return null;
580
+ }
581
+ const runUpdatedMs = codexAppServerDateValueMs(run.updatedAt || run.startedAt || run.at);
582
+ const claimExpired = Boolean(
583
+ runUpdatedMs &&
584
+ nowMs - runUpdatedMs >= CODEX_APP_SERVER_PROMPT_CLAIM_GRACE_MS
585
+ );
586
+ return (
587
+ (run.active === true || CODEX_APP_SERVER_ACTIVE_RUN_STATES.has(normalizeRunState(run.state))) &&
588
+ normalizeCodexRunText(run.state) === CODEX_APP_SERVER_RUN_STATE.STARTING &&
589
+ !normalizeCodexRunText(run.providerThreadId) &&
590
+ !normalizeCodexRunText(run.providerTurnId) &&
591
+ promptDeliveryActive !== true &&
592
+ (admissionTaskFinished(session, run) || claimExpired)
593
+ ) ? run : null;
594
+ }
595
+
596
+ async function recoverAbandonedCodexAppServerPromptClaim(runtime, session = {}, options = {}) {
597
+ const run = abandonedCodexAppServerPromptClaim(session, options);
598
+ if (!run || !session?.sessionId || typeof runtime?.store?.writeAgentRunEvent !== "function") {
599
+ return {
600
+ recovered: false,
601
+ session
602
+ };
603
+ }
604
+ const error = "Codex app-server prompt delivery ended before a provider turn was created.";
605
+ await runtime.store.writeAgentRunEvent(session.sessionId, CODEX_APP_SERVER_AGENT_RUN_ID, {
606
+ event: {
607
+ kind: "codex-prompt-delivery-abandoned",
608
+ message: error,
609
+ state: CODEX_APP_SERVER_RUN_STATE.FAILED
610
+ },
611
+ patch: {
612
+ error,
613
+ provider: CODEX_AGENT_PROVIDER,
614
+ providerInterface: "codex_app_server",
615
+ providerStatus: "delivery_failed",
616
+ providerThreadId: "",
617
+ providerTurnId: "",
618
+ state: CODEX_APP_SERVER_RUN_STATE.FAILED,
619
+ updatedAt: new Date().toISOString()
620
+ }
621
+ });
622
+ debugLog("appServerPrompt.abandoned", {
623
+ runUpdatedAt: normalizeCodexRunText(run.updatedAt),
624
+ sessionId: session.sessionId
625
+ });
626
+ return {
627
+ recovered: true,
628
+ session: await runtime.getSession(session.sessionId)
629
+ };
630
+ }
631
+
632
+ return {
633
+ completedTurns: codexAppServerCompletedTurns,
634
+ processedTurns: codexAppServerProcessedTurns,
635
+ activeTimers: codexAppServerActiveTimers,
636
+ finalizingTimers: codexAppServerFinalizingTimers,
637
+ agentRunPatch,
638
+ turnResultWasProcessed: codexAppServerTurnResultWasProcessed,
639
+ claimTurnStart: claimCodexAppServerTurnStart,
640
+ claimMessageStart: claimCodexAppServerMessageStart,
641
+ writeAgentRun: writeCodexAppServerAgentRun,
642
+ clearActiveTimer: clearCodexAppServerActiveTimer,
643
+ clearFinalizingTimer: clearCodexAppServerFinalizingTimer,
644
+ clearSessionRecoveryTimers: clearCodexAppServerSessionRecoveryTimers,
645
+ agentRunRealtimePayload: codexAppServerAgentRunRealtimePayload,
646
+ publishAgentRun: publishCodexAppServerAgentRun,
647
+ markTurnFinalizing: markCodexAppServerTurnFinalizing,
648
+ markTurnIdle: markCodexAppServerTurnIdle,
649
+ resolveTurnId: resolveCodexAppServerTurnId,
650
+ finalizingExpired: codexAppServerFinalizingExpired,
651
+ finalizingRemainingMs: codexAppServerFinalizingRemainingMs,
652
+ turnWasCompleted: codexAppServerTurnWasCompleted,
653
+ runInputSource: codexAppServerRunInputSource,
654
+ writeUserMessageOwnership: writeCodexAppServerUserMessageOwnership,
655
+ recoverAbandonedPromptClaim: recoverAbandonedCodexAppServerPromptClaim,
656
+ failOrphanedPromptDelivery: failOrphanedCodexAppServerPromptDelivery
657
+ };
658
+ }