@jskit-ai/assistant-core 0.1.213 → 0.1.215
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/examples/conversation/package.json +5 -5
- package/fixtures/responsive-assistant/ConversationFixture.vue +7 -1
- package/package.json +5 -5
- package/src/client/conversation/AssistantConversationElement.vue +5 -1
- package/src/client/conversation/AssistantTranscript.vue +10 -1
- package/src/server/conversation/agentRun.js +38 -11
- package/src/server/conversation/claudeHistory.js +40 -5
- package/src/server/conversation/claudeProcess.js +16 -2
- package/src/server/conversation/claudeTurn.js +75 -16
- package/src/server/conversation/codexConfiguration.js +1 -1
- package/src/server/conversation/codexHistoryAdapter.js +11 -4
- package/src/server/conversation/codexNotificationQueue.js +11 -2
- package/src/server/conversation/codexObservationControl.js +8 -8
- package/src/server/conversation/codexOutput.js +20 -15
- package/src/server/conversation/codexProvider.js +40 -3
- package/src/server/conversation/codexProviderOwner.js +110 -1
- package/src/server/conversation/codexTurn.js +3 -0
- package/src/server/conversation/continuity.js +24 -6
- package/src/server/conversation/index.js +1 -1
- package/src/server/conversation/nativeHistoryExport.js +97 -0
- package/src/server/conversation/openCodeClient.js +47 -1
- package/src/server/conversation/openCodeRuntime.js +132 -24
- package/src/server/conversation/openCodeTurn.js +65 -24
- package/src/server/conversation/providers/api.js +7 -0
- package/src/server/conversation/providers/claudeDriver.js +52 -7
- package/src/server/conversation/providers/codex.js +19 -2
- package/src/server/conversation/providers/codexDriver.js +92 -14
- package/src/server/conversation/providers/opencodeDriver.js +31 -9
- package/src/server/conversation/runtime.js +418 -64
- package/src/server/conversation/runtimeStateUpgrade.js +77 -2
- package/src/server/conversation/streams.js +25 -5
- package/src/server/conversation/tools.js +2 -2
- package/src/server/conversation/transcript.js +23 -8
- package/src/server/hosts/claudeHistory.js +1 -0
- package/src/server/hosts/claudeProcess.js +1 -0
- package/src/server/hosts/codexProvider.js +3 -0
- package/src/server/hosts/nativeHistory.js +1 -0
- package/src/server/hosts/openCodeClient.js +1 -0
- package/src/server/hosts/openCodeTurn.js +1 -0
- package/src/server/index.js +1 -1
- package/src/server/lib/assistantToolLoop.js +99 -2
- package/test/aiConnectionClient.test.js +57 -0
- package/test/assistantBoundedToolLoop.test.js +332 -0
- package/test/assistantScroll.browser.test.js +151 -0
- package/test/claudeConversationSession.test.js +1329 -3
- package/test/claudeProcess.test.js +101 -0
- package/test/claudeTurn.test.js +236 -1
- package/test/codexConversationSession.test.js +1257 -12
- package/test/codexHistoryAdapter.test.js +74 -1
- package/test/codexTurn.test.js +282 -1
- package/test/conversation.test.js +83 -0
- package/test/conversationAgentRun.test.js +181 -0
- package/test/conversationRuntime.test.js +95 -0
- package/test/conversationRuntimeStateUpgrade.test.js +89 -0
- package/test/conversationStreaming.test.js +85 -0
- package/test/conversationTools.test.js +112 -1
- package/test/openCodeClient.test.js +186 -0
- package/test/openCodeRuntime.test.js +78 -0
- package/test/opencodeConversationSession.test.js +637 -5
|
@@ -8,14 +8,14 @@
|
|
|
8
8
|
"build": "vite build"
|
|
9
9
|
},
|
|
10
10
|
"dependencies": {
|
|
11
|
-
"@jskit-ai/assistant-core": "0.1.
|
|
12
|
-
"@jskit-ai/http-runtime": "0.1.
|
|
13
|
-
"@jskit-ai/kernel": "0.1.
|
|
11
|
+
"@jskit-ai/assistant-core": "0.1.215",
|
|
12
|
+
"@jskit-ai/http-runtime": "0.1.235",
|
|
13
|
+
"@jskit-ai/kernel": "0.1.237",
|
|
14
14
|
"@mdi/js": "^7.4.47",
|
|
15
15
|
"vue": "^3.5.13",
|
|
16
16
|
"vuetify": "^4.0.0",
|
|
17
|
-
"@jskit-ai/connectors-core": "0.1.
|
|
18
|
-
"@jskit-ai/connectors-catalog": "0.1.
|
|
17
|
+
"@jskit-ai/connectors-core": "0.1.54",
|
|
18
|
+
"@jskit-ai/connectors-catalog": "0.1.54"
|
|
19
19
|
},
|
|
20
20
|
"devDependencies": {
|
|
21
21
|
"@vitejs/plugin-vue": "^6.0.7",
|
|
@@ -16,6 +16,8 @@ const previewCanTake = ref(true);
|
|
|
16
16
|
const previewDelivery = createAssistantMessageDelivery();
|
|
17
17
|
const avatarFixture = new URLSearchParams(location.search).has("avatar");
|
|
18
18
|
const avatarToolsFixture = new URLSearchParams(location.search).has("avatarTools");
|
|
19
|
+
const updateErrorFixture = new URLSearchParams(location.search).has("update-errors");
|
|
20
|
+
const errorReloads = ref(0);
|
|
19
21
|
const showAvatar = ref(avatarFixture);
|
|
20
22
|
const avatarSize = ref("compact");
|
|
21
23
|
const short = ref(avatarFixture);
|
|
@@ -136,7 +138,8 @@ const adapter = reactive({
|
|
|
136
138
|
assistant: { messageId: `answer-${index}`, outputId: `output-${index}`, role: "assistant",
|
|
137
139
|
text: `Support answer ${index + 1}: Keep your recovery email available.\n\nRead the **account instructions** and follow the [recovery link](https://example.com/help). A long message wraps inside this narrow conversation without taking space from the composer.` }
|
|
138
140
|
})) : [],
|
|
139
|
-
visible: true, reloadable: avatarFixture,
|
|
141
|
+
visible: true, reloadable: avatarFixture && !updateErrorFixture, error: "", errorReloadable: updateErrorFixture,
|
|
142
|
+
scrollKey: "fixture", welcomeMessage: "Composer responsiveness fixture"
|
|
140
143
|
},
|
|
141
144
|
composer: {
|
|
142
145
|
draft,
|
|
@@ -153,6 +156,7 @@ const adapter = reactive({
|
|
|
153
156
|
models: capabilitiesEnabled ? models : undefined,
|
|
154
157
|
questions: computed(() => showQuestions.value ? questionState : undefined),
|
|
155
158
|
actions: {
|
|
159
|
+
reload() { errorReloads.value += 1; adapter.conversation.error = ""; },
|
|
156
160
|
setDraft(value) { draft.value = value; },
|
|
157
161
|
submit(payload) { attachmentReceipts.value = payload.attachments; attachments.clearAttachments(); submissions.value += 1; draft.value = ""; phase.value = "active"; },
|
|
158
162
|
stop() { phase.value = "stopping"; }
|
|
@@ -166,6 +170,8 @@ const adapter = reactive({
|
|
|
166
170
|
<button v-if="attribution" @click="assistantLabel = 'Next assistant'">Change assistant</button>
|
|
167
171
|
<button v-for="state in ['idle', 'active', 'reconnecting', 'stopping', 'stopped']" :key="state" @click="phase = state">External {{ state }}</button>
|
|
168
172
|
<button @click="narrow = !narrow">Resize pane</button>
|
|
173
|
+
<button v-if="updateErrorFixture" @click="adapter.conversation.error = 'Conversation updates are temporarily unavailable. Reload to reconnect; the assistant may still be working.'">Show update error</button>
|
|
174
|
+
<output v-if="updateErrorFixture">Error reloads: {{ errorReloads }}</output>
|
|
169
175
|
<button @click="feedback = !feedback">Toggle action feedback</button>
|
|
170
176
|
<button v-if="supportEnabled" @click="suggestionModel = 'other-model'">Change suggestion model</button>
|
|
171
177
|
<button v-if="supportEnabled" @click="customActivity = !customActivity">Custom activity</button>
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@jskit-ai/assistant-core",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.215",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"scripts": {
|
|
6
6
|
"test": "node --test --test-concurrency=1"
|
|
@@ -47,8 +47,8 @@
|
|
|
47
47
|
"@ai-sdk/perplexity": "^4.0.43",
|
|
48
48
|
"@ai-sdk/togetherai": "^3.0.49",
|
|
49
49
|
"@ai-sdk/xai": "^4.0.58",
|
|
50
|
-
"@jskit-ai/resource-core": "0.1.
|
|
51
|
-
"@jskit-ai/resource-crud-core": "0.1.
|
|
50
|
+
"@jskit-ai/resource-core": "0.1.179",
|
|
51
|
+
"@jskit-ai/resource-crud-core": "0.1.179",
|
|
52
52
|
"@mdi/js": "^7.4.47",
|
|
53
53
|
"@openrouter/ai-sdk-provider": "^3.0.0",
|
|
54
54
|
"ai": "^7.0.101",
|
|
@@ -57,8 +57,8 @@
|
|
|
57
57
|
"ws": "^8.20.1"
|
|
58
58
|
},
|
|
59
59
|
"peerDependencies": {
|
|
60
|
-
"@jskit-ai/http-runtime": "0.1.
|
|
61
|
-
"@jskit-ai/kernel": "0.1.
|
|
60
|
+
"@jskit-ai/http-runtime": "0.1.235",
|
|
61
|
+
"@jskit-ai/kernel": "0.1.237",
|
|
62
62
|
"vue": "^3.5.13",
|
|
63
63
|
"vuetify": "^4.0.0"
|
|
64
64
|
},
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
<template>
|
|
2
|
-
<section ref="container" class="assistant-conversation" :aria-label="label">
|
|
2
|
+
<section ref="container" class="assistant-conversation" :class="{ 'assistant-conversation--error': conversation.error }" :aria-label="label">
|
|
3
3
|
<section
|
|
4
4
|
v-if="$slots.avatar" class="assistant-conversation__avatar"
|
|
5
5
|
:class="{ 'assistant-conversation__avatar--hidden': avatarSize === 'hidden' }" aria-label="Conversation avatar"
|
|
@@ -389,7 +389,10 @@ defineExpose({ focus: () => input.value?.focus(), submit, stop });
|
|
|
389
389
|
<style scoped>
|
|
390
390
|
.assistant-conversation__preview-actions { display: flex; justify-content: flex-end; }
|
|
391
391
|
.assistant-conversation { position: relative; display: flex; flex-direction: column; min-height: 0; min-width: 0; height: 100%; gap: 0; container: assistant-conversation / inline-size; }
|
|
392
|
+
.assistant-conversation--error { overflow-y: auto; }
|
|
392
393
|
.assistant-conversation__avatar { position: absolute; top: 0; right: 0; z-index: 3; max-width: 100%; min-width: 0; }
|
|
394
|
+
.assistant-conversation--error .assistant-conversation__avatar { position: relative; align-self: flex-end; flex: 0 0 auto; }
|
|
395
|
+
.assistant-conversation--error .assistant-conversation__avatar-controls { position: static; }
|
|
393
396
|
.assistant-conversation__avatar-controls { position: absolute; top: 0; right: 100%; display: flex; align-items: center; justify-content: flex-end; min-height: 44px; }
|
|
394
397
|
.assistant-conversation__avatar--hidden .assistant-conversation__avatar-controls { right: 0; }
|
|
395
398
|
.assistant-conversation__avatar-size { min-width: 44px; min-height: 44px; }
|
|
@@ -401,6 +404,7 @@ defineExpose({ focus: () => input.value?.focus(), submit, stop });
|
|
|
401
404
|
.assistant-conversation__avatar-visual :deep(> *) { max-width: 100%; max-height: 100%; }
|
|
402
405
|
.assistant-conversation__goal { display: flex; justify-content: flex-end; flex: 0 0 auto; }
|
|
403
406
|
.assistant-conversation__transcript { flex: 1 1 auto; min-height: 0; }
|
|
407
|
+
.assistant-conversation--error .assistant-conversation__transcript { min-height: min-content; }
|
|
404
408
|
.assistant-conversation__composer { flex: 0 0 auto; }
|
|
405
409
|
.assistant-conversation__attachment-error { flex-basis: 100%; margin: 0; }
|
|
406
410
|
.assistant-conversation__setting { min-width: 8rem; max-width: 16rem; }
|
|
@@ -440,6 +440,9 @@ const KEYBOARD_SCROLL_KEYS = new Set([
|
|
|
440
440
|
"Spacebar"
|
|
441
441
|
]);
|
|
442
442
|
const timeFormatter = new Intl.DateTimeFormat(undefined, {
|
|
443
|
+
year: "numeric",
|
|
444
|
+
month: "short",
|
|
445
|
+
day: "2-digit",
|
|
443
446
|
hour: "2-digit",
|
|
444
447
|
minute: "2-digit"
|
|
445
448
|
});
|
|
@@ -1103,7 +1106,12 @@ watch(() => props.visible, (visible) => {
|
|
|
1103
1106
|
}
|
|
1104
1107
|
|
|
1105
1108
|
.assistant-transcript--error {
|
|
1106
|
-
grid-template-rows:
|
|
1109
|
+
grid-template-rows: max-content minmax(0, 1fr);
|
|
1110
|
+
min-height: min-content;
|
|
1111
|
+
}
|
|
1112
|
+
|
|
1113
|
+
.assistant-transcript--error .assistant-transcript__body {
|
|
1114
|
+
contain: size;
|
|
1107
1115
|
}
|
|
1108
1116
|
|
|
1109
1117
|
.assistant-transcript__error {
|
|
@@ -1115,6 +1123,7 @@ watch(() => props.visible, (visible) => {
|
|
|
1115
1123
|
.assistant-transcript__error-content {
|
|
1116
1124
|
align-items: flex-start;
|
|
1117
1125
|
display: flex;
|
|
1126
|
+
flex-direction: column;
|
|
1118
1127
|
gap: 0.5rem;
|
|
1119
1128
|
}
|
|
1120
1129
|
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { isPlainObject, normalizeText } from "./normalize.js";
|
|
2
2
|
import { createReentrantConversationStorage } from "./storage.js";
|
|
3
|
+
import { isCompletedEnvelopeTurn } from "./transcript.js";
|
|
3
4
|
import { normalizeCodexAppServerRunState, codexAppServerRunStateIsActive,
|
|
4
5
|
codexAppServerRunStateIsTerminal } from "./codexTurn.js";
|
|
5
6
|
|
|
@@ -134,7 +135,7 @@ export function createCodexConversationStore({ storage, scope, segmentId, isCurr
|
|
|
134
135
|
assertCurrent(sessionId);
|
|
135
136
|
return transcript[name](sessionId, ...args);
|
|
136
137
|
};
|
|
137
|
-
async function
|
|
138
|
+
async function nativeAuthorship(transaction, identity) {
|
|
138
139
|
if (!identity?.threadId || !identity.turnId) return undefined;
|
|
139
140
|
const { runtime } = await transaction.readMetadata();
|
|
140
141
|
const run = runtime.binding?.codexAppServerRun;
|
|
@@ -143,17 +144,32 @@ export function createCodexConversationStore({ storage, scope, segmentId, isCurr
|
|
|
143
144
|
for (const turnId of (await transaction.listTurnIds()).reverse()) {
|
|
144
145
|
const turn = await transaction.readTurn(turnId);
|
|
145
146
|
if (turn?.metadata?.runtime?.segmentId !== segmentId) continue;
|
|
146
|
-
if (turn.user?.messageId === run.outerTurnId) return "user";
|
|
147
|
-
if (turn.system?.messageId === run.outerTurnId && turn.system.origin === "application")
|
|
147
|
+
if (turn.user?.messageId === run.outerTurnId) return { origin: "user", messageId: run.outerTurnId, turnId };
|
|
148
|
+
if (turn.system?.messageId === run.outerTurnId && turn.system.origin === "application") {
|
|
149
|
+
return { origin: "application", messageId: run.outerTurnId, turnId,
|
|
150
|
+
...(isCompletedEnvelopeTurn(turn) ? { completedEnvelope: true } : {}) };
|
|
151
|
+
}
|
|
148
152
|
}
|
|
149
153
|
const request = runtime.request;
|
|
150
|
-
if (request?.messageId === run.outerTurnId && ["user", "application"].includes(request.origin))
|
|
154
|
+
if (request?.messageId === run.outerTurnId && ["user", "application"].includes(request.origin)) {
|
|
155
|
+
return { origin: request.origin, messageId: request.messageId,
|
|
156
|
+
...(request.origin === "application" && request.completedEnvelope === true ? { completedEnvelope: true } : {}) };
|
|
157
|
+
}
|
|
151
158
|
return undefined;
|
|
152
159
|
}
|
|
153
|
-
const writer = name => (sessionId, input) => runStore.mutateSession(sessionId, async transaction => {
|
|
154
|
-
const
|
|
160
|
+
const writer = name => (sessionId, input = {}) => runStore.mutateSession(sessionId, async transaction => {
|
|
161
|
+
const authorship = await nativeAuthorship(transaction, input.nativeIdentity);
|
|
162
|
+
if (authorship?.completedEnvelope && !authorship.turnId) {
|
|
163
|
+
throw new Error("The native output's original turn no longer exists.");
|
|
164
|
+
}
|
|
165
|
+
// Queued human rows do not own an already admitted private native response.
|
|
166
|
+
// Corrections retain their original explicit target and message identity.
|
|
167
|
+
const written = await transcript[name](sessionId, {
|
|
168
|
+
...input, ...(name !== "upsertConversationAssistantMessage" && authorship?.completedEnvelope && authorship.turnId
|
|
169
|
+
? { turnId: authorship.turnId } : {})
|
|
170
|
+
});
|
|
155
171
|
if (!written || written.metadata?.runtime?.segmentId) return written;
|
|
156
|
-
const origin =
|
|
172
|
+
const origin = authorship?.origin;
|
|
157
173
|
await transaction.updateTurnMetadata(written.turnId, { runtime: {
|
|
158
174
|
status: "complete", engine: "codex", segmentId, ...(origin ? { origin } : {}),
|
|
159
175
|
...(input.nativeIdentity?.turnId ? { nativeTurnId: input.nativeIdentity.turnId } : {})
|
|
@@ -191,6 +207,8 @@ export function createCodexConversationStore({ storage, scope, segmentId, isCurr
|
|
|
191
207
|
...(request.data !== undefined ? { data: request.data } : {}), ...(request.goal ? { goal: request.goal } : {}),
|
|
192
208
|
turnMetadata: { ...input.turnMetadata, runtime: { ...input.turnMetadata?.runtime,
|
|
193
209
|
status: "running", engine: "codex", segmentId, origin: request.origin || "user",
|
|
210
|
+
...(metadata.runtime.request?.messageId === input.messageId && metadata.runtime.request.completedEnvelope === true
|
|
211
|
+
? { completedEnvelope: true } : {}),
|
|
194
212
|
...(input.nativeIdentity?.turnId ? { nativeTurnId: input.nativeIdentity.turnId } : {}),
|
|
195
213
|
...(request.goal ? { goalMessageId: request.messageId } : {}) } }
|
|
196
214
|
});
|
|
@@ -212,11 +230,20 @@ export function createCodexConversationStore({ storage, scope, segmentId, isCurr
|
|
|
212
230
|
});
|
|
213
231
|
},
|
|
214
232
|
updateConversationStream(sessionId, input) {
|
|
215
|
-
return runStore.mutateSession(sessionId, async transaction =>
|
|
216
|
-
|
|
217
|
-
|
|
233
|
+
return runStore.mutateSession(sessionId, async transaction => {
|
|
234
|
+
const authorship = await nativeAuthorship(transaction, input.nativeIdentity);
|
|
235
|
+
return streams.update(sessionId, { ...input, origin: authorship?.origin, authorship });
|
|
236
|
+
});
|
|
237
|
+
},
|
|
238
|
+
completeConversationStreamMessage(sessionId, messageId, options) {
|
|
239
|
+
assertCurrent(sessionId);
|
|
240
|
+
if (options?.nativeIdentity) return runStore.mutateSession(sessionId, async transaction => {
|
|
241
|
+
const authorship = await nativeAuthorship(transaction, options.nativeIdentity);
|
|
242
|
+
return streams.complete(sessionId, messageId, { ...options, authorship,
|
|
243
|
+
...(!authorship ? { text: undefined } : {}) });
|
|
244
|
+
});
|
|
245
|
+
return streams.complete(sessionId, messageId, options);
|
|
218
246
|
},
|
|
219
|
-
completeConversationStreamMessage(sessionId, messageId) { assertCurrent(sessionId); return streams.complete(sessionId, messageId); },
|
|
220
247
|
clearConversationStream(sessionId) { assertCurrent(sessionId); return streams.clear(sessionId); }
|
|
221
248
|
});
|
|
222
249
|
}
|
|
@@ -87,7 +87,11 @@ function claudeMessageBlocks(frame, { includeNested = false } = {}) {
|
|
|
87
87
|
|
|
88
88
|
async function readClaudeHistory(options) {
|
|
89
89
|
const file = await claudeHistoryPath(options);
|
|
90
|
-
|
|
90
|
+
const inspected = {
|
|
91
|
+
...(options.includeUserMessages ? { userMessages: [] } : {}),
|
|
92
|
+
...(options.includeFinalCarriers ? { completedResults: [], failedResults: [], structuredOutputs: [] } : {})
|
|
93
|
+
};
|
|
94
|
+
if (!file || file.size === 0) return { exists: false, messages: [], userIds: [], text: "", ...inspected };
|
|
91
95
|
if (file.size > 256 * 1024 * 1024) throw new Error("Claude history exceeds the supported read limit.");
|
|
92
96
|
const messages = new Map();
|
|
93
97
|
const userIds = [];
|
|
@@ -98,9 +102,10 @@ async function readClaudeHistory(options) {
|
|
|
98
102
|
let leafUuid = "";
|
|
99
103
|
let rewound = false;
|
|
100
104
|
// Snapshot the file length. A writer can be in the middle of its last frame;
|
|
101
|
-
//
|
|
105
|
+
// Live history reads allow that unfinished tail. Stopped maintenance can
|
|
106
|
+
// require the existing strict parser without changing live observation.
|
|
102
107
|
for await (const frame of readClaudeJsonFrames(createReadStream(file.path, { end: file.size - 1 }), {
|
|
103
|
-
allowIncompleteTail: true
|
|
108
|
+
allowIncompleteTail: options.allowIncompleteTail ?? true
|
|
104
109
|
})) {
|
|
105
110
|
if (frame.isSidechain) continue;
|
|
106
111
|
frames.push(frame);
|
|
@@ -139,12 +144,42 @@ async function readClaudeHistory(options) {
|
|
|
139
144
|
(typeof frame.message?.content === "string" || frame.message?.content?.some((block) => block.type === "text"))) {
|
|
140
145
|
userIds.push(frame.uuid);
|
|
141
146
|
lastUserId = frame.uuid;
|
|
147
|
+
if (options.includeUserMessages && !frame.parent_tool_use_id) {
|
|
148
|
+
const content = frame.message.content;
|
|
149
|
+
const text = typeof content === "string" ? content : content
|
|
150
|
+
.filter(block => block.type === "text" && typeof block.text === "string").map(block => block.text).join("\n\n");
|
|
151
|
+
inspected.userMessages.push({ id: frame.uuid, text });
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
if (options.includeFinalCarriers && !frame.parent_tool_use_id && !frame.isMeta) {
|
|
155
|
+
if (frame.type === "result" && frame.subtype === "success" && !frame.is_error &&
|
|
156
|
+
!["aborted_streaming", "aborted_tools"].includes(frame.terminal_reason)) {
|
|
157
|
+
// Same final normalization as the live native turn; this is an actual
|
|
158
|
+
// saved result, never a result invented from a tool-use candidate.
|
|
159
|
+
const text = typeof frame.structured_output === "object" ? JSON.stringify(frame.structured_output) : String(frame.result || "");
|
|
160
|
+
inspected.completedResults.push({ id: frame.uuid || "", userId: lastUserId, text });
|
|
161
|
+
} else if (frame.type === "result") {
|
|
162
|
+
// Preserve the live terminal-failure/interruption contradiction without
|
|
163
|
+
// copying native error details into stopped application inspection.
|
|
164
|
+
inspected.failedResults.push({ id: frame.uuid || "", userId: lastUserId });
|
|
165
|
+
}
|
|
166
|
+
if (frame.type === "assistant") {
|
|
167
|
+
for (const block of frame.message?.content || []) {
|
|
168
|
+
if (block.type === "tool_use" && block.name === "StructuredOutput" && typeof block.id === "string" && block.id &&
|
|
169
|
+
block.input && typeof block.input === "object" && !Array.isArray(block.input)) {
|
|
170
|
+
// Raw native carrier only. A stopped importer must independently
|
|
171
|
+
// verify the original completed receipt and exact final/user custody.
|
|
172
|
+
inspected.structuredOutputs.push({ id: frame.uuid || "", userId: lastUserId,
|
|
173
|
+
toolUseId: block.id, text: JSON.stringify(block.input) });
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
}
|
|
142
177
|
}
|
|
143
178
|
for (const message of claudeMessageBlocks(frame)) {
|
|
144
179
|
messages.set(message.id, { ...message, userId: lastUserId });
|
|
145
180
|
}
|
|
146
181
|
}
|
|
147
|
-
return { exists: true, messages: [...messages.values()], userIds, goal,
|
|
182
|
+
return { exists: true, messages: [...messages.values()], userIds, goal, ...inspected,
|
|
148
183
|
text: [...messages.values()].filter((message) => message.role === "assistant" && message.userId === lastUserId)
|
|
149
184
|
.map((message) => message.text).join("\n") };
|
|
150
185
|
}
|
|
@@ -155,7 +190,7 @@ export async function deleteClaudeConversationHistory({ configRoot, workdir, con
|
|
|
155
190
|
if (file) await rm(file.path, { force: true });
|
|
156
191
|
}
|
|
157
192
|
|
|
158
|
-
export { claudeMessageBlocks, readClaudeHistory, requireClaudeSessionId, archivedClaudeProjectDirectory, matchingClaudeProjectDirectories };
|
|
193
|
+
export { claudeHistoryPath, claudeMessageBlocks, readClaudeHistory, requireClaudeSessionId, archivedClaudeProjectDirectory, matchingClaudeProjectDirectories };
|
|
159
194
|
|
|
160
195
|
const TRANSCRIPT_PATH_PATTERN = /\.jsonl(?:\.(?:superseded|orphaned)-.*)?$/u;
|
|
161
196
|
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
1
2
|
import { nativeAiProvider } from "../../shared/nativeProviders.js";
|
|
2
3
|
import { Duplex } from "node:stream";
|
|
3
4
|
import { stat } from "node:fs/promises";
|
|
@@ -65,6 +66,11 @@ async function readNativeClaudeCodeAuthStatus({ env, credentialHome, command, co
|
|
|
65
66
|
}
|
|
66
67
|
}
|
|
67
68
|
|
|
69
|
+
/** Exact existing native connection pin, shared with stopped metadata conversion. */
|
|
70
|
+
export function claudeConnectionIdentity(configRoot, providerId, baseUrl, apiKey) {
|
|
71
|
+
return createHash("sha256").update(JSON.stringify([configRoot, providerId, baseUrl, apiKey])).digest("hex");
|
|
72
|
+
}
|
|
73
|
+
|
|
68
74
|
/** Native provider routing; the application supplies only its authorized selection. */
|
|
69
75
|
export function claudeModelConfiguration({ providerId, model: modelId }, connection) {
|
|
70
76
|
const provider = nativeAiProvider(providerId);
|
|
@@ -305,7 +311,7 @@ export async function createClaudeCodeProcess({
|
|
|
305
311
|
signal?.throwIfAborted();
|
|
306
312
|
native = await execution.start({ command, args: claudeCodeArguments(options), cwd: workdir,
|
|
307
313
|
env: environment, stream: true, limits: executionLimits });
|
|
308
|
-
await onStarted?.(native.id);
|
|
314
|
+
await onStarted?.(native.id, stop);
|
|
309
315
|
signal?.throwIfAborted();
|
|
310
316
|
client = createClaudeJsonClient({ stream: Duplex.from({ readable: native.stdout, writable: native.stdin }), onEvent, onFailure,
|
|
311
317
|
onControlRequest(request, context) {
|
|
@@ -317,9 +323,17 @@ export async function createClaudeCodeProcess({
|
|
|
317
323
|
}
|
|
318
324
|
});
|
|
319
325
|
signal?.addEventListener("abort", abort, { once: true });
|
|
320
|
-
const
|
|
326
|
+
const initializing = client.initialize(commandWrapper ? { hooks: {
|
|
321
327
|
PreToolUse: [{ matcher: "^Bash$", hookCallbackIds: ["jskit-command-wrapper"], timeout: 30 }]
|
|
322
328
|
} } : {});
|
|
329
|
+
const initialization = await (native.exited ? Promise.race([
|
|
330
|
+
initializing,
|
|
331
|
+
native.exited.then(processExit => {
|
|
332
|
+
throw Object.assign(new Error("Claude exited before initialization completed."), {
|
|
333
|
+
code: "assistant_claude_startup_exited", processExit
|
|
334
|
+
});
|
|
335
|
+
})
|
|
336
|
+
]) : initializing);
|
|
323
337
|
return { client, initialization, executionId: native.id, stop };
|
|
324
338
|
} catch (error) {
|
|
325
339
|
// A rejected start may already own an execution and its cleanup proof.
|
|
@@ -203,7 +203,7 @@ export function createClaudeConversationOwner({
|
|
|
203
203
|
if (saved && typeof saved.then === "function") saved = await saved;
|
|
204
204
|
if (!main && !saved && !create && !recoveringCleanup) throw createError("This Claude conversation is unavailable.");
|
|
205
205
|
const state = saved ? JSON.parse(saved) : {};
|
|
206
|
-
const entry = { id, key, main, persistent: state.persistent === true, context, process: null,
|
|
206
|
+
const entry = { id, key, main, persistent: state.persistent === true, context, process: null, startupCleanup: null,
|
|
207
207
|
executionId: state.executionId || (recoveringCleanup ? cleanupExecutionId : ""),
|
|
208
208
|
accountIdentities: state.accountIdentities || (state.accountIdentity ? { anthropic: state.accountIdentity } : {}),
|
|
209
209
|
accountIdentity: state.accountIdentity || "", sent: state.sent === true, nativeWorkdir: state.nativeWorkdir || nativeWorkdir,
|
|
@@ -312,7 +312,9 @@ export function createClaudeConversationOwner({
|
|
|
312
312
|
if (options && typeof options.then === "function") options = await options;
|
|
313
313
|
try { entry.process = await createProcess({ ...options,
|
|
314
314
|
workdir: entry.nativeWorkdir, sessionId: entry.id, resume: entry.sent,
|
|
315
|
-
onStarted: async executionId => {
|
|
315
|
+
onStarted: async (executionId, stop) => {
|
|
316
|
+
if (typeof stop === "function") entry.startupCleanup = { executionId, stop };
|
|
317
|
+
if (entry.executionId === executionId) return;
|
|
316
318
|
entry.executionId = executionId;
|
|
317
319
|
await store.save(entry, { execution: true });
|
|
318
320
|
await onEvent(entry, { type: "execution", context, executionId });
|
|
@@ -380,9 +382,13 @@ export function createClaudeConversationOwner({
|
|
|
380
382
|
await onEvent(entry, { type: "execution", context, executionId: entry.executionId });
|
|
381
383
|
} catch (bindingError) { error.bindingError = bindingError; }
|
|
382
384
|
}
|
|
383
|
-
if (store.releaseExecution && error.stopProof?.scopeEmpty)
|
|
385
|
+
if (store.releaseExecution && error.stopProof?.scopeEmpty) {
|
|
386
|
+
await store.releaseExecution(entry);
|
|
387
|
+
if (entry.startupCleanup?.executionId === error.executionId) entry.startupCleanup = null;
|
|
388
|
+
}
|
|
384
389
|
throw error;
|
|
385
390
|
}
|
|
391
|
+
if (entry.startupCleanup?.executionId === entry.executionId) entry.startupCleanup = null;
|
|
386
392
|
return entry.process;
|
|
387
393
|
}
|
|
388
394
|
|
|
@@ -461,7 +467,14 @@ export function createClaudeConversationOwner({
|
|
|
461
467
|
if (!command) await entry.nativeTurn.updateState("active");
|
|
462
468
|
if (applicationCommand && conversationTurn) {
|
|
463
469
|
const value = deliveryResult();
|
|
464
|
-
|
|
470
|
+
const active = applicationCommand.active;
|
|
471
|
+
const previousCompletion = active.completion;
|
|
472
|
+
if (active.finishIfCurrent) {
|
|
473
|
+
active.completion = Promise.withResolvers();
|
|
474
|
+
active.completion.promise.catch(() => {});
|
|
475
|
+
}
|
|
476
|
+
active.delivery = { value, completion: active.finishIfCurrent ? active.completion.promise : entry.completion?.promise };
|
|
477
|
+
if (active.finishIfCurrent) previousCompletion.resolve();
|
|
465
478
|
// The original canonical admission and publication precede Core's same
|
|
466
479
|
// accepted-request gate; native tools cannot race an unaccepted user.
|
|
467
480
|
await applicationCommand.accept({ nativeResult: { value }, conversationTurn, nativeTurnId: entry.turn?.id });
|
|
@@ -715,13 +728,13 @@ export function createClaudeConversationOwner({
|
|
|
715
728
|
command.signal.throwIfAborted();
|
|
716
729
|
const entry = await acquire(command.context);
|
|
717
730
|
const active = control.current;
|
|
718
|
-
const applicationCommand = active
|
|
731
|
+
const applicationCommand = active && (active.tools || active.finishIfCurrent) ? { active, accept: command.accept } : null;
|
|
719
732
|
if (applicationCommand) {
|
|
720
733
|
if (entry.disposed || entry.command && entry.command !== active) throw new Error("This Claude conversation is closed or already working.");
|
|
721
734
|
if (active.entry && active.entry !== entry) throw new Error("Claude's application tools belong to another native conversation.");
|
|
722
735
|
active.entry = entry;
|
|
723
736
|
entry.command = active;
|
|
724
|
-
entry.toolSchemas = active.tools
|
|
737
|
+
entry.toolSchemas = active.tools?.schemas || [];
|
|
725
738
|
}
|
|
726
739
|
const value = await send(entry, { ...command.input.nativeMessage, onPromptSending: command.beforeDispatch }, { applicationCommand });
|
|
727
740
|
const completion = value.conversationTurn ? entry.completion?.promise : null;
|
|
@@ -742,12 +755,12 @@ export function createClaudeConversationOwner({
|
|
|
742
755
|
async function run(control, command, { acquire, reportFailure, entry: selectedEntry, maximumOutput, maximumToolCalls = 32, timeoutMs } = {}) {
|
|
743
756
|
if (control.disposed || control.current) throw new Error("This Claude conversation is closed or already working.");
|
|
744
757
|
const binding = Boolean(store.releaseExecution);
|
|
745
|
-
const { configuration, context, input, tools, signal, beforeDispatch, accept, onMessage, onEvent } = command;
|
|
758
|
+
const { configuration, context, input, tools, signal, beforeDispatch, accept, onMessage, onEvent, onNativeToolUse } = command;
|
|
746
759
|
const completion = Promise.withResolvers();
|
|
747
760
|
completion.promise.catch(() => {});
|
|
748
|
-
const active = { completion, onMessage, onEvent, signal, tools, admitted: false, nativeTools: new Map(),
|
|
761
|
+
const active = { completion, onMessage, onEvent, onNativeToolUse, signal, tools, admitted: false, nativeTools: new Map(),
|
|
749
762
|
messageId: input.messageId, goal: input.goal, nativeToolCount: 0, toolWork: new Set(), maximumToolCalls,
|
|
750
|
-
context, failure: Promise.withResolvers() };
|
|
763
|
+
context, finishIfCurrent: command.finishIfCurrent, failure: Promise.withResolvers() };
|
|
751
764
|
let failure;
|
|
752
765
|
active.failure.promise.catch(error => { failure = error; });
|
|
753
766
|
control.current = active;
|
|
@@ -804,6 +817,10 @@ export function createClaudeConversationOwner({
|
|
|
804
817
|
catch (error) { problem = error; }
|
|
805
818
|
}
|
|
806
819
|
if (command.finishIfCurrent && !await command.finishIfCurrent(() => active.delivery === settling)) continue;
|
|
820
|
+
if (!problem && typeof result === "function") {
|
|
821
|
+
try { result = await result(); }
|
|
822
|
+
catch (error) { problem = error; }
|
|
823
|
+
}
|
|
807
824
|
problem = dispatchFailure || failure || problem;
|
|
808
825
|
if (!problem && settling?.completion && (binding ? result.status !== "completed" : !result.ok)) {
|
|
809
826
|
problem = Object.assign(new Error(result.error || (binding ? "Claude interrupted this answer." : `Claude turn ${result.status}.`)), { status: result.status });
|
|
@@ -826,7 +843,7 @@ export function createClaudeConversationOwner({
|
|
|
826
843
|
}
|
|
827
844
|
} else {
|
|
828
845
|
await cancellation?.catch(() => {});
|
|
829
|
-
if (
|
|
846
|
+
if (active.entry) {
|
|
830
847
|
try {
|
|
831
848
|
if (problem || signal.aborted) await active.entry.nativeTurn.stopProcess(problem?.message || "Work stopped.");
|
|
832
849
|
} catch (error) {
|
|
@@ -921,6 +938,11 @@ export function createClaudeConversationTurn({ conversationId, onEvent = async (
|
|
|
921
938
|
messages: current.messages, error: entry.turn?.error || "", status };
|
|
922
939
|
}
|
|
923
940
|
|
|
941
|
+
function resolveCompletion(result) {
|
|
942
|
+
entry.completion?.resolve(result);
|
|
943
|
+
if (entry.command?.finishIfCurrent) entry.command.completion.resolve(result);
|
|
944
|
+
}
|
|
945
|
+
|
|
924
946
|
async function updateState(state, message = "") {
|
|
925
947
|
if (!entry.turn) return;
|
|
926
948
|
const active = ["starting", "active", "finalizing"].includes(state);
|
|
@@ -947,10 +969,11 @@ export function createClaudeConversationTurn({ conversationId, onEvent = async (
|
|
|
947
969
|
const active = read().active;
|
|
948
970
|
if (!binding) entry.stopping = true;
|
|
949
971
|
const operation = async () => {
|
|
972
|
+
const startup = entry.startupCleanup?.executionId === entry.executionId ? entry.startupCleanup : null;
|
|
950
973
|
let proof;
|
|
951
974
|
try {
|
|
952
975
|
if (!binding || entry.process || entry.executionId) {
|
|
953
|
-
proof = await stopClaudeCodeProcess({ process: entry.process, executionId: entry.executionId,
|
|
976
|
+
proof = await stopClaudeCodeProcess({ process: entry.process || startup, executionId: entry.executionId,
|
|
954
977
|
stopExecution: process.stopExecution });
|
|
955
978
|
}
|
|
956
979
|
} catch (error) {
|
|
@@ -966,12 +989,25 @@ export function createClaudeConversationTurn({ conversationId, onEvent = async (
|
|
|
966
989
|
entry.exitProof = proof;
|
|
967
990
|
stop(binding ? reason : reason || "Claude stopped before acknowledging the prompt.");
|
|
968
991
|
if (binding) {
|
|
992
|
+
if (entry.startupCleanup === startup) entry.startupCleanup = null;
|
|
969
993
|
if (active) entry.completion?.resolve({ status: "interrupted", error: reason || "Work stopped." });
|
|
970
994
|
return;
|
|
971
995
|
}
|
|
972
|
-
if (entry.turn?.active)
|
|
973
|
-
|
|
974
|
-
|
|
996
|
+
if (entry.turn?.active) {
|
|
997
|
+
try { await updateState("interrupted", reason); }
|
|
998
|
+
catch (error) {
|
|
999
|
+
// Native cleanup is verified even when the host refuses its checkpoint.
|
|
1000
|
+
// Retain that refusal without publishing a completed answer or active work.
|
|
1001
|
+
setState("interrupted", error.message);
|
|
1002
|
+
await onEvent({ type: "save" });
|
|
1003
|
+
await onEvent({ type: "state", state: "interrupted", active: false, message: error.message });
|
|
1004
|
+
resolveCompletion(readResult());
|
|
1005
|
+
if (entry.startupCleanup === startup) entry.startupCleanup = null;
|
|
1006
|
+
throw error;
|
|
1007
|
+
}
|
|
1008
|
+
} else await onEvent({ type: "save" });
|
|
1009
|
+
resolveCompletion(readResult());
|
|
1010
|
+
if (entry.startupCleanup === startup) entry.startupCleanup = null;
|
|
975
1011
|
return entry.exitProof;
|
|
976
1012
|
};
|
|
977
1013
|
if (!binding) return operation();
|
|
@@ -980,9 +1016,32 @@ export function createClaudeConversationTurn({ conversationId, onEvent = async (
|
|
|
980
1016
|
return entry.stopPending;
|
|
981
1017
|
}
|
|
982
1018
|
|
|
983
|
-
async function publishEntryEvent(event) {
|
|
1019
|
+
async function publishEntryEvent(event, deferred = false) {
|
|
984
1020
|
if (entry.stopping) return;
|
|
985
1021
|
const binding = Boolean(process.releaseExecution);
|
|
1022
|
+
const command = entry.command;
|
|
1023
|
+
if (event.type === "settled" && !binding && !deferred && command?.finishIfCurrent) {
|
|
1024
|
+
const delivery = command.delivery;
|
|
1025
|
+
const completion = entry.completion;
|
|
1026
|
+
const owner = acceptedInput;
|
|
1027
|
+
// Release the native queue before the common run joins reserved steering.
|
|
1028
|
+
// That same accepted delivery then performs the original retained checkpoint.
|
|
1029
|
+
command.completion.resolve(async () => {
|
|
1030
|
+
if (entry.command !== command || command.delivery !== delivery || entry.completion !== completion ||
|
|
1031
|
+
command.signal.aborted || !currentReceiptOwner(owner)) {
|
|
1032
|
+
clearFinalAssistantResult();
|
|
1033
|
+
throw failure("Claude's completion belongs to a retired native input.");
|
|
1034
|
+
}
|
|
1035
|
+
try {
|
|
1036
|
+
await publishEntryEvent(event, true);
|
|
1037
|
+
return readResult();
|
|
1038
|
+
} catch (error) {
|
|
1039
|
+
clearFinalAssistantResult();
|
|
1040
|
+
throw error;
|
|
1041
|
+
}
|
|
1042
|
+
});
|
|
1043
|
+
return;
|
|
1044
|
+
}
|
|
986
1045
|
if (event.type === "admitted") {
|
|
987
1046
|
if (event.userEcho) entry.sent = true;
|
|
988
1047
|
if (!binding) await onEvent({ type: "save" });
|
|
@@ -1002,7 +1061,7 @@ export function createClaudeConversationTurn({ conversationId, onEvent = async (
|
|
|
1002
1061
|
}
|
|
1003
1062
|
if (event.type === "settled") {
|
|
1004
1063
|
if (event.source === "result") await onEvent({ type: "provider-update", change: "goal" });
|
|
1005
|
-
|
|
1064
|
+
resolveCompletion(binding ? event : readResult());
|
|
1006
1065
|
}
|
|
1007
1066
|
return published;
|
|
1008
1067
|
}
|
|
@@ -943,7 +943,7 @@ export function codexProviderModelCatalog({ models }) {
|
|
|
943
943
|
supports_reasoning_summaries: false,
|
|
944
944
|
default_reasoning_summary: "none",
|
|
945
945
|
support_verbosity: false,
|
|
946
|
-
truncation_policy: { mode: "
|
|
946
|
+
truncation_policy: { mode: "tokens", limit: 10000 },
|
|
947
947
|
context_window: model.contextWindow,
|
|
948
948
|
max_context_window: model.contextWindow,
|
|
949
949
|
effective_context_window_percent: 95,
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { open, realpath } from "node:fs/promises";
|
|
2
|
+
import { isUtf8 } from "node:buffer";
|
|
2
3
|
import { createServer } from "node:http";
|
|
3
4
|
import path from "node:path";
|
|
4
5
|
import { Readable } from "node:stream";
|
|
@@ -120,8 +121,9 @@ function retainedUserRecord(item) {
|
|
|
120
121
|
}
|
|
121
122
|
|
|
122
123
|
// Scan a fixed snapshot without retaining the lifetime transcript. A single
|
|
123
|
-
// record still has a transport-sized bound; incomplete
|
|
124
|
-
|
|
124
|
+
// record still has a transport-sized bound; recovery ignores incomplete appends.
|
|
125
|
+
// Stopped offline inspection can require complete rows without changing recovery.
|
|
126
|
+
async function* readCodexHistoryRows(file, start, end, signal, { strictComplete = false } = {}) {
|
|
125
127
|
const chunk = Buffer.alloc(64 * 1024);
|
|
126
128
|
let pending = [];
|
|
127
129
|
let size = 0;
|
|
@@ -141,7 +143,11 @@ async function* readCodexHistoryRows(file, start, end, signal) {
|
|
|
141
143
|
pending.push(Buffer.from(chunk.subarray(from, until)));
|
|
142
144
|
if (complete) {
|
|
143
145
|
signal.throwIfAborted();
|
|
144
|
-
if (size)
|
|
146
|
+
if (size) {
|
|
147
|
+
const bytes = Buffer.concat(pending, size);
|
|
148
|
+
if (strictComplete && !isUtf8(bytes)) throw compactionHistoryError("the saved history contains invalid UTF-8.");
|
|
149
|
+
yield { row: JSON.parse(bytes.toString("utf8")), offset: lineStart };
|
|
150
|
+
}
|
|
145
151
|
pending = [];
|
|
146
152
|
size = 0;
|
|
147
153
|
lineStart = offset + newline + 1;
|
|
@@ -150,6 +156,7 @@ async function* readCodexHistoryRows(file, start, end, signal) {
|
|
|
150
156
|
}
|
|
151
157
|
offset += bytesRead;
|
|
152
158
|
}
|
|
159
|
+
if (strictComplete && size) throw compactionHistoryError("the saved history has an incomplete final record.");
|
|
153
160
|
}
|
|
154
161
|
|
|
155
162
|
// Native compaction keeps the original rollout on disk. Foreign providers
|
|
@@ -456,4 +463,4 @@ async function prepareCodexHistory(params, client, { baseUrl, modelProviderId, .
|
|
|
456
463
|
} };
|
|
457
464
|
}
|
|
458
465
|
|
|
459
|
-
export { startCodexHistoryAdapter, translateCodexHistory, prepareCodexHistory };
|
|
466
|
+
export { startCodexHistoryAdapter, translateCodexHistory, prepareCodexHistory, readCodexHistoryRows };
|
|
@@ -40,8 +40,7 @@ export function createCodexAppServerNotificationQueue({
|
|
|
40
40
|
});
|
|
41
41
|
}
|
|
42
42
|
|
|
43
|
-
async function
|
|
44
|
-
const taskSessionKey = namespace(sessionId);
|
|
43
|
+
async function drainCodexAppServerNotificationTasksForKey(taskSessionKey) {
|
|
45
44
|
while (true) {
|
|
46
45
|
const task = codexAppServerNotificationTasks.get(taskSessionKey);
|
|
47
46
|
if (!task) {
|
|
@@ -51,6 +50,15 @@ export function createCodexAppServerNotificationQueue({
|
|
|
51
50
|
}
|
|
52
51
|
}
|
|
53
52
|
|
|
53
|
+
async function drainCodexAppServerNotificationTasks(sessionId = "") {
|
|
54
|
+
return drainCodexAppServerNotificationTasksForKey(namespace(sessionId));
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function captureCodexAppServerNotificationDrain(sessionId = "") {
|
|
58
|
+
const taskSessionKey = namespace(sessionId);
|
|
59
|
+
return () => drainCodexAppServerNotificationTasksForKey(taskSessionKey);
|
|
60
|
+
}
|
|
61
|
+
|
|
54
62
|
function queueCodexAppServerStream(context, classification, deliver) {
|
|
55
63
|
if (isClosing() || !classification.itemId || !classification.turnId) return;
|
|
56
64
|
const pending = codexAppServerPendingStreams.get(context.sessionKey);
|
|
@@ -97,6 +105,7 @@ export function createCodexAppServerNotificationQueue({
|
|
|
97
105
|
pendingReasoning: codexAppServerPendingReasoning,
|
|
98
106
|
run: runCodexAppServerNotificationTask,
|
|
99
107
|
drain: drainCodexAppServerNotificationTasks,
|
|
108
|
+
captureDrain: captureCodexAppServerNotificationDrain,
|
|
100
109
|
stream: queueCodexAppServerStream,
|
|
101
110
|
reasoning: queueCodexAppServerReasoning
|
|
102
111
|
};
|