@jskit-ai/assistant-core 0.2.7 → 0.2.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/examples/conversation/package.json +5 -5
- package/package.json +18 -7
- package/src/client/browser.js +21 -0
- package/src/client/conversation/AssistantAttachmentQueue.vue +25 -108
- package/src/client/conversation/AssistantComposerActions.vue +1 -1
- package/src/client/conversation/AssistantConversationElement.vue +14 -52
- package/src/client/conversation/AssistantGoalControl.vue +13 -10
- package/src/client/conversation/AssistantPromptInput.vue +6 -91
- package/src/client/conversation/AssistantTranscript.css +505 -0
- package/src/client/conversation/AssistantTranscript.vue +24 -1197
- package/src/client/conversation/attachmentFiles.js +158 -0
- package/src/client/conversation/attachmentQueueItems.js +121 -0
- package/src/client/conversation/attachmentUploadRows.js +265 -0
- package/src/client/conversation/conversationDisplay.js +54 -0
- package/src/client/conversation/messageDelivery.js +13 -9
- package/src/client/conversation/retainedConversation.js +14 -3
- package/src/client/conversation/submitText.js +13 -2
- package/src/client/conversation/transcriptEntries.js +225 -0
- package/src/client/conversation/useAssistantAttachments.js +86 -388
- package/src/client/conversation/useAssistantQuestions.js +16 -3
- package/src/client/conversation/useAssistantSuggestions.js +16 -2
- package/src/client/conversation/useScrollToBottom.js +17 -19
- package/src/client/conversation/useTextareaAutoGrow.js +115 -0
- package/src/client/conversation/useTranscriptScrollFollow.js +525 -0
- package/src/client/lib/assistantApi.js +192 -161
- package/src/client/types.js +110 -0
- package/src/server/conversation/attachments.js +21 -6
- package/src/server/conversation/boundedToolLoop.js +1 -0
- package/src/server/conversation/claudeClient.js +71 -20
- package/src/server/conversation/claudeCommandRun.js +191 -0
- package/src/server/conversation/claudeHistory.js +7 -0
- package/src/server/conversation/claudeNativeTurn.js +708 -0
- package/src/server/conversation/claudeProcess.js +186 -112
- package/src/server/conversation/claudeProcessStart.js +214 -0
- package/src/server/conversation/claudePromptDelivery.js +183 -0
- package/src/server/conversation/claudeSessionCleanup.js +248 -0
- package/src/server/conversation/claudeTools.js +15 -3
- package/src/server/conversation/claudeTurn.js +212 -1388
- package/src/server/conversation/codexAccountReader.js +168 -0
- package/src/server/conversation/codexAuthPreflight.js +5 -4
- package/src/server/conversation/codexClient.js +18 -6
- package/src/server/conversation/codexConfiguration.js +8 -844
- package/src/server/conversation/codexConversationStops.js +377 -0
- package/src/server/conversation/codexConversationStore.js +119 -85
- package/src/server/conversation/codexConversationTurns.js +378 -0
- package/src/server/conversation/codexConversations.js +134 -866
- package/src/server/conversation/codexDetachedConversation.js +203 -0
- package/src/server/conversation/codexDetachedTurn.js +145 -104
- package/src/server/conversation/codexEvents.js +5 -200
- package/src/server/conversation/codexGoalCommands.js +14 -4
- package/src/server/conversation/codexHelperExecution.js +57 -57
- package/src/server/conversation/codexHelperThreadLedger.js +30 -288
- package/src/server/conversation/codexHelperThreadRecord.js +304 -0
- package/src/server/conversation/codexHelperThreadRecovery.js +54 -76
- package/src/server/conversation/codexHelperThreads.js +94 -93
- package/src/server/conversation/codexHistoryAdapter.js +13 -8
- package/src/server/conversation/codexIsolation.js +672 -0
- package/src/server/conversation/codexLiveItems.js +379 -0
- package/src/server/conversation/codexMessageCommands.js +68 -286
- package/src/server/conversation/codexMessageDelivery.js +46 -37
- package/src/server/conversation/codexModelCatalog.js +86 -7
- package/src/server/conversation/codexNotificationObserver.js +81 -88
- package/src/server/conversation/codexNotificationQueue.js +21 -10
- package/src/server/conversation/codexObservationControl.js +105 -89
- package/src/server/conversation/codexOutput.js +84 -419
- package/src/server/conversation/codexProcess.js +29 -1053
- package/src/server/conversation/codexProcessLeader.js +1 -0
- package/src/server/conversation/codexPromptDispatch.js +241 -0
- package/src/server/conversation/codexProvider.js +147 -1807
- package/src/server/conversation/codexProviderAuth.js +348 -0
- package/src/server/conversation/codexProviderInvalidation.js +371 -0
- package/src/server/conversation/codexProviderInventories.js +437 -0
- package/src/server/conversation/codexProviderOwner.js +149 -957
- package/src/server/conversation/codexProviderRelease.js +465 -0
- package/src/server/conversation/codexProviderSelection.js +26 -15
- package/src/server/conversation/codexProviderThreads.js +698 -0
- package/src/server/conversation/codexProviderTurns.js +453 -0
- package/src/server/conversation/codexReasoning.js +32 -23
- package/src/server/conversation/codexRenewalCommands.js +18 -6
- package/src/server/conversation/codexRenewalThreads.js +7 -22
- package/src/server/conversation/codexResultSettlement.js +70 -88
- package/src/server/conversation/codexRunJournal.js +91 -97
- package/src/server/conversation/codexRuntime.js +426 -0
- package/src/server/conversation/codexRuntimeInventory.js +113 -0
- package/src/server/conversation/codexRuntimeLifecycle.js +39 -13
- package/src/server/conversation/codexRuntimeStart.js +431 -0
- package/src/server/conversation/codexRuntimeStop.js +324 -0
- package/src/server/conversation/codexSessionCleanup.js +25 -10
- package/src/server/conversation/codexSessionReconciliation.js +21 -7
- package/src/server/conversation/codexThreadReadiness.js +49 -52
- package/src/server/conversation/codexThreadSnapshot.js +210 -0
- package/src/server/conversation/codexThreadStatus.js +48 -62
- package/src/server/conversation/codexTurn.js +171 -55
- package/src/server/conversation/codexTurnActivity.js +210 -0
- package/src/server/conversation/codexTurnControl.js +44 -48
- package/src/server/conversation/codexTurnEndings.js +610 -0
- package/src/server/conversation/codexTurnRecovery.js +97 -848
- package/src/server/conversation/codexTurnState.js +138 -111
- package/src/server/conversation/completedResponses.js +227 -0
- package/src/server/conversation/configuration.js +2 -2
- package/src/server/conversation/continuity.js +43 -6
- package/src/server/conversation/conversationDelivery.js +394 -0
- package/src/server/conversation/conversationEntries.js +365 -0
- package/src/server/conversation/conversationEvents.js +192 -0
- package/src/server/conversation/conversationGoals.js +187 -0
- package/src/server/conversation/conversationHandles.js +237 -0
- package/src/server/conversation/conversationReads.js +206 -0
- package/src/server/conversation/conversationRefusals.js +58 -0
- package/src/server/conversation/conversationReplacement.js +254 -0
- package/src/server/conversation/conversationSending.js +273 -0
- package/src/server/conversation/conversationStopping.js +121 -0
- package/src/server/conversation/conversationTurnDelivery.js +413 -0
- package/src/server/conversation/conversationTurns.js +511 -0
- package/src/server/conversation/deliveryTiming.js +25 -3
- package/src/server/conversation/fileStorage.js +11 -6
- package/src/server/conversation/hookBridge.js +14 -3
- package/src/server/conversation/index.js +1 -1
- package/src/server/conversation/localExecution.js +82 -625
- package/src/server/conversation/nativeCommandCallbacks.js +10 -0
- package/src/server/conversation/nativeConversationLifecycle.js +379 -0
- package/src/server/conversation/nativeHistoryExport.js +27 -18
- package/src/server/conversation/normalize.js +9 -0
- package/src/server/conversation/openCodeClient.js +295 -366
- package/src/server/conversation/openCodeNativeHistory.js +141 -0
- package/src/server/conversation/openCodeNativeSession.js +79 -0
- package/src/server/conversation/openCodeProcess.js +109 -53
- package/src/server/conversation/openCodeProcessOwner.js +565 -0
- package/src/server/conversation/openCodeRuntime.js +111 -1814
- package/src/server/conversation/openCodeSessionTurns.js +547 -0
- package/src/server/conversation/openCodeTemporaryConversations.js +394 -0
- package/src/server/conversation/openCodeTools.js +28 -14
- package/src/server/conversation/openCodeTurn.js +80 -74
- package/src/server/conversation/openCodeTurnMonitor.js +388 -0
- package/src/server/conversation/processIdentity.js +582 -0
- package/src/server/conversation/provider.js +21 -6
- package/src/server/conversation/providers/api.js +268 -107
- package/src/server/conversation/providers/claude.js +29 -6
- package/src/server/conversation/providers/claudeDriver.js +325 -250
- package/src/server/conversation/providers/codex.js +69 -33
- package/src/server/conversation/providers/codexDriver.js +503 -1081
- package/src/server/conversation/providers/codexDriverConnection.js +331 -0
- package/src/server/conversation/providers/codexDriverTurns.js +464 -0
- package/src/server/conversation/providers/opencode.js +30 -4
- package/src/server/conversation/providers/opencodeDriver.js +321 -659
- package/src/server/conversation/providers/opencodeStandalone.js +475 -0
- package/src/server/conversation/providers/scoped.js +64 -19
- package/src/server/conversation/runtime.js +171 -2863
- package/src/server/conversation/storage.js +91 -50
- package/src/server/conversation/streams.js +15 -2
- package/src/server/conversation/structuredOutput.js +7 -11
- package/src/server/conversation/systemPrompt.js +13 -1
- package/src/server/conversation/tools.js +47 -15
- package/src/server/conversation/transcript.js +49 -20
- package/src/server/hosts/claudeTurn.js +2 -4
- package/src/server/hosts/codexConfiguration.js +6 -8
- package/src/server/hosts/codexEvents.js +4 -2
- package/src/server/hosts/codexProcess.js +1 -1
- package/src/server/hosts/codexProvider.js +11 -9
- package/src/server/hosts/codexTurn.js +3 -3
- package/src/server/hosts/openCodeClient.js +4 -2
- package/src/server/index.js +0 -2
- package/src/server/lib/aiConnectionClient.js +35 -25
- package/src/server/lib/ndjson.js +1 -0
- package/src/server/lib/providers/anthropicClient.js +15 -6
- package/src/server/lib/providers/common.js +10 -6
- package/src/server/lib/providers/openAiCompatibleClient.js +40 -23
- package/src/server/lib/serviceToolCatalog.js +29 -283
- package/src/server/lib/serviceToolEntries.js +270 -0
- package/src/server/types.js +78 -0
- package/src/shared/assistantResource.js +0 -23
- package/src/shared/conversation/answerChoiceSugar.js +2 -14
- package/src/shared/conversation/messageInputSugar.js +18 -0
- package/src/shared/conversation/numberedQuestionSugar.js +6 -16
- package/src/shared/conversation/pagination.js +1 -1
- package/src/shared/conversation/questions.js +1 -0
- package/src/shared/conversation/streaming.js +1 -0
- package/src/shared/index.js +0 -1
- package/src/testing/conversationStorageContract.js +20 -0
- package/test/aiConnectionClient.test.js +42 -1
- package/test/apiConversationDriver.test.js +217 -38
- package/test/assistantBoundedToolLoop.test.js +1 -1
- package/test/claudeConversationSession.test.js +15 -11
- package/test/claudeInstructionResume.test.js +1 -1
- package/test/claudeProcess.test.js +9 -9
- package/test/claudeTurn.test.js +21 -8
- package/test/codexAccountChange.test.js +5 -3
- package/test/codexConversationSession.test.js +22 -21
- package/test/codexEvents.test.js +3 -1
- package/test/codexHelperDeletion.test.js +3 -7
- package/test/codexIsolation.test.js +6 -6
- package/test/codexModelCatalog.test.js +6 -7
- package/test/codexProcess.test.js +3 -3
- package/test/codexThreadSelection.test.js +4 -1
- package/test/codexTurn.test.js +29 -20
- package/test/conversationApiFacilities.test.js +11 -9
- package/test/conversationAttachments.test.js +3 -3
- package/test/conversationConfiguration.test.js +4 -4
- package/test/conversationNativeStorage.test.js +11 -10
- package/test/conversationReplacement.test.js +13 -13
- package/test/conversationRuntime.test.js +9 -5
- package/test/conversationSession.test.js +78 -22
- package/test/conversationTools.test.js +47 -26
- package/test/fileConversationStorage.test.js +10 -3
- package/test/fixtures/codexHelperThreadLedger.js +3 -3
- package/test/frameworkActions.test.js +33 -18
- package/test/localConversationExecution.test.js +4 -4
- package/test/localExecution.test.js +1 -1
- package/test/openCodeConnectionChange.test.js +3 -2
- package/test/openCodeRuntime.test.js +8 -7
- package/test/openCodeTools.test.js +1 -1
- package/test/opencodeConversationSession.test.js +4 -3
- package/test/serviceToolCatalog.test.js +3 -8
- package/src/server/lib/assistantCompletion.js +0 -190
- package/src/server/lib/assistantToolLoop.js +0 -259
|
@@ -0,0 +1,610 @@
|
|
|
1
|
+
import { codexAppServerTurnStatusIsActive, codexAppServerThreadStatus, codexAppServerReadThreadStatus, codexAppServerAgentRun, codexAppServerTurnOwnsActiveGoal, codexAppServerTurnCanReceiveProviderCompletion, codexAppServerTurnAwaitsProviderIdentity, codexAppServerTurnCanReceiveProviderActivity, codexAppServerTurnKey } from "./codexTurnState.js";
|
|
2
|
+
import { codexAppServerThreadTurnId } from "./codexThreadSnapshot.js";
|
|
3
|
+
import { normalizeText } from "@jskit-ai/kernel/shared/support/normalize";
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* How a tracked turn ends: it completes, or it stops after a provider failure or a reply that could not be
|
|
7
|
+
* delivered. Each ending checks that the provider has released the turn first.
|
|
8
|
+
* @param {object} dependencies
|
|
9
|
+
* @param {import("./codexRunJournal.js").CodexRunJournal} dependencies.journal
|
|
10
|
+
* @param {import("./codexOutput.js").CodexAppServerOutput} dependencies.output
|
|
11
|
+
* @param {import("./codexResultSettlement.js").CodexResultSettlement} dependencies.settlement
|
|
12
|
+
* @param {import("./codexTurn.js").CodexCreateRuntime} dependencies.createRuntime
|
|
13
|
+
* @param {import("./codexTurn.js").CodexDebugLog} dependencies.debugLog
|
|
14
|
+
* @param {import("./codexTurn.js").CodexDebugError} dependencies.debugError
|
|
15
|
+
* @param {import("./codexTurn.js").CodexTurnStateReader} dependencies.turnState
|
|
16
|
+
* @param {number} dependencies.failureDetailGraceMs
|
|
17
|
+
* @param {import("./codexTurn.js").CodexTurnOutcomes} dependencies.turnOutcomes
|
|
18
|
+
* @param {(...details: any[]) => Promise<unknown>} dependencies.outcomeNotice
|
|
19
|
+
* @param {(details: { error?: string }) => string} dependencies.resultDeliveryFailureMessage
|
|
20
|
+
* @param {import("./codexTurn.js").CodexCaptureContext} dependencies.captureContext
|
|
21
|
+
* @param {import("./codexTurn.js").CodexRunInContext} dependencies.runInContext
|
|
22
|
+
* @param {import("./codexNotificationQueue.js").CodexAppServerNotificationQueue} dependencies.notificationQueue
|
|
23
|
+
* @param {boolean} dependencies.finalizingGraceAfterHistoryRead
|
|
24
|
+
* @param {(sessionId: string, delayMs?: number) => void} dependencies.scheduleActiveRecovery
|
|
25
|
+
*/
|
|
26
|
+
export function createCodexTurnEndings({
|
|
27
|
+
journal,
|
|
28
|
+
output,
|
|
29
|
+
settlement,
|
|
30
|
+
createRuntime,
|
|
31
|
+
debugLog,
|
|
32
|
+
debugError,
|
|
33
|
+
turnState,
|
|
34
|
+
failureDetailGraceMs,
|
|
35
|
+
turnOutcomes,
|
|
36
|
+
outcomeNotice,
|
|
37
|
+
resultDeliveryFailureMessage,
|
|
38
|
+
captureContext,
|
|
39
|
+
runInContext,
|
|
40
|
+
notificationQueue,
|
|
41
|
+
finalizingGraceAfterHistoryRead,
|
|
42
|
+
scheduleActiveRecovery
|
|
43
|
+
}) {
|
|
44
|
+
const retainFinalizingTimer = finalizingGraceAfterHistoryRead || failureDetailGraceMs > 0;
|
|
45
|
+
|
|
46
|
+
async function codexAppServerProviderBlocksTurnRelease(sessionId = "", provider = null, threadId = "", turnId = "", {
|
|
47
|
+
source = ""
|
|
48
|
+
} = {}) {
|
|
49
|
+
const normalizedSessionId = normalizeText(sessionId);
|
|
50
|
+
const normalizedThreadId = normalizeText(threadId);
|
|
51
|
+
const normalizedTurnId = normalizeText(turnId);
|
|
52
|
+
if (!normalizedSessionId || !normalizedThreadId) {
|
|
53
|
+
return false;
|
|
54
|
+
}
|
|
55
|
+
const runtime = await createRuntime();
|
|
56
|
+
const session = await runtime.getSession(normalizedSessionId);
|
|
57
|
+
const turn = turnState(session);
|
|
58
|
+
if (!turn.active || !codexAppServerTurnCanReceiveProviderActivity(turn, normalizedThreadId, normalizedTurnId)) {
|
|
59
|
+
return false;
|
|
60
|
+
}
|
|
61
|
+
try {
|
|
62
|
+
const providerThread = await codexAppServerReadThreadStatus(provider, normalizedThreadId, {
|
|
63
|
+
observeLatestTurn: true
|
|
64
|
+
});
|
|
65
|
+
if (!providerThread) {
|
|
66
|
+
return false;
|
|
67
|
+
}
|
|
68
|
+
const status = codexAppServerThreadStatus(providerThread);
|
|
69
|
+
const providerTurnId = codexAppServerThreadTurnId(providerThread);
|
|
70
|
+
if (
|
|
71
|
+
providerTurnId &&
|
|
72
|
+
providerTurnId !== normalizeText(turn.turnId)
|
|
73
|
+
) {
|
|
74
|
+
debugLog("appServerTurn.releaseAllowedSuccessor", {
|
|
75
|
+
providerTurnId,
|
|
76
|
+
sessionId: normalizedSessionId,
|
|
77
|
+
source: normalizeText(source),
|
|
78
|
+
threadId: normalizedThreadId,
|
|
79
|
+
turnId: turn.turnId
|
|
80
|
+
});
|
|
81
|
+
return false;
|
|
82
|
+
}
|
|
83
|
+
if (!codexAppServerTurnStatusIsActive(status)) {
|
|
84
|
+
return false;
|
|
85
|
+
}
|
|
86
|
+
debugLog("appServerTurn.releaseBlockedActive", {
|
|
87
|
+
sessionId: normalizedSessionId,
|
|
88
|
+
source: normalizeText(source),
|
|
89
|
+
status,
|
|
90
|
+
threadId: normalizedThreadId,
|
|
91
|
+
turnId: normalizedTurnId || turn.turnId
|
|
92
|
+
});
|
|
93
|
+
scheduleActiveRecovery(normalizedSessionId);
|
|
94
|
+
return true;
|
|
95
|
+
} catch (error) {
|
|
96
|
+
debugLog("appServerTurn.releaseCheck.error", {
|
|
97
|
+
error: debugError(error),
|
|
98
|
+
sessionId: normalizedSessionId,
|
|
99
|
+
source: normalizeText(source),
|
|
100
|
+
threadId: normalizedThreadId,
|
|
101
|
+
turnId: normalizedTurnId || turn.turnId
|
|
102
|
+
});
|
|
103
|
+
return false;
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
async function completeCodexAppServerTurn(sessionId = "", threadId = "", turnId = "", {
|
|
108
|
+
provider = null,
|
|
109
|
+
status = "completed",
|
|
110
|
+
verifyInactive = true
|
|
111
|
+
} = {}) {
|
|
112
|
+
const normalizedSessionId = normalizeText(sessionId);
|
|
113
|
+
const normalizedThreadId = normalizeText(threadId);
|
|
114
|
+
const normalizedTurnId = normalizeText(turnId);
|
|
115
|
+
const normalizedStatus = normalizeText(status) || "completed";
|
|
116
|
+
const runtime = await createRuntime();
|
|
117
|
+
const session = await runtime.getSession(normalizedSessionId);
|
|
118
|
+
const existingTurn = turnState(session);
|
|
119
|
+
const continuesOwnedGoal = codexAppServerTurnOwnsActiveGoal(
|
|
120
|
+
existingTurn,
|
|
121
|
+
normalizedThreadId
|
|
122
|
+
);
|
|
123
|
+
if (codexAppServerTurnAwaitsProviderIdentity(existingTurn, normalizedThreadId, normalizedTurnId)) {
|
|
124
|
+
return {
|
|
125
|
+
ok: true,
|
|
126
|
+
processed: false,
|
|
127
|
+
reason: "turn_identity_pending"
|
|
128
|
+
};
|
|
129
|
+
}
|
|
130
|
+
if (!normalizedTurnId) {
|
|
131
|
+
if (!codexAppServerTurnCanReceiveProviderCompletion(existingTurn, normalizedThreadId, "")) {
|
|
132
|
+
output.cleanupUntrackedTurn(normalizedThreadId, normalizedTurnId);
|
|
133
|
+
debugLog("appServerTurn.complete.stale", {
|
|
134
|
+
currentState: existingTurn.state,
|
|
135
|
+
currentStatus: existingTurn.status,
|
|
136
|
+
currentThreadId: existingTurn.threadId,
|
|
137
|
+
currentTurnId: existingTurn.turnId,
|
|
138
|
+
sessionId: normalizedSessionId,
|
|
139
|
+
status: normalizedStatus,
|
|
140
|
+
threadId: normalizedThreadId,
|
|
141
|
+
turnId: normalizedTurnId
|
|
142
|
+
});
|
|
143
|
+
return {
|
|
144
|
+
ok: true,
|
|
145
|
+
processed: false,
|
|
146
|
+
reason: "stale_turn_state"
|
|
147
|
+
};
|
|
148
|
+
}
|
|
149
|
+
if (verifyInactive && await codexAppServerProviderBlocksTurnRelease(normalizedSessionId, provider, normalizedThreadId, normalizedTurnId, {
|
|
150
|
+
source: "complete_missing_turn"
|
|
151
|
+
})) {
|
|
152
|
+
return {
|
|
153
|
+
ok: true,
|
|
154
|
+
processed: false,
|
|
155
|
+
reason: "provider_still_active",
|
|
156
|
+
status: "inProgress"
|
|
157
|
+
};
|
|
158
|
+
}
|
|
159
|
+
await journal.markTurnIdle(normalizedSessionId, {
|
|
160
|
+
status: normalizedStatus,
|
|
161
|
+
threadId: normalizedThreadId,
|
|
162
|
+
turnId: normalizedTurnId
|
|
163
|
+
});
|
|
164
|
+
return {
|
|
165
|
+
ok: true,
|
|
166
|
+
processed: false,
|
|
167
|
+
reason: "missing_turn"
|
|
168
|
+
};
|
|
169
|
+
}
|
|
170
|
+
if (!codexAppServerTurnCanReceiveProviderCompletion(existingTurn, normalizedThreadId, normalizedTurnId)) {
|
|
171
|
+
output.finalizedTurns.add(output.resultFinalizationKey(
|
|
172
|
+
normalizedSessionId,
|
|
173
|
+
normalizedThreadId,
|
|
174
|
+
normalizedTurnId
|
|
175
|
+
));
|
|
176
|
+
output.cleanupUntrackedTurn(normalizedThreadId, normalizedTurnId);
|
|
177
|
+
debugLog("appServerTurn.complete.stale", {
|
|
178
|
+
currentState: existingTurn.state,
|
|
179
|
+
currentStatus: existingTurn.status,
|
|
180
|
+
currentThreadId: existingTurn.threadId,
|
|
181
|
+
currentTurnId: existingTurn.turnId,
|
|
182
|
+
sessionId: normalizedSessionId,
|
|
183
|
+
status: normalizedStatus,
|
|
184
|
+
threadId: normalizedThreadId,
|
|
185
|
+
turnId: normalizedTurnId
|
|
186
|
+
});
|
|
187
|
+
return {
|
|
188
|
+
ok: true,
|
|
189
|
+
processed: false,
|
|
190
|
+
reason: "stale_turn_state"
|
|
191
|
+
};
|
|
192
|
+
}
|
|
193
|
+
if (verifyInactive && await codexAppServerProviderBlocksTurnRelease(normalizedSessionId, provider, normalizedThreadId, normalizedTurnId, {
|
|
194
|
+
source: "complete"
|
|
195
|
+
})) {
|
|
196
|
+
return {
|
|
197
|
+
ok: true,
|
|
198
|
+
processed: false,
|
|
199
|
+
reason: "provider_still_active",
|
|
200
|
+
status: "inProgress"
|
|
201
|
+
};
|
|
202
|
+
}
|
|
203
|
+
if (journal.runInputSource(codexAppServerAgentRun(session)) === "terminal") {
|
|
204
|
+
journal.completedTurns.add(codexAppServerTurnKey(normalizedThreadId, normalizedTurnId));
|
|
205
|
+
output.cleanupUntrackedTurn(normalizedThreadId, normalizedTurnId);
|
|
206
|
+
await journal.markTurnIdle(normalizedSessionId, {
|
|
207
|
+
status: normalizedStatus,
|
|
208
|
+
threadId: normalizedThreadId,
|
|
209
|
+
turnId: normalizedTurnId
|
|
210
|
+
});
|
|
211
|
+
return {
|
|
212
|
+
ok: true,
|
|
213
|
+
processed: true,
|
|
214
|
+
reason: "terminal_turn_completed"
|
|
215
|
+
};
|
|
216
|
+
}
|
|
217
|
+
const alreadyFinalizing = existingTurn.state === "finalizing" &&
|
|
218
|
+
existingTurn.threadId === normalizedThreadId &&
|
|
219
|
+
existingTurn.turnId === normalizedTurnId;
|
|
220
|
+
journal.completedTurns.add(codexAppServerTurnKey(normalizedThreadId, normalizedTurnId));
|
|
221
|
+
if (!alreadyFinalizing) {
|
|
222
|
+
await journal.markTurnFinalizing(normalizedSessionId, {
|
|
223
|
+
status: normalizedStatus,
|
|
224
|
+
threadId: normalizedThreadId,
|
|
225
|
+
turnId: normalizedTurnId
|
|
226
|
+
});
|
|
227
|
+
}
|
|
228
|
+
if (continuesOwnedGoal) {
|
|
229
|
+
// Keep the outer chat turn active so the next provider turn can adopt
|
|
230
|
+
// its chat ownership without exposing this internal final as an answer.
|
|
231
|
+
journal.clearFinalizingTimer(
|
|
232
|
+
normalizedSessionId,
|
|
233
|
+
normalizedThreadId,
|
|
234
|
+
normalizedTurnId
|
|
235
|
+
);
|
|
236
|
+
return {
|
|
237
|
+
ok: true,
|
|
238
|
+
processed: true,
|
|
239
|
+
reason: "goal_continuation_pending"
|
|
240
|
+
};
|
|
241
|
+
}
|
|
242
|
+
return recoverCodexAppServerFinalizingTurn(
|
|
243
|
+
normalizedSessionId,
|
|
244
|
+
normalizedThreadId,
|
|
245
|
+
normalizedTurnId,
|
|
246
|
+
{
|
|
247
|
+
status: normalizedStatus
|
|
248
|
+
}
|
|
249
|
+
);
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
function codexAppServerStoppedTurnMessage(status = "", error = "", outcome = "") {
|
|
253
|
+
const normalizedStatus = normalizeText(status);
|
|
254
|
+
let base = "Codex app-server failed before completing this turn.";
|
|
255
|
+
if (normalizedStatus === "interrupted") {
|
|
256
|
+
base = outcome === turnOutcomes.CONTROL_RECONFIGURATION
|
|
257
|
+
? "Codex app-server was interrupted while its controls were restored."
|
|
258
|
+
: "Codex app-server was interrupted before completing this turn.";
|
|
259
|
+
}
|
|
260
|
+
const normalizedError = normalizeText(error);
|
|
261
|
+
return normalizedError ? `${base} ${normalizedError}` : base;
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
async function stopCodexAppServerTurnWithProviderFailure(sessionId = "", threadId = "", turnId = "", {
|
|
265
|
+
error = "",
|
|
266
|
+
ok = false,
|
|
267
|
+
outcome = "",
|
|
268
|
+
provider = null,
|
|
269
|
+
status = "failed",
|
|
270
|
+
usageLimitExceeded = false,
|
|
271
|
+
verifyInactive = true,
|
|
272
|
+
deferFailureDetails = false,
|
|
273
|
+
failureDetailReady = false
|
|
274
|
+
} = {}) {
|
|
275
|
+
const observedAt = Date.now();
|
|
276
|
+
const normalizedSessionId = normalizeText(sessionId);
|
|
277
|
+
const normalizedThreadId = normalizeText(threadId);
|
|
278
|
+
const normalizedTurnId = normalizeText(turnId);
|
|
279
|
+
const normalizedStatus = normalizeText(status) || "failed";
|
|
280
|
+
let resolvedOutcome = normalizeText(outcome);
|
|
281
|
+
if (!resolvedOutcome) {
|
|
282
|
+
if (normalizedStatus === "interrupted") {
|
|
283
|
+
const recorded = provider ? provider.interruptionOutcome(normalizedThreadId, normalizedTurnId) : "";
|
|
284
|
+
resolvedOutcome = [turnOutcomes.USER_CANCELLED, turnOutcomes.CONTROL_RECONFIGURATION].includes(recorded)
|
|
285
|
+
? recorded : turnOutcomes.INTERRUPTED;
|
|
286
|
+
} else {
|
|
287
|
+
resolvedOutcome = turnOutcomes.PROVIDER_FAILURE;
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
const runtime = await createRuntime();
|
|
291
|
+
const staleFailure = async () => {
|
|
292
|
+
const turn = turnState(await runtime.getSession(normalizedSessionId));
|
|
293
|
+
if (codexAppServerTurnCanReceiveProviderCompletion(turn, normalizedThreadId, normalizedTurnId)) {
|
|
294
|
+
return null;
|
|
295
|
+
}
|
|
296
|
+
debugLog("appServerTurn.failure.stale", {
|
|
297
|
+
currentState: turn.state,
|
|
298
|
+
currentStatus: turn.status,
|
|
299
|
+
currentThreadId: turn.threadId,
|
|
300
|
+
currentTurnId: turn.turnId,
|
|
301
|
+
sessionId: normalizedSessionId,
|
|
302
|
+
status: normalizedStatus,
|
|
303
|
+
threadId: normalizedThreadId,
|
|
304
|
+
turnId: normalizedTurnId
|
|
305
|
+
});
|
|
306
|
+
return {
|
|
307
|
+
ok: true,
|
|
308
|
+
processed: false,
|
|
309
|
+
reason: "stale_turn_state"
|
|
310
|
+
};
|
|
311
|
+
};
|
|
312
|
+
const staleBeforeRecovery = await staleFailure();
|
|
313
|
+
if (staleBeforeRecovery) {
|
|
314
|
+
return staleBeforeRecovery;
|
|
315
|
+
}
|
|
316
|
+
const key = output.resultFinalizationKey(normalizedSessionId, normalizedThreadId, normalizedTurnId);
|
|
317
|
+
const pendingTimer = journal.finalizingTimers.get(key);
|
|
318
|
+
const pendingDetail = pendingTimer?.failureDetail;
|
|
319
|
+
if (failureDetailGraceMs > 0 && !failureDetailReady && (deferFailureDetails || pendingDetail) &&
|
|
320
|
+
![turnOutcomes.USER_CANCELLED, turnOutcomes.CONTROL_RECONFIGURATION].includes(resolvedOutcome)) {
|
|
321
|
+
if (!pendingDetail) {
|
|
322
|
+
scheduleCodexAppServerFinalizingRecovery(normalizedSessionId, normalizedThreadId, normalizedTurnId, {
|
|
323
|
+
failureDetail: { error, ok, outcome: resolvedOutcome, provider, status: normalizedStatus,
|
|
324
|
+
usageLimitExceeded, verifyInactive,
|
|
325
|
+
expiresAt: Math.min(observedAt + failureDetailGraceMs,
|
|
326
|
+
pendingTimer?.expiresAt ?? Infinity) }
|
|
327
|
+
});
|
|
328
|
+
}
|
|
329
|
+
return { ok: true, processed: false, reason: "failure_detail_pending", status: normalizedStatus };
|
|
330
|
+
}
|
|
331
|
+
if (resolvedOutcome === turnOutcomes.PROVIDER_FAILURE) {
|
|
332
|
+
// Saved output is still useful, but cannot turn a known provider failure
|
|
333
|
+
// into successful completion. Persist it without settling the turn;
|
|
334
|
+
// the failure notice and checkpoint below own the terminal outcome.
|
|
335
|
+
await output.submitAssistantResult(normalizedSessionId, normalizedThreadId, normalizedTurnId, {
|
|
336
|
+
recoverFromProvider: true
|
|
337
|
+
});
|
|
338
|
+
} else {
|
|
339
|
+
const recovered = await settlement.recoverFinalResponseBeforeOutcome(
|
|
340
|
+
normalizedSessionId,
|
|
341
|
+
normalizedThreadId,
|
|
342
|
+
normalizedTurnId,
|
|
343
|
+
normalizedStatus
|
|
344
|
+
);
|
|
345
|
+
if (recovered?.processed) {
|
|
346
|
+
return recovered;
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
// History recovery awaited the provider. Confirm ownership and activity
|
|
350
|
+
// again before publishing a failure: a successor, Stop or goal
|
|
351
|
+
// continuation may own the run after that gap.
|
|
352
|
+
const staleAfterRecovery = await staleFailure();
|
|
353
|
+
if (staleAfterRecovery) {
|
|
354
|
+
return staleAfterRecovery;
|
|
355
|
+
}
|
|
356
|
+
if (verifyInactive && await codexAppServerProviderBlocksTurnRelease(normalizedSessionId, provider, normalizedThreadId, normalizedTurnId, {
|
|
357
|
+
source: "provider_failure"
|
|
358
|
+
})) {
|
|
359
|
+
return {
|
|
360
|
+
ok: true,
|
|
361
|
+
processed: false,
|
|
362
|
+
reason: "provider_still_active",
|
|
363
|
+
status: "inProgress"
|
|
364
|
+
};
|
|
365
|
+
}
|
|
366
|
+
const message = codexAppServerStoppedTurnMessage(normalizedStatus, error, resolvedOutcome);
|
|
367
|
+
await outcomeNotice(
|
|
368
|
+
runtime,
|
|
369
|
+
normalizedSessionId,
|
|
370
|
+
normalizedThreadId,
|
|
371
|
+
normalizedTurnId,
|
|
372
|
+
resolvedOutcome,
|
|
373
|
+
error,
|
|
374
|
+
{ usageLimitExceeded }
|
|
375
|
+
);
|
|
376
|
+
await journal.markTurnIdle(normalizedSessionId, {
|
|
377
|
+
error: message,
|
|
378
|
+
status: normalizedStatus,
|
|
379
|
+
threadId: normalizedThreadId,
|
|
380
|
+
turnId: normalizedTurnId,
|
|
381
|
+
turnOutcome: resolvedOutcome
|
|
382
|
+
});
|
|
383
|
+
output.cleanupUntrackedTurn(normalizedThreadId, normalizedTurnId);
|
|
384
|
+
return {
|
|
385
|
+
ok,
|
|
386
|
+
error: message,
|
|
387
|
+
status: normalizedStatus
|
|
388
|
+
};
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
async function stopCodexAppServerTurnWithResultDeliveryFailure(sessionId = "", threadId = "", turnId = "", {
|
|
392
|
+
error = "",
|
|
393
|
+
reason = "",
|
|
394
|
+
status = "completed"
|
|
395
|
+
} = {}) {
|
|
396
|
+
const normalizedSessionId = normalizeText(sessionId);
|
|
397
|
+
const normalizedThreadId = normalizeText(threadId);
|
|
398
|
+
const normalizedStatus = normalizeText(status) || "completed";
|
|
399
|
+
const normalizedTurnId = await journal.resolveTurnId(normalizedSessionId, normalizedThreadId, turnId);
|
|
400
|
+
const recovered = await settlement.recoverFinalResponseBeforeOutcome(
|
|
401
|
+
normalizedSessionId,
|
|
402
|
+
normalizedThreadId,
|
|
403
|
+
normalizedTurnId,
|
|
404
|
+
normalizedStatus
|
|
405
|
+
);
|
|
406
|
+
if (recovered?.processed) {
|
|
407
|
+
return recovered;
|
|
408
|
+
}
|
|
409
|
+
// The history read can outlive this turn: a successor or Stop may own the
|
|
410
|
+
// run by now, so the outcome is decided against the run read after it.
|
|
411
|
+
const runtime = await createRuntime();
|
|
412
|
+
const currentTurn = turnState(await runtime.getSession(normalizedSessionId));
|
|
413
|
+
if (!codexAppServerTurnCanReceiveProviderCompletion(currentTurn, normalizedThreadId, normalizedTurnId)) {
|
|
414
|
+
output.finalizedTurns.add(output.resultFinalizationKey(
|
|
415
|
+
normalizedSessionId,
|
|
416
|
+
normalizedThreadId,
|
|
417
|
+
normalizedTurnId
|
|
418
|
+
));
|
|
419
|
+
output.cleanupUntrackedTurn(normalizedThreadId, normalizedTurnId);
|
|
420
|
+
debugLog("appServerAgentResult.missing.stale", {
|
|
421
|
+
currentState: currentTurn.state,
|
|
422
|
+
currentStatus: currentTurn.status,
|
|
423
|
+
currentThreadId: currentTurn.threadId,
|
|
424
|
+
currentTurnId: currentTurn.turnId,
|
|
425
|
+
reason: normalizeText(reason),
|
|
426
|
+
sessionId: normalizedSessionId,
|
|
427
|
+
status: normalizedStatus,
|
|
428
|
+
threadId: normalizedThreadId,
|
|
429
|
+
turnId: normalizedTurnId
|
|
430
|
+
});
|
|
431
|
+
return {
|
|
432
|
+
ok: true,
|
|
433
|
+
processed: false,
|
|
434
|
+
reason: "stale_turn_state",
|
|
435
|
+
status: currentTurn.status
|
|
436
|
+
};
|
|
437
|
+
}
|
|
438
|
+
const message = resultDeliveryFailureMessage({
|
|
439
|
+
error
|
|
440
|
+
});
|
|
441
|
+
await outcomeNotice(
|
|
442
|
+
runtime,
|
|
443
|
+
normalizedSessionId,
|
|
444
|
+
normalizedThreadId,
|
|
445
|
+
normalizedTurnId,
|
|
446
|
+
turnOutcomes.RESPONSE_DELIVERY_FAILURE
|
|
447
|
+
);
|
|
448
|
+
await journal.markTurnIdle(normalizedSessionId, {
|
|
449
|
+
error: message,
|
|
450
|
+
status: normalizedStatus,
|
|
451
|
+
threadId: normalizedThreadId,
|
|
452
|
+
turnId: normalizedTurnId,
|
|
453
|
+
turnOutcome: turnOutcomes.RESPONSE_DELIVERY_FAILURE
|
|
454
|
+
});
|
|
455
|
+
output.cleanupUntrackedTurn(normalizedThreadId, normalizedTurnId);
|
|
456
|
+
debugLog("appServerAgentResult.missing", {
|
|
457
|
+
error: normalizeText(error),
|
|
458
|
+
reason: normalizeText(reason),
|
|
459
|
+
sessionId: normalizedSessionId,
|
|
460
|
+
threadId: normalizedThreadId,
|
|
461
|
+
turnId: normalizedTurnId
|
|
462
|
+
});
|
|
463
|
+
return {
|
|
464
|
+
ok: false,
|
|
465
|
+
error: message,
|
|
466
|
+
status: normalizedStatus
|
|
467
|
+
};
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
function scheduleCodexAppServerFinalizingRecovery(sessionId = "", threadId = "", turnId = "", {
|
|
471
|
+
completedAt = "",
|
|
472
|
+
status = "completed",
|
|
473
|
+
updatedAt = "",
|
|
474
|
+
failureDetail = null
|
|
475
|
+
} = {}) {
|
|
476
|
+
const normalizedSessionId = normalizeText(sessionId);
|
|
477
|
+
const normalizedThreadId = normalizeText(threadId);
|
|
478
|
+
const normalizedTurnId = normalizeText(turnId);
|
|
479
|
+
if (!normalizedSessionId || !normalizedThreadId || !normalizedTurnId) {
|
|
480
|
+
return;
|
|
481
|
+
}
|
|
482
|
+
journal.clearFinalizingTimer(normalizedSessionId, normalizedThreadId, normalizedTurnId);
|
|
483
|
+
const key = output.resultFinalizationKey(normalizedSessionId, normalizedThreadId, normalizedTurnId);
|
|
484
|
+
const projectContext = captureContext();
|
|
485
|
+
const delayMs = failureDetail
|
|
486
|
+
? Math.max(0, failureDetail.expiresAt - Date.now())
|
|
487
|
+
: journal.finalizingRemainingMs({ completedAt, state: "finalizing", updatedAt });
|
|
488
|
+
// One record per pending deadline: `recovery` is the in-flight expiry
|
|
489
|
+
// (retained-timer policy only), `expiresAt` the first deadline that a later
|
|
490
|
+
// failure-detail grace may not exceed, `failureDetail` a deferred provider
|
|
491
|
+
// failure, and `startedAt` the post-read clock that rereads must keep.
|
|
492
|
+
const entry = {
|
|
493
|
+
timer: null,
|
|
494
|
+
recovery: null,
|
|
495
|
+
expiresAt: failureDetailGraceMs > 0 ? Date.now() + delayMs : null,
|
|
496
|
+
failureDetail,
|
|
497
|
+
startedAt: !failureDetail && finalizingGraceAfterHistoryRead ? completedAt : ""
|
|
498
|
+
};
|
|
499
|
+
entry.timer = setTimeout(() => {
|
|
500
|
+
if (failureDetail) {
|
|
501
|
+
notificationQueue.run({ projectContext, provider: failureDetail.provider, sessionId: normalizedSessionId }, async () => {
|
|
502
|
+
if (journal.finalizingTimers.get(key) !== entry) return;
|
|
503
|
+
try {
|
|
504
|
+
return await stopCodexAppServerTurnWithProviderFailure(normalizedSessionId, normalizedThreadId, normalizedTurnId,
|
|
505
|
+
{ ...failureDetail, failureDetailReady: true });
|
|
506
|
+
} finally {
|
|
507
|
+
if (journal.finalizingTimers.get(key) === entry) {
|
|
508
|
+
journal.clearFinalizingTimer(normalizedSessionId, normalizedThreadId, normalizedTurnId);
|
|
509
|
+
}
|
|
510
|
+
}
|
|
511
|
+
});
|
|
512
|
+
return;
|
|
513
|
+
}
|
|
514
|
+
// The post-read policy keeps its first clock while expiry rereads history.
|
|
515
|
+
if (!retainFinalizingTimer) journal.finalizingTimers.delete(key);
|
|
516
|
+
const recovery = runInContext(
|
|
517
|
+
projectContext,
|
|
518
|
+
() => recoverCodexAppServerFinalizingTurn(
|
|
519
|
+
normalizedSessionId,
|
|
520
|
+
normalizedThreadId,
|
|
521
|
+
normalizedTurnId,
|
|
522
|
+
{ status, timerRecovery: finalizingGraceAfterHistoryRead }
|
|
523
|
+
)
|
|
524
|
+
);
|
|
525
|
+
if (retainFinalizingTimer) {
|
|
526
|
+
entry.recovery = recovery;
|
|
527
|
+
void recovery.then(
|
|
528
|
+
() => { if (entry.recovery === recovery) entry.recovery = null; },
|
|
529
|
+
error => {
|
|
530
|
+
if (entry.recovery === recovery) entry.recovery = null;
|
|
531
|
+
debugLog("appServerFinalizingRecovery.error", {
|
|
532
|
+
error: debugError(error), sessionId: normalizedSessionId,
|
|
533
|
+
threadId: normalizedThreadId, turnId: normalizedTurnId
|
|
534
|
+
});
|
|
535
|
+
}
|
|
536
|
+
);
|
|
537
|
+
} else {
|
|
538
|
+
void recovery;
|
|
539
|
+
}
|
|
540
|
+
}, delayMs);
|
|
541
|
+
entry.timer.unref();
|
|
542
|
+
journal.finalizingTimers.set(key, entry);
|
|
543
|
+
}
|
|
544
|
+
|
|
545
|
+
async function recoverCodexAppServerFinalizingTurn(sessionId = "", threadId = "", turnId = "", {
|
|
546
|
+
status = "completed", timerRecovery = false
|
|
547
|
+
} = {}) {
|
|
548
|
+
const normalizedSessionId = normalizeText(sessionId);
|
|
549
|
+
const normalizedThreadId = normalizeText(threadId);
|
|
550
|
+
const normalizedTurnId = normalizeText(turnId);
|
|
551
|
+
const key = output.resultFinalizationKey(normalizedSessionId, normalizedThreadId, normalizedTurnId);
|
|
552
|
+
const priorTimer = retainFinalizingTimer ? journal.finalizingTimers.get(key) : null;
|
|
553
|
+
if (!timerRecovery && priorTimer?.recovery) return priorTimer.recovery;
|
|
554
|
+
const runtime = await createRuntime();
|
|
555
|
+
const session = await runtime.getSession(normalizedSessionId);
|
|
556
|
+
const turn = turnState(session);
|
|
557
|
+
if (codexAppServerTurnOwnsActiveGoal(turn, normalizedThreadId)) {
|
|
558
|
+
journal.clearFinalizingTimer(normalizedSessionId, normalizedThreadId, normalizedTurnId);
|
|
559
|
+
return { ok: true, processed: false, reason: "goal_continuation_pending" };
|
|
560
|
+
}
|
|
561
|
+
const result = await settlement.finalizeAssistantResult(normalizedSessionId, normalizedThreadId, normalizedTurnId,
|
|
562
|
+
{ recoverFromProvider: true, status });
|
|
563
|
+
if (result?.processed) {
|
|
564
|
+
return result;
|
|
565
|
+
}
|
|
566
|
+
// A later failure owns the same first deadline, even when this successful
|
|
567
|
+
// history read was already in flight under the completion-based clock.
|
|
568
|
+
const currentTimer = journal.finalizingTimers.get(key);
|
|
569
|
+
if (currentTimer?.failureDetail) return result;
|
|
570
|
+
if (retainFinalizingTimer && priorTimer && !currentTimer) return result;
|
|
571
|
+
let graceTurn = turn;
|
|
572
|
+
if (finalizingGraceAfterHistoryRead) {
|
|
573
|
+
const readReturnedAt = new Date().toISOString();
|
|
574
|
+
const currentTurn = turnState(await runtime.getSession(normalizedSessionId));
|
|
575
|
+
const timer = journal.finalizingTimers.get(key);
|
|
576
|
+
if (timer?.failureDetail || (priorTimer && !timer)) return result;
|
|
577
|
+
if (currentTurn.state !== "finalizing" || currentTurn.threadId !== normalizedThreadId ||
|
|
578
|
+
currentTurn.turnId !== normalizedTurnId) {
|
|
579
|
+
journal.clearFinalizingTimer(normalizedSessionId, normalizedThreadId, normalizedTurnId);
|
|
580
|
+
return { ...result, reason: "stale_turn_state" };
|
|
581
|
+
}
|
|
582
|
+
if (codexAppServerTurnOwnsActiveGoal(currentTurn, normalizedThreadId)) {
|
|
583
|
+
journal.clearFinalizingTimer(normalizedSessionId, normalizedThreadId, normalizedTurnId);
|
|
584
|
+
return { ok: true, processed: false, reason: "goal_continuation_pending" };
|
|
585
|
+
}
|
|
586
|
+
if (!timerRecovery && timer?.recovery) return timer.recovery;
|
|
587
|
+
// This local clock never changes the native completion timestamp.
|
|
588
|
+
graceTurn = { ...currentTurn, completedAt: timer?.startedAt || readReturnedAt };
|
|
589
|
+
}
|
|
590
|
+
if (!journal.finalizingExpired(graceTurn)) {
|
|
591
|
+
scheduleCodexAppServerFinalizingRecovery(normalizedSessionId, normalizedThreadId, normalizedTurnId, {
|
|
592
|
+
completedAt: graceTurn.completedAt,
|
|
593
|
+
status,
|
|
594
|
+
updatedAt: graceTurn.updatedAt
|
|
595
|
+
});
|
|
596
|
+
return result;
|
|
597
|
+
}
|
|
598
|
+
return stopCodexAppServerTurnWithResultDeliveryFailure(normalizedSessionId, normalizedThreadId, normalizedTurnId,
|
|
599
|
+
{ error: result?.error, reason: result?.reason || "missing_assistant_text", status });
|
|
600
|
+
}
|
|
601
|
+
|
|
602
|
+
return Object.freeze({
|
|
603
|
+
completeTurn: completeCodexAppServerTurn,
|
|
604
|
+
stopTurnWithProviderFailure: stopCodexAppServerTurnWithProviderFailure,
|
|
605
|
+
stopTurnWithResultDeliveryFailure: stopCodexAppServerTurnWithResultDeliveryFailure,
|
|
606
|
+
recoverFinalizingTurn: recoverCodexAppServerFinalizingTurn
|
|
607
|
+
});
|
|
608
|
+
}
|
|
609
|
+
|
|
610
|
+
/** @typedef {ReturnType<typeof createCodexTurnEndings>} CodexTurnEndings */
|