@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,663 @@
|
|
|
1
|
+
import {
|
|
2
|
+
codexAppServerTurnStatusIsSuccessfulComplete,
|
|
3
|
+
codexAppServerTurnStatusIsProviderFailure,
|
|
4
|
+
normalizeCodexRunText,
|
|
5
|
+
codexAppServerErrorMessage,
|
|
6
|
+
codexAppServerSteerFailure,
|
|
7
|
+
codexAppServerMessageRequiresNewTurn,
|
|
8
|
+
codexAppServerMessageDeferred,
|
|
9
|
+
codexAppServerMessageText,
|
|
10
|
+
codexAppServerMessageDisplayText,
|
|
11
|
+
codexPromptInputFromRequest
|
|
12
|
+
} from "./codexTurnState.js";
|
|
13
|
+
import {
|
|
14
|
+
codexAppServerRequestIsInvalid,
|
|
15
|
+
codexAppServerThreadIsMissing,
|
|
16
|
+
codexLocalImageInput,
|
|
17
|
+
ensureCodexAppServerThread,
|
|
18
|
+
sendCodexAppServerPrompt
|
|
19
|
+
} from "./codexProvider.js";
|
|
20
|
+
import crypto from "node:crypto";
|
|
21
|
+
import { isPlainObject } from "./normalize.js";
|
|
22
|
+
|
|
23
|
+
// Original Main message admission, preparation, dispatch and steering.
|
|
24
|
+
// Prompt delivery and receipt state remain the same run-owner references.
|
|
25
|
+
export function createCodexMessageCommands({
|
|
26
|
+
output,
|
|
27
|
+
journal,
|
|
28
|
+
recovery,
|
|
29
|
+
threadStatus,
|
|
30
|
+
selection,
|
|
31
|
+
observer,
|
|
32
|
+
delivery,
|
|
33
|
+
promptDeliveries: codexAppServerPromptDeliveries,
|
|
34
|
+
createRuntime,
|
|
35
|
+
debugError,
|
|
36
|
+
debugLog,
|
|
37
|
+
errorPrefix,
|
|
38
|
+
messageIdPrefix,
|
|
39
|
+
messageMetadata,
|
|
40
|
+
namespace,
|
|
41
|
+
providerSessions,
|
|
42
|
+
recoverAdmission,
|
|
43
|
+
steerFailedCode,
|
|
44
|
+
turnAlreadyRunningError,
|
|
45
|
+
turnState
|
|
46
|
+
}) {
|
|
47
|
+
const {
|
|
48
|
+
submitAssistantResult: submitCodexAppServerAssistantResult,
|
|
49
|
+
splitReasoningTurn: splitCodexAppServerReasoningTurn
|
|
50
|
+
} = output;
|
|
51
|
+
const {
|
|
52
|
+
recoverAbandonedPromptClaim: recoverAbandonedCodexAppServerPromptClaim,
|
|
53
|
+
claimMessageStart: claimCodexAppServerMessageStart,
|
|
54
|
+
markTurnIdle: markCodexAppServerTurnIdle,
|
|
55
|
+
writeUserMessageOwnership: writeCodexAppServerUserMessageOwnership
|
|
56
|
+
} = journal;
|
|
57
|
+
const {
|
|
58
|
+
stopTurnWithProviderFailure: stopCodexAppServerTurnWithProviderFailure,
|
|
59
|
+
completeTurn: completeCodexAppServerTurn,
|
|
60
|
+
markTurnActive: markCodexAppServerTurnActive
|
|
61
|
+
} = recovery;
|
|
62
|
+
const {
|
|
63
|
+
reconcileThreadStatus: reconcileCodexAppServerThreadStatus
|
|
64
|
+
} = threadStatus;
|
|
65
|
+
const {
|
|
66
|
+
codexAppServerMessageContext,
|
|
67
|
+
codexAppServerSessionObserverOptions,
|
|
68
|
+
rememberCodexAppServerPreparedThread
|
|
69
|
+
} = selection;
|
|
70
|
+
const {
|
|
71
|
+
subscribeEvents: subscribeCodexAppServerEvents
|
|
72
|
+
} = observer;
|
|
73
|
+
const {
|
|
74
|
+
writeDeliveredUserMessage: writeCodexAppServerDeliveredUserMessage,
|
|
75
|
+
pendingUserMessages: codexAppServerPendingUserMessages
|
|
76
|
+
} = delivery;
|
|
77
|
+
|
|
78
|
+
async function selectCodexAppServerMessageTurn(sessionId, { messageId = "", startedAt = Date.now() } = {}, context = {}) {
|
|
79
|
+
if (providerSessions) context = codexAppServerMessageContext(sessionId, context);
|
|
80
|
+
const { runtime, session } = context;
|
|
81
|
+
let currentSession = session;
|
|
82
|
+
let turn = turnState(currentSession);
|
|
83
|
+
if (
|
|
84
|
+
turn.state === "starting" &&
|
|
85
|
+
!turn.turnId &&
|
|
86
|
+
!codexAppServerPromptDeliveries.has(namespace(sessionId))
|
|
87
|
+
) {
|
|
88
|
+
const abandonedClaim = await (recoverAdmission || recoverAbandonedCodexAppServerPromptClaim)(
|
|
89
|
+
runtime,
|
|
90
|
+
currentSession,
|
|
91
|
+
{ promptDeliveryActive: false }
|
|
92
|
+
);
|
|
93
|
+
currentSession = abandonedClaim.session;
|
|
94
|
+
turn = turnState(currentSession);
|
|
95
|
+
}
|
|
96
|
+
if (turn.status === "observation_lost") {
|
|
97
|
+
if (turn.active) {
|
|
98
|
+
throw Object.assign(new Error("Codex observation was lost and a stop is not yet verified. Retry after the service has stopped."), {
|
|
99
|
+
code: `${errorPrefix}codex_observation_lost`
|
|
100
|
+
});
|
|
101
|
+
}
|
|
102
|
+
const recovered = await submitCodexAppServerAssistantResult(sessionId, turn.threadId, turn.turnId, { recoverFromProvider: true });
|
|
103
|
+
if (recovered.reason === "error") throw new Error(recovered.error);
|
|
104
|
+
return { value: codexAppServerMessageRequiresNewTurn({ reason: "observation_lost", threadId: turn.threadId, turnId: turn.turnId }), session: currentSession };
|
|
105
|
+
}
|
|
106
|
+
const threadId = normalizeCodexRunText(turn.threadId) || context.threadId(currentSession);
|
|
107
|
+
if (!threadId) {
|
|
108
|
+
debugLog("appServerMessage.newTurn", {
|
|
109
|
+
messageId,
|
|
110
|
+
reason: "thread_missing",
|
|
111
|
+
sessionId,
|
|
112
|
+
threadId: "",
|
|
113
|
+
turnId: ""
|
|
114
|
+
});
|
|
115
|
+
return { value: codexAppServerMessageRequiresNewTurn({
|
|
116
|
+
reason: "thread_missing"
|
|
117
|
+
}), session: currentSession };
|
|
118
|
+
}
|
|
119
|
+
const acquired = await context.acquireProvider(currentSession);
|
|
120
|
+
const provider = acquired.provider;
|
|
121
|
+
if (acquired.reused) {
|
|
122
|
+
debugLog("appServerMessage.providerReused", {
|
|
123
|
+
messageId,
|
|
124
|
+
sessionId,
|
|
125
|
+
threadId,
|
|
126
|
+
turnId: normalizeCodexRunText(turn.turnId)
|
|
127
|
+
});
|
|
128
|
+
}
|
|
129
|
+
const providerReadyAt = Date.now();
|
|
130
|
+
debugLog("appServerMessage.providerReady", {
|
|
131
|
+
durationMs: providerReadyAt - startedAt,
|
|
132
|
+
messageId,
|
|
133
|
+
sessionId
|
|
134
|
+
});
|
|
135
|
+
try {
|
|
136
|
+
await reconcileCodexAppServerThreadStatus(sessionId, provider, threadId, {
|
|
137
|
+
source: "message_delivery"
|
|
138
|
+
});
|
|
139
|
+
} catch (error) {
|
|
140
|
+
if (turn.active || !codexAppServerThreadIsMissing(error, threadId)) {
|
|
141
|
+
throw error;
|
|
142
|
+
}
|
|
143
|
+
debugLog("appServerMessage.newTurn", {
|
|
144
|
+
error: debugError(error),
|
|
145
|
+
messageId,
|
|
146
|
+
reason: "provider_thread_missing",
|
|
147
|
+
sessionId,
|
|
148
|
+
threadId,
|
|
149
|
+
turnId: normalizeCodexRunText(turn.turnId)
|
|
150
|
+
});
|
|
151
|
+
return { value: codexAppServerMessageRequiresNewTurn({
|
|
152
|
+
reason: "provider_thread_missing",
|
|
153
|
+
threadId,
|
|
154
|
+
turnId: normalizeCodexRunText(turn.turnId)
|
|
155
|
+
}), session: currentSession };
|
|
156
|
+
}
|
|
157
|
+
debugLog("appServerMessage.reconciled", {
|
|
158
|
+
durationMs: Date.now() - providerReadyAt,
|
|
159
|
+
messageId,
|
|
160
|
+
sessionId
|
|
161
|
+
});
|
|
162
|
+
currentSession = await runtime.getSession(sessionId);
|
|
163
|
+
turn = turnState(currentSession);
|
|
164
|
+
const turnId = normalizeCodexRunText(turn.turnId);
|
|
165
|
+
const goalAwaitingTurn = !turn.active && turn.goalStatus === "active" && turn.goalThreadId === threadId;
|
|
166
|
+
if (!turn.active && !goalAwaitingTurn) {
|
|
167
|
+
debugLog("appServerMessage.newTurn", {
|
|
168
|
+
messageId,
|
|
169
|
+
reason: "provider_idle",
|
|
170
|
+
sessionId,
|
|
171
|
+
threadId,
|
|
172
|
+
turnId
|
|
173
|
+
});
|
|
174
|
+
return { value: codexAppServerMessageRequiresNewTurn({
|
|
175
|
+
reason: "provider_idle",
|
|
176
|
+
threadId,
|
|
177
|
+
turnId
|
|
178
|
+
}), session: currentSession };
|
|
179
|
+
}
|
|
180
|
+
if (goalAwaitingTurn || !turnId || turn.state === "finalizing") {
|
|
181
|
+
return { value: {
|
|
182
|
+
code: steerFailedCode,
|
|
183
|
+
delivered: false,
|
|
184
|
+
error: "The active assistant turn is not ready to accept this message yet.",
|
|
185
|
+
ok: false,
|
|
186
|
+
operationOutcome: "active_turn_not_ready",
|
|
187
|
+
refreshRecommended: true,
|
|
188
|
+
retryable: true,
|
|
189
|
+
threadId,
|
|
190
|
+
turnId
|
|
191
|
+
}, session: currentSession };
|
|
192
|
+
}
|
|
193
|
+
return { session: currentSession, provider, threadId, turnId };
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
async function prepareCodexAppServerMessageThread(sessionId, input, prepared, preparation) {
|
|
197
|
+
const facility = preparation.threadPreparation(input, prepared);
|
|
198
|
+
const { messageId, startedAt, workdir } = prepared;
|
|
199
|
+
const operation = async currentSession => {
|
|
200
|
+
const source = await facility.provider(currentSession);
|
|
201
|
+
const connection = source.provider ? null : providerSessions.context(
|
|
202
|
+
normalizeCodexRunText(sessionId), source.providerOptions
|
|
203
|
+
);
|
|
204
|
+
const providerAlreadyAvailable = source.provider
|
|
205
|
+
? source.providerAlreadyAvailable
|
|
206
|
+
: providerSessions.owner.providers.get(connection.providerKey)?.isAvailable?.() === true;
|
|
207
|
+
if (!providerAlreadyAvailable && facility.running) await facility.running();
|
|
208
|
+
let stageStartedAt = Date.now();
|
|
209
|
+
const provider = source.provider || await providerSessions.owner.ensureSession(connection, source.mainThreadId);
|
|
210
|
+
debugLog("appServerPrompt.stage", {
|
|
211
|
+
durationMs: Date.now() - stageStartedAt, messageId, sessionId, stage: "provider"
|
|
212
|
+
});
|
|
213
|
+
stageStartedAt = Date.now();
|
|
214
|
+
const observerOptions = source.observerOptions || codexAppServerSessionObserverOptions(sessionId, source.providerOptions);
|
|
215
|
+
const thread = await ensureCodexAppServerThread({
|
|
216
|
+
...(source.preparation || facility.preparation(currentSession)),
|
|
217
|
+
observeThread: threadId => subscribeCodexAppServerEvents(sessionId, provider, threadId, observerOptions),
|
|
218
|
+
provider,
|
|
219
|
+
workdir: source.workdir ?? workdir
|
|
220
|
+
});
|
|
221
|
+
if (source.requirePaginatedHistory && thread.thread.raw?.historyMode !== "paginated") {
|
|
222
|
+
throw new Error("Codex did not create the required paginated history. Update Codex before starting this conversation.");
|
|
223
|
+
}
|
|
224
|
+
debugLog("appServerPrompt.stage", {
|
|
225
|
+
durationMs: Date.now() - stageStartedAt, messageId, sessionId, stage: "thread"
|
|
226
|
+
});
|
|
227
|
+
return { currentSession, provider, providerAlreadyAvailable, providerOptions: source.providerOptions,
|
|
228
|
+
thread, observerOptions, connection, ...(source.settings ? { settings: source.settings } : {}) };
|
|
229
|
+
};
|
|
230
|
+
try {
|
|
231
|
+
// Send's provider acquisition stays inside the application startup gate.
|
|
232
|
+
const threadPreparation = facility.gate
|
|
233
|
+
? await facility.gate(operation)
|
|
234
|
+
: await operation(prepared.session);
|
|
235
|
+
const { currentSession: preparedSession, provider, providerAlreadyAvailable, providerOptions,
|
|
236
|
+
thread, observerOptions, connection, ...settings } = threadPreparation;
|
|
237
|
+
debugLog("appServerPrompt.stage", {
|
|
238
|
+
durationMs: Date.now() - startedAt, messageId, sessionId, stage: "startup-ready"
|
|
239
|
+
});
|
|
240
|
+
if (!Object.hasOwn(facility, "failureThreadId")) prepared.activeThreadId = thread.threadId;
|
|
241
|
+
subscribeCodexAppServerEvents(sessionId, provider, thread.threadId, observerOptions);
|
|
242
|
+
if (connection) rememberCodexAppServerPreparedThread(sessionId, providerOptions, prepared, thread);
|
|
243
|
+
if (facility.ready) await facility.ready();
|
|
244
|
+
return { preparedSession, provider, providerAlreadyAvailable, providerOptions, thread, ...settings };
|
|
245
|
+
} finally {
|
|
246
|
+
// Standalone preparation historically retained the exact binding on
|
|
247
|
+
// failure as well as success; Main records it after the startup gate.
|
|
248
|
+
if (Object.hasOwn(facility, "failureThreadId")) prepared.activeThreadId = facility.failureThreadId;
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
async function startCodexAppServerMessage(sessionId, input = {}, options = {}, prepare) {
|
|
253
|
+
const startedAt = Date.now();
|
|
254
|
+
const agentSettings = isPlainObject(options.agentSettings)
|
|
255
|
+
? options.agentSettings
|
|
256
|
+
: isPlainObject(input.agentSettings) ? input.agentSettings : {};
|
|
257
|
+
const actorContext = options.actorContext || null;
|
|
258
|
+
const messageId = normalizeCodexRunText(input.messageId) ||
|
|
259
|
+
`${messageIdPrefix}${crypto.randomUUID()}`;
|
|
260
|
+
const userRequest = codexPromptInputFromRequest(input);
|
|
261
|
+
if (!userRequest && options.preparedInput === undefined) {
|
|
262
|
+
return { value: {
|
|
263
|
+
ok: false,
|
|
264
|
+
error: "Codex message is empty."
|
|
265
|
+
} };
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
const context = await prepare.readContext(options);
|
|
269
|
+
if (context.ok === false) {
|
|
270
|
+
return { value: context };
|
|
271
|
+
}
|
|
272
|
+
const { runtime } = context;
|
|
273
|
+
const turnMetadata = { ...input.turnMetadata, ...await messageMetadata.actor?.(actorContext) };
|
|
274
|
+
debugLog("appServerPrompt.start", {
|
|
275
|
+
durationMs: Date.now() - startedAt,
|
|
276
|
+
messageId,
|
|
277
|
+
sessionId
|
|
278
|
+
});
|
|
279
|
+
|
|
280
|
+
const claim = await claimCodexAppServerMessageStart(runtime, sessionId, messageId);
|
|
281
|
+
if (!claim?.claimed) {
|
|
282
|
+
debugLog("appServerPrompt.claimObserved", {
|
|
283
|
+
code: String(claim?.response?.value?.code || ""),
|
|
284
|
+
messageId,
|
|
285
|
+
operationOutcome: String(claim?.response?.value?.operationOutcome || ""),
|
|
286
|
+
sessionId
|
|
287
|
+
});
|
|
288
|
+
return claim?.response || { value: {
|
|
289
|
+
ok: false,
|
|
290
|
+
error: turnAlreadyRunningError
|
|
291
|
+
} };
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
const normalizedSessionId = normalizeCodexRunText(sessionId);
|
|
295
|
+
const promptDeliveryKey = namespace(normalizedSessionId);
|
|
296
|
+
codexAppServerPromptDeliveries.add(promptDeliveryKey);
|
|
297
|
+
const prepared = {
|
|
298
|
+
...context, agentSettings, actorContext, messageId, userRequest, startedAt,
|
|
299
|
+
activeThreadId: ""
|
|
300
|
+
};
|
|
301
|
+
let providerFailure = "";
|
|
302
|
+
let turnFailureHandled = false;
|
|
303
|
+
try {
|
|
304
|
+
Object.assign(prepared, await prepareCodexAppServerMessageThread(sessionId, input, prepared, prepare));
|
|
305
|
+
const { provider, providerAlreadyAvailable, thread } = prepared;
|
|
306
|
+
await markCodexAppServerTurnActive(sessionId, {
|
|
307
|
+
status: "starting",
|
|
308
|
+
threadId: thread.threadId
|
|
309
|
+
});
|
|
310
|
+
const prompt = await prepare.prepareMessage(input, prepared, { starting: true });
|
|
311
|
+
const started = await startCodexAppServerTurn(sessionId, input, {
|
|
312
|
+
runtime, provider, threadId: thread.threadId, promptDeliveryKey, messageId, renderedPrompt: prompt.renderedPrompt, turnMetadata,
|
|
313
|
+
preparedInput: options.preparedInput,
|
|
314
|
+
get turnSettings() { return prompt.turnSettings; }
|
|
315
|
+
});
|
|
316
|
+
if (started.turnFailureHandled) {
|
|
317
|
+
turnFailureHandled = true;
|
|
318
|
+
throw started.error;
|
|
319
|
+
}
|
|
320
|
+
const { delivery, deliveredTurnId } = started;
|
|
321
|
+
providerFailure = started.providerFailure;
|
|
322
|
+
await prepare.finishMessage(input, prepared, { delivery });
|
|
323
|
+
const currentSession = await runtime.getSession(sessionId);
|
|
324
|
+
debugLog("appServerPrompt.delivered", {
|
|
325
|
+
messageId,
|
|
326
|
+
sessionId,
|
|
327
|
+
threadId: thread.threadId,
|
|
328
|
+
turnId: deliveredTurnId
|
|
329
|
+
});
|
|
330
|
+
return { value: {
|
|
331
|
+
...(providerFailure ? { error: providerFailure } : {}),
|
|
332
|
+
ok: !providerFailure,
|
|
333
|
+
connectionReused: providerAlreadyAvailable,
|
|
334
|
+
turnMetadata,
|
|
335
|
+
turnId: delivery.turn?.id || ""
|
|
336
|
+
}, session: currentSession, nativeIdentity: { threadId: thread.threadId, turnId: deliveredTurnId } };
|
|
337
|
+
} catch (error) {
|
|
338
|
+
if (!turnFailureHandled) {
|
|
339
|
+
await markCodexAppServerTurnIdle(sessionId, {
|
|
340
|
+
error: codexAppServerErrorMessage(error, "Codex app-server prompt delivery failed."),
|
|
341
|
+
status: "failed",
|
|
342
|
+
threadId: prepared.activeThreadId
|
|
343
|
+
}).catch(() => null);
|
|
344
|
+
}
|
|
345
|
+
return await prepare.finishMessage(input, prepared, { error });
|
|
346
|
+
} finally {
|
|
347
|
+
codexAppServerPromptDeliveries.delete(promptDeliveryKey);
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
async function sendCodexAppServerMessage(sessionId, input = {}, options = {}, prepare) {
|
|
352
|
+
const startedAt = Date.now();
|
|
353
|
+
const turnOwnership = options.turnOwnership || null;
|
|
354
|
+
const message = codexAppServerMessageText(input);
|
|
355
|
+
const displayMessage = codexAppServerMessageDisplayText(input, message);
|
|
356
|
+
const messageId = normalizeCodexRunText(input?.messageId);
|
|
357
|
+
if (!message && options.preparedInput === undefined) {
|
|
358
|
+
return { value: {
|
|
359
|
+
code: steerFailedCode,
|
|
360
|
+
error: "Codex message input is empty.",
|
|
361
|
+
ok: false,
|
|
362
|
+
operationOutcome: "message_empty",
|
|
363
|
+
refreshRecommended: false
|
|
364
|
+
} };
|
|
365
|
+
}
|
|
366
|
+
const context = await prepare.readContext(options);
|
|
367
|
+
if (context.ok === false) {
|
|
368
|
+
return { value: context };
|
|
369
|
+
}
|
|
370
|
+
const { runtime, session } = context;
|
|
371
|
+
if (
|
|
372
|
+
messageId &&
|
|
373
|
+
typeof runtime.store.conversationMessageIdExists === "function" &&
|
|
374
|
+
await runtime.store.conversationMessageIdExists(sessionId, messageId)
|
|
375
|
+
) {
|
|
376
|
+
return { value: {
|
|
377
|
+
delivered: true,
|
|
378
|
+
duplicate: true,
|
|
379
|
+
ok: true,
|
|
380
|
+
operationOutcome: "message_already_delivered"
|
|
381
|
+
}, session };
|
|
382
|
+
}
|
|
383
|
+
const actorContext = options.actorContext || null;
|
|
384
|
+
const turnMetadata = { ...input.turnMetadata, ...await messageMetadata.actor?.(actorContext) };
|
|
385
|
+
debugLog("appServerMessage.contextReady", {
|
|
386
|
+
durationMs: Date.now() - startedAt,
|
|
387
|
+
messageId,
|
|
388
|
+
sessionId
|
|
389
|
+
});
|
|
390
|
+
const selected = await selectCodexAppServerMessageTurn(sessionId, { messageId, startedAt }, context.selection);
|
|
391
|
+
if (Object.hasOwn(selected, "value")) return selected;
|
|
392
|
+
const { provider, threadId, turnId } = selected;
|
|
393
|
+
const prepared = { ...context, turnOwnership, messageId, actorContext };
|
|
394
|
+
const policy = await prepare.prepareMessage(input, prepared, { starting: false, selected });
|
|
395
|
+
if (policy) return policy;
|
|
396
|
+
const clientUserMessageId = messageId || `${messageIdPrefix}${crypto.randomUUID()}`;
|
|
397
|
+
return steerCodexAppServerTurn(sessionId, input, {
|
|
398
|
+
runtime, provider, threadId, turnId, message, displayMessage, messageId, clientUserMessageId, turnMetadata,
|
|
399
|
+
preparedInput: options.preparedInput
|
|
400
|
+
});
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
async function dispatchCodexAppServerMessage(sessionId, input = {}, options = {}, prepare) {
|
|
404
|
+
const result = await sendCodexAppServerMessage(sessionId, input, options, prepare);
|
|
405
|
+
if (result.value?.newTurnRequired !== true) {
|
|
406
|
+
return result;
|
|
407
|
+
}
|
|
408
|
+
const message = codexAppServerMessageText(input);
|
|
409
|
+
const messageId = normalizeCodexRunText(input?.messageId) ||
|
|
410
|
+
`${messageIdPrefix}${crypto.randomUUID()}`;
|
|
411
|
+
const started = await startCodexAppServerMessage(sessionId, isPlainObject(input) ? {
|
|
412
|
+
...input,
|
|
413
|
+
message,
|
|
414
|
+
messageId
|
|
415
|
+
} : {
|
|
416
|
+
message,
|
|
417
|
+
messageId
|
|
418
|
+
}, {
|
|
419
|
+
agentSettings: input?.agentSettings || {},
|
|
420
|
+
runtime: options.runtime || null,
|
|
421
|
+
session: options.session || null,
|
|
422
|
+
actorContext: options.actorContext || null,
|
|
423
|
+
preparedInput: options.preparedInput
|
|
424
|
+
}, prepare);
|
|
425
|
+
// The original unavailable-worktree return starts its policy operation,
|
|
426
|
+
// releases the prompt marker in finally, then awaits the returned value.
|
|
427
|
+
started.value = await started.value;
|
|
428
|
+
if (started.value?.ok === false || started.value?.delivered === false) {
|
|
429
|
+
return started;
|
|
430
|
+
}
|
|
431
|
+
const runtime = await createRuntime(sessionId);
|
|
432
|
+
const displayMessage = codexAppServerMessageDisplayText(input, message);
|
|
433
|
+
const conversationTurn = await writeCodexAppServerDeliveredUserMessage(
|
|
434
|
+
runtime,
|
|
435
|
+
sessionId,
|
|
436
|
+
displayMessage || message,
|
|
437
|
+
messageId,
|
|
438
|
+
started.value.turnMetadata || null,
|
|
439
|
+
input?.displayAttachments,
|
|
440
|
+
started.nativeIdentity
|
|
441
|
+
);
|
|
442
|
+
return {
|
|
443
|
+
...started,
|
|
444
|
+
value: {
|
|
445
|
+
...started.value,
|
|
446
|
+
conversationTurn,
|
|
447
|
+
conversationTurns: [conversationTurn],
|
|
448
|
+
delivered: true,
|
|
449
|
+
deliveryMode: "new_turn",
|
|
450
|
+
newTurnRequired: false
|
|
451
|
+
}
|
|
452
|
+
};
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
async function startCodexAppServerTurn(sessionId, input = {}, context = {}) {
|
|
456
|
+
const { runtime, provider, threadId, promptDeliveryKey, messageId, renderedPrompt, turnMetadata } = context;
|
|
457
|
+
let providerFailure = "";
|
|
458
|
+
let delivery = null;
|
|
459
|
+
const clientUserMessageId = messageId;
|
|
460
|
+
await writeCodexAppServerUserMessageOwnership(runtime.store, sessionId, clientUserMessageId, {
|
|
461
|
+
eventKind: "codex-app-server-user-message-owned",
|
|
462
|
+
owned: true
|
|
463
|
+
});
|
|
464
|
+
try {
|
|
465
|
+
await input.onPromptSending?.({ threadId: threadId, displayAttachments: input.displayAttachments, turnMetadata });
|
|
466
|
+
const response = sendCodexAppServerPrompt({
|
|
467
|
+
clientUserMessageId,
|
|
468
|
+
prompt: renderedPrompt,
|
|
469
|
+
attachments: input.attachments,
|
|
470
|
+
provider,
|
|
471
|
+
threadId
|
|
472
|
+
}, context);
|
|
473
|
+
const receipt = codexAppServerPendingUserMessages.get(
|
|
474
|
+
`${promptDeliveryKey}\0${clientUserMessageId}`
|
|
475
|
+
)?.receipt;
|
|
476
|
+
// An exact, persisted user-message receipt confirms delivery too.
|
|
477
|
+
// Finish bookkeeping once; a late RPC reply must not hold admission
|
|
478
|
+
// or change a turn that has already continued or completed.
|
|
479
|
+
delivery = receipt
|
|
480
|
+
? await Promise.race([response, receipt.promise.then((turn) => ({ turn }))])
|
|
481
|
+
: await response;
|
|
482
|
+
} catch (error) {
|
|
483
|
+
if (Number.isInteger(error?.code) && error.code < 0) await input.onPromptRejected?.();
|
|
484
|
+
await writeCodexAppServerUserMessageOwnership(runtime.store, sessionId, clientUserMessageId, {
|
|
485
|
+
eventKind: "codex-app-server-user-message-released",
|
|
486
|
+
owned: false
|
|
487
|
+
});
|
|
488
|
+
await markCodexAppServerTurnIdle(sessionId, {
|
|
489
|
+
error: codexAppServerErrorMessage(error, "Codex app-server prompt delivery failed."),
|
|
490
|
+
status: "failed",
|
|
491
|
+
threadId: threadId
|
|
492
|
+
});
|
|
493
|
+
return { error, turnFailureHandled: true };
|
|
494
|
+
}
|
|
495
|
+
const deliveredTurnId = normalizeCodexRunText(delivery.turn?.id);
|
|
496
|
+
const deliveredTurnStatus = normalizeCodexRunText(delivery.turn?.status || delivery.turn?.raw?.status);
|
|
497
|
+
if (!deliveredTurnId) {
|
|
498
|
+
throw new Error("Codex app-server accepted the prompt without returning a turn id.");
|
|
499
|
+
}
|
|
500
|
+
await markCodexAppServerTurnActive(sessionId, {
|
|
501
|
+
requireTrackedTurn: true,
|
|
502
|
+
status: "inProgress",
|
|
503
|
+
threadId: threadId,
|
|
504
|
+
turnId: deliveredTurnId
|
|
505
|
+
});
|
|
506
|
+
if (codexAppServerTurnStatusIsProviderFailure(deliveredTurnStatus)) {
|
|
507
|
+
providerFailure = `Codex turn ${deliveredTurnStatus}.`;
|
|
508
|
+
await stopCodexAppServerTurnWithProviderFailure(sessionId, threadId, deliveredTurnId, {
|
|
509
|
+
provider,
|
|
510
|
+
status: deliveredTurnStatus
|
|
511
|
+
});
|
|
512
|
+
} else if (codexAppServerTurnStatusIsSuccessfulComplete(deliveredTurnStatus)) {
|
|
513
|
+
const completion = await completeCodexAppServerTurn(sessionId, threadId, deliveredTurnId, {
|
|
514
|
+
provider,
|
|
515
|
+
status: deliveredTurnStatus
|
|
516
|
+
});
|
|
517
|
+
if (completion?.ok === false) {
|
|
518
|
+
providerFailure = normalizeCodexRunText(completion.error) || "Codex completed, but its response could not be processed.";
|
|
519
|
+
}
|
|
520
|
+
}
|
|
521
|
+
return { delivery, deliveredTurnId, providerFailure };
|
|
522
|
+
}
|
|
523
|
+
|
|
524
|
+
async function steerCodexAppServerTurn(sessionId, input = {}, context = {}) {
|
|
525
|
+
const { runtime, provider, threadId, turnId, message, displayMessage, messageId, clientUserMessageId, turnMetadata } = context;
|
|
526
|
+
let currentSession;
|
|
527
|
+
await writeCodexAppServerUserMessageOwnership(runtime.store, sessionId, clientUserMessageId, {
|
|
528
|
+
eventKind: "codex-app-server-user-message-owned",
|
|
529
|
+
owned: true
|
|
530
|
+
});
|
|
531
|
+
debugLog("appServerMessage.activeTurn.start", {
|
|
532
|
+
messageId,
|
|
533
|
+
sessionId,
|
|
534
|
+
threadId,
|
|
535
|
+
turnId
|
|
536
|
+
});
|
|
537
|
+
async function recoverAfterSteerFailure(error = null) {
|
|
538
|
+
await writeCodexAppServerUserMessageOwnership(runtime.store, sessionId, clientUserMessageId, {
|
|
539
|
+
eventKind: "codex-app-server-user-message-released",
|
|
540
|
+
owned: false
|
|
541
|
+
});
|
|
542
|
+
await reconcileCodexAppServerThreadStatus(sessionId, provider, threadId, {
|
|
543
|
+
source: "message_delivery_steer_race"
|
|
544
|
+
}).catch(() => null);
|
|
545
|
+
currentSession = await runtime.getSession(sessionId);
|
|
546
|
+
const currentTurn = turnState(currentSession);
|
|
547
|
+
const sameTurnIsActive = currentTurn.active === true &&
|
|
548
|
+
normalizeCodexRunText(currentTurn.threadId) === threadId &&
|
|
549
|
+
normalizeCodexRunText(currentTurn.turnId) === turnId &&
|
|
550
|
+
currentTurn.state !== "finalizing";
|
|
551
|
+
if (sameTurnIsActive) {
|
|
552
|
+
return codexAppServerRequestIsInvalid(error, "turn/steer")
|
|
553
|
+
? { value: codexAppServerMessageDeferred({ threadId, turnId }, steerFailedCode), session: currentSession }
|
|
554
|
+
: null;
|
|
555
|
+
}
|
|
556
|
+
debugLog("appServerMessage.newTurn", {
|
|
557
|
+
error: debugError(error),
|
|
558
|
+
messageId,
|
|
559
|
+
reason: "active_turn_completed_before_delivery",
|
|
560
|
+
sessionId,
|
|
561
|
+
threadId,
|
|
562
|
+
turnId
|
|
563
|
+
});
|
|
564
|
+
return { value: codexAppServerMessageRequiresNewTurn({
|
|
565
|
+
reason: "active_turn_completed_before_delivery",
|
|
566
|
+
threadId,
|
|
567
|
+
turnId
|
|
568
|
+
}), session: currentSession };
|
|
569
|
+
}
|
|
570
|
+
let result;
|
|
571
|
+
try {
|
|
572
|
+
await input.onPromptSending?.({ threadId, displayAttachments: input.displayAttachments, turnMetadata });
|
|
573
|
+
const response = provider.steerTurn(
|
|
574
|
+
threadId,
|
|
575
|
+
turnId,
|
|
576
|
+
context.preparedInput === undefined
|
|
577
|
+
? input.attachments?.some((attachment) => attachment.contentType?.startsWith("image/"))
|
|
578
|
+
? [message, ...codexLocalImageInput(input.attachments)]
|
|
579
|
+
: message
|
|
580
|
+
: context.preparedInput,
|
|
581
|
+
{
|
|
582
|
+
clientUserMessageId
|
|
583
|
+
}
|
|
584
|
+
);
|
|
585
|
+
const receipt = codexAppServerPendingUserMessages.get(
|
|
586
|
+
`${namespace(sessionId)}\0${clientUserMessageId}`
|
|
587
|
+
)?.receipt;
|
|
588
|
+
result = receipt ? await Promise.race([response, receipt.promise]) : await response;
|
|
589
|
+
} catch (error) {
|
|
590
|
+
if (Number.isInteger(error?.code) && error.code < 0) await input.onPromptRejected?.();
|
|
591
|
+
const recovered = await recoverAfterSteerFailure(error);
|
|
592
|
+
if (recovered) {
|
|
593
|
+
return recovered;
|
|
594
|
+
}
|
|
595
|
+
debugLog("appServerMessage.activeTurn.error", {
|
|
596
|
+
error: debugError(error),
|
|
597
|
+
messageId,
|
|
598
|
+
sessionId,
|
|
599
|
+
threadId,
|
|
600
|
+
turnId
|
|
601
|
+
});
|
|
602
|
+
throw error;
|
|
603
|
+
}
|
|
604
|
+
const steerFailure = codexAppServerSteerFailure(result, steerFailedCode);
|
|
605
|
+
if (steerFailure) {
|
|
606
|
+
const recovered = await recoverAfterSteerFailure(result);
|
|
607
|
+
if (recovered) {
|
|
608
|
+
return recovered;
|
|
609
|
+
}
|
|
610
|
+
debugLog("appServerMessage.activeTurn.failed", {
|
|
611
|
+
error: steerFailure.error,
|
|
612
|
+
messageId,
|
|
613
|
+
operationOutcome: steerFailure.operationOutcome,
|
|
614
|
+
sessionId,
|
|
615
|
+
threadId,
|
|
616
|
+
turnId
|
|
617
|
+
});
|
|
618
|
+
return { value: {
|
|
619
|
+
...steerFailure,
|
|
620
|
+
result,
|
|
621
|
+
threadId,
|
|
622
|
+
turnId
|
|
623
|
+
} };
|
|
624
|
+
}
|
|
625
|
+
const conversationTurn = await writeCodexAppServerDeliveredUserMessage(
|
|
626
|
+
runtime,
|
|
627
|
+
sessionId,
|
|
628
|
+
displayMessage || message,
|
|
629
|
+
messageId,
|
|
630
|
+
turnMetadata,
|
|
631
|
+
input?.displayAttachments,
|
|
632
|
+
{ threadId, turnId }
|
|
633
|
+
);
|
|
634
|
+
splitCodexAppServerReasoningTurn(threadId, turnId);
|
|
635
|
+
currentSession = await runtime.getSession(sessionId);
|
|
636
|
+
debugLog("appServerMessage.activeTurn.done", {
|
|
637
|
+
conversationTurnId: normalizeCodexRunText(conversationTurn?.turnId || conversationTurn?.id),
|
|
638
|
+
messageId,
|
|
639
|
+
sessionId,
|
|
640
|
+
threadId,
|
|
641
|
+
turnId
|
|
642
|
+
});
|
|
643
|
+
return { value: {
|
|
644
|
+
conversationTurn,
|
|
645
|
+
conversationTurns: [conversationTurn],
|
|
646
|
+
delivered: true,
|
|
647
|
+
deliveryMode: "active_turn",
|
|
648
|
+
newTurnRequired: false,
|
|
649
|
+
ok: true,
|
|
650
|
+
operationOutcome: "delivered_to_active_turn",
|
|
651
|
+
result,
|
|
652
|
+
threadId,
|
|
653
|
+
turnId
|
|
654
|
+
}, session: currentSession };
|
|
655
|
+
}
|
|
656
|
+
|
|
657
|
+
return {
|
|
658
|
+
selectMessageTurn: selectCodexAppServerMessageTurn,
|
|
659
|
+
dispatchMessage: dispatchCodexAppServerMessage,
|
|
660
|
+
startTurn: startCodexAppServerTurn,
|
|
661
|
+
steerTurn: steerCodexAppServerTurn
|
|
662
|
+
};
|
|
663
|
+
}
|