@jskit-ai/assistant-core 0.1.199 → 0.1.201
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,332 @@
|
|
|
1
|
+
import { normalizeText, isPlainObject } from "./normalize.js";
|
|
2
|
+
import { classifyCodexAppServerEvent, codexAppServerNotificationParams, codexAppServerNotificationThreadId, codexAppServerNotificationTurnId, codexAppServerNotificationTurnStatus, codexAppServerNotificationError, codexAppServerStatusFromValue, codexAppServerProviderThreadAssistantSegments, codexAppServerProviderTurnId, codexAppServerProviderTurnStatus, codexAppServerProviderTurnForOperation, codexAppServerProviderTurnText, codexAppServerProviderTurnError } from "./codexEvents.js";
|
|
3
|
+
import { codexAppServerTurnStatusIsProviderFailure, codexAppServerTurnStatusIsSuccessfulComplete } from "./codexTurnState.js";
|
|
4
|
+
|
|
5
|
+
const CODEX_APP_SERVER_DETACHED_TURN_TIMEOUT_MS = 180_000;
|
|
6
|
+
const CODEX_APP_SERVER_DETACHED_FAILURE_DETAIL_GRACE_MS = 500;
|
|
7
|
+
|
|
8
|
+
function codexAppServerTurnTokenUsage(notification = {}) {
|
|
9
|
+
if (normalizeText(notification.method) !== "thread/tokenUsage/updated") {
|
|
10
|
+
return null;
|
|
11
|
+
}
|
|
12
|
+
const params = codexAppServerNotificationParams(notification);
|
|
13
|
+
const tokenUsage = isPlainObject(params.tokenUsage) ? params.tokenUsage : {};
|
|
14
|
+
const turnUsage = isPlainObject(tokenUsage.last) ? tokenUsage.last : tokenUsage;
|
|
15
|
+
const snapshot = {};
|
|
16
|
+
for (const field of [
|
|
17
|
+
"cachedInputTokens",
|
|
18
|
+
"cacheWriteInputTokens",
|
|
19
|
+
"inputTokens",
|
|
20
|
+
"outputTokens",
|
|
21
|
+
"reasoningOutputTokens",
|
|
22
|
+
"totalTokens"
|
|
23
|
+
]) {
|
|
24
|
+
const value = Number(turnUsage[field]);
|
|
25
|
+
if (Number.isSafeInteger(value) && value >= 0) {
|
|
26
|
+
snapshot[field] = value;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
return Object.keys(snapshot).length ? Object.freeze(snapshot) : null;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function createCodexAppServerDetachedTurnWatcher(provider = null, threadId = "", {
|
|
33
|
+
includeThreadHistory = true,
|
|
34
|
+
allowEmptyResponse = false,
|
|
35
|
+
onEvent = null,
|
|
36
|
+
timeoutMs = CODEX_APP_SERVER_DETACHED_TURN_TIMEOUT_MS
|
|
37
|
+
} = {}) {
|
|
38
|
+
const normalizedThreadId = normalizeText(threadId);
|
|
39
|
+
let targetTurnId = "";
|
|
40
|
+
let finalText = "";
|
|
41
|
+
let usage = null;
|
|
42
|
+
let failureDetailTimeout = null;
|
|
43
|
+
let settled = false;
|
|
44
|
+
let timeout = null;
|
|
45
|
+
let connectionCheck = null;
|
|
46
|
+
let unsubscribe = null;
|
|
47
|
+
let pendingCompletionStatus = "";
|
|
48
|
+
let pendingFailure = null;
|
|
49
|
+
let resolveWaiter = null;
|
|
50
|
+
let rejectWaiter = null;
|
|
51
|
+
|
|
52
|
+
function cleanup() {
|
|
53
|
+
clearTimeout(timeout);
|
|
54
|
+
timeout = null;
|
|
55
|
+
clearInterval(connectionCheck);
|
|
56
|
+
connectionCheck = null;
|
|
57
|
+
clearTimeout(failureDetailTimeout);
|
|
58
|
+
failureDetailTimeout = null;
|
|
59
|
+
unsubscribe?.();
|
|
60
|
+
unsubscribe = null;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function finish(result = {}) {
|
|
64
|
+
if (settled) {
|
|
65
|
+
return;
|
|
66
|
+
}
|
|
67
|
+
settled = true;
|
|
68
|
+
cleanup();
|
|
69
|
+
resolveWaiter?.(result);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
function fail(error) {
|
|
73
|
+
if (settled) {
|
|
74
|
+
return;
|
|
75
|
+
}
|
|
76
|
+
settled = true;
|
|
77
|
+
cleanup();
|
|
78
|
+
rejectWaiter?.(error);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
function emitWatcherEvent(classification = {}, notification) {
|
|
82
|
+
if (typeof onEvent !== "function" || !classification?.kind) {
|
|
83
|
+
return;
|
|
84
|
+
}
|
|
85
|
+
onEvent({
|
|
86
|
+
...classification,
|
|
87
|
+
threadId: classification.threadId || normalizedThreadId,
|
|
88
|
+
turnId: classification.turnId || targetTurnId
|
|
89
|
+
}, notification);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
async function resultFromThread() {
|
|
93
|
+
if (
|
|
94
|
+
!includeThreadHistory ||
|
|
95
|
+
!normalizedThreadId ||
|
|
96
|
+
!targetTurnId ||
|
|
97
|
+
typeof provider?.readThread !== "function"
|
|
98
|
+
) {
|
|
99
|
+
return {
|
|
100
|
+
status: "",
|
|
101
|
+
statusType: "",
|
|
102
|
+
text: ""
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
const thread = await provider.readThread(normalizedThreadId);
|
|
106
|
+
const rawStatus = thread.raw?.status || thread.response?.thread?.status;
|
|
107
|
+
return {
|
|
108
|
+
status: codexAppServerStatusFromValue(rawStatus),
|
|
109
|
+
statusType: normalizeText(typeof rawStatus === "string" ? rawStatus : rawStatus?.type),
|
|
110
|
+
text: codexAppServerProviderThreadAssistantSegments(thread, targetTurnId)
|
|
111
|
+
.map((segment) => segment.text)
|
|
112
|
+
.join("\n\n")
|
|
113
|
+
};
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
function failAfterDetailGrace(error) {
|
|
117
|
+
if (settled || failureDetailTimeout) {
|
|
118
|
+
return;
|
|
119
|
+
}
|
|
120
|
+
failureDetailTimeout = setTimeout(() => {
|
|
121
|
+
failureDetailTimeout = null;
|
|
122
|
+
fail(error);
|
|
123
|
+
}, CODEX_APP_SERVER_DETACHED_FAILURE_DETAIL_GRACE_MS);
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
async function finishFromCompletion(status = "completed") {
|
|
127
|
+
try {
|
|
128
|
+
if (!targetTurnId) {
|
|
129
|
+
pendingCompletionStatus = normalizeText(status) || "completed";
|
|
130
|
+
return;
|
|
131
|
+
}
|
|
132
|
+
const authoritative = await resultFromThread().catch(() => ({
|
|
133
|
+
status: "",
|
|
134
|
+
statusType: "",
|
|
135
|
+
text: ""
|
|
136
|
+
}));
|
|
137
|
+
finalText = authoritative.text || finalText;
|
|
138
|
+
if (!finalText && !allowEmptyResponse) {
|
|
139
|
+
pendingCompletionStatus = normalizeText(status) || "completed";
|
|
140
|
+
const systemError = authoritative.statusType === "systemError" || authoritative.status === "failed";
|
|
141
|
+
failAfterDetailGrace(new Error(systemError
|
|
142
|
+
? "Codex app-server thread entered a system error before producing an assistant response."
|
|
143
|
+
: "Codex app-server completed without producing an assistant response."));
|
|
144
|
+
return;
|
|
145
|
+
}
|
|
146
|
+
finish({
|
|
147
|
+
status,
|
|
148
|
+
text: finalText,
|
|
149
|
+
threadId: normalizedThreadId,
|
|
150
|
+
turnId: targetTurnId,
|
|
151
|
+
usage
|
|
152
|
+
});
|
|
153
|
+
} catch (error) {
|
|
154
|
+
fail(error);
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
function notificationMatches(notification = {}) {
|
|
159
|
+
const notificationThreadId = codexAppServerNotificationThreadId(notification);
|
|
160
|
+
if (notificationThreadId && notificationThreadId !== normalizedThreadId) {
|
|
161
|
+
return false;
|
|
162
|
+
}
|
|
163
|
+
const notificationTurnId = codexAppServerNotificationTurnId(notification);
|
|
164
|
+
return !targetTurnId || !notificationTurnId || notificationTurnId === targetTurnId;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
return {
|
|
168
|
+
async completeNow(status = "completed") {
|
|
169
|
+
await finishFromCompletion(status);
|
|
170
|
+
},
|
|
171
|
+
failNow(error) {
|
|
172
|
+
fail(error);
|
|
173
|
+
},
|
|
174
|
+
failAfterDetailGrace(error) {
|
|
175
|
+
failAfterDetailGrace(error);
|
|
176
|
+
},
|
|
177
|
+
setTurnId(turnId = "") {
|
|
178
|
+
targetTurnId = normalizeText(turnId);
|
|
179
|
+
if (pendingFailure) {
|
|
180
|
+
failAfterDetailGrace(pendingFailure);
|
|
181
|
+
return;
|
|
182
|
+
}
|
|
183
|
+
if (pendingCompletionStatus) {
|
|
184
|
+
const status = pendingCompletionStatus;
|
|
185
|
+
pendingCompletionStatus = "";
|
|
186
|
+
void finishFromCompletion(status);
|
|
187
|
+
}
|
|
188
|
+
},
|
|
189
|
+
wait() {
|
|
190
|
+
if (settled) {
|
|
191
|
+
return Promise.reject(new Error("Codex app-server detached turn watcher was already settled."));
|
|
192
|
+
}
|
|
193
|
+
return new Promise((resolve, reject) => {
|
|
194
|
+
resolveWaiter = resolve;
|
|
195
|
+
rejectWaiter = reject;
|
|
196
|
+
if (timeoutMs > 0) {
|
|
197
|
+
timeout = setTimeout(() => {
|
|
198
|
+
fail(new Error("Timed out waiting for Codex app-server response."));
|
|
199
|
+
}, timeoutMs);
|
|
200
|
+
} else {
|
|
201
|
+
// Interactive repair turns may take longer than bounded helper jobs.
|
|
202
|
+
// Retain their watcher until completion, interruption, or connection loss.
|
|
203
|
+
const generation = provider?.currentConnectionGeneration?.();
|
|
204
|
+
connectionCheck = setInterval(() => {
|
|
205
|
+
if (provider?.isAvailable?.() === false ||
|
|
206
|
+
provider?.currentConnectionGeneration?.() !== generation) {
|
|
207
|
+
fail(new Error("Connection to Codex was lost while the temporary AI was working."));
|
|
208
|
+
}
|
|
209
|
+
}, 1000);
|
|
210
|
+
connectionCheck.unref?.();
|
|
211
|
+
}
|
|
212
|
+
unsubscribe = typeof provider?.subscribe === "function"
|
|
213
|
+
? provider.subscribe((notification = {}) => {
|
|
214
|
+
if (!notificationMatches(notification)) {
|
|
215
|
+
return;
|
|
216
|
+
}
|
|
217
|
+
usage = codexAppServerTurnTokenUsage(notification) || usage;
|
|
218
|
+
const classification = classifyCodexAppServerEvent(notification);
|
|
219
|
+
emitWatcherEvent(classification, notification);
|
|
220
|
+
if (
|
|
221
|
+
classification.kind === "provider_error" &&
|
|
222
|
+
classification.text &&
|
|
223
|
+
codexAppServerNotificationParams(notification).willRetry !== true
|
|
224
|
+
) {
|
|
225
|
+
const error = new Error(classification.text);
|
|
226
|
+
if (!targetTurnId) {
|
|
227
|
+
pendingFailure = error;
|
|
228
|
+
return;
|
|
229
|
+
}
|
|
230
|
+
fail(error);
|
|
231
|
+
return;
|
|
232
|
+
}
|
|
233
|
+
if (classification.kind === "final_assistant_result" && classification.text) {
|
|
234
|
+
finalText = classification.text;
|
|
235
|
+
if (pendingCompletionStatus && targetTurnId) {
|
|
236
|
+
const status = pendingCompletionStatus;
|
|
237
|
+
pendingCompletionStatus = "";
|
|
238
|
+
clearTimeout(failureDetailTimeout);
|
|
239
|
+
failureDetailTimeout = null;
|
|
240
|
+
void finishFromCompletion(status);
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
const method = normalizeText(notification.method);
|
|
244
|
+
if (method !== "turn/completed" && method !== "thread/status/changed") {
|
|
245
|
+
return;
|
|
246
|
+
}
|
|
247
|
+
const status = codexAppServerNotificationTurnStatus(notification) || "completed";
|
|
248
|
+
if (codexAppServerTurnStatusIsProviderFailure(status)) {
|
|
249
|
+
const error = new Error(codexAppServerNotificationError(notification) || `Codex app-server turn ${status}.`);
|
|
250
|
+
if (!targetTurnId) {
|
|
251
|
+
pendingFailure = error;
|
|
252
|
+
return;
|
|
253
|
+
}
|
|
254
|
+
failAfterDetailGrace(error);
|
|
255
|
+
return;
|
|
256
|
+
}
|
|
257
|
+
if (codexAppServerTurnStatusIsSuccessfulComplete(status)) {
|
|
258
|
+
void finishFromCompletion(status);
|
|
259
|
+
}
|
|
260
|
+
})
|
|
261
|
+
: null;
|
|
262
|
+
});
|
|
263
|
+
}
|
|
264
|
+
};
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
function codexAppServerTurnError(reason, details = {}, providerMessage = "") {
|
|
268
|
+
const retryable = reason === "failed";
|
|
269
|
+
const error = new Error(providerMessage || (reason === "identity_missing"
|
|
270
|
+
? "Codex did not return the exact turn identity."
|
|
271
|
+
: `Codex app-server turn ${details.status}.`));
|
|
272
|
+
error.code = `codex_app_server_turn_${reason}`;
|
|
273
|
+
error.details = { ...details, retryable };
|
|
274
|
+
error.retryable = retryable;
|
|
275
|
+
return error;
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
async function waitForCodexAppServerTurn(provider, threadId = "", turn = null, {
|
|
279
|
+
timeoutMs = CODEX_APP_SERVER_DETACHED_TURN_TIMEOUT_MS,
|
|
280
|
+
createError = codexAppServerTurnError
|
|
281
|
+
} = {}) {
|
|
282
|
+
const normalizedThreadId = normalizeText(threadId || "");
|
|
283
|
+
const turnId = codexAppServerProviderTurnId(turn || {});
|
|
284
|
+
const status = codexAppServerProviderTurnStatus(turn || {});
|
|
285
|
+
if (!normalizedThreadId || !turnId) {
|
|
286
|
+
throw createError("identity_missing", { threadId: normalizedThreadId, turnId });
|
|
287
|
+
}
|
|
288
|
+
if (codexAppServerTurnStatusIsProviderFailure(status)) {
|
|
289
|
+
throw createError(
|
|
290
|
+
"failed",
|
|
291
|
+
{ status, threadId: normalizedThreadId, turnId },
|
|
292
|
+
codexAppServerProviderTurnError(turn || {})
|
|
293
|
+
);
|
|
294
|
+
}
|
|
295
|
+
const existingText = codexAppServerProviderTurnText({
|
|
296
|
+
raw: { turns: [turn] }
|
|
297
|
+
}, turnId);
|
|
298
|
+
if (codexAppServerTurnStatusIsSuccessfulComplete(status) && existingText) {
|
|
299
|
+
return {
|
|
300
|
+
status,
|
|
301
|
+
text: existingText,
|
|
302
|
+
threadId: normalizedThreadId,
|
|
303
|
+
turnId,
|
|
304
|
+
usage: null
|
|
305
|
+
};
|
|
306
|
+
}
|
|
307
|
+
const watcher = createCodexAppServerDetachedTurnWatcher(provider, normalizedThreadId, {
|
|
308
|
+
timeoutMs
|
|
309
|
+
});
|
|
310
|
+
watcher.setTurnId(turnId);
|
|
311
|
+
const pending = watcher.wait();
|
|
312
|
+
void pending.catch(() => null);
|
|
313
|
+
if (codexAppServerTurnStatusIsSuccessfulComplete(status)) {
|
|
314
|
+
await watcher.completeNow(status);
|
|
315
|
+
} else if (typeof provider?.readThread === "function") {
|
|
316
|
+
const latestThread = await provider.readThread(normalizedThreadId);
|
|
317
|
+
const latestTurn = codexAppServerProviderTurnForOperation(latestThread, { turnId });
|
|
318
|
+
const latestStatus = codexAppServerProviderTurnStatus(latestTurn || {});
|
|
319
|
+
if (codexAppServerTurnStatusIsProviderFailure(latestStatus)) {
|
|
320
|
+
watcher.failNow(createError(
|
|
321
|
+
"failed",
|
|
322
|
+
{ status: latestStatus, threadId: normalizedThreadId, turnId },
|
|
323
|
+
codexAppServerProviderTurnError(latestTurn || {})
|
|
324
|
+
));
|
|
325
|
+
} else if (codexAppServerTurnStatusIsSuccessfulComplete(latestStatus)) {
|
|
326
|
+
await watcher.completeNow(latestStatus);
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
return pending;
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
export { CODEX_APP_SERVER_DETACHED_TURN_TIMEOUT_MS, createCodexAppServerDetachedTurnWatcher, waitForCodexAppServerTurn };
|
|
@@ -3,6 +3,61 @@ import {
|
|
|
3
3
|
normalizeText
|
|
4
4
|
} from "./normalize.js";
|
|
5
5
|
|
|
6
|
+
const CODEX_TOKEN_USAGE_METHOD = "thread/tokenUsage/updated";
|
|
7
|
+
|
|
8
|
+
function nonNegativeInteger(value) {
|
|
9
|
+
if (value === null || value === undefined || String(value).trim() === "") {
|
|
10
|
+
return null;
|
|
11
|
+
}
|
|
12
|
+
const number = Number(value);
|
|
13
|
+
return Number.isSafeInteger(number) && number >= 0 ? number : null;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
function codexContextUsageFromNotification(notification = {}) {
|
|
17
|
+
if (normalizeText(notification?.method) !== CODEX_TOKEN_USAGE_METHOD) {
|
|
18
|
+
return null;
|
|
19
|
+
}
|
|
20
|
+
const params = notification?.params && typeof notification.params === "object"
|
|
21
|
+
? notification.params
|
|
22
|
+
: {};
|
|
23
|
+
const tokenUsage = params.tokenUsage && typeof params.tokenUsage === "object"
|
|
24
|
+
? params.tokenUsage
|
|
25
|
+
: {};
|
|
26
|
+
const last = tokenUsage.last && typeof tokenUsage.last === "object"
|
|
27
|
+
? tokenUsage.last
|
|
28
|
+
: {};
|
|
29
|
+
const total = tokenUsage.total && typeof tokenUsage.total === "object"
|
|
30
|
+
? tokenUsage.total
|
|
31
|
+
: {};
|
|
32
|
+
const usedTokens = nonNegativeInteger(last.totalTokens);
|
|
33
|
+
const inputTokens = nonNegativeInteger(last.inputTokens);
|
|
34
|
+
const cumulativeTokens = nonNegativeInteger(total.totalTokens);
|
|
35
|
+
const windowTokens = nonNegativeInteger(tokenUsage.modelContextWindow);
|
|
36
|
+
const threadId = normalizeText(params.threadId);
|
|
37
|
+
const turnId = normalizeText(params.turnId);
|
|
38
|
+
if (
|
|
39
|
+
!threadId ||
|
|
40
|
+
!turnId ||
|
|
41
|
+
usedTokens === null ||
|
|
42
|
+
inputTokens === null ||
|
|
43
|
+
cumulativeTokens === null ||
|
|
44
|
+
!windowTokens ||
|
|
45
|
+
usedTokens > windowTokens ||
|
|
46
|
+
inputTokens > usedTokens ||
|
|
47
|
+
cumulativeTokens < usedTokens
|
|
48
|
+
) {
|
|
49
|
+
return null;
|
|
50
|
+
}
|
|
51
|
+
return Object.freeze({
|
|
52
|
+
cumulativeTokens,
|
|
53
|
+
inputTokens,
|
|
54
|
+
threadId,
|
|
55
|
+
turnId,
|
|
56
|
+
usedTokens,
|
|
57
|
+
windowTokens
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
|
|
6
61
|
const CODEX_APP_SERVER_CONTEXT_COMPACTION_SIGNALS = new Set(
|
|
7
62
|
[
|
|
8
63
|
"contextcompaction",
|
|
@@ -346,6 +401,11 @@ function classifyCodexAppServerEvent(notification = {}) {
|
|
|
346
401
|
threadId: codexAppServerNotificationThreadId(notification),
|
|
347
402
|
turnId: codexAppServerNotificationTurnId(notification)
|
|
348
403
|
};
|
|
404
|
+
if (itemType === "contextCompaction" && (method === "item/started" || method === "item/completed")) {
|
|
405
|
+
base.phase = method === "item/started" ? "compacting" : "working";
|
|
406
|
+
} else if (method === "error" && codexAppServerNotificationParams(notification).willRetry === true) {
|
|
407
|
+
base.phase = "retrying";
|
|
408
|
+
}
|
|
349
409
|
|
|
350
410
|
if (method === "item/agentMessage/delta") {
|
|
351
411
|
const { delta } = codexAppServerNotificationParams(notification);
|
|
@@ -376,6 +436,7 @@ function classifyCodexAppServerEvent(notification = {}) {
|
|
|
376
436
|
return {
|
|
377
437
|
...base,
|
|
378
438
|
kind: "reasoning_summary",
|
|
439
|
+
delta: codexAppServerContentText(codexAppServerNotificationParams(notification).delta),
|
|
379
440
|
text: normalizeText(codexAppServerContentText(codexAppServerNotificationParams(notification).delta))
|
|
380
441
|
};
|
|
381
442
|
}
|
|
@@ -462,6 +523,7 @@ function classifyCodexAppServerEvent(notification = {}) {
|
|
|
462
523
|
return {
|
|
463
524
|
...base,
|
|
464
525
|
kind: "status",
|
|
526
|
+
...(codexAppServerNotificationTurnStatus(notification) === "inProgress" ? { phase: "working" } : {}),
|
|
465
527
|
text: codexAppServerNotificationTurnStatus(notification)
|
|
466
528
|
};
|
|
467
529
|
}
|
|
@@ -544,7 +606,7 @@ function codexAppServerProviderThread(value = {}) {
|
|
|
544
606
|
}
|
|
545
607
|
|
|
546
608
|
function codexAppServerProviderTurnId(turn = {}) {
|
|
547
|
-
return normalizeText(turn.id || turn.turnId || turn.turn_id || turn.turn?.id);
|
|
609
|
+
return normalizeText(turn.id || turn.turnId || turn.turn_id || turn.turn?.id || "");
|
|
548
610
|
}
|
|
549
611
|
|
|
550
612
|
function codexAppServerProviderTurnItems(turn = {}) {
|
|
@@ -623,8 +685,139 @@ function codexAppServerOutputOwnerTurnId({
|
|
|
623
685
|
: normalizeText(notificationTurnId);
|
|
624
686
|
}
|
|
625
687
|
|
|
688
|
+
function codexAppServerThreadRawValue(thread = {}) {
|
|
689
|
+
if (isPlainObject(thread.raw)) {
|
|
690
|
+
return thread.raw;
|
|
691
|
+
}
|
|
692
|
+
if (isPlainObject(thread.response?.thread)) {
|
|
693
|
+
return thread.response.thread;
|
|
694
|
+
}
|
|
695
|
+
return isPlainObject(thread) ? thread : {};
|
|
696
|
+
}
|
|
697
|
+
|
|
698
|
+
function codexAppServerThreadTurnId(thread = {}) {
|
|
699
|
+
const observedTurnId = normalizeText(thread.observedTurn?.id || "");
|
|
700
|
+
if (observedTurnId) {
|
|
701
|
+
return observedTurnId;
|
|
702
|
+
}
|
|
703
|
+
const rawThread = codexAppServerThreadRawValue(thread);
|
|
704
|
+
const status = isPlainObject(rawThread.status) ? rawThread.status : {};
|
|
705
|
+
return normalizeText(
|
|
706
|
+
thread.turnId ||
|
|
707
|
+
thread.turn_id ||
|
|
708
|
+
thread.turn?.id ||
|
|
709
|
+
rawThread.turnId ||
|
|
710
|
+
rawThread.turn_id ||
|
|
711
|
+
rawThread.turn?.id ||
|
|
712
|
+
rawThread.currentTurnId ||
|
|
713
|
+
rawThread.current_turn_id ||
|
|
714
|
+
rawThread.activeTurnId ||
|
|
715
|
+
rawThread.active_turn_id ||
|
|
716
|
+
status.turnId ||
|
|
717
|
+
status.turn_id ||
|
|
718
|
+
status.turn?.id ||
|
|
719
|
+
status.currentTurnId ||
|
|
720
|
+
status.current_turn_id ||
|
|
721
|
+
status.activeTurnId ||
|
|
722
|
+
status.active_turn_id ||
|
|
723
|
+
""
|
|
724
|
+
);
|
|
725
|
+
}
|
|
726
|
+
|
|
727
|
+
function codexAppServerThreadError(thread = {}) {
|
|
728
|
+
const rawThread = codexAppServerThreadRawValue(thread);
|
|
729
|
+
const status = isPlainObject(rawThread.status) ? rawThread.status : {};
|
|
730
|
+
return codexAppServerErrorText(rawThread.error || status.error);
|
|
731
|
+
}
|
|
732
|
+
|
|
733
|
+
function codexAppServerProviderThreadTurns(thread = null) {
|
|
734
|
+
const rawThread = codexAppServerThreadRawValue(thread || {});
|
|
735
|
+
return (Array.isArray(rawThread.turns) ? rawThread.turns : [])
|
|
736
|
+
.filter((turn) => isPlainObject(turn));
|
|
737
|
+
}
|
|
738
|
+
|
|
739
|
+
function codexAppServerProviderTurnStatus(turn = {}) {
|
|
740
|
+
return codexAppServerStatusFromValue(turn.status || turn.state);
|
|
741
|
+
}
|
|
742
|
+
|
|
743
|
+
function codexAppServerProviderTurnClientIds(turn = {}) {
|
|
744
|
+
return codexAppServerProviderTurnItems(turn)
|
|
745
|
+
.map((item) => normalizeText(
|
|
746
|
+
item.clientId ||
|
|
747
|
+
item.client_id ||
|
|
748
|
+
item.clientUserMessageId ||
|
|
749
|
+
item.client_user_message_id ||
|
|
750
|
+
""
|
|
751
|
+
))
|
|
752
|
+
.filter(Boolean);
|
|
753
|
+
}
|
|
754
|
+
|
|
755
|
+
function codexAppServerProviderTurnForOperation(thread = null, {
|
|
756
|
+
clientMessageId = "",
|
|
757
|
+
turnId = ""
|
|
758
|
+
} = {}) {
|
|
759
|
+
const normalizedTurnId = normalizeText(turnId || "");
|
|
760
|
+
const normalizedClientMessageId = normalizeText(clientMessageId || "");
|
|
761
|
+
const turns = codexAppServerProviderThreadTurns(thread);
|
|
762
|
+
if (normalizedTurnId) {
|
|
763
|
+
return turns.find((turn) => (
|
|
764
|
+
codexAppServerProviderTurnId(turn) === normalizedTurnId
|
|
765
|
+
)) || null;
|
|
766
|
+
}
|
|
767
|
+
if (!normalizedClientMessageId) {
|
|
768
|
+
return null;
|
|
769
|
+
}
|
|
770
|
+
return [...turns].reverse().find((turn) => (
|
|
771
|
+
codexAppServerProviderTurnClientIds(turn).includes(normalizedClientMessageId)
|
|
772
|
+
)) || null;
|
|
773
|
+
}
|
|
774
|
+
|
|
775
|
+
// Native snapshot matching from the original renewal handover/seed paths.
|
|
776
|
+
// The application projects these classifications into its approval/error policy.
|
|
777
|
+
function inspectCodexAppServerRenewalThread(thread = null, {
|
|
778
|
+
clientMessageId = "",
|
|
779
|
+
turnId: expectedTurnId = "",
|
|
780
|
+
requireFresh = false
|
|
781
|
+
} = {}) {
|
|
782
|
+
const snapshotTurns = codexAppServerProviderThreadTurns(thread);
|
|
783
|
+
const targetTurn = codexAppServerProviderTurnForOperation(thread, {
|
|
784
|
+
clientMessageId,
|
|
785
|
+
turnId: expectedTurnId
|
|
786
|
+
});
|
|
787
|
+
if (expectedTurnId && !targetTurn) return { targetTurn, failure: "turn_missing" };
|
|
788
|
+
if (!requireFresh && !targetTurn && snapshotTurns.length === 0) return { targetTurn, failure: "history_missing" };
|
|
789
|
+
if (requireFresh && snapshotTurns.some((turn) => (
|
|
790
|
+
!targetTurn || codexAppServerProviderTurnId(turn) !== codexAppServerProviderTurnId(targetTurn)
|
|
791
|
+
))) return { targetTurn, failure: "unrelated_history" };
|
|
792
|
+
return { targetTurn, failure: "" };
|
|
793
|
+
}
|
|
794
|
+
|
|
795
|
+
function codexAppServerProviderTurnText(thread = null, turnId = "") {
|
|
796
|
+
return codexAppServerProviderThreadAssistantSegments(thread || {}, turnId)
|
|
797
|
+
.map((segment) => segment.text)
|
|
798
|
+
.join("\n\n")
|
|
799
|
+
.trim();
|
|
800
|
+
}
|
|
801
|
+
|
|
802
|
+
function codexAppServerProviderTurnError(turn = {}) {
|
|
803
|
+
return codexAppServerErrorText(turn.error || turn.status?.error || turn.state?.error);
|
|
804
|
+
}
|
|
805
|
+
|
|
626
806
|
export {
|
|
807
|
+
codexContextUsageFromNotification,
|
|
627
808
|
classifyCodexAppServerEvent,
|
|
809
|
+
codexAppServerThreadRawValue,
|
|
810
|
+
codexAppServerThreadTurnId,
|
|
811
|
+
codexAppServerThreadError,
|
|
812
|
+
codexAppServerProviderThreadTurns,
|
|
813
|
+
codexAppServerProviderTurnId,
|
|
814
|
+
codexAppServerProviderTurnStatus,
|
|
815
|
+
codexAppServerProviderTurnItems,
|
|
816
|
+
codexAppServerProviderTurnClientIds,
|
|
817
|
+
codexAppServerProviderTurnForOperation,
|
|
818
|
+
inspectCodexAppServerRenewalThread,
|
|
819
|
+
codexAppServerProviderTurnText,
|
|
820
|
+
codexAppServerProviderTurnError,
|
|
628
821
|
codexAppServerAssistantItemText,
|
|
629
822
|
codexAppServerContentText,
|
|
630
823
|
codexAppServerContextRefreshReason,
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
import { CODEX_APP_SERVER_AGENT_RUN_ID } from "./codexTurnState.js";
|
|
2
|
+
|
|
3
|
+
// Private composition of unchanged operations from the original run owner.
|
|
4
|
+
export function createCodexGoalCommands({
|
|
5
|
+
conversationContext,
|
|
6
|
+
ensureThreadReady,
|
|
7
|
+
publish,
|
|
8
|
+
readCodexAppServerAgentRunForSession,
|
|
9
|
+
reconcileCodexAppServerGoalUpdated,
|
|
10
|
+
submitCodexAppServerAssistantResult,
|
|
11
|
+
subscribeCodexAppServerEvents,
|
|
12
|
+
turnState
|
|
13
|
+
}) {
|
|
14
|
+
async function prepareCodexAppServerGoalContext(sessionId, options, preparation, { prepareThread = false } = {}) {
|
|
15
|
+
let context;
|
|
16
|
+
if (prepareThread) {
|
|
17
|
+
const prepared = await preparation.readiness(ensureThreadReady);
|
|
18
|
+
if (prepared.ok === false) return prepared;
|
|
19
|
+
context = await conversationContext(sessionId, {}, {});
|
|
20
|
+
} else {
|
|
21
|
+
// This is the current command's authorized context. Goal controls
|
|
22
|
+
// may deliberately retain a pinned selection after a model switch.
|
|
23
|
+
context = await conversationContext(sessionId, {}, options === undefined ? {} : options);
|
|
24
|
+
}
|
|
25
|
+
return preparation.project(context, options);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function validateCodexAppServerGoalInput(input = {}) {
|
|
29
|
+
if (!["set", "pause", "resume", "cancel"].includes(input.action)) {
|
|
30
|
+
return { ok: false, error: "Choose set, pause, resume, or cancel." };
|
|
31
|
+
}
|
|
32
|
+
if (input.action === "set" && (typeof input.objective !== "string" || !input.objective.trim() ||
|
|
33
|
+
(input.tokenBudget !== undefined && (!Number.isSafeInteger(input.tokenBudget) || input.tokenBudget <= 0)))) {
|
|
34
|
+
return { ok: false, error: "Enter a goal objective and an optional positive whole-number token budget." };
|
|
35
|
+
}
|
|
36
|
+
return null;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
async function updateCodexAppServerGoal(sessionId, input = {}, {
|
|
40
|
+
context: initialContext,
|
|
41
|
+
prepareConversation
|
|
42
|
+
} = {}) {
|
|
43
|
+
let context = initialContext;
|
|
44
|
+
if (context.ok === false) return context;
|
|
45
|
+
let threadId = context.threadId;
|
|
46
|
+
if (threadId !== input.threadId || (!threadId && input.action !== "set")) {
|
|
47
|
+
return { ok: false, error: "The Codex conversation changed. Refresh the goal before trying again." };
|
|
48
|
+
}
|
|
49
|
+
if (input.action === "set") {
|
|
50
|
+
if (turnState(context.session).status === "observation_lost") {
|
|
51
|
+
return { ok: false, error: "Resume or send a message to restore the stopped conversation before setting a goal." };
|
|
52
|
+
}
|
|
53
|
+
if (!threadId) {
|
|
54
|
+
context = await prepareConversation();
|
|
55
|
+
if (context.ok === false) return context;
|
|
56
|
+
threadId = context.threadId;
|
|
57
|
+
}
|
|
58
|
+
const { goal: previousGoal } = await context.provider.readGoal(threadId);
|
|
59
|
+
if (previousGoal && (previousGoal.status !== "complete" || previousGoal.createdAt !== input.createdAt)) {
|
|
60
|
+
return { ok: false, error: "The Codex goal changed. Refresh it before setting a new goal." };
|
|
61
|
+
}
|
|
62
|
+
subscribeCodexAppServerEvents(sessionId, context.provider, threadId, context);
|
|
63
|
+
const result = await context.provider.setGoal(threadId, {
|
|
64
|
+
objective: input.objective, tokenBudget: input.tokenBudget
|
|
65
|
+
});
|
|
66
|
+
await reconcileCodexAppServerGoalUpdated(sessionId, context.provider, threadId, {
|
|
67
|
+
params: { goal: result.goal }
|
|
68
|
+
});
|
|
69
|
+
await publish(sessionId, { reason: "codex-goal", nativeGoal: { threadId, goal: result.goal || null } });
|
|
70
|
+
return { ok: true, status: "available", threadId, goal: result.goal || null };
|
|
71
|
+
}
|
|
72
|
+
const { goal } = await context.provider.readGoal(threadId);
|
|
73
|
+
if (!goal || goal.status === "complete" || goal.createdAt !== input.createdAt || goal.objective !== input.objective) {
|
|
74
|
+
return { ok: false, error: "The Codex goal changed. Refresh it before trying again." };
|
|
75
|
+
}
|
|
76
|
+
if (input.action === "cancel") {
|
|
77
|
+
subscribeCodexAppServerEvents(sessionId, context.provider, threadId, context);
|
|
78
|
+
await context.provider.clearGoal(threadId);
|
|
79
|
+
await reconcileCodexAppServerGoalUpdated(sessionId, context.provider, threadId, {
|
|
80
|
+
method: "thread/goal/cleared",
|
|
81
|
+
params: { threadId }
|
|
82
|
+
});
|
|
83
|
+
await publish(sessionId, { reason: "codex-goal", nativeGoal: { threadId, goal: null } });
|
|
84
|
+
return { ok: true, status: "available", threadId, goal: null };
|
|
85
|
+
}
|
|
86
|
+
if (input.action === "pause" && !["active", "paused"].includes(goal.status)) {
|
|
87
|
+
return { ok: false, error: "This goal is already stopped. Refresh its details." };
|
|
88
|
+
}
|
|
89
|
+
const observationLost = turnState(context.session).status === "observation_lost";
|
|
90
|
+
if (observationLost && turnState(context.session).active) {
|
|
91
|
+
return { ok: false, error: "Codex has not yet confirmed a stop. Retry after the service has stopped." };
|
|
92
|
+
}
|
|
93
|
+
if (input.action === "resume" && !["paused", "blocked", "usageLimited", ...(observationLost ? ["active"] : [])].includes(goal.status)) {
|
|
94
|
+
return { ok: false, error: "This goal cannot be resumed from its current status. Refresh its details." };
|
|
95
|
+
}
|
|
96
|
+
subscribeCodexAppServerEvents(sessionId, context.provider, threadId, context);
|
|
97
|
+
if (observationLost && input.action === "resume") {
|
|
98
|
+
const recovered = await submitCodexAppServerAssistantResult(sessionId, threadId, turnState(context.session).turnId, { recoverFromProvider: true });
|
|
99
|
+
if (recovered.reason === "error") throw new Error(recovered.error);
|
|
100
|
+
await context.runtime.store.mutateSession(sessionId, async () => {
|
|
101
|
+
const run = await readCodexAppServerAgentRunForSession(context.runtime.store, sessionId);
|
|
102
|
+
if (run.active || run.providerThreadId !== threadId) throw new Error("The assistant changed. Refresh before resuming.");
|
|
103
|
+
await context.runtime.store.writeAgentRunEvent(sessionId, CODEX_APP_SERVER_AGENT_RUN_ID, {
|
|
104
|
+
event: { kind: "codex-observation-explicit-resume" },
|
|
105
|
+
patch: { providerStatus: "interrupted", error: "" }
|
|
106
|
+
});
|
|
107
|
+
});
|
|
108
|
+
await context.provider.resumeThread(threadId, context.resumeOptions);
|
|
109
|
+
}
|
|
110
|
+
const result = await context.provider.setGoalStatus(threadId, input.action === "pause" ? "paused" : "active");
|
|
111
|
+
await reconcileCodexAppServerGoalUpdated(sessionId, context.provider, threadId, {
|
|
112
|
+
params: { goal: result.goal }
|
|
113
|
+
});
|
|
114
|
+
await publish(sessionId, { reason: "codex-goal", nativeGoal: { threadId, goal: result.goal || null } });
|
|
115
|
+
return { ok: true, status: "available", goal: result.goal || null };
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
return {
|
|
120
|
+
prepareCodexAppServerGoalContext,
|
|
121
|
+
validateCodexAppServerGoalInput,
|
|
122
|
+
updateCodexAppServerGoal
|
|
123
|
+
};
|
|
124
|
+
}
|