@jskit-ai/assistant-core 0.1.200 → 0.1.202
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/examples/conversation/README.md +8 -1
- package/examples/conversation/package.json +5 -5
- package/fixtures/responsive-assistant/App.vue +4 -1
- package/package.json +99 -36
- package/src/client/conversation/AssistantConversationElement.vue +27 -4
- package/src/client/conversation/AssistantConversationStatus.vue +24 -0
- package/src/client/conversation/AssistantGoalControl.vue +8 -5
- package/src/client/conversation/AssistantTranscript.vue +10 -3
- package/src/client/conversation/index.js +2 -0
- package/src/client/conversation/messageDelivery.js +115 -32
- package/src/client/conversation/retainedConversation.js +32 -0
- package/src/client/conversation/useAssistantQuestions.js +76 -0
- package/src/client/lib/assistantApi.js +72 -2
- package/src/server/conversation/agentRun.js +222 -0
- package/src/server/conversation/attachments.js +93 -0
- package/src/server/conversation/claudeClient.js +221 -0
- package/src/server/conversation/claudeHistory.js +256 -0
- package/src/server/conversation/claudeProcess.js +327 -0
- package/src/server/conversation/claudeTools.js +42 -0
- package/src/server/conversation/claudeTurn.js +1082 -0
- package/src/server/conversation/codexConfiguration.js +1183 -0
- package/src/server/conversation/codexConversations.js +1082 -0
- package/src/server/conversation/codexDetachedTurn.js +332 -0
- package/src/server/conversation/codexEvents.js +194 -1
- package/src/server/conversation/codexGoalCommands.js +124 -0
- package/src/server/conversation/codexHelperExecution.js +306 -0
- package/src/server/conversation/codexHelperThreadLedger.js +804 -0
- package/src/server/conversation/codexHelperThreadRecovery.js +395 -0
- package/src/server/conversation/codexHelperThreads.js +638 -0
- package/src/server/conversation/codexHistoryAdapter.js +369 -0
- package/src/server/conversation/codexMessageCommands.js +663 -0
- package/src/server/conversation/codexMessageDelivery.js +211 -0
- package/src/server/conversation/codexNativeHistoryExport.js +95 -0
- package/src/server/conversation/codexNotificationObserver.js +482 -0
- package/src/server/conversation/codexNotificationQueue.js +103 -0
- package/src/server/conversation/codexObservationControl.js +174 -0
- package/src/server/conversation/codexOutput.js +931 -0
- package/src/server/conversation/codexProcess.js +1559 -0
- package/src/server/conversation/codexProcessLeader.js +14 -0
- package/src/server/conversation/codexProvider.js +2710 -0
- package/src/server/conversation/codexProviderOwner.js +1058 -0
- package/src/server/conversation/codexProviderSelection.js +218 -0
- package/src/server/conversation/codexReasoning.js +314 -0
- package/src/server/conversation/codexRenewalCommands.js +290 -0
- package/src/server/conversation/codexResultSettlement.js +409 -0
- package/src/server/conversation/codexRunJournal.js +658 -0
- package/src/server/conversation/codexRuntimeLifecycle.js +288 -0
- package/src/server/conversation/codexSessionCleanup.js +96 -0
- package/src/server/conversation/codexSessionReconciliation.js +284 -0
- package/src/server/conversation/codexThreadReadiness.js +397 -0
- package/src/server/conversation/codexThreadStatus.js +354 -0
- package/src/server/conversation/codexTurn.js +701 -255
- package/src/server/conversation/codexTurnControl.js +317 -0
- package/src/server/conversation/codexTurnRecovery.js +990 -0
- package/src/server/conversation/codexTurnState.js +549 -0
- package/src/server/conversation/commandHook.js +18 -0
- package/src/server/conversation/commandWrapper.js +37 -0
- package/src/server/conversation/configuration.js +43 -0
- package/src/server/conversation/continuity.js +289 -0
- package/src/server/conversation/fileStorage.js +55 -0
- package/src/server/conversation/hookBridge.js +19 -0
- package/src/server/conversation/index.js +13 -0
- package/src/server/conversation/localExecution.js +665 -0
- package/src/server/conversation/memoryStorage.js +7 -72
- package/src/server/conversation/nativeHistoryExport.js +90 -0
- package/src/server/conversation/nativeStorage.js +23 -0
- package/src/server/conversation/openCodeClient.js +209 -19
- package/src/server/conversation/openCodePlugin.js +84 -0
- package/src/server/conversation/openCodeProcess.js +476 -0
- package/src/server/conversation/openCodeRuntime.js +1578 -0
- package/src/server/conversation/openCodeTools.js +60 -0
- package/src/server/conversation/openCodeTurn.js +557 -0
- package/src/server/conversation/provider.js +30 -0
- package/src/server/conversation/providers/api.js +136 -0
- package/src/server/conversation/providers/claude.js +63 -0
- package/src/server/conversation/providers/claudeDriver.js +425 -0
- package/src/server/conversation/providers/codex.js +248 -0
- package/src/server/conversation/providers/codexDriver.js +1078 -0
- package/src/server/conversation/providers/opencode.js +49 -0
- package/src/server/conversation/providers/opencodeDriver.js +650 -0
- package/src/server/conversation/providers/scoped.js +312 -0
- package/src/server/conversation/runtime.js +2220 -0
- package/src/server/conversation/runtimeStateUpgrade.js +125 -0
- package/src/server/conversation/storage.js +184 -0
- package/src/server/conversation/streams.js +7 -2
- package/src/server/conversation/structuredOutput.js +240 -0
- package/src/server/conversation/systemPrompt.js +54 -0
- package/src/server/conversation/tools.js +84 -0
- package/src/server/conversation/transcript.js +5 -4
- package/src/server/hosts/claudeHistory.js +5 -0
- package/src/server/hosts/claudeProcess.js +11 -0
- package/src/server/hosts/claudeTurn.js +4 -0
- package/src/server/hosts/codexConfiguration.js +20 -0
- package/src/server/hosts/codexEvents.js +13 -0
- package/src/server/hosts/codexProcess.js +12 -0
- package/src/server/hosts/codexProvider.js +23 -0
- package/src/server/hosts/codexTurn.js +20 -0
- package/src/server/hosts/nativeHistory.js +3 -0
- package/src/server/hosts/openCodeClient.js +3 -0
- package/src/server/hosts/openCodeProcess.js +23 -0
- package/src/server/hosts/openCodeTurn.js +7 -0
- package/src/server/index.js +2 -0
- package/src/server/lib/aiConnectionClient.js +13 -6
- package/src/server/lib/assistantCompletion.js +190 -0
- package/src/server/lib/assistantToolLoop.js +314 -0
- package/src/server/lib/serviceToolCatalog.js +25 -10
- package/src/shared/assistantResponseText.js +29 -0
- package/src/shared/conversation/attachments.js +19 -0
- package/src/shared/conversation/index.js +3 -0
- package/src/shared/conversation/pagination.js +78 -0
- package/src/shared/conversation/patches.js +150 -0
- package/src/shared/conversation/questions.js +48 -0
- package/src/shared/conversation/streaming.js +7 -4
- package/src/shared/index.js +2 -0
- package/src/shared/nativeProviders.js +45 -0
- package/src/testing/conversationStorageContract.js +31 -1
- package/src/testing/nativeCodex.js +11 -0
- package/src/testing/nativeOpenCode.js +3 -0
- package/test/aiConnectionClient.test.js +2 -0
- package/test/apiConversationDriver.test.js +73 -0
- package/test/assistantApiSurfaceHeader.test.js +85 -0
- package/test/assistantBoundedToolLoop.test.js +285 -0
- package/test/assistantScroll.browser.test.js +16 -0
- package/test/claudeClient.test.js +189 -0
- package/test/claudeConversationSession.test.js +1173 -0
- package/test/claudeInstructionResume.test.js +82 -0
- package/test/claudeProcess.test.js +204 -0
- package/test/claudeTurn.test.js +253 -0
- package/test/codexConversationSession.test.js +2122 -0
- package/test/codexGoal.test.js +37 -0
- package/test/codexHelperDeletion.test.js +163 -0
- package/test/codexHelperThreadLedger.test.js +494 -0
- package/test/codexHistoryAdapter.test.js +541 -0
- package/test/codexInteractiveArguments.test.js +30 -0
- package/test/codexIsolation.test.js +756 -0
- package/test/codexModelCatalog.test.js +362 -0
- package/test/codexNativeHistoryExport.test.js +81 -0
- package/test/codexThreadSelection.test.js +136 -0
- package/test/codexTurn.test.js +2158 -0
- package/test/conversation.test.js +110 -0
- package/test/conversationAgentRun.test.js +326 -0
- package/test/conversationApiFacilities.test.js +90 -0
- package/test/conversationAttachments.test.js +238 -0
- package/test/conversationCommandWrapper.test.js +48 -0
- package/test/conversationConfiguration.test.js +174 -0
- package/test/conversationNativeStorage.test.js +25 -0
- package/test/conversationReplacement.test.js +172 -0
- package/test/conversationRuntime.test.js +241 -0
- package/test/conversationRuntimeStateUpgrade.test.js +144 -0
- package/test/conversationSession.test.js +1080 -0
- package/test/conversationStreaming.test.js +68 -0
- package/test/conversationSystemPrompt.test.js +84 -0
- package/test/conversationTools.test.js +416 -0
- package/test/fileConversationStorage.test.js +73 -0
- package/test/fixtures/codexHelperThreadLedger.js +39 -0
- package/test/localConversationExecution.test.js +90 -0
- package/test/messageDelivery.test.js +234 -1
- package/test/nativeConversationRetirement.test.js +215 -0
- package/test/openCodeClient.test.js +515 -0
- package/test/openCodeProcess.test.js +177 -0
- package/test/openCodeRuntime.test.js +322 -0
- package/test/openCodeTools.test.js +287 -0
- package/test/opencodeConversationSession.test.js +789 -0
- package/test/serviceToolCatalog.test.js +24 -0
|
@@ -0,0 +1,990 @@
|
|
|
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";
|
|
25
|
+
|
|
26
|
+
// Original active/finalizing recovery, provider activity and completion decisions.
|
|
27
|
+
// Timers and receipts remain owned by the shared journal/output/settlement instances.
|
|
28
|
+
export function createCodexTurnRecovery({
|
|
29
|
+
journal,
|
|
30
|
+
output,
|
|
31
|
+
settlement,
|
|
32
|
+
promptDeliveries,
|
|
33
|
+
namespace,
|
|
34
|
+
createRuntime,
|
|
35
|
+
createStore,
|
|
36
|
+
acquireProvider,
|
|
37
|
+
turnState,
|
|
38
|
+
turnStateFromAgentRun,
|
|
39
|
+
debugLog,
|
|
40
|
+
debugError,
|
|
41
|
+
captureContext,
|
|
42
|
+
runInContext,
|
|
43
|
+
activeReconcileMs,
|
|
44
|
+
hasRuntime,
|
|
45
|
+
recoverAdmission,
|
|
46
|
+
turnOutcomes,
|
|
47
|
+
outcomeNotice,
|
|
48
|
+
resultDeliveryFailureMessage
|
|
49
|
+
}) {
|
|
50
|
+
const {
|
|
51
|
+
finalizedTurns: codexAppServerFinalizedTurns,
|
|
52
|
+
resultFinalizationKey: codexAppServerResultFinalizationKey,
|
|
53
|
+
readAgentRunForSession: readCodexAppServerAgentRunForSession,
|
|
54
|
+
cleanupUntrackedTurn: cleanupCodexAppServerUntrackedTurn
|
|
55
|
+
} = output;
|
|
56
|
+
const {
|
|
57
|
+
completedTurns: codexAppServerCompletedTurns,
|
|
58
|
+
activeTimers: codexAppServerActiveTimers,
|
|
59
|
+
finalizingTimers: codexAppServerFinalizingTimers,
|
|
60
|
+
turnResultWasProcessed: codexAppServerTurnResultWasProcessed,
|
|
61
|
+
clearActiveTimer: clearCodexAppServerActiveTimer,
|
|
62
|
+
clearFinalizingTimer: clearCodexAppServerFinalizingTimer,
|
|
63
|
+
publishAgentRun: publishCodexAppServerAgentRun,
|
|
64
|
+
markTurnFinalizing: markCodexAppServerTurnFinalizing,
|
|
65
|
+
markTurnIdle: markCodexAppServerTurnIdle,
|
|
66
|
+
resolveTurnId: resolveCodexAppServerTurnId,
|
|
67
|
+
finalizingExpired: codexAppServerFinalizingExpired,
|
|
68
|
+
finalizingRemainingMs: codexAppServerFinalizingRemainingMs,
|
|
69
|
+
turnWasCompleted: codexAppServerTurnWasCompleted,
|
|
70
|
+
runInputSource: codexAppServerRunInputSource,
|
|
71
|
+
writeUserMessageOwnership: writeCodexAppServerUserMessageOwnership,
|
|
72
|
+
recoverAbandonedPromptClaim: recoverAbandonedCodexAppServerPromptClaim,
|
|
73
|
+
failOrphanedPromptDelivery: failOrphanedCodexAppServerPromptDelivery
|
|
74
|
+
} = journal;
|
|
75
|
+
const {
|
|
76
|
+
finalizeAssistantResult: finalizeCodexAppServerAssistantResult,
|
|
77
|
+
recoverFinalResponseBeforeOutcome: recoverCodexAppServerFinalResponseBeforeOutcome,
|
|
78
|
+
adoptSuccessorTurn: adoptCodexAppServerSuccessorTurn
|
|
79
|
+
} = settlement;
|
|
80
|
+
const codexAppServerPromptDeliveries = promptDeliveries;
|
|
81
|
+
|
|
82
|
+
function scheduleCodexAppServerActiveRecovery(sessionId = "", delayMs = activeReconcileMs) {
|
|
83
|
+
const normalizedSessionId = normalizeCodexRunText(sessionId);
|
|
84
|
+
const sessionKey = namespace(normalizedSessionId);
|
|
85
|
+
const projectContext = captureContext();
|
|
86
|
+
if (!normalizedSessionId || codexAppServerActiveTimers.has(sessionKey)) {
|
|
87
|
+
return;
|
|
88
|
+
}
|
|
89
|
+
const timer = setTimeout(() => {
|
|
90
|
+
codexAppServerActiveTimers.delete(sessionKey);
|
|
91
|
+
void runInContext(
|
|
92
|
+
projectContext,
|
|
93
|
+
() => recoverCodexAppServerActiveTurn(normalizedSessionId)
|
|
94
|
+
);
|
|
95
|
+
}, delayMs);
|
|
96
|
+
timer.unref?.();
|
|
97
|
+
codexAppServerActiveTimers.set(sessionKey, timer);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
function scheduleCodexAppServerFinalizingRecovery(sessionId = "", threadId = "", turnId = "", {
|
|
101
|
+
completedAt = "",
|
|
102
|
+
status = "completed",
|
|
103
|
+
updatedAt = ""
|
|
104
|
+
} = {}) {
|
|
105
|
+
const normalizedSessionId = normalizeCodexRunText(sessionId);
|
|
106
|
+
const normalizedThreadId = normalizeCodexRunText(threadId);
|
|
107
|
+
const normalizedTurnId = normalizeCodexRunText(turnId);
|
|
108
|
+
if (!normalizedSessionId || !normalizedThreadId || !normalizedTurnId) {
|
|
109
|
+
return;
|
|
110
|
+
}
|
|
111
|
+
clearCodexAppServerFinalizingTimer(normalizedSessionId, normalizedThreadId, normalizedTurnId);
|
|
112
|
+
const key = codexAppServerResultFinalizationKey(
|
|
113
|
+
normalizedSessionId,
|
|
114
|
+
normalizedThreadId,
|
|
115
|
+
normalizedTurnId
|
|
116
|
+
);
|
|
117
|
+
const projectContext = captureContext();
|
|
118
|
+
const delayMs = codexAppServerFinalizingRemainingMs({
|
|
119
|
+
completedAt,
|
|
120
|
+
state: "finalizing",
|
|
121
|
+
updatedAt
|
|
122
|
+
});
|
|
123
|
+
const timer = setTimeout(() => {
|
|
124
|
+
codexAppServerFinalizingTimers.delete(key);
|
|
125
|
+
void runInContext(
|
|
126
|
+
projectContext,
|
|
127
|
+
() => recoverCodexAppServerFinalizingTurn(
|
|
128
|
+
normalizedSessionId,
|
|
129
|
+
normalizedThreadId,
|
|
130
|
+
normalizedTurnId,
|
|
131
|
+
{ status }
|
|
132
|
+
)
|
|
133
|
+
);
|
|
134
|
+
}, delayMs);
|
|
135
|
+
timer.unref?.();
|
|
136
|
+
codexAppServerFinalizingTimers.set(key, timer);
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
async function reconcileCodexAppServerActiveTurn(session = {}, {
|
|
140
|
+
provider: suppliedProvider = null,
|
|
141
|
+
runtime = null
|
|
142
|
+
} = {}) {
|
|
143
|
+
const sessionId = normalizeCodexRunText(session.sessionId);
|
|
144
|
+
const trackedTurn = turnState(session);
|
|
145
|
+
if (!sessionId || !trackedTurn.active || !trackedTurn.threadId || !hasRuntime(session)) {
|
|
146
|
+
return session;
|
|
147
|
+
}
|
|
148
|
+
const provider = suppliedProvider || await acquireProvider({ sessionId, runtime, session });
|
|
149
|
+
if (typeof provider?.readThreadStatus !== "function") {
|
|
150
|
+
if (trackedTurn.state === "finalizing") {
|
|
151
|
+
await recoverCodexAppServerFinalizingTurn(
|
|
152
|
+
sessionId,
|
|
153
|
+
trackedTurn.threadId,
|
|
154
|
+
trackedTurn.turnId,
|
|
155
|
+
{
|
|
156
|
+
status: trackedTurn.status || "completed"
|
|
157
|
+
}
|
|
158
|
+
);
|
|
159
|
+
const runtime = await createRuntime();
|
|
160
|
+
return runtime.getSession(sessionId);
|
|
161
|
+
}
|
|
162
|
+
return session;
|
|
163
|
+
}
|
|
164
|
+
const activeRuntime = runtime || await createRuntime();
|
|
165
|
+
let currentSession = session;
|
|
166
|
+
let currentTurn = trackedTurn;
|
|
167
|
+
let thread = await codexAppServerReadThreadStatus(provider, trackedTurn.threadId);
|
|
168
|
+
let status = codexAppServerThreadStatus(thread);
|
|
169
|
+
let providerTurnId = codexAppServerThreadTurnId(thread);
|
|
170
|
+
const promptDeliveryIsLocal = codexAppServerPromptDeliveries.has(
|
|
171
|
+
namespace(sessionId)
|
|
172
|
+
);
|
|
173
|
+
if (
|
|
174
|
+
currentTurn.state === "starting" &&
|
|
175
|
+
!currentTurn.turnId &&
|
|
176
|
+
!promptDeliveryIsLocal
|
|
177
|
+
) {
|
|
178
|
+
const ownership = codexAppServerPendingUserMessageOwnership(
|
|
179
|
+
codexAppServerAgentRun(currentSession)
|
|
180
|
+
);
|
|
181
|
+
if (!ownership || typeof provider?.readThread !== "function") {
|
|
182
|
+
return failOrphanedCodexAppServerPromptDelivery(
|
|
183
|
+
activeRuntime,
|
|
184
|
+
currentSession,
|
|
185
|
+
currentTurn
|
|
186
|
+
);
|
|
187
|
+
}
|
|
188
|
+
const providerThread = await provider.readThread(currentTurn.threadId);
|
|
189
|
+
const ownedProviderTurn = codexAppServerProviderTurnForOperation(providerThread, {
|
|
190
|
+
clientMessageId: ownership.clientId
|
|
191
|
+
});
|
|
192
|
+
if (!ownedProviderTurn) {
|
|
193
|
+
return failOrphanedCodexAppServerPromptDelivery(
|
|
194
|
+
activeRuntime,
|
|
195
|
+
currentSession,
|
|
196
|
+
currentTurn
|
|
197
|
+
);
|
|
198
|
+
}
|
|
199
|
+
thread = {
|
|
200
|
+
...providerThread,
|
|
201
|
+
observedTurn: ownedProviderTurn
|
|
202
|
+
};
|
|
203
|
+
status = codexAppServerThreadStatus(thread);
|
|
204
|
+
providerTurnId = codexAppServerThreadTurnId(thread);
|
|
205
|
+
if (!providerTurnId) {
|
|
206
|
+
return failOrphanedCodexAppServerPromptDelivery(
|
|
207
|
+
activeRuntime,
|
|
208
|
+
currentSession,
|
|
209
|
+
currentTurn
|
|
210
|
+
);
|
|
211
|
+
}
|
|
212
|
+
await markCodexAppServerProviderTurnActive(sessionId, {
|
|
213
|
+
inputSource: ownership.inputSource,
|
|
214
|
+
source: "owned_prompt_recovery",
|
|
215
|
+
status: codexAppServerTurnStatusIsActive(status) ? status : "inProgress",
|
|
216
|
+
threadId: currentTurn.threadId,
|
|
217
|
+
turnId: providerTurnId
|
|
218
|
+
});
|
|
219
|
+
await writeCodexAppServerUserMessageOwnership(
|
|
220
|
+
activeRuntime.store,
|
|
221
|
+
sessionId,
|
|
222
|
+
ownership.clientId,
|
|
223
|
+
{
|
|
224
|
+
eventKind: "codex-app-server-user-message-consumed",
|
|
225
|
+
owned: false
|
|
226
|
+
}
|
|
227
|
+
);
|
|
228
|
+
currentSession = await activeRuntime.getSession(sessionId);
|
|
229
|
+
currentTurn = turnState(currentSession);
|
|
230
|
+
debugLog("appServerPrompt.recovered", {
|
|
231
|
+
clientMessageId: ownership.clientId,
|
|
232
|
+
sessionId,
|
|
233
|
+
status,
|
|
234
|
+
threadId: currentTurn.threadId,
|
|
235
|
+
turnId: currentTurn.turnId
|
|
236
|
+
});
|
|
237
|
+
}
|
|
238
|
+
if (
|
|
239
|
+
providerTurnId &&
|
|
240
|
+
codexAppServerTurnCanAdoptSuccessor(currentTurn, currentTurn.threadId, providerTurnId)
|
|
241
|
+
) {
|
|
242
|
+
const adoption = await adoptCodexAppServerSuccessorTurn(sessionId, {
|
|
243
|
+
previousTurnId: currentTurn.turnId,
|
|
244
|
+
source: "active_reconciliation",
|
|
245
|
+
status: "inProgress",
|
|
246
|
+
threadId: currentTurn.threadId,
|
|
247
|
+
turnId: providerTurnId
|
|
248
|
+
});
|
|
249
|
+
currentSession = await activeRuntime.getSession(sessionId);
|
|
250
|
+
currentTurn = turnState(currentSession);
|
|
251
|
+
if (!adoption.processed && adoption.reason !== "already_current") {
|
|
252
|
+
if (currentTurn.state === "starting") {
|
|
253
|
+
scheduleCodexAppServerActiveRecovery(sessionId);
|
|
254
|
+
}
|
|
255
|
+
return currentSession;
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
if (currentTurn.state === "finalizing") {
|
|
259
|
+
await recoverCodexAppServerFinalizingTurn(sessionId, currentTurn.threadId, currentTurn.turnId, {
|
|
260
|
+
status: currentTurn.status || "completed"
|
|
261
|
+
});
|
|
262
|
+
return activeRuntime.getSession(sessionId);
|
|
263
|
+
}
|
|
264
|
+
if (currentTurn.state === "starting") {
|
|
265
|
+
if (codexAppServerTurnStatusIsActive(status) && providerTurnId) {
|
|
266
|
+
await markCodexAppServerProviderTurnActive(sessionId, {
|
|
267
|
+
source: "active_reconciliation",
|
|
268
|
+
status,
|
|
269
|
+
threadId: currentTurn.threadId,
|
|
270
|
+
turnId: providerTurnId
|
|
271
|
+
});
|
|
272
|
+
return activeRuntime.getSession(sessionId);
|
|
273
|
+
}
|
|
274
|
+
if (codexAppServerTurnStatusIsActive(status) || !status) {
|
|
275
|
+
scheduleCodexAppServerActiveRecovery(sessionId);
|
|
276
|
+
return currentSession;
|
|
277
|
+
}
|
|
278
|
+
if (!promptDeliveryIsLocal && (!providerTurnId || !codexAppServerTurnStatusIsComplete(status))) {
|
|
279
|
+
return failOrphanedCodexAppServerPromptDelivery(activeRuntime, currentSession, currentTurn);
|
|
280
|
+
}
|
|
281
|
+
if (promptDeliveryIsLocal) {
|
|
282
|
+
return currentSession;
|
|
283
|
+
}
|
|
284
|
+
await markCodexAppServerProviderTurnActive(sessionId, {
|
|
285
|
+
source: "active_reconciliation",
|
|
286
|
+
status: "inProgress",
|
|
287
|
+
threadId: currentTurn.threadId,
|
|
288
|
+
turnId: providerTurnId
|
|
289
|
+
});
|
|
290
|
+
currentSession = await activeRuntime.getSession(sessionId);
|
|
291
|
+
currentTurn = turnState(currentSession);
|
|
292
|
+
}
|
|
293
|
+
if (!status || codexAppServerTurnStatusIsActive(status)) {
|
|
294
|
+
if (currentTurn.state === "starting") {
|
|
295
|
+
scheduleCodexAppServerActiveRecovery(sessionId);
|
|
296
|
+
}
|
|
297
|
+
return currentSession;
|
|
298
|
+
}
|
|
299
|
+
if (!codexAppServerTurnStatusIsComplete(status)) {
|
|
300
|
+
return currentSession;
|
|
301
|
+
}
|
|
302
|
+
const completedTurnId = providerTurnId || currentTurn.turnId;
|
|
303
|
+
debugLog("appServerTurn.reconcile.complete", {
|
|
304
|
+
sessionId,
|
|
305
|
+
status,
|
|
306
|
+
threadId: currentTurn.threadId,
|
|
307
|
+
turnId: completedTurnId
|
|
308
|
+
});
|
|
309
|
+
if (codexAppServerTurnStatusIsProviderFailure(status)) {
|
|
310
|
+
await stopCodexAppServerTurnWithProviderFailure(sessionId, currentTurn.threadId, completedTurnId, {
|
|
311
|
+
error: codexAppServerThreadError(thread),
|
|
312
|
+
provider,
|
|
313
|
+
status
|
|
314
|
+
});
|
|
315
|
+
} else if (codexAppServerTurnStatusIsSuccessfulComplete(status)) {
|
|
316
|
+
await completeCodexAppServerTurn(sessionId, currentTurn.threadId, completedTurnId, {
|
|
317
|
+
status,
|
|
318
|
+
verifyInactive: false
|
|
319
|
+
});
|
|
320
|
+
}
|
|
321
|
+
return activeRuntime.getSession(sessionId);
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
async function codexAppServerProviderBlocksTurnRelease(sessionId = "", provider = null, threadId = "", turnId = "", {
|
|
325
|
+
source = ""
|
|
326
|
+
} = {}) {
|
|
327
|
+
const normalizedSessionId = normalizeCodexRunText(sessionId);
|
|
328
|
+
const normalizedThreadId = normalizeCodexRunText(threadId);
|
|
329
|
+
const normalizedTurnId = normalizeCodexRunText(turnId);
|
|
330
|
+
if (!normalizedSessionId || !normalizedThreadId) {
|
|
331
|
+
return false;
|
|
332
|
+
}
|
|
333
|
+
const runtime = await createRuntime();
|
|
334
|
+
const session = await runtime.getSession(normalizedSessionId);
|
|
335
|
+
const turn = turnState(session);
|
|
336
|
+
if (!turn.active || !codexAppServerTurnCanReceiveProviderActivity(turn, normalizedThreadId, normalizedTurnId)) {
|
|
337
|
+
return false;
|
|
338
|
+
}
|
|
339
|
+
try {
|
|
340
|
+
const providerThread = await codexAppServerReadThreadStatus(provider, normalizedThreadId, {
|
|
341
|
+
observeLatestTurn: true
|
|
342
|
+
});
|
|
343
|
+
if (!providerThread) {
|
|
344
|
+
return false;
|
|
345
|
+
}
|
|
346
|
+
const status = codexAppServerThreadStatus(providerThread);
|
|
347
|
+
const providerTurnId = codexAppServerThreadTurnId(providerThread);
|
|
348
|
+
if (
|
|
349
|
+
providerTurnId &&
|
|
350
|
+
providerTurnId !== normalizeCodexRunText(turn.turnId)
|
|
351
|
+
) {
|
|
352
|
+
debugLog("appServerTurn.releaseAllowedSuccessor", {
|
|
353
|
+
providerTurnId,
|
|
354
|
+
sessionId: normalizedSessionId,
|
|
355
|
+
source: normalizeCodexRunText(source),
|
|
356
|
+
threadId: normalizedThreadId,
|
|
357
|
+
turnId: turn.turnId
|
|
358
|
+
});
|
|
359
|
+
return false;
|
|
360
|
+
}
|
|
361
|
+
if (!codexAppServerTurnStatusIsActive(status)) {
|
|
362
|
+
return false;
|
|
363
|
+
}
|
|
364
|
+
debugLog("appServerTurn.releaseBlockedActive", {
|
|
365
|
+
sessionId: normalizedSessionId,
|
|
366
|
+
source: normalizeCodexRunText(source),
|
|
367
|
+
status,
|
|
368
|
+
threadId: normalizedThreadId,
|
|
369
|
+
turnId: normalizedTurnId || turn.turnId
|
|
370
|
+
});
|
|
371
|
+
scheduleCodexAppServerActiveRecovery(normalizedSessionId);
|
|
372
|
+
return true;
|
|
373
|
+
} catch (error) {
|
|
374
|
+
debugLog("appServerTurn.releaseCheck.error", {
|
|
375
|
+
error: debugError(error),
|
|
376
|
+
sessionId: normalizedSessionId,
|
|
377
|
+
source: normalizeCodexRunText(source),
|
|
378
|
+
threadId: normalizedThreadId,
|
|
379
|
+
turnId: normalizedTurnId || turn.turnId
|
|
380
|
+
});
|
|
381
|
+
return false;
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
async function markCodexAppServerTurnActive(sessionId = "", input = {}) {
|
|
386
|
+
if (input.requireTrackedTurn === true) {
|
|
387
|
+
const runtime = await createRuntime();
|
|
388
|
+
const session = await runtime.getSession(sessionId);
|
|
389
|
+
const turn = turnState(session);
|
|
390
|
+
if (!codexAppServerTurnCanReceiveProviderActivity(turn, input.threadId, input.turnId)) {
|
|
391
|
+
debugLog("appServerTurn.active.ignored", {
|
|
392
|
+
currentState: turn.state,
|
|
393
|
+
currentStatus: turn.status,
|
|
394
|
+
currentThreadId: turn.threadId,
|
|
395
|
+
currentTurnId: turn.turnId,
|
|
396
|
+
sessionId: normalizeCodexRunText(sessionId),
|
|
397
|
+
threadId: normalizeCodexRunText(input.threadId),
|
|
398
|
+
turnId: normalizeCodexRunText(input.turnId)
|
|
399
|
+
});
|
|
400
|
+
return {
|
|
401
|
+
ok: true,
|
|
402
|
+
processed: false,
|
|
403
|
+
reason: "untracked_terminal_turn"
|
|
404
|
+
};
|
|
405
|
+
}
|
|
406
|
+
}
|
|
407
|
+
const status = normalizeCodexRunText(input.status) || "inProgress";
|
|
408
|
+
const result = await publishCodexAppServerAgentRun(sessionId, {
|
|
409
|
+
inputSource: normalizeCodexRunText(input.inputSource),
|
|
410
|
+
phase: input.phase,
|
|
411
|
+
publishReason: "codex-app-server-turn-active",
|
|
412
|
+
observedRun: input.observedRun,
|
|
413
|
+
runState: status === "starting" ? CODEX_APP_SERVER_RUN_STATE.STARTING : CODEX_APP_SERVER_RUN_STATE.ACTIVE,
|
|
414
|
+
status,
|
|
415
|
+
threadId: normalizeCodexRunText(input.threadId),
|
|
416
|
+
turnId: normalizeCodexRunText(input.turnId)
|
|
417
|
+
});
|
|
418
|
+
if (status === "starting") {
|
|
419
|
+
scheduleCodexAppServerActiveRecovery(sessionId);
|
|
420
|
+
} else {
|
|
421
|
+
clearCodexAppServerActiveTimer(sessionId);
|
|
422
|
+
}
|
|
423
|
+
return result;
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
async function markCodexAppServerProviderTurnActive(sessionId = "", input = {}) {
|
|
427
|
+
const normalizedSessionId = normalizeCodexRunText(sessionId);
|
|
428
|
+
const normalizedThreadId = normalizeCodexRunText(input.threadId);
|
|
429
|
+
const normalizedTurnId = normalizeCodexRunText(input.turnId);
|
|
430
|
+
const requestedStatus = normalizeCodexRunText(input.status) || "inProgress";
|
|
431
|
+
const store = await createStore(normalizedSessionId);
|
|
432
|
+
const persistedRun = await readCodexAppServerAgentRunForSession(
|
|
433
|
+
store,
|
|
434
|
+
normalizedSessionId
|
|
435
|
+
);
|
|
436
|
+
const persistedTurn = turnStateFromAgentRun(persistedRun || {});
|
|
437
|
+
const requestedInputSource = normalizeCodexRunText(input.inputSource);
|
|
438
|
+
if (
|
|
439
|
+
persistedTurn.active &&
|
|
440
|
+
persistedTurn.state === "active" &&
|
|
441
|
+
persistedTurn.threadId === normalizedThreadId &&
|
|
442
|
+
persistedTurn.turnId === normalizedTurnId &&
|
|
443
|
+
codexAppServerTurnStatusIsActive(requestedStatus) &&
|
|
444
|
+
(!requestedInputSource || requestedInputSource === persistedTurn.inputSource)
|
|
445
|
+
) {
|
|
446
|
+
clearCodexAppServerActiveTimer(normalizedSessionId);
|
|
447
|
+
return {
|
|
448
|
+
ok: true,
|
|
449
|
+
processed: false,
|
|
450
|
+
reason: "already_active"
|
|
451
|
+
};
|
|
452
|
+
}
|
|
453
|
+
const runtime = await createRuntime();
|
|
454
|
+
let session = await runtime.getSession(normalizedSessionId);
|
|
455
|
+
let turn = turnState(session);
|
|
456
|
+
if (
|
|
457
|
+
input.observedRun &&
|
|
458
|
+
[CODEX_APP_SERVER_RUN_STATE.FAILED, CODEX_APP_SERVER_RUN_STATE.INTERRUPTED].includes(turn.runState) &&
|
|
459
|
+
turn.threadId === normalizedThreadId &&
|
|
460
|
+
turn.turnId === normalizedTurnId &&
|
|
461
|
+
!codexAppServerTurnResultWasProcessed(session, normalizedThreadId, normalizedTurnId)
|
|
462
|
+
) {
|
|
463
|
+
const recovered = await markCodexAppServerTurnActive(normalizedSessionId, {
|
|
464
|
+
inputSource: turn.inputSource,
|
|
465
|
+
observedRun: input.observedRun,
|
|
466
|
+
status: requestedStatus,
|
|
467
|
+
threadId: normalizedThreadId,
|
|
468
|
+
turnId: normalizedTurnId
|
|
469
|
+
});
|
|
470
|
+
if (recovered.processed !== false) {
|
|
471
|
+
codexAppServerCompletedTurns.delete(codexAppServerTurnKey(normalizedThreadId, normalizedTurnId));
|
|
472
|
+
codexAppServerFinalizedTurns.delete(codexAppServerResultFinalizationKey(
|
|
473
|
+
normalizedSessionId, normalizedThreadId, normalizedTurnId
|
|
474
|
+
));
|
|
475
|
+
}
|
|
476
|
+
return recovered;
|
|
477
|
+
}
|
|
478
|
+
if (codexAppServerTurnWasCompleted(session, normalizedThreadId, normalizedTurnId)) {
|
|
479
|
+
debugLog("appServerProviderTurn.completed.ignored", {
|
|
480
|
+
currentState: turn.state,
|
|
481
|
+
currentThreadId: turn.threadId,
|
|
482
|
+
currentTurnId: turn.turnId,
|
|
483
|
+
sessionId: normalizedSessionId,
|
|
484
|
+
threadId: normalizedThreadId,
|
|
485
|
+
turnId: normalizedTurnId
|
|
486
|
+
});
|
|
487
|
+
return {
|
|
488
|
+
ok: true,
|
|
489
|
+
processed: false,
|
|
490
|
+
reason: "completed_turn"
|
|
491
|
+
};
|
|
492
|
+
}
|
|
493
|
+
if (
|
|
494
|
+
turn.state === "finalizing" &&
|
|
495
|
+
codexAppServerTurnCanAdoptSuccessor(turn, normalizedThreadId, normalizedTurnId) &&
|
|
496
|
+
codexAppServerTurnResultWasProcessed(session, normalizedThreadId, turn.turnId)
|
|
497
|
+
) {
|
|
498
|
+
await finalizeCodexAppServerAssistantResult(
|
|
499
|
+
normalizedSessionId,
|
|
500
|
+
normalizedThreadId,
|
|
501
|
+
turn.turnId,
|
|
502
|
+
{
|
|
503
|
+
status: turn.status || "completed"
|
|
504
|
+
}
|
|
505
|
+
);
|
|
506
|
+
session = await runtime.getSession(normalizedSessionId);
|
|
507
|
+
turn = turnState(session);
|
|
508
|
+
}
|
|
509
|
+
const currentRun = codexAppServerAgentRun(session);
|
|
510
|
+
const pendingInputSource = codexAppServerPendingUserMessageClientIds(currentRun).length > 0
|
|
511
|
+
? codexAppServerRunInputSource(currentRun)
|
|
512
|
+
: "";
|
|
513
|
+
const goalInputSource = codexAppServerTurnOwnsActiveGoal(turn, normalizedThreadId)
|
|
514
|
+
? codexAppServerRunInputSource(currentRun)
|
|
515
|
+
: "";
|
|
516
|
+
const inputSource = normalizeCodexRunText(input.inputSource) || pendingInputSource || goalInputSource;
|
|
517
|
+
if (codexAppServerTurnCanReceiveProviderActivity(turn, normalizedThreadId, normalizedTurnId)) {
|
|
518
|
+
return markCodexAppServerTurnActive(normalizedSessionId, {
|
|
519
|
+
inputSource,
|
|
520
|
+
status: requestedStatus,
|
|
521
|
+
threadId: normalizedThreadId,
|
|
522
|
+
turnId: normalizedTurnId
|
|
523
|
+
});
|
|
524
|
+
}
|
|
525
|
+
if (codexAppServerTurnCanAdoptSuccessor(turn, normalizedThreadId, normalizedTurnId)) {
|
|
526
|
+
return adoptCodexAppServerSuccessorTurn(normalizedSessionId, {
|
|
527
|
+
previousTurnId: turn.turnId,
|
|
528
|
+
source: normalizeCodexRunText(input.source) || "provider_activity",
|
|
529
|
+
status: requestedStatus,
|
|
530
|
+
threadId: normalizedThreadId,
|
|
531
|
+
turnId: normalizedTurnId
|
|
532
|
+
});
|
|
533
|
+
}
|
|
534
|
+
if (turn.active || !normalizedTurnId) {
|
|
535
|
+
debugLog("appServerProviderTurn.active.ignored", {
|
|
536
|
+
currentState: turn.state,
|
|
537
|
+
currentStatus: turn.status,
|
|
538
|
+
currentThreadId: turn.threadId,
|
|
539
|
+
currentTurnId: turn.turnId,
|
|
540
|
+
sessionId: normalizedSessionId,
|
|
541
|
+
threadId: normalizedThreadId,
|
|
542
|
+
turnId: normalizedTurnId
|
|
543
|
+
});
|
|
544
|
+
return {
|
|
545
|
+
ok: true,
|
|
546
|
+
processed: false,
|
|
547
|
+
reason: turn.active ? "active_turn_mismatch" : "missing_turn"
|
|
548
|
+
};
|
|
549
|
+
}
|
|
550
|
+
return markCodexAppServerTurnActive(normalizedSessionId, {
|
|
551
|
+
inputSource: inputSource || "terminal",
|
|
552
|
+
status: requestedStatus,
|
|
553
|
+
threadId: normalizedThreadId,
|
|
554
|
+
turnId: normalizedTurnId
|
|
555
|
+
});
|
|
556
|
+
}
|
|
557
|
+
|
|
558
|
+
async function recoverCodexAppServerFinalizingTurn(sessionId = "", threadId = "", turnId = "", {
|
|
559
|
+
status = "completed"
|
|
560
|
+
} = {}) {
|
|
561
|
+
const normalizedSessionId = normalizeCodexRunText(sessionId);
|
|
562
|
+
const normalizedThreadId = normalizeCodexRunText(threadId);
|
|
563
|
+
const normalizedTurnId = normalizeCodexRunText(turnId);
|
|
564
|
+
const runtime = await createRuntime();
|
|
565
|
+
const session = await runtime.getSession(normalizedSessionId);
|
|
566
|
+
const turn = turnState(session);
|
|
567
|
+
if (codexAppServerTurnOwnsActiveGoal(turn, normalizedThreadId)) {
|
|
568
|
+
clearCodexAppServerFinalizingTimer(
|
|
569
|
+
normalizedSessionId,
|
|
570
|
+
normalizedThreadId,
|
|
571
|
+
normalizedTurnId
|
|
572
|
+
);
|
|
573
|
+
return {
|
|
574
|
+
ok: true,
|
|
575
|
+
processed: false,
|
|
576
|
+
reason: "goal_continuation_pending"
|
|
577
|
+
};
|
|
578
|
+
}
|
|
579
|
+
const result = await finalizeCodexAppServerAssistantResult(
|
|
580
|
+
normalizedSessionId,
|
|
581
|
+
normalizedThreadId,
|
|
582
|
+
normalizedTurnId,
|
|
583
|
+
{
|
|
584
|
+
recoverFromProvider: true,
|
|
585
|
+
status
|
|
586
|
+
}
|
|
587
|
+
);
|
|
588
|
+
if (result?.processed) {
|
|
589
|
+
return result;
|
|
590
|
+
}
|
|
591
|
+
if (!codexAppServerFinalizingExpired(turn)) {
|
|
592
|
+
scheduleCodexAppServerFinalizingRecovery(normalizedSessionId, normalizedThreadId, normalizedTurnId, {
|
|
593
|
+
completedAt: turn.completedAt,
|
|
594
|
+
status,
|
|
595
|
+
updatedAt: turn.updatedAt
|
|
596
|
+
});
|
|
597
|
+
return result;
|
|
598
|
+
}
|
|
599
|
+
return stopCodexAppServerTurnWithResultDeliveryFailure(
|
|
600
|
+
normalizedSessionId,
|
|
601
|
+
normalizedThreadId,
|
|
602
|
+
normalizedTurnId,
|
|
603
|
+
{
|
|
604
|
+
error: result?.error,
|
|
605
|
+
reason: result?.reason || "missing_assistant_text",
|
|
606
|
+
status
|
|
607
|
+
}
|
|
608
|
+
);
|
|
609
|
+
}
|
|
610
|
+
|
|
611
|
+
async function recoverCodexAppServerActiveTurn(sessionId = "", {
|
|
612
|
+
provider = null,
|
|
613
|
+
retryOnError = true,
|
|
614
|
+
runtime: suppliedRuntime = null
|
|
615
|
+
} = {}) {
|
|
616
|
+
const normalizedSessionId = normalizeCodexRunText(sessionId);
|
|
617
|
+
if (!normalizedSessionId) {
|
|
618
|
+
return null;
|
|
619
|
+
}
|
|
620
|
+
try {
|
|
621
|
+
const runtime = suppliedRuntime || await createRuntime();
|
|
622
|
+
const storedSession = await runtime.getSession(normalizedSessionId);
|
|
623
|
+
const abandonedClaim = await (recoverAdmission || recoverAbandonedCodexAppServerPromptClaim)(
|
|
624
|
+
runtime,
|
|
625
|
+
storedSession,
|
|
626
|
+
{
|
|
627
|
+
promptDeliveryActive: codexAppServerPromptDeliveries.has(
|
|
628
|
+
namespace(normalizedSessionId)
|
|
629
|
+
)
|
|
630
|
+
}
|
|
631
|
+
);
|
|
632
|
+
const session = abandonedClaim.session;
|
|
633
|
+
if (abandonedClaim.recovered) {
|
|
634
|
+
return session;
|
|
635
|
+
}
|
|
636
|
+
const turn = turnState(session);
|
|
637
|
+
if (!["active", "starting"].includes(turn.state) || !turn.threadId) {
|
|
638
|
+
return session;
|
|
639
|
+
}
|
|
640
|
+
const reconciledSession = await reconcileCodexAppServerActiveTurn(session, {
|
|
641
|
+
provider,
|
|
642
|
+
runtime
|
|
643
|
+
});
|
|
644
|
+
const currentTurn = turnState(reconciledSession);
|
|
645
|
+
if (currentTurn.state === "starting" && currentTurn.threadId) {
|
|
646
|
+
scheduleCodexAppServerActiveRecovery(normalizedSessionId);
|
|
647
|
+
}
|
|
648
|
+
return reconciledSession;
|
|
649
|
+
} catch (error) {
|
|
650
|
+
debugLog("appServerTurn.reconcile.error", {
|
|
651
|
+
error: debugError(error),
|
|
652
|
+
sessionId: normalizedSessionId
|
|
653
|
+
});
|
|
654
|
+
if (!retryOnError) {
|
|
655
|
+
throw error;
|
|
656
|
+
}
|
|
657
|
+
const store = await Promise.resolve()
|
|
658
|
+
.then(() => createStore(normalizedSessionId))
|
|
659
|
+
.catch(() => null);
|
|
660
|
+
const run = store
|
|
661
|
+
? await readCodexAppServerAgentRunForSession(store, normalizedSessionId).catch(() => null)
|
|
662
|
+
: null;
|
|
663
|
+
if (turnStateFromAgentRun(run || {}).state === "starting") {
|
|
664
|
+
scheduleCodexAppServerActiveRecovery(normalizedSessionId);
|
|
665
|
+
}
|
|
666
|
+
return null;
|
|
667
|
+
}
|
|
668
|
+
}
|
|
669
|
+
|
|
670
|
+
async function completeCodexAppServerTurn(sessionId = "", threadId = "", turnId = "", {
|
|
671
|
+
provider = null,
|
|
672
|
+
status = "completed",
|
|
673
|
+
verifyInactive = true
|
|
674
|
+
} = {}) {
|
|
675
|
+
const normalizedSessionId = normalizeCodexRunText(sessionId);
|
|
676
|
+
const normalizedThreadId = normalizeCodexRunText(threadId);
|
|
677
|
+
const normalizedTurnId = normalizeCodexRunText(turnId);
|
|
678
|
+
const normalizedStatus = normalizeCodexRunText(status) || "completed";
|
|
679
|
+
const runtime = await createRuntime();
|
|
680
|
+
const session = await runtime.getSession(normalizedSessionId);
|
|
681
|
+
const existingTurn = turnState(session);
|
|
682
|
+
const continuesOwnedGoal = codexAppServerTurnOwnsActiveGoal(
|
|
683
|
+
existingTurn,
|
|
684
|
+
normalizedThreadId
|
|
685
|
+
);
|
|
686
|
+
if (codexAppServerTurnAwaitsProviderIdentity(existingTurn, normalizedThreadId, normalizedTurnId)) {
|
|
687
|
+
return {
|
|
688
|
+
ok: true,
|
|
689
|
+
processed: false,
|
|
690
|
+
reason: "turn_identity_pending"
|
|
691
|
+
};
|
|
692
|
+
}
|
|
693
|
+
if (!normalizedTurnId) {
|
|
694
|
+
if (!codexAppServerTurnCanReceiveProviderCompletion(existingTurn, normalizedThreadId, "")) {
|
|
695
|
+
cleanupCodexAppServerUntrackedTurn(normalizedThreadId, normalizedTurnId);
|
|
696
|
+
debugLog("appServerTurn.complete.stale", {
|
|
697
|
+
currentState: existingTurn.state,
|
|
698
|
+
currentStatus: existingTurn.status,
|
|
699
|
+
currentThreadId: existingTurn.threadId,
|
|
700
|
+
currentTurnId: existingTurn.turnId,
|
|
701
|
+
sessionId: normalizedSessionId,
|
|
702
|
+
status: normalizedStatus,
|
|
703
|
+
threadId: normalizedThreadId,
|
|
704
|
+
turnId: normalizedTurnId
|
|
705
|
+
});
|
|
706
|
+
return {
|
|
707
|
+
ok: true,
|
|
708
|
+
processed: false,
|
|
709
|
+
reason: "stale_turn_state"
|
|
710
|
+
};
|
|
711
|
+
}
|
|
712
|
+
if (verifyInactive && await codexAppServerProviderBlocksTurnRelease(normalizedSessionId, provider, normalizedThreadId, normalizedTurnId, {
|
|
713
|
+
source: "complete_missing_turn"
|
|
714
|
+
})) {
|
|
715
|
+
return {
|
|
716
|
+
ok: true,
|
|
717
|
+
processed: false,
|
|
718
|
+
reason: "provider_still_active",
|
|
719
|
+
status: "inProgress"
|
|
720
|
+
};
|
|
721
|
+
}
|
|
722
|
+
await markCodexAppServerTurnIdle(normalizedSessionId, {
|
|
723
|
+
status: normalizedStatus,
|
|
724
|
+
threadId: normalizedThreadId,
|
|
725
|
+
turnId: normalizedTurnId
|
|
726
|
+
});
|
|
727
|
+
return {
|
|
728
|
+
ok: true,
|
|
729
|
+
processed: false,
|
|
730
|
+
reason: "missing_turn"
|
|
731
|
+
};
|
|
732
|
+
}
|
|
733
|
+
if (!codexAppServerTurnCanReceiveProviderCompletion(existingTurn, normalizedThreadId, normalizedTurnId)) {
|
|
734
|
+
codexAppServerFinalizedTurns.add(codexAppServerResultFinalizationKey(
|
|
735
|
+
normalizedSessionId,
|
|
736
|
+
normalizedThreadId,
|
|
737
|
+
normalizedTurnId
|
|
738
|
+
));
|
|
739
|
+
cleanupCodexAppServerUntrackedTurn(normalizedThreadId, normalizedTurnId);
|
|
740
|
+
debugLog("appServerTurn.complete.stale", {
|
|
741
|
+
currentState: existingTurn.state,
|
|
742
|
+
currentStatus: existingTurn.status,
|
|
743
|
+
currentThreadId: existingTurn.threadId,
|
|
744
|
+
currentTurnId: existingTurn.turnId,
|
|
745
|
+
sessionId: normalizedSessionId,
|
|
746
|
+
status: normalizedStatus,
|
|
747
|
+
threadId: normalizedThreadId,
|
|
748
|
+
turnId: normalizedTurnId
|
|
749
|
+
});
|
|
750
|
+
return {
|
|
751
|
+
ok: true,
|
|
752
|
+
processed: false,
|
|
753
|
+
reason: "stale_turn_state"
|
|
754
|
+
};
|
|
755
|
+
}
|
|
756
|
+
if (verifyInactive && await codexAppServerProviderBlocksTurnRelease(normalizedSessionId, provider, normalizedThreadId, normalizedTurnId, {
|
|
757
|
+
source: "complete"
|
|
758
|
+
})) {
|
|
759
|
+
return {
|
|
760
|
+
ok: true,
|
|
761
|
+
processed: false,
|
|
762
|
+
reason: "provider_still_active",
|
|
763
|
+
status: "inProgress"
|
|
764
|
+
};
|
|
765
|
+
}
|
|
766
|
+
if (codexAppServerRunInputSource(codexAppServerAgentRun(session)) === "terminal") {
|
|
767
|
+
codexAppServerCompletedTurns.add(codexAppServerTurnKey(normalizedThreadId, normalizedTurnId));
|
|
768
|
+
cleanupCodexAppServerUntrackedTurn(normalizedThreadId, normalizedTurnId);
|
|
769
|
+
await markCodexAppServerTurnIdle(normalizedSessionId, {
|
|
770
|
+
status: normalizedStatus,
|
|
771
|
+
threadId: normalizedThreadId,
|
|
772
|
+
turnId: normalizedTurnId
|
|
773
|
+
});
|
|
774
|
+
return {
|
|
775
|
+
ok: true,
|
|
776
|
+
processed: true,
|
|
777
|
+
reason: "terminal_turn_completed"
|
|
778
|
+
};
|
|
779
|
+
}
|
|
780
|
+
const alreadyFinalizing = existingTurn.state === "finalizing" &&
|
|
781
|
+
existingTurn.threadId === normalizedThreadId &&
|
|
782
|
+
existingTurn.turnId === normalizedTurnId;
|
|
783
|
+
codexAppServerCompletedTurns.add(codexAppServerTurnKey(normalizedThreadId, normalizedTurnId));
|
|
784
|
+
if (!alreadyFinalizing) {
|
|
785
|
+
await markCodexAppServerTurnFinalizing(normalizedSessionId, {
|
|
786
|
+
status: normalizedStatus,
|
|
787
|
+
threadId: normalizedThreadId,
|
|
788
|
+
turnId: normalizedTurnId
|
|
789
|
+
});
|
|
790
|
+
}
|
|
791
|
+
if (continuesOwnedGoal) {
|
|
792
|
+
// Keep the outer chat turn active so the next provider turn can adopt
|
|
793
|
+
// its chat ownership without exposing this internal final as an answer.
|
|
794
|
+
clearCodexAppServerFinalizingTimer(
|
|
795
|
+
normalizedSessionId,
|
|
796
|
+
normalizedThreadId,
|
|
797
|
+
normalizedTurnId
|
|
798
|
+
);
|
|
799
|
+
return {
|
|
800
|
+
ok: true,
|
|
801
|
+
processed: true,
|
|
802
|
+
reason: "goal_continuation_pending"
|
|
803
|
+
};
|
|
804
|
+
}
|
|
805
|
+
return recoverCodexAppServerFinalizingTurn(
|
|
806
|
+
normalizedSessionId,
|
|
807
|
+
normalizedThreadId,
|
|
808
|
+
normalizedTurnId,
|
|
809
|
+
{
|
|
810
|
+
status: normalizedStatus
|
|
811
|
+
}
|
|
812
|
+
);
|
|
813
|
+
}
|
|
814
|
+
|
|
815
|
+
function codexAppServerStoppedTurnMessage(status = "", error = "") {
|
|
816
|
+
const normalizedStatus = normalizeCodexRunText(status);
|
|
817
|
+
const base = normalizedStatus === "interrupted"
|
|
818
|
+
? "Codex app-server was interrupted before completing this turn."
|
|
819
|
+
: "Codex app-server failed before completing this turn.";
|
|
820
|
+
const normalizedError = normalizeCodexRunText(error);
|
|
821
|
+
return normalizedError ? `${base} ${normalizedError}` : base;
|
|
822
|
+
}
|
|
823
|
+
|
|
824
|
+
async function stopCodexAppServerTurnWithProviderFailure(sessionId = "", threadId = "", turnId = "", {
|
|
825
|
+
error = "",
|
|
826
|
+
ok = false,
|
|
827
|
+
outcome = turnOutcomes.PROVIDER_FAILURE,
|
|
828
|
+
provider = null,
|
|
829
|
+
status = "failed",
|
|
830
|
+
usageLimitExceeded = false,
|
|
831
|
+
verifyInactive = true
|
|
832
|
+
} = {}) {
|
|
833
|
+
const normalizedSessionId = normalizeCodexRunText(sessionId);
|
|
834
|
+
const normalizedThreadId = normalizeCodexRunText(threadId);
|
|
835
|
+
const normalizedTurnId = normalizeCodexRunText(turnId);
|
|
836
|
+
const normalizedStatus = normalizeCodexRunText(status) || "failed";
|
|
837
|
+
const runtime = await createRuntime();
|
|
838
|
+
const session = await runtime.getSession(normalizedSessionId);
|
|
839
|
+
const turn = turnState(session);
|
|
840
|
+
if (!codexAppServerTurnCanReceiveProviderCompletion(turn, normalizedThreadId, normalizedTurnId)) {
|
|
841
|
+
debugLog("appServerTurn.failure.stale", {
|
|
842
|
+
currentState: turn.state,
|
|
843
|
+
currentStatus: turn.status,
|
|
844
|
+
currentThreadId: turn.threadId,
|
|
845
|
+
currentTurnId: turn.turnId,
|
|
846
|
+
sessionId: normalizedSessionId,
|
|
847
|
+
status: normalizedStatus,
|
|
848
|
+
threadId: normalizedThreadId,
|
|
849
|
+
turnId: normalizedTurnId
|
|
850
|
+
});
|
|
851
|
+
return {
|
|
852
|
+
ok: true,
|
|
853
|
+
processed: false,
|
|
854
|
+
reason: "stale_turn_state"
|
|
855
|
+
};
|
|
856
|
+
}
|
|
857
|
+
const recovered = await recoverCodexAppServerFinalResponseBeforeOutcome(
|
|
858
|
+
normalizedSessionId,
|
|
859
|
+
normalizedThreadId,
|
|
860
|
+
normalizedTurnId,
|
|
861
|
+
normalizedStatus
|
|
862
|
+
);
|
|
863
|
+
if (recovered?.processed) {
|
|
864
|
+
return recovered;
|
|
865
|
+
}
|
|
866
|
+
// History recovery awaited the provider. Confirm activity again before
|
|
867
|
+
// publishing a failure, because the goal may have continued in that gap.
|
|
868
|
+
if (verifyInactive && await codexAppServerProviderBlocksTurnRelease(normalizedSessionId, provider, normalizedThreadId, normalizedTurnId, {
|
|
869
|
+
source: "provider_failure"
|
|
870
|
+
})) {
|
|
871
|
+
return {
|
|
872
|
+
ok: true,
|
|
873
|
+
processed: false,
|
|
874
|
+
reason: "provider_still_active",
|
|
875
|
+
status: "inProgress"
|
|
876
|
+
};
|
|
877
|
+
}
|
|
878
|
+
const message = codexAppServerStoppedTurnMessage(normalizedStatus, error);
|
|
879
|
+
await outcomeNotice(
|
|
880
|
+
runtime,
|
|
881
|
+
normalizedSessionId,
|
|
882
|
+
normalizedThreadId,
|
|
883
|
+
normalizedTurnId,
|
|
884
|
+
outcome,
|
|
885
|
+
error,
|
|
886
|
+
{ usageLimitExceeded }
|
|
887
|
+
);
|
|
888
|
+
await markCodexAppServerTurnIdle(normalizedSessionId, {
|
|
889
|
+
error: message,
|
|
890
|
+
status: normalizedStatus,
|
|
891
|
+
threadId: normalizedThreadId,
|
|
892
|
+
turnId: normalizedTurnId,
|
|
893
|
+
turnOutcome: outcome
|
|
894
|
+
});
|
|
895
|
+
cleanupCodexAppServerUntrackedTurn(normalizedThreadId, normalizedTurnId);
|
|
896
|
+
return {
|
|
897
|
+
ok,
|
|
898
|
+
error: message,
|
|
899
|
+
status: normalizedStatus
|
|
900
|
+
};
|
|
901
|
+
}
|
|
902
|
+
|
|
903
|
+
async function stopCodexAppServerTurnWithResultDeliveryFailure(sessionId = "", threadId = "", turnId = "", {
|
|
904
|
+
error = "",
|
|
905
|
+
reason = "",
|
|
906
|
+
status = "completed"
|
|
907
|
+
} = {}) {
|
|
908
|
+
const normalizedSessionId = normalizeCodexRunText(sessionId);
|
|
909
|
+
const normalizedThreadId = normalizeCodexRunText(threadId);
|
|
910
|
+
const normalizedStatus = normalizeCodexRunText(status) || "completed";
|
|
911
|
+
const normalizedTurnId = await resolveCodexAppServerTurnId(normalizedSessionId, normalizedThreadId, turnId);
|
|
912
|
+
const runtime = await createRuntime();
|
|
913
|
+
const currentSession = await runtime.getSession(normalizedSessionId);
|
|
914
|
+
const currentTurn = turnState(currentSession);
|
|
915
|
+
const recovered = await recoverCodexAppServerFinalResponseBeforeOutcome(
|
|
916
|
+
normalizedSessionId,
|
|
917
|
+
normalizedThreadId,
|
|
918
|
+
normalizedTurnId,
|
|
919
|
+
normalizedStatus
|
|
920
|
+
);
|
|
921
|
+
if (recovered?.processed) {
|
|
922
|
+
return recovered;
|
|
923
|
+
}
|
|
924
|
+
if (!codexAppServerTurnCanReceiveProviderCompletion(currentTurn, normalizedThreadId, normalizedTurnId)) {
|
|
925
|
+
codexAppServerFinalizedTurns.add(codexAppServerResultFinalizationKey(
|
|
926
|
+
normalizedSessionId,
|
|
927
|
+
normalizedThreadId,
|
|
928
|
+
normalizedTurnId
|
|
929
|
+
));
|
|
930
|
+
cleanupCodexAppServerUntrackedTurn(normalizedThreadId, normalizedTurnId);
|
|
931
|
+
debugLog("appServerAgentResult.missing.stale", {
|
|
932
|
+
currentState: currentTurn.state,
|
|
933
|
+
currentStatus: currentTurn.status,
|
|
934
|
+
currentThreadId: currentTurn.threadId,
|
|
935
|
+
currentTurnId: currentTurn.turnId,
|
|
936
|
+
reason: normalizeCodexRunText(reason),
|
|
937
|
+
sessionId: normalizedSessionId,
|
|
938
|
+
status: normalizedStatus,
|
|
939
|
+
threadId: normalizedThreadId,
|
|
940
|
+
turnId: normalizedTurnId
|
|
941
|
+
});
|
|
942
|
+
return {
|
|
943
|
+
ok: true,
|
|
944
|
+
processed: false,
|
|
945
|
+
reason: "stale_turn_state",
|
|
946
|
+
status: currentTurn.status
|
|
947
|
+
};
|
|
948
|
+
}
|
|
949
|
+
const message = resultDeliveryFailureMessage({
|
|
950
|
+
error
|
|
951
|
+
});
|
|
952
|
+
await outcomeNotice(
|
|
953
|
+
runtime,
|
|
954
|
+
normalizedSessionId,
|
|
955
|
+
normalizedThreadId,
|
|
956
|
+
normalizedTurnId,
|
|
957
|
+
turnOutcomes.RESPONSE_DELIVERY_FAILURE
|
|
958
|
+
);
|
|
959
|
+
await markCodexAppServerTurnIdle(normalizedSessionId, {
|
|
960
|
+
error: message,
|
|
961
|
+
status: normalizedStatus,
|
|
962
|
+
threadId: normalizedThreadId,
|
|
963
|
+
turnId: normalizedTurnId,
|
|
964
|
+
turnOutcome: turnOutcomes.RESPONSE_DELIVERY_FAILURE
|
|
965
|
+
});
|
|
966
|
+
cleanupCodexAppServerUntrackedTurn(normalizedThreadId, normalizedTurnId);
|
|
967
|
+
debugLog("appServerAgentResult.missing", {
|
|
968
|
+
error: normalizeCodexRunText(error),
|
|
969
|
+
reason: normalizeCodexRunText(reason),
|
|
970
|
+
sessionId: normalizedSessionId,
|
|
971
|
+
threadId: normalizedThreadId,
|
|
972
|
+
turnId: normalizedTurnId
|
|
973
|
+
});
|
|
974
|
+
return {
|
|
975
|
+
ok: false,
|
|
976
|
+
error: message,
|
|
977
|
+
status: normalizedStatus
|
|
978
|
+
};
|
|
979
|
+
}
|
|
980
|
+
|
|
981
|
+
return {
|
|
982
|
+
scheduleActiveRecovery: scheduleCodexAppServerActiveRecovery,
|
|
983
|
+
markTurnActive: markCodexAppServerTurnActive,
|
|
984
|
+
markProviderTurnActive: markCodexAppServerProviderTurnActive,
|
|
985
|
+
recoverFinalizingTurn: recoverCodexAppServerFinalizingTurn,
|
|
986
|
+
recoverActiveTurn: recoverCodexAppServerActiveTurn,
|
|
987
|
+
completeTurn: completeCodexAppServerTurn,
|
|
988
|
+
stopTurnWithProviderFailure: stopCodexAppServerTurnWithProviderFailure
|
|
989
|
+
};
|
|
990
|
+
}
|