@jskit-ai/assistant-core 0.2.7 → 0.2.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (214) hide show
  1. package/examples/conversation/package.json +5 -5
  2. package/package.json +18 -7
  3. package/src/client/browser.js +21 -0
  4. package/src/client/conversation/AssistantAttachmentQueue.vue +25 -108
  5. package/src/client/conversation/AssistantComposerActions.vue +1 -1
  6. package/src/client/conversation/AssistantConversationElement.vue +14 -52
  7. package/src/client/conversation/AssistantGoalControl.vue +13 -10
  8. package/src/client/conversation/AssistantPromptInput.vue +6 -91
  9. package/src/client/conversation/AssistantTranscript.css +505 -0
  10. package/src/client/conversation/AssistantTranscript.vue +24 -1197
  11. package/src/client/conversation/attachmentFiles.js +158 -0
  12. package/src/client/conversation/attachmentQueueItems.js +121 -0
  13. package/src/client/conversation/attachmentUploadRows.js +265 -0
  14. package/src/client/conversation/conversationDisplay.js +54 -0
  15. package/src/client/conversation/messageDelivery.js +13 -9
  16. package/src/client/conversation/retainedConversation.js +14 -3
  17. package/src/client/conversation/submitText.js +13 -2
  18. package/src/client/conversation/transcriptEntries.js +225 -0
  19. package/src/client/conversation/useAssistantAttachments.js +86 -388
  20. package/src/client/conversation/useAssistantQuestions.js +16 -3
  21. package/src/client/conversation/useAssistantSuggestions.js +16 -2
  22. package/src/client/conversation/useScrollToBottom.js +17 -19
  23. package/src/client/conversation/useTextareaAutoGrow.js +115 -0
  24. package/src/client/conversation/useTranscriptScrollFollow.js +525 -0
  25. package/src/client/lib/assistantApi.js +192 -161
  26. package/src/client/types.js +110 -0
  27. package/src/server/conversation/attachments.js +21 -6
  28. package/src/server/conversation/boundedToolLoop.js +1 -0
  29. package/src/server/conversation/claudeClient.js +71 -20
  30. package/src/server/conversation/claudeCommandRun.js +191 -0
  31. package/src/server/conversation/claudeHistory.js +7 -0
  32. package/src/server/conversation/claudeNativeTurn.js +708 -0
  33. package/src/server/conversation/claudeProcess.js +186 -112
  34. package/src/server/conversation/claudeProcessStart.js +214 -0
  35. package/src/server/conversation/claudePromptDelivery.js +183 -0
  36. package/src/server/conversation/claudeSessionCleanup.js +248 -0
  37. package/src/server/conversation/claudeTools.js +15 -3
  38. package/src/server/conversation/claudeTurn.js +212 -1388
  39. package/src/server/conversation/codexAccountReader.js +168 -0
  40. package/src/server/conversation/codexAuthPreflight.js +5 -4
  41. package/src/server/conversation/codexClient.js +18 -6
  42. package/src/server/conversation/codexConfiguration.js +8 -844
  43. package/src/server/conversation/codexConversationStops.js +377 -0
  44. package/src/server/conversation/codexConversationStore.js +119 -85
  45. package/src/server/conversation/codexConversationTurns.js +378 -0
  46. package/src/server/conversation/codexConversations.js +134 -866
  47. package/src/server/conversation/codexDetachedConversation.js +203 -0
  48. package/src/server/conversation/codexDetachedTurn.js +145 -104
  49. package/src/server/conversation/codexEvents.js +5 -200
  50. package/src/server/conversation/codexGoalCommands.js +14 -4
  51. package/src/server/conversation/codexHelperExecution.js +57 -57
  52. package/src/server/conversation/codexHelperThreadLedger.js +30 -288
  53. package/src/server/conversation/codexHelperThreadRecord.js +304 -0
  54. package/src/server/conversation/codexHelperThreadRecovery.js +54 -76
  55. package/src/server/conversation/codexHelperThreads.js +94 -93
  56. package/src/server/conversation/codexHistoryAdapter.js +13 -8
  57. package/src/server/conversation/codexIsolation.js +672 -0
  58. package/src/server/conversation/codexLiveItems.js +379 -0
  59. package/src/server/conversation/codexMessageCommands.js +68 -286
  60. package/src/server/conversation/codexMessageDelivery.js +46 -37
  61. package/src/server/conversation/codexModelCatalog.js +86 -7
  62. package/src/server/conversation/codexNotificationObserver.js +81 -88
  63. package/src/server/conversation/codexNotificationQueue.js +21 -10
  64. package/src/server/conversation/codexObservationControl.js +105 -89
  65. package/src/server/conversation/codexOutput.js +84 -419
  66. package/src/server/conversation/codexProcess.js +29 -1053
  67. package/src/server/conversation/codexProcessLeader.js +1 -0
  68. package/src/server/conversation/codexPromptDispatch.js +241 -0
  69. package/src/server/conversation/codexProvider.js +147 -1807
  70. package/src/server/conversation/codexProviderAuth.js +348 -0
  71. package/src/server/conversation/codexProviderInvalidation.js +371 -0
  72. package/src/server/conversation/codexProviderInventories.js +437 -0
  73. package/src/server/conversation/codexProviderOwner.js +149 -957
  74. package/src/server/conversation/codexProviderRelease.js +465 -0
  75. package/src/server/conversation/codexProviderSelection.js +26 -15
  76. package/src/server/conversation/codexProviderThreads.js +698 -0
  77. package/src/server/conversation/codexProviderTurns.js +453 -0
  78. package/src/server/conversation/codexReasoning.js +32 -23
  79. package/src/server/conversation/codexRenewalCommands.js +18 -6
  80. package/src/server/conversation/codexRenewalThreads.js +7 -22
  81. package/src/server/conversation/codexResultSettlement.js +70 -88
  82. package/src/server/conversation/codexRunJournal.js +91 -97
  83. package/src/server/conversation/codexRuntime.js +426 -0
  84. package/src/server/conversation/codexRuntimeInventory.js +113 -0
  85. package/src/server/conversation/codexRuntimeLifecycle.js +39 -13
  86. package/src/server/conversation/codexRuntimeStart.js +431 -0
  87. package/src/server/conversation/codexRuntimeStop.js +324 -0
  88. package/src/server/conversation/codexSessionCleanup.js +25 -10
  89. package/src/server/conversation/codexSessionReconciliation.js +21 -7
  90. package/src/server/conversation/codexThreadReadiness.js +49 -52
  91. package/src/server/conversation/codexThreadSnapshot.js +210 -0
  92. package/src/server/conversation/codexThreadStatus.js +48 -62
  93. package/src/server/conversation/codexTurn.js +171 -55
  94. package/src/server/conversation/codexTurnActivity.js +210 -0
  95. package/src/server/conversation/codexTurnControl.js +44 -48
  96. package/src/server/conversation/codexTurnEndings.js +610 -0
  97. package/src/server/conversation/codexTurnRecovery.js +97 -848
  98. package/src/server/conversation/codexTurnState.js +138 -111
  99. package/src/server/conversation/completedResponses.js +227 -0
  100. package/src/server/conversation/configuration.js +2 -2
  101. package/src/server/conversation/continuity.js +43 -6
  102. package/src/server/conversation/conversationDelivery.js +394 -0
  103. package/src/server/conversation/conversationEntries.js +365 -0
  104. package/src/server/conversation/conversationEvents.js +192 -0
  105. package/src/server/conversation/conversationGoals.js +187 -0
  106. package/src/server/conversation/conversationHandles.js +237 -0
  107. package/src/server/conversation/conversationReads.js +206 -0
  108. package/src/server/conversation/conversationRefusals.js +58 -0
  109. package/src/server/conversation/conversationReplacement.js +254 -0
  110. package/src/server/conversation/conversationSending.js +273 -0
  111. package/src/server/conversation/conversationStopping.js +121 -0
  112. package/src/server/conversation/conversationTurnDelivery.js +413 -0
  113. package/src/server/conversation/conversationTurns.js +511 -0
  114. package/src/server/conversation/deliveryTiming.js +25 -3
  115. package/src/server/conversation/fileStorage.js +11 -6
  116. package/src/server/conversation/hookBridge.js +14 -3
  117. package/src/server/conversation/index.js +1 -1
  118. package/src/server/conversation/localExecution.js +82 -625
  119. package/src/server/conversation/nativeCommandCallbacks.js +10 -0
  120. package/src/server/conversation/nativeConversationLifecycle.js +379 -0
  121. package/src/server/conversation/nativeHistoryExport.js +27 -18
  122. package/src/server/conversation/normalize.js +9 -0
  123. package/src/server/conversation/openCodeClient.js +295 -366
  124. package/src/server/conversation/openCodeNativeHistory.js +141 -0
  125. package/src/server/conversation/openCodeNativeSession.js +79 -0
  126. package/src/server/conversation/openCodeProcess.js +109 -53
  127. package/src/server/conversation/openCodeProcessOwner.js +565 -0
  128. package/src/server/conversation/openCodeRuntime.js +111 -1814
  129. package/src/server/conversation/openCodeSessionTurns.js +547 -0
  130. package/src/server/conversation/openCodeTemporaryConversations.js +394 -0
  131. package/src/server/conversation/openCodeTools.js +28 -14
  132. package/src/server/conversation/openCodeTurn.js +80 -74
  133. package/src/server/conversation/openCodeTurnMonitor.js +388 -0
  134. package/src/server/conversation/processIdentity.js +582 -0
  135. package/src/server/conversation/provider.js +21 -6
  136. package/src/server/conversation/providers/api.js +268 -107
  137. package/src/server/conversation/providers/claude.js +29 -6
  138. package/src/server/conversation/providers/claudeDriver.js +325 -250
  139. package/src/server/conversation/providers/codex.js +69 -33
  140. package/src/server/conversation/providers/codexDriver.js +503 -1081
  141. package/src/server/conversation/providers/codexDriverConnection.js +331 -0
  142. package/src/server/conversation/providers/codexDriverTurns.js +464 -0
  143. package/src/server/conversation/providers/opencode.js +30 -4
  144. package/src/server/conversation/providers/opencodeDriver.js +321 -659
  145. package/src/server/conversation/providers/opencodeStandalone.js +475 -0
  146. package/src/server/conversation/providers/scoped.js +64 -19
  147. package/src/server/conversation/runtime.js +171 -2863
  148. package/src/server/conversation/storage.js +91 -50
  149. package/src/server/conversation/streams.js +15 -2
  150. package/src/server/conversation/structuredOutput.js +7 -11
  151. package/src/server/conversation/systemPrompt.js +13 -1
  152. package/src/server/conversation/tools.js +47 -15
  153. package/src/server/conversation/transcript.js +49 -20
  154. package/src/server/hosts/claudeTurn.js +2 -4
  155. package/src/server/hosts/codexConfiguration.js +6 -8
  156. package/src/server/hosts/codexEvents.js +4 -2
  157. package/src/server/hosts/codexProcess.js +1 -1
  158. package/src/server/hosts/codexProvider.js +11 -9
  159. package/src/server/hosts/codexTurn.js +3 -3
  160. package/src/server/hosts/openCodeClient.js +4 -2
  161. package/src/server/index.js +0 -2
  162. package/src/server/lib/aiConnectionClient.js +35 -25
  163. package/src/server/lib/ndjson.js +1 -0
  164. package/src/server/lib/providers/anthropicClient.js +15 -6
  165. package/src/server/lib/providers/common.js +10 -6
  166. package/src/server/lib/providers/openAiCompatibleClient.js +40 -23
  167. package/src/server/lib/serviceToolCatalog.js +29 -283
  168. package/src/server/lib/serviceToolEntries.js +270 -0
  169. package/src/server/types.js +78 -0
  170. package/src/shared/assistantResource.js +0 -23
  171. package/src/shared/conversation/answerChoiceSugar.js +2 -14
  172. package/src/shared/conversation/messageInputSugar.js +18 -0
  173. package/src/shared/conversation/numberedQuestionSugar.js +6 -16
  174. package/src/shared/conversation/pagination.js +1 -1
  175. package/src/shared/conversation/questions.js +1 -0
  176. package/src/shared/conversation/streaming.js +1 -0
  177. package/src/shared/index.js +0 -1
  178. package/src/testing/conversationStorageContract.js +20 -0
  179. package/test/aiConnectionClient.test.js +42 -1
  180. package/test/apiConversationDriver.test.js +217 -38
  181. package/test/assistantBoundedToolLoop.test.js +1 -1
  182. package/test/claudeConversationSession.test.js +15 -11
  183. package/test/claudeInstructionResume.test.js +1 -1
  184. package/test/claudeProcess.test.js +9 -9
  185. package/test/claudeTurn.test.js +21 -8
  186. package/test/codexAccountChange.test.js +5 -3
  187. package/test/codexConversationSession.test.js +22 -21
  188. package/test/codexEvents.test.js +3 -1
  189. package/test/codexHelperDeletion.test.js +3 -7
  190. package/test/codexIsolation.test.js +6 -6
  191. package/test/codexModelCatalog.test.js +6 -7
  192. package/test/codexProcess.test.js +3 -3
  193. package/test/codexThreadSelection.test.js +4 -1
  194. package/test/codexTurn.test.js +29 -20
  195. package/test/conversationApiFacilities.test.js +11 -9
  196. package/test/conversationAttachments.test.js +3 -3
  197. package/test/conversationConfiguration.test.js +4 -4
  198. package/test/conversationNativeStorage.test.js +11 -10
  199. package/test/conversationReplacement.test.js +13 -13
  200. package/test/conversationRuntime.test.js +9 -5
  201. package/test/conversationSession.test.js +78 -22
  202. package/test/conversationTools.test.js +47 -26
  203. package/test/fileConversationStorage.test.js +10 -3
  204. package/test/fixtures/codexHelperThreadLedger.js +3 -3
  205. package/test/frameworkActions.test.js +33 -18
  206. package/test/localConversationExecution.test.js +4 -4
  207. package/test/localExecution.test.js +1 -1
  208. package/test/openCodeConnectionChange.test.js +3 -2
  209. package/test/openCodeRuntime.test.js +8 -7
  210. package/test/openCodeTools.test.js +1 -1
  211. package/test/opencodeConversationSession.test.js +4 -3
  212. package/test/serviceToolCatalog.test.js +3 -8
  213. package/src/server/lib/assistantCompletion.js +0 -190
  214. package/src/server/lib/assistantToolLoop.js +0 -259
@@ -1,30 +1,37 @@
1
- import {
2
- codexAppServerTurnStatusIsActive,
3
- codexAppServerTurnStatusIsComplete,
4
- codexAppServerTurnStatusIsSuccessfulComplete,
5
- codexAppServerTurnStatusIsProviderFailure,
6
- codexAppServerThreadStatus,
7
- codexAppServerReadThreadStatus,
8
- normalizeCodexRunText,
9
- codexAppServerAgentRun,
10
- codexAppServerPendingUserMessageClientIds,
11
- codexAppServerPendingUserMessageOwnership,
12
- codexAppServerTurnOwnsActiveGoal,
13
- codexAppServerTurnCanReceiveProviderCompletion,
14
- codexAppServerTurnAwaitsProviderIdentity,
15
- codexAppServerTurnCanReceiveProviderActivity,
16
- codexAppServerTurnCanAdoptSuccessor,
17
- codexAppServerTurnKey,
18
- CODEX_APP_SERVER_RUN_STATE
19
- } from "./codexTurnState.js";
20
- import {
21
- codexAppServerProviderTurnForOperation,
22
- codexAppServerThreadTurnId,
23
- codexAppServerThreadError
24
- } from "./codexEvents.js";
1
+ import { codexAppServerTurnStatusIsActive, codexAppServerTurnStatusIsComplete, codexAppServerTurnStatusIsSuccessfulComplete, codexAppServerTurnStatusIsProviderFailure, codexAppServerThreadStatus, codexAppServerReadThreadStatus, codexAppServerAgentRun, codexAppServerPendingUserMessageOwnership, codexAppServerTurnCanAdoptSuccessor } from "./codexTurnState.js";
2
+ import { codexAppServerProviderTurnForOperation, codexAppServerThreadTurnId, codexAppServerThreadError } from "./codexThreadSnapshot.js";
3
+ import { createCodexTurnActivity } from "./codexTurnActivity.js";
4
+ import { createCodexTurnEndings } from "./codexTurnEndings.js";
5
+ import { normalizeText } from "@jskit-ai/kernel/shared/support/normalize";
25
6
 
26
- // Original active/finalizing recovery, provider activity and completion decisions.
27
- // Timers and receipts remain owned by the shared journal/output/settlement instances.
7
+ /**
8
+ * Active and finalizing turn recovery: the timers that reread the provider, and the reconciliation of the tracked
9
+ * turn with what the provider reports. Timers and receipts remain owned by the shared journal, output and settlement.
10
+ * @param {object} dependencies
11
+ * @param {import("./codexRunJournal.js").CodexRunJournal} dependencies.journal
12
+ * @param {import("./codexOutput.js").CodexAppServerOutput} dependencies.output
13
+ * @param {import("./codexResultSettlement.js").CodexResultSettlement} dependencies.settlement
14
+ * @param {Set<string>} dependencies.promptDeliveries
15
+ * @param {import("./codexTurn.js").CodexSessionNamespace} dependencies.namespace
16
+ * @param {import("./codexTurn.js").CodexCreateRuntime} dependencies.createRuntime
17
+ * @param {import("./codexTurn.js").CodexCreateStore} dependencies.createStore
18
+ * @param {(input: { sessionId: string, runtime: any, session: object }) => Promise<any>} dependencies.acquireProvider
19
+ * @param {import("./codexTurn.js").CodexTurnStateReader} dependencies.turnState
20
+ * @param {import("./codexTurn.js").CodexAgentRunTurnStateReader} dependencies.turnStateFromAgentRun
21
+ * @param {import("./codexTurn.js").CodexDebugLog} dependencies.debugLog
22
+ * @param {import("./codexTurn.js").CodexDebugError} dependencies.debugError
23
+ * @param {import("./codexTurn.js").CodexCaptureContext} dependencies.captureContext
24
+ * @param {import("./codexTurn.js").CodexRunInContext} dependencies.runInContext
25
+ * @param {number} dependencies.activeReconcileMs
26
+ * @param {boolean} dependencies.finalizingGraceAfterHistoryRead
27
+ * @param {number} dependencies.failureDetailGraceMs
28
+ * @param {import("./codexNotificationQueue.js").CodexAppServerNotificationQueue} dependencies.notificationQueue
29
+ * @param {(session: object) => boolean} dependencies.hasRuntime
30
+ * @param {import("./codexTurn.js").CodexAdmissionRecovery} dependencies.recoverAdmission
31
+ * @param {import("./codexTurn.js").CodexTurnOutcomes} dependencies.turnOutcomes
32
+ * @param {(...details: any[]) => Promise<unknown>} dependencies.outcomeNotice
33
+ * @param {(details: { error?: string }) => string} dependencies.resultDeliveryFailureMessage
34
+ */
28
35
  export function createCodexTurnRecovery({
29
36
  journal,
30
37
  output,
@@ -50,135 +57,52 @@ export function createCodexTurnRecovery({
50
57
  outcomeNotice,
51
58
  resultDeliveryFailureMessage
52
59
  }) {
53
- const {
54
- finalizedTurns: codexAppServerFinalizedTurns,
55
- resultFinalizationKey: codexAppServerResultFinalizationKey,
56
- readAgentRunForSession: readCodexAppServerAgentRunForSession,
57
- submitAssistantResult: submitCodexAppServerAssistantResult,
58
- cleanupUntrackedTurn: cleanupCodexAppServerUntrackedTurn
59
- } = output;
60
- const {
61
- completedTurns: codexAppServerCompletedTurns,
62
- activeTimers: codexAppServerActiveTimers,
63
- finalizingTimers: codexAppServerFinalizingTimers,
64
- turnResultWasProcessed: codexAppServerTurnResultWasProcessed,
65
- clearActiveTimer: clearCodexAppServerActiveTimer,
66
- clearFinalizingTimer: clearCodexAppServerFinalizingTimer,
67
- publishAgentRun: publishCodexAppServerAgentRun,
68
- markTurnFinalizing: markCodexAppServerTurnFinalizing,
69
- markTurnIdle: markCodexAppServerTurnIdle,
70
- resolveTurnId: resolveCodexAppServerTurnId,
71
- finalizingExpired: codexAppServerFinalizingExpired,
72
- finalizingRemainingMs: codexAppServerFinalizingRemainingMs,
73
- turnWasCompleted: codexAppServerTurnWasCompleted,
74
- runInputSource: codexAppServerRunInputSource,
75
- writeUserMessageOwnership: writeCodexAppServerUserMessageOwnership,
76
- recoverAbandonedPromptClaim: recoverAbandonedCodexAppServerPromptClaim,
77
- failOrphanedPromptDelivery: failOrphanedCodexAppServerPromptDelivery
78
- } = journal;
79
- const {
80
- finalizeAssistantResult: finalizeCodexAppServerAssistantResult,
81
- recoverFinalResponseBeforeOutcome: recoverCodexAppServerFinalResponseBeforeOutcome,
82
- adoptSuccessorTurn: adoptCodexAppServerSuccessorTurn
83
- } = settlement;
84
- const codexAppServerPromptDeliveries = promptDeliveries;
85
- const retainFinalizingTimer = finalizingGraceAfterHistoryRead || failureDetailGraceMs > 0;
60
+ const activity = createCodexTurnActivity({
61
+ journal,
62
+ output,
63
+ settlement,
64
+ createRuntime,
65
+ createStore,
66
+ debugLog,
67
+ turnState,
68
+ turnStateFromAgentRun,
69
+ scheduleActiveRecovery: scheduleCodexAppServerActiveRecovery
70
+ });
71
+ const endings = createCodexTurnEndings({
72
+ journal,
73
+ output,
74
+ settlement,
75
+ createRuntime,
76
+ debugLog,
77
+ debugError,
78
+ turnState,
79
+ failureDetailGraceMs,
80
+ turnOutcomes,
81
+ outcomeNotice,
82
+ resultDeliveryFailureMessage,
83
+ captureContext,
84
+ runInContext,
85
+ notificationQueue,
86
+ finalizingGraceAfterHistoryRead,
87
+ scheduleActiveRecovery: scheduleCodexAppServerActiveRecovery
88
+ });
86
89
 
87
90
  function scheduleCodexAppServerActiveRecovery(sessionId = "", delayMs = activeReconcileMs) {
88
- const normalizedSessionId = normalizeCodexRunText(sessionId);
91
+ const normalizedSessionId = normalizeText(sessionId);
89
92
  const sessionKey = namespace(normalizedSessionId);
90
93
  const projectContext = captureContext();
91
- if (!normalizedSessionId || codexAppServerActiveTimers.has(sessionKey)) {
94
+ if (!normalizedSessionId || journal.activeTimers.has(sessionKey)) {
92
95
  return;
93
96
  }
94
97
  const timer = setTimeout(() => {
95
- codexAppServerActiveTimers.delete(sessionKey);
98
+ journal.activeTimers.delete(sessionKey);
96
99
  void runInContext(
97
100
  projectContext,
98
101
  () => recoverCodexAppServerActiveTurn(normalizedSessionId)
99
102
  );
100
103
  }, delayMs);
101
- timer.unref?.();
102
- codexAppServerActiveTimers.set(sessionKey, timer);
103
- }
104
-
105
- function scheduleCodexAppServerFinalizingRecovery(sessionId = "", threadId = "", turnId = "", {
106
- completedAt = "",
107
- status = "completed",
108
- updatedAt = "",
109
- failureDetail = null
110
- } = {}) {
111
- const normalizedSessionId = normalizeCodexRunText(sessionId);
112
- const normalizedThreadId = normalizeCodexRunText(threadId);
113
- const normalizedTurnId = normalizeCodexRunText(turnId);
114
- if (!normalizedSessionId || !normalizedThreadId || !normalizedTurnId) {
115
- return;
116
- }
117
- clearCodexAppServerFinalizingTimer(normalizedSessionId, normalizedThreadId, normalizedTurnId);
118
- const key = codexAppServerResultFinalizationKey(
119
- normalizedSessionId,
120
- normalizedThreadId,
121
- normalizedTurnId
122
- );
123
- const projectContext = captureContext();
124
- const delayMs = failureDetail
125
- ? Math.max(0, failureDetail.expiresAt - Date.now())
126
- : codexAppServerFinalizingRemainingMs({ completedAt, state: "finalizing", updatedAt });
127
- // One record per pending deadline: `recovery` is the in-flight expiry
128
- // (retained-timer policy only), `expiresAt` the first deadline that a later
129
- // failure-detail grace may not exceed, `failureDetail` a deferred provider
130
- // failure, and `startedAt` the post-read clock that rereads must keep.
131
- const entry = {
132
- timer: null,
133
- recovery: null,
134
- expiresAt: failureDetailGraceMs > 0 ? Date.now() + delayMs : null,
135
- failureDetail,
136
- startedAt: !failureDetail && finalizingGraceAfterHistoryRead ? completedAt : ""
137
- };
138
- entry.timer = setTimeout(() => {
139
- if (failureDetail) {
140
- notificationQueue.run({ projectContext, provider: failureDetail.provider, sessionId: normalizedSessionId }, async () => {
141
- if (codexAppServerFinalizingTimers.get(key) !== entry) return;
142
- try {
143
- return await stopCodexAppServerTurnWithProviderFailure(normalizedSessionId, normalizedThreadId, normalizedTurnId,
144
- { ...failureDetail, failureDetailReady: true });
145
- } finally {
146
- if (codexAppServerFinalizingTimers.get(key) === entry) {
147
- clearCodexAppServerFinalizingTimer(normalizedSessionId, normalizedThreadId, normalizedTurnId);
148
- }
149
- }
150
- });
151
- return;
152
- }
153
- // The post-read policy keeps its first clock while expiry rereads history.
154
- if (!retainFinalizingTimer) codexAppServerFinalizingTimers.delete(key);
155
- const recovery = runInContext(
156
- projectContext,
157
- () => recoverCodexAppServerFinalizingTurn(
158
- normalizedSessionId,
159
- normalizedThreadId,
160
- normalizedTurnId,
161
- { status, timerRecovery: finalizingGraceAfterHistoryRead }
162
- )
163
- );
164
- if (retainFinalizingTimer) {
165
- entry.recovery = recovery;
166
- void recovery.then(
167
- () => { if (entry.recovery === recovery) entry.recovery = null; },
168
- error => {
169
- if (entry.recovery === recovery) entry.recovery = null;
170
- debugLog("appServerFinalizingRecovery.error", {
171
- error: debugError(error), sessionId: normalizedSessionId,
172
- threadId: normalizedThreadId, turnId: normalizedTurnId
173
- });
174
- }
175
- );
176
- } else {
177
- void recovery;
178
- }
179
- }, delayMs);
180
- entry.timer.unref?.();
181
- codexAppServerFinalizingTimers.set(key, entry);
104
+ timer.unref();
105
+ journal.activeTimers.set(sessionKey, timer);
182
106
  }
183
107
 
184
108
  async function reconcileCodexAppServerActiveTurn(session = {}, {
@@ -186,15 +110,15 @@ export function createCodexTurnRecovery({
186
110
  runtime = null,
187
111
  providerError = null
188
112
  } = {}) {
189
- const sessionId = normalizeCodexRunText(session.sessionId);
113
+ const sessionId = normalizeText(session.sessionId);
190
114
  const trackedTurn = turnState(session);
191
115
  if (!sessionId || !trackedTurn.active || !trackedTurn.threadId || !hasRuntime(session)) {
192
116
  return session;
193
117
  }
194
118
  const provider = suppliedProvider || await acquireProvider({ sessionId, runtime, session });
195
- if (typeof provider?.readThreadStatus !== "function") {
119
+ if (!provider) {
196
120
  if (trackedTurn.state === "finalizing") {
197
- await recoverCodexAppServerFinalizingTurn(
121
+ await endings.recoverFinalizingTurn(
198
122
  sessionId,
199
123
  trackedTurn.threadId,
200
124
  trackedTurn.turnId,
@@ -213,7 +137,7 @@ export function createCodexTurnRecovery({
213
137
  let thread = await codexAppServerReadThreadStatus(provider, trackedTurn.threadId);
214
138
  let status = codexAppServerThreadStatus(thread);
215
139
  let providerTurnId = codexAppServerThreadTurnId(thread);
216
- const promptDeliveryIsLocal = codexAppServerPromptDeliveries.has(
140
+ const promptDeliveryIsLocal = promptDeliveries.has(
217
141
  namespace(sessionId)
218
142
  );
219
143
  if (
@@ -224,8 +148,8 @@ export function createCodexTurnRecovery({
224
148
  const ownership = codexAppServerPendingUserMessageOwnership(
225
149
  codexAppServerAgentRun(currentSession)
226
150
  );
227
- if (!ownership || typeof provider?.readThread !== "function") {
228
- return failOrphanedCodexAppServerPromptDelivery(
151
+ if (!ownership) {
152
+ return journal.failOrphanedPromptDelivery(
229
153
  activeRuntime,
230
154
  currentSession,
231
155
  currentTurn
@@ -236,7 +160,7 @@ export function createCodexTurnRecovery({
236
160
  clientMessageId: ownership.clientId
237
161
  });
238
162
  if (!ownedProviderTurn) {
239
- return failOrphanedCodexAppServerPromptDelivery(
163
+ return journal.failOrphanedPromptDelivery(
240
164
  activeRuntime,
241
165
  currentSession,
242
166
  currentTurn
@@ -249,20 +173,20 @@ export function createCodexTurnRecovery({
249
173
  status = codexAppServerThreadStatus(thread);
250
174
  providerTurnId = codexAppServerThreadTurnId(thread);
251
175
  if (!providerTurnId) {
252
- return failOrphanedCodexAppServerPromptDelivery(
176
+ return journal.failOrphanedPromptDelivery(
253
177
  activeRuntime,
254
178
  currentSession,
255
179
  currentTurn
256
180
  );
257
181
  }
258
- await markCodexAppServerProviderTurnActive(sessionId, {
182
+ await activity.markProviderTurnActive(sessionId, {
259
183
  inputSource: ownership.inputSource,
260
184
  source: "owned_prompt_recovery",
261
185
  status: codexAppServerTurnStatusIsActive(status) ? status : "inProgress",
262
186
  threadId: currentTurn.threadId,
263
187
  turnId: providerTurnId
264
188
  });
265
- await writeCodexAppServerUserMessageOwnership(
189
+ await journal.writeUserMessageOwnership(
266
190
  activeRuntime.store,
267
191
  sessionId,
268
192
  ownership.clientId,
@@ -285,7 +209,7 @@ export function createCodexTurnRecovery({
285
209
  providerTurnId &&
286
210
  codexAppServerTurnCanAdoptSuccessor(currentTurn, currentTurn.threadId, providerTurnId)
287
211
  ) {
288
- const adoption = await adoptCodexAppServerSuccessorTurn(sessionId, {
212
+ const adoption = await settlement.adoptSuccessorTurn(sessionId, {
289
213
  previousTurnId: currentTurn.turnId,
290
214
  source: "active_reconciliation",
291
215
  status: "inProgress",
@@ -302,14 +226,14 @@ export function createCodexTurnRecovery({
302
226
  }
303
227
  }
304
228
  if (currentTurn.state === "finalizing") {
305
- await recoverCodexAppServerFinalizingTurn(sessionId, currentTurn.threadId, currentTurn.turnId, {
229
+ await endings.recoverFinalizingTurn(sessionId, currentTurn.threadId, currentTurn.turnId, {
306
230
  status: currentTurn.status || "completed"
307
231
  });
308
232
  return activeRuntime.getSession(sessionId);
309
233
  }
310
234
  if (currentTurn.state === "starting") {
311
235
  if (codexAppServerTurnStatusIsActive(status) && providerTurnId) {
312
- await markCodexAppServerProviderTurnActive(sessionId, {
236
+ await activity.markProviderTurnActive(sessionId, {
313
237
  source: "active_reconciliation",
314
238
  status,
315
239
  threadId: currentTurn.threadId,
@@ -326,9 +250,9 @@ export function createCodexTurnRecovery({
326
250
  return currentSession;
327
251
  }
328
252
  if (!providerTurnId || !codexAppServerTurnStatusIsComplete(status)) {
329
- return failOrphanedCodexAppServerPromptDelivery(activeRuntime, currentSession, currentTurn);
253
+ return journal.failOrphanedPromptDelivery(activeRuntime, currentSession, currentTurn);
330
254
  }
331
- await markCodexAppServerProviderTurnActive(sessionId, {
255
+ await activity.markProviderTurnActive(sessionId, {
332
256
  source: "active_reconciliation",
333
257
  status: "inProgress",
334
258
  threadId: currentTurn.threadId,
@@ -355,14 +279,14 @@ export function createCodexTurnRecovery({
355
279
  });
356
280
  if (codexAppServerTurnStatusIsProviderFailure(status)) {
357
281
  const exactError = providerError?.threadId === currentTurn.threadId && providerError?.turnId === completedTurnId;
358
- await stopCodexAppServerTurnWithProviderFailure(sessionId, currentTurn.threadId, completedTurnId, {
282
+ await endings.stopTurnWithProviderFailure(sessionId, currentTurn.threadId, completedTurnId, {
359
283
  error: exactError ? providerError.error : codexAppServerThreadError(thread),
360
284
  failureDetailReady: exactError,
361
285
  provider,
362
286
  status
363
287
  });
364
288
  } else if (codexAppServerTurnStatusIsSuccessfulComplete(status)) {
365
- await completeCodexAppServerTurn(sessionId, currentTurn.threadId, completedTurnId, {
289
+ await endings.completeTurn(sessionId, currentTurn.threadId, completedTurnId, {
366
290
  status,
367
291
  verifyInactive: false
368
292
  });
@@ -370,338 +294,24 @@ export function createCodexTurnRecovery({
370
294
  return activeRuntime.getSession(sessionId);
371
295
  }
372
296
 
373
- async function codexAppServerProviderBlocksTurnRelease(sessionId = "", provider = null, threadId = "", turnId = "", {
374
- source = ""
375
- } = {}) {
376
- const normalizedSessionId = normalizeCodexRunText(sessionId);
377
- const normalizedThreadId = normalizeCodexRunText(threadId);
378
- const normalizedTurnId = normalizeCodexRunText(turnId);
379
- if (!normalizedSessionId || !normalizedThreadId) {
380
- return false;
381
- }
382
- const runtime = await createRuntime();
383
- const session = await runtime.getSession(normalizedSessionId);
384
- const turn = turnState(session);
385
- if (!turn.active || !codexAppServerTurnCanReceiveProviderActivity(turn, normalizedThreadId, normalizedTurnId)) {
386
- return false;
387
- }
388
- try {
389
- const providerThread = await codexAppServerReadThreadStatus(provider, normalizedThreadId, {
390
- observeLatestTurn: true
391
- });
392
- if (!providerThread) {
393
- return false;
394
- }
395
- const status = codexAppServerThreadStatus(providerThread);
396
- const providerTurnId = codexAppServerThreadTurnId(providerThread);
397
- if (
398
- providerTurnId &&
399
- providerTurnId !== normalizeCodexRunText(turn.turnId)
400
- ) {
401
- debugLog("appServerTurn.releaseAllowedSuccessor", {
402
- providerTurnId,
403
- sessionId: normalizedSessionId,
404
- source: normalizeCodexRunText(source),
405
- threadId: normalizedThreadId,
406
- turnId: turn.turnId
407
- });
408
- return false;
409
- }
410
- if (!codexAppServerTurnStatusIsActive(status)) {
411
- return false;
412
- }
413
- debugLog("appServerTurn.releaseBlockedActive", {
414
- sessionId: normalizedSessionId,
415
- source: normalizeCodexRunText(source),
416
- status,
417
- threadId: normalizedThreadId,
418
- turnId: normalizedTurnId || turn.turnId
419
- });
420
- scheduleCodexAppServerActiveRecovery(normalizedSessionId);
421
- return true;
422
- } catch (error) {
423
- debugLog("appServerTurn.releaseCheck.error", {
424
- error: debugError(error),
425
- sessionId: normalizedSessionId,
426
- source: normalizeCodexRunText(source),
427
- threadId: normalizedThreadId,
428
- turnId: normalizedTurnId || turn.turnId
429
- });
430
- return false;
431
- }
432
- }
433
-
434
- async function markCodexAppServerTurnActive(sessionId = "", input = {}) {
435
- if (input.requireTrackedTurn === true) {
436
- const runtime = await createRuntime();
437
- const session = await runtime.getSession(sessionId);
438
- const turn = turnState(session);
439
- if (!codexAppServerTurnCanReceiveProviderActivity(turn, input.threadId, input.turnId)) {
440
- debugLog("appServerTurn.active.ignored", {
441
- currentState: turn.state,
442
- currentStatus: turn.status,
443
- currentThreadId: turn.threadId,
444
- currentTurnId: turn.turnId,
445
- sessionId: normalizeCodexRunText(sessionId),
446
- threadId: normalizeCodexRunText(input.threadId),
447
- turnId: normalizeCodexRunText(input.turnId)
448
- });
449
- return {
450
- ok: true,
451
- processed: false,
452
- reason: "untracked_terminal_turn"
453
- };
454
- }
455
- }
456
- const status = normalizeCodexRunText(input.status) || "inProgress";
457
- const result = await publishCodexAppServerAgentRun(sessionId, {
458
- inputSource: normalizeCodexRunText(input.inputSource),
459
- phase: input.phase,
460
- publishReason: "codex-app-server-turn-active",
461
- observedRun: input.observedRun,
462
- runState: status === "starting" ? CODEX_APP_SERVER_RUN_STATE.STARTING : CODEX_APP_SERVER_RUN_STATE.ACTIVE,
463
- status,
464
- threadId: normalizeCodexRunText(input.threadId),
465
- turnId: normalizeCodexRunText(input.turnId)
466
- });
467
- if (status === "starting") {
468
- scheduleCodexAppServerActiveRecovery(sessionId);
469
- } else {
470
- clearCodexAppServerActiveTimer(sessionId);
471
- }
472
- return result;
473
- }
474
-
475
- async function markCodexAppServerProviderTurnActive(sessionId = "", input = {}) {
476
- const normalizedSessionId = normalizeCodexRunText(sessionId);
477
- const normalizedThreadId = normalizeCodexRunText(input.threadId);
478
- const normalizedTurnId = normalizeCodexRunText(input.turnId);
479
- const requestedStatus = normalizeCodexRunText(input.status) || "inProgress";
480
- const store = await createStore(normalizedSessionId);
481
- const persistedRun = await readCodexAppServerAgentRunForSession(
482
- store,
483
- normalizedSessionId
484
- );
485
- const persistedTurn = turnStateFromAgentRun(persistedRun || {});
486
- const requestedInputSource = normalizeCodexRunText(input.inputSource);
487
- if (
488
- persistedTurn.active &&
489
- persistedTurn.state === "active" &&
490
- persistedTurn.threadId === normalizedThreadId &&
491
- persistedTurn.turnId === normalizedTurnId &&
492
- codexAppServerTurnStatusIsActive(requestedStatus) &&
493
- (!requestedInputSource || requestedInputSource === persistedTurn.inputSource)
494
- ) {
495
- clearCodexAppServerActiveTimer(normalizedSessionId);
496
- return {
497
- ok: true,
498
- processed: false,
499
- reason: "already_active"
500
- };
501
- }
502
- const runtime = await createRuntime();
503
- let session = await runtime.getSession(normalizedSessionId);
504
- let turn = turnState(session);
505
- if (
506
- input.observedRun &&
507
- [CODEX_APP_SERVER_RUN_STATE.FAILED, CODEX_APP_SERVER_RUN_STATE.INTERRUPTED].includes(turn.runState) &&
508
- turn.threadId === normalizedThreadId &&
509
- turn.turnId === normalizedTurnId &&
510
- !codexAppServerTurnResultWasProcessed(session, normalizedThreadId, normalizedTurnId)
511
- ) {
512
- const recovered = await markCodexAppServerTurnActive(normalizedSessionId, {
513
- inputSource: turn.inputSource,
514
- observedRun: input.observedRun,
515
- status: requestedStatus,
516
- threadId: normalizedThreadId,
517
- turnId: normalizedTurnId
518
- });
519
- if (recovered.processed !== false) {
520
- codexAppServerCompletedTurns.delete(codexAppServerTurnKey(normalizedThreadId, normalizedTurnId));
521
- codexAppServerFinalizedTurns.delete(codexAppServerResultFinalizationKey(
522
- normalizedSessionId, normalizedThreadId, normalizedTurnId
523
- ));
524
- }
525
- return recovered;
526
- }
527
- if (codexAppServerTurnWasCompleted(session, normalizedThreadId, normalizedTurnId)) {
528
- debugLog("appServerProviderTurn.completed.ignored", {
529
- currentState: turn.state,
530
- currentThreadId: turn.threadId,
531
- currentTurnId: turn.turnId,
532
- sessionId: normalizedSessionId,
533
- threadId: normalizedThreadId,
534
- turnId: normalizedTurnId
535
- });
536
- return {
537
- ok: true,
538
- processed: false,
539
- reason: "completed_turn"
540
- };
541
- }
542
- if (
543
- turn.state === "finalizing" &&
544
- codexAppServerTurnCanAdoptSuccessor(turn, normalizedThreadId, normalizedTurnId) &&
545
- codexAppServerTurnResultWasProcessed(session, normalizedThreadId, turn.turnId)
546
- ) {
547
- await finalizeCodexAppServerAssistantResult(
548
- normalizedSessionId,
549
- normalizedThreadId,
550
- turn.turnId,
551
- {
552
- status: turn.status || "completed"
553
- }
554
- );
555
- session = await runtime.getSession(normalizedSessionId);
556
- turn = turnState(session);
557
- }
558
- const currentRun = codexAppServerAgentRun(session);
559
- const pendingInputSource = codexAppServerPendingUserMessageClientIds(currentRun).length > 0
560
- ? codexAppServerRunInputSource(currentRun)
561
- : "";
562
- const goalInputSource = codexAppServerTurnOwnsActiveGoal(turn, normalizedThreadId)
563
- ? codexAppServerRunInputSource(currentRun)
564
- : "";
565
- const inputSource = normalizeCodexRunText(input.inputSource) || pendingInputSource || goalInputSource;
566
- if (codexAppServerTurnCanReceiveProviderActivity(turn, normalizedThreadId, normalizedTurnId)) {
567
- return markCodexAppServerTurnActive(normalizedSessionId, {
568
- inputSource,
569
- status: requestedStatus,
570
- threadId: normalizedThreadId,
571
- turnId: normalizedTurnId
572
- });
573
- }
574
- if (codexAppServerTurnCanAdoptSuccessor(turn, normalizedThreadId, normalizedTurnId)) {
575
- return adoptCodexAppServerSuccessorTurn(normalizedSessionId, {
576
- previousTurnId: turn.turnId,
577
- source: normalizeCodexRunText(input.source) || "provider_activity",
578
- status: requestedStatus,
579
- threadId: normalizedThreadId,
580
- turnId: normalizedTurnId
581
- });
582
- }
583
- if (turn.active || !normalizedTurnId) {
584
- debugLog("appServerProviderTurn.active.ignored", {
585
- currentState: turn.state,
586
- currentStatus: turn.status,
587
- currentThreadId: turn.threadId,
588
- currentTurnId: turn.turnId,
589
- sessionId: normalizedSessionId,
590
- threadId: normalizedThreadId,
591
- turnId: normalizedTurnId
592
- });
593
- return {
594
- ok: true,
595
- processed: false,
596
- reason: turn.active ? "active_turn_mismatch" : "missing_turn"
597
- };
598
- }
599
- return markCodexAppServerTurnActive(normalizedSessionId, {
600
- inputSource: inputSource || "terminal",
601
- status: requestedStatus,
602
- threadId: normalizedThreadId,
603
- turnId: normalizedTurnId
604
- });
605
- }
606
-
607
- async function recoverCodexAppServerFinalizingTurn(sessionId = "", threadId = "", turnId = "", {
608
- status = "completed", timerRecovery = false
609
- } = {}) {
610
- const normalizedSessionId = normalizeCodexRunText(sessionId);
611
- const normalizedThreadId = normalizeCodexRunText(threadId);
612
- const normalizedTurnId = normalizeCodexRunText(turnId);
613
- const key = codexAppServerResultFinalizationKey(normalizedSessionId, normalizedThreadId, normalizedTurnId);
614
- const priorTimer = retainFinalizingTimer ? codexAppServerFinalizingTimers.get(key) : null;
615
- if (!timerRecovery && priorTimer?.recovery) return priorTimer.recovery;
616
- const runtime = await createRuntime();
617
- const session = await runtime.getSession(normalizedSessionId);
618
- const turn = turnState(session);
619
- if (codexAppServerTurnOwnsActiveGoal(turn, normalizedThreadId)) {
620
- clearCodexAppServerFinalizingTimer(
621
- normalizedSessionId,
622
- normalizedThreadId,
623
- normalizedTurnId
624
- );
625
- return {
626
- ok: true,
627
- processed: false,
628
- reason: "goal_continuation_pending"
629
- };
630
- }
631
- const result = await finalizeCodexAppServerAssistantResult(
632
- normalizedSessionId,
633
- normalizedThreadId,
634
- normalizedTurnId,
635
- {
636
- recoverFromProvider: true,
637
- status
638
- }
639
- );
640
- if (result?.processed) {
641
- return result;
642
- }
643
- // A later failure owns the same first deadline, even when this successful
644
- // history read was already in flight under the completion-based clock.
645
- const currentTimer = codexAppServerFinalizingTimers.get(key);
646
- if (currentTimer?.failureDetail) return result;
647
- if (retainFinalizingTimer && priorTimer && !currentTimer) return result;
648
- let graceTurn = turn;
649
- if (finalizingGraceAfterHistoryRead) {
650
- const readReturnedAt = new Date().toISOString();
651
- const currentTurn = turnState(await runtime.getSession(normalizedSessionId));
652
- const timer = codexAppServerFinalizingTimers.get(key);
653
- if (timer?.failureDetail || (priorTimer && !timer)) return result;
654
- if (currentTurn.state !== "finalizing" || currentTurn.threadId !== normalizedThreadId ||
655
- currentTurn.turnId !== normalizedTurnId) {
656
- clearCodexAppServerFinalizingTimer(normalizedSessionId, normalizedThreadId, normalizedTurnId);
657
- return { ...result, reason: "stale_turn_state" };
658
- }
659
- if (codexAppServerTurnOwnsActiveGoal(currentTurn, normalizedThreadId)) {
660
- clearCodexAppServerFinalizingTimer(normalizedSessionId, normalizedThreadId, normalizedTurnId);
661
- return { ok: true, processed: false, reason: "goal_continuation_pending" };
662
- }
663
- if (!timerRecovery && timer?.recovery) return timer.recovery;
664
- // This local clock never changes the native completion timestamp.
665
- graceTurn = { ...currentTurn, completedAt: timer?.startedAt || readReturnedAt };
666
- }
667
- if (!codexAppServerFinalizingExpired(graceTurn)) {
668
- scheduleCodexAppServerFinalizingRecovery(normalizedSessionId, normalizedThreadId, normalizedTurnId, {
669
- completedAt: graceTurn.completedAt,
670
- status,
671
- updatedAt: graceTurn.updatedAt
672
- });
673
- return result;
674
- }
675
- return stopCodexAppServerTurnWithResultDeliveryFailure(
676
- normalizedSessionId,
677
- normalizedThreadId,
678
- normalizedTurnId,
679
- {
680
- error: result?.error,
681
- reason: result?.reason || "missing_assistant_text",
682
- status
683
- }
684
- );
685
- }
686
-
687
297
  async function recoverCodexAppServerActiveTurn(sessionId = "", {
688
298
  provider = null,
689
299
  retryOnError = true,
690
300
  runtime: suppliedRuntime = null,
691
301
  providerError = null
692
302
  } = {}) {
693
- const normalizedSessionId = normalizeCodexRunText(sessionId);
303
+ const normalizedSessionId = normalizeText(sessionId);
694
304
  if (!normalizedSessionId) {
695
305
  return null;
696
306
  }
697
307
  try {
698
308
  const runtime = suppliedRuntime || await createRuntime();
699
309
  const storedSession = await runtime.getSession(normalizedSessionId);
700
- const abandonedClaim = await (recoverAdmission || recoverAbandonedCodexAppServerPromptClaim)(
310
+ const abandonedClaim = await recoverAdmission(
701
311
  runtime,
702
312
  storedSession,
703
313
  {
704
- promptDeliveryActive: codexAppServerPromptDeliveries.has(
314
+ promptDeliveryActive: promptDeliveries.has(
705
315
  namespace(normalizedSessionId)
706
316
  )
707
317
  }
@@ -736,7 +346,7 @@ export function createCodexTurnRecovery({
736
346
  .then(() => createStore(normalizedSessionId))
737
347
  .catch(() => null);
738
348
  const run = store
739
- ? await readCodexAppServerAgentRunForSession(store, normalizedSessionId).catch(() => null)
349
+ ? await output.readAgentRunForSession(store, normalizedSessionId).catch(() => null)
740
350
  : null;
741
351
  if (turnStateFromAgentRun(run || {}).state === "starting") {
742
352
  scheduleCodexAppServerActiveRecovery(normalizedSessionId);
@@ -745,376 +355,15 @@ export function createCodexTurnRecovery({
745
355
  }
746
356
  }
747
357
 
748
- async function completeCodexAppServerTurn(sessionId = "", threadId = "", turnId = "", {
749
- provider = null,
750
- status = "completed",
751
- verifyInactive = true
752
- } = {}) {
753
- const normalizedSessionId = normalizeCodexRunText(sessionId);
754
- const normalizedThreadId = normalizeCodexRunText(threadId);
755
- const normalizedTurnId = normalizeCodexRunText(turnId);
756
- const normalizedStatus = normalizeCodexRunText(status) || "completed";
757
- const runtime = await createRuntime();
758
- const session = await runtime.getSession(normalizedSessionId);
759
- const existingTurn = turnState(session);
760
- const continuesOwnedGoal = codexAppServerTurnOwnsActiveGoal(
761
- existingTurn,
762
- normalizedThreadId
763
- );
764
- if (codexAppServerTurnAwaitsProviderIdentity(existingTurn, normalizedThreadId, normalizedTurnId)) {
765
- return {
766
- ok: true,
767
- processed: false,
768
- reason: "turn_identity_pending"
769
- };
770
- }
771
- if (!normalizedTurnId) {
772
- if (!codexAppServerTurnCanReceiveProviderCompletion(existingTurn, normalizedThreadId, "")) {
773
- cleanupCodexAppServerUntrackedTurn(normalizedThreadId, normalizedTurnId);
774
- debugLog("appServerTurn.complete.stale", {
775
- currentState: existingTurn.state,
776
- currentStatus: existingTurn.status,
777
- currentThreadId: existingTurn.threadId,
778
- currentTurnId: existingTurn.turnId,
779
- sessionId: normalizedSessionId,
780
- status: normalizedStatus,
781
- threadId: normalizedThreadId,
782
- turnId: normalizedTurnId
783
- });
784
- return {
785
- ok: true,
786
- processed: false,
787
- reason: "stale_turn_state"
788
- };
789
- }
790
- if (verifyInactive && await codexAppServerProviderBlocksTurnRelease(normalizedSessionId, provider, normalizedThreadId, normalizedTurnId, {
791
- source: "complete_missing_turn"
792
- })) {
793
- return {
794
- ok: true,
795
- processed: false,
796
- reason: "provider_still_active",
797
- status: "inProgress"
798
- };
799
- }
800
- await markCodexAppServerTurnIdle(normalizedSessionId, {
801
- status: normalizedStatus,
802
- threadId: normalizedThreadId,
803
- turnId: normalizedTurnId
804
- });
805
- return {
806
- ok: true,
807
- processed: false,
808
- reason: "missing_turn"
809
- };
810
- }
811
- if (!codexAppServerTurnCanReceiveProviderCompletion(existingTurn, normalizedThreadId, normalizedTurnId)) {
812
- codexAppServerFinalizedTurns.add(codexAppServerResultFinalizationKey(
813
- normalizedSessionId,
814
- normalizedThreadId,
815
- normalizedTurnId
816
- ));
817
- cleanupCodexAppServerUntrackedTurn(normalizedThreadId, normalizedTurnId);
818
- debugLog("appServerTurn.complete.stale", {
819
- currentState: existingTurn.state,
820
- currentStatus: existingTurn.status,
821
- currentThreadId: existingTurn.threadId,
822
- currentTurnId: existingTurn.turnId,
823
- sessionId: normalizedSessionId,
824
- status: normalizedStatus,
825
- threadId: normalizedThreadId,
826
- turnId: normalizedTurnId
827
- });
828
- return {
829
- ok: true,
830
- processed: false,
831
- reason: "stale_turn_state"
832
- };
833
- }
834
- if (verifyInactive && await codexAppServerProviderBlocksTurnRelease(normalizedSessionId, provider, normalizedThreadId, normalizedTurnId, {
835
- source: "complete"
836
- })) {
837
- return {
838
- ok: true,
839
- processed: false,
840
- reason: "provider_still_active",
841
- status: "inProgress"
842
- };
843
- }
844
- if (codexAppServerRunInputSource(codexAppServerAgentRun(session)) === "terminal") {
845
- codexAppServerCompletedTurns.add(codexAppServerTurnKey(normalizedThreadId, normalizedTurnId));
846
- cleanupCodexAppServerUntrackedTurn(normalizedThreadId, normalizedTurnId);
847
- await markCodexAppServerTurnIdle(normalizedSessionId, {
848
- status: normalizedStatus,
849
- threadId: normalizedThreadId,
850
- turnId: normalizedTurnId
851
- });
852
- return {
853
- ok: true,
854
- processed: true,
855
- reason: "terminal_turn_completed"
856
- };
857
- }
858
- const alreadyFinalizing = existingTurn.state === "finalizing" &&
859
- existingTurn.threadId === normalizedThreadId &&
860
- existingTurn.turnId === normalizedTurnId;
861
- codexAppServerCompletedTurns.add(codexAppServerTurnKey(normalizedThreadId, normalizedTurnId));
862
- if (!alreadyFinalizing) {
863
- await markCodexAppServerTurnFinalizing(normalizedSessionId, {
864
- status: normalizedStatus,
865
- threadId: normalizedThreadId,
866
- turnId: normalizedTurnId
867
- });
868
- }
869
- if (continuesOwnedGoal) {
870
- // Keep the outer chat turn active so the next provider turn can adopt
871
- // its chat ownership without exposing this internal final as an answer.
872
- clearCodexAppServerFinalizingTimer(
873
- normalizedSessionId,
874
- normalizedThreadId,
875
- normalizedTurnId
876
- );
877
- return {
878
- ok: true,
879
- processed: true,
880
- reason: "goal_continuation_pending"
881
- };
882
- }
883
- return recoverCodexAppServerFinalizingTurn(
884
- normalizedSessionId,
885
- normalizedThreadId,
886
- normalizedTurnId,
887
- {
888
- status: normalizedStatus
889
- }
890
- );
891
- }
892
-
893
- function codexAppServerStoppedTurnMessage(status = "", error = "", outcome = "") {
894
- const normalizedStatus = normalizeCodexRunText(status);
895
- let base = "Codex app-server failed before completing this turn.";
896
- if (normalizedStatus === "interrupted") {
897
- base = outcome === turnOutcomes.CONTROL_RECONFIGURATION
898
- ? "Codex app-server was interrupted while its controls were restored."
899
- : "Codex app-server was interrupted before completing this turn.";
900
- }
901
- const normalizedError = normalizeCodexRunText(error);
902
- return normalizedError ? `${base} ${normalizedError}` : base;
903
- }
904
-
905
- async function stopCodexAppServerTurnWithProviderFailure(sessionId = "", threadId = "", turnId = "", {
906
- error = "",
907
- ok = false,
908
- outcome = "",
909
- provider = null,
910
- status = "failed",
911
- usageLimitExceeded = false,
912
- verifyInactive = true,
913
- deferFailureDetails = false,
914
- failureDetailReady = false
915
- } = {}) {
916
- const observedAt = Date.now();
917
- const normalizedSessionId = normalizeCodexRunText(sessionId);
918
- const normalizedThreadId = normalizeCodexRunText(threadId);
919
- const normalizedTurnId = normalizeCodexRunText(turnId);
920
- const normalizedStatus = normalizeCodexRunText(status) || "failed";
921
- let resolvedOutcome = normalizeCodexRunText(outcome);
922
- if (!resolvedOutcome) {
923
- if (normalizedStatus === "interrupted") {
924
- const recorded = provider?.interruptionOutcome?.(normalizedThreadId, normalizedTurnId);
925
- resolvedOutcome = [turnOutcomes.USER_CANCELLED, turnOutcomes.CONTROL_RECONFIGURATION].includes(recorded)
926
- ? recorded : turnOutcomes.INTERRUPTED;
927
- } else {
928
- resolvedOutcome = turnOutcomes.PROVIDER_FAILURE;
929
- }
930
- }
931
- const runtime = await createRuntime();
932
- const staleFailure = async () => {
933
- const turn = turnState(await runtime.getSession(normalizedSessionId));
934
- if (codexAppServerTurnCanReceiveProviderCompletion(turn, normalizedThreadId, normalizedTurnId)) {
935
- return null;
936
- }
937
- debugLog("appServerTurn.failure.stale", {
938
- currentState: turn.state,
939
- currentStatus: turn.status,
940
- currentThreadId: turn.threadId,
941
- currentTurnId: turn.turnId,
942
- sessionId: normalizedSessionId,
943
- status: normalizedStatus,
944
- threadId: normalizedThreadId,
945
- turnId: normalizedTurnId
946
- });
947
- return {
948
- ok: true,
949
- processed: false,
950
- reason: "stale_turn_state"
951
- };
952
- };
953
- const staleBeforeRecovery = await staleFailure();
954
- if (staleBeforeRecovery) {
955
- return staleBeforeRecovery;
956
- }
957
- const key = codexAppServerResultFinalizationKey(normalizedSessionId, normalizedThreadId, normalizedTurnId);
958
- const pendingTimer = codexAppServerFinalizingTimers.get(key);
959
- const pendingDetail = pendingTimer?.failureDetail;
960
- if (failureDetailGraceMs > 0 && !failureDetailReady && (deferFailureDetails || pendingDetail) &&
961
- ![turnOutcomes.USER_CANCELLED, turnOutcomes.CONTROL_RECONFIGURATION].includes(resolvedOutcome)) {
962
- if (!pendingDetail) {
963
- scheduleCodexAppServerFinalizingRecovery(normalizedSessionId, normalizedThreadId, normalizedTurnId, {
964
- failureDetail: { error, ok, outcome: resolvedOutcome, provider, status: normalizedStatus,
965
- usageLimitExceeded, verifyInactive,
966
- expiresAt: Math.min(observedAt + failureDetailGraceMs,
967
- pendingTimer?.expiresAt ?? Infinity) }
968
- });
969
- }
970
- return { ok: true, processed: false, reason: "failure_detail_pending", status: normalizedStatus };
971
- }
972
- if (resolvedOutcome === turnOutcomes.PROVIDER_FAILURE) {
973
- // Saved output is still useful, but cannot turn a known provider failure
974
- // into successful completion. Persist it without settling the turn;
975
- // the failure notice and checkpoint below own the terminal outcome.
976
- await submitCodexAppServerAssistantResult(normalizedSessionId, normalizedThreadId, normalizedTurnId, {
977
- recoverFromProvider: true
978
- });
979
- } else {
980
- const recovered = await recoverCodexAppServerFinalResponseBeforeOutcome(
981
- normalizedSessionId,
982
- normalizedThreadId,
983
- normalizedTurnId,
984
- normalizedStatus
985
- );
986
- if (recovered?.processed) {
987
- return recovered;
988
- }
989
- }
990
- // History recovery awaited the provider. Confirm ownership and activity
991
- // again before publishing a failure: a successor, Stop or goal
992
- // continuation may own the run after that gap.
993
- const staleAfterRecovery = await staleFailure();
994
- if (staleAfterRecovery) {
995
- return staleAfterRecovery;
996
- }
997
- if (verifyInactive && await codexAppServerProviderBlocksTurnRelease(normalizedSessionId, provider, normalizedThreadId, normalizedTurnId, {
998
- source: "provider_failure"
999
- })) {
1000
- return {
1001
- ok: true,
1002
- processed: false,
1003
- reason: "provider_still_active",
1004
- status: "inProgress"
1005
- };
1006
- }
1007
- const message = codexAppServerStoppedTurnMessage(normalizedStatus, error, resolvedOutcome);
1008
- await outcomeNotice(
1009
- runtime,
1010
- normalizedSessionId,
1011
- normalizedThreadId,
1012
- normalizedTurnId,
1013
- resolvedOutcome,
1014
- error,
1015
- { usageLimitExceeded }
1016
- );
1017
- await markCodexAppServerTurnIdle(normalizedSessionId, {
1018
- error: message,
1019
- status: normalizedStatus,
1020
- threadId: normalizedThreadId,
1021
- turnId: normalizedTurnId,
1022
- turnOutcome: resolvedOutcome
1023
- });
1024
- cleanupCodexAppServerUntrackedTurn(normalizedThreadId, normalizedTurnId);
1025
- return {
1026
- ok,
1027
- error: message,
1028
- status: normalizedStatus
1029
- };
1030
- }
1031
-
1032
- async function stopCodexAppServerTurnWithResultDeliveryFailure(sessionId = "", threadId = "", turnId = "", {
1033
- error = "",
1034
- reason = "",
1035
- status = "completed"
1036
- } = {}) {
1037
- const normalizedSessionId = normalizeCodexRunText(sessionId);
1038
- const normalizedThreadId = normalizeCodexRunText(threadId);
1039
- const normalizedStatus = normalizeCodexRunText(status) || "completed";
1040
- const normalizedTurnId = await resolveCodexAppServerTurnId(normalizedSessionId, normalizedThreadId, turnId);
1041
- const recovered = await recoverCodexAppServerFinalResponseBeforeOutcome(
1042
- normalizedSessionId,
1043
- normalizedThreadId,
1044
- normalizedTurnId,
1045
- normalizedStatus
1046
- );
1047
- if (recovered?.processed) {
1048
- return recovered;
1049
- }
1050
- // The history read can outlive this turn: a successor or Stop may own the
1051
- // run by now, so the outcome is decided against the run read after it.
1052
- const runtime = await createRuntime();
1053
- const currentTurn = turnState(await runtime.getSession(normalizedSessionId));
1054
- if (!codexAppServerTurnCanReceiveProviderCompletion(currentTurn, normalizedThreadId, normalizedTurnId)) {
1055
- codexAppServerFinalizedTurns.add(codexAppServerResultFinalizationKey(
1056
- normalizedSessionId,
1057
- normalizedThreadId,
1058
- normalizedTurnId
1059
- ));
1060
- cleanupCodexAppServerUntrackedTurn(normalizedThreadId, normalizedTurnId);
1061
- debugLog("appServerAgentResult.missing.stale", {
1062
- currentState: currentTurn.state,
1063
- currentStatus: currentTurn.status,
1064
- currentThreadId: currentTurn.threadId,
1065
- currentTurnId: currentTurn.turnId,
1066
- reason: normalizeCodexRunText(reason),
1067
- sessionId: normalizedSessionId,
1068
- status: normalizedStatus,
1069
- threadId: normalizedThreadId,
1070
- turnId: normalizedTurnId
1071
- });
1072
- return {
1073
- ok: true,
1074
- processed: false,
1075
- reason: "stale_turn_state",
1076
- status: currentTurn.status
1077
- };
1078
- }
1079
- const message = resultDeliveryFailureMessage({
1080
- error
1081
- });
1082
- await outcomeNotice(
1083
- runtime,
1084
- normalizedSessionId,
1085
- normalizedThreadId,
1086
- normalizedTurnId,
1087
- turnOutcomes.RESPONSE_DELIVERY_FAILURE
1088
- );
1089
- await markCodexAppServerTurnIdle(normalizedSessionId, {
1090
- error: message,
1091
- status: normalizedStatus,
1092
- threadId: normalizedThreadId,
1093
- turnId: normalizedTurnId,
1094
- turnOutcome: turnOutcomes.RESPONSE_DELIVERY_FAILURE
1095
- });
1096
- cleanupCodexAppServerUntrackedTurn(normalizedThreadId, normalizedTurnId);
1097
- debugLog("appServerAgentResult.missing", {
1098
- error: normalizeCodexRunText(error),
1099
- reason: normalizeCodexRunText(reason),
1100
- sessionId: normalizedSessionId,
1101
- threadId: normalizedThreadId,
1102
- turnId: normalizedTurnId
1103
- });
1104
- return {
1105
- ok: false,
1106
- error: message,
1107
- status: normalizedStatus
1108
- };
1109
- }
1110
-
1111
- return {
358
+ return Object.freeze({
1112
359
  scheduleActiveRecovery: scheduleCodexAppServerActiveRecovery,
1113
- markTurnActive: markCodexAppServerTurnActive,
1114
- markProviderTurnActive: markCodexAppServerProviderTurnActive,
1115
- recoverFinalizingTurn: recoverCodexAppServerFinalizingTurn,
360
+ markTurnActive: activity.markTurnActive,
361
+ markProviderTurnActive: activity.markProviderTurnActive,
362
+ recoverFinalizingTurn: endings.recoverFinalizingTurn,
1116
363
  recoverActiveTurn: recoverCodexAppServerActiveTurn,
1117
- completeTurn: completeCodexAppServerTurn,
1118
- stopTurnWithProviderFailure: stopCodexAppServerTurnWithProviderFailure
1119
- };
364
+ completeTurn: endings.completeTurn,
365
+ stopTurnWithProviderFailure: endings.stopTurnWithProviderFailure
366
+ });
1120
367
  }
368
+
369
+ /** @typedef {ReturnType<typeof createCodexTurnRecovery>} CodexTurnRecovery */