@jskit-ai/assistant-core 0.1.213 → 0.1.214
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 +28 -8
- 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 +31 -0
- 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
|
@@ -12,6 +12,8 @@ import { createCodexAppServerRunOwner, codexAppServerTurnState, codexAppServerFr
|
|
|
12
12
|
import { createLocalConversationExecution } from "../localExecution.js";
|
|
13
13
|
import { validateConversationConfiguration, validateConnectionModel } from "../configuration.js";
|
|
14
14
|
import { codexCommandHookCommand } from "../commandWrapper.js";
|
|
15
|
+
import { isCompletedEnvelopeTurn } from "../transcript.js";
|
|
16
|
+
import { createInertCodexConversationBinding } from "../runtimeStateUpgrade.js";
|
|
15
17
|
|
|
16
18
|
const hash = value => createHash("sha256").update(JSON.stringify(value)).digest("hex");
|
|
17
19
|
const goalValue = goal => goal ? {
|
|
@@ -221,7 +223,7 @@ export function createCodexConversationDriver({ connections, host = {}, limits =
|
|
|
221
223
|
return native.runOwner.runDetachedConversation(sessionId, input, options);
|
|
222
224
|
},
|
|
223
225
|
async createBinding() {
|
|
224
|
-
return {
|
|
226
|
+
return createInertCodexConversationBinding({ workdir: await realpath(workdir), configRoot });
|
|
225
227
|
},
|
|
226
228
|
async open({ binding, writeBinding, onFailure: reportFailure, conversation }) {
|
|
227
229
|
const supplied = conversation?.native;
|
|
@@ -281,8 +283,29 @@ export function createCodexConversationDriver({ connections, host = {}, limits =
|
|
|
281
283
|
const boundedWriter = name => (id, input) => {
|
|
282
284
|
const limit = name === "writeConversationThinkingMessage" ? 4 * 1024 * 1024
|
|
283
285
|
: name.includes("AssistantMessage") ? Math.min(maximumOutput, maximumFinalReply) : maximumOutput;
|
|
284
|
-
|
|
285
|
-
return nativeStore[name](id, input);
|
|
286
|
+
const length = input.text?.length || 0;
|
|
287
|
+
if (length <= limit) return nativeStore[name](id, input);
|
|
288
|
+
if (!name.includes("AssistantMessage") || length > maximumOutput) throw new Error("Codex output exceeded the configured limit.");
|
|
289
|
+
return nativeStore.mutateSession(id, async transaction => {
|
|
290
|
+
const { runtime: state } = await transaction.readMetadata();
|
|
291
|
+
const identity = input.nativeIdentity;
|
|
292
|
+
const run = state.binding?.codexAppServerRun;
|
|
293
|
+
if (identity?.threadId && identity.turnId && state.engine === "codex" &&
|
|
294
|
+
state.binding.threadId === identity.threadId && run?.providerThreadId === identity.threadId &&
|
|
295
|
+
run.providerTurnId === identity.turnId && run.outerTurnId) {
|
|
296
|
+
for (const turnId of (await transaction.listTurnIds()).reverse()) {
|
|
297
|
+
const turn = await transaction.readTurn(turnId);
|
|
298
|
+
if (turn?.system?.messageId !== run.outerTurnId) continue;
|
|
299
|
+
if (turn.system.origin === "application" && !turn.system.goal && isCompletedEnvelopeTurn(turn) &&
|
|
300
|
+
turn.metadata.runtime.engine === "codex" && turn.metadata.runtime.segmentId === state.segmentId &&
|
|
301
|
+
turn.metadata.runtime.nativeTurnId === identity.turnId) {
|
|
302
|
+
return nativeStore[name](id, input);
|
|
303
|
+
}
|
|
304
|
+
break;
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
throw new Error("Codex output exceeded the configured limit.");
|
|
308
|
+
});
|
|
286
309
|
};
|
|
287
310
|
const runtime = supplied ? conversation.runtime : { ...conversation.runtime, store: { ...nativeStore,
|
|
288
311
|
writeConversationAssistantMessage: boundedWriter("writeConversationAssistantMessage"),
|
|
@@ -303,6 +326,7 @@ export function createCodexConversationDriver({ connections, host = {}, limits =
|
|
|
303
326
|
}
|
|
304
327
|
} };
|
|
305
328
|
const owner = supplied?.runOwner || createCodexAppServerRunOwner({
|
|
329
|
+
persistCommentary: conversation.persistCommentary,
|
|
306
330
|
finalizingGraceMs: limits.codexFinalizingGraceMs,
|
|
307
331
|
finalizingGraceAfterHistoryRead: limits.codexFinalizingGraceAfterHistoryRead,
|
|
308
332
|
failureDetailGraceMs: limits.codexFailureDetailGraceMs,
|
|
@@ -331,6 +355,8 @@ export function createCodexConversationDriver({ connections, host = {}, limits =
|
|
|
331
355
|
}
|
|
332
356
|
}
|
|
333
357
|
});
|
|
358
|
+
// Retain this queue's exact opening namespace, never its admission grant.
|
|
359
|
+
const drainNotifications = owner.notificationQueue.captureDrain(sessionId);
|
|
334
360
|
async function publishNative(event) {
|
|
335
361
|
if (supplied) binding.threadId = await conversation.identity.read();
|
|
336
362
|
await conversation.publish(event);
|
|
@@ -406,10 +432,11 @@ export function createCodexConversationDriver({ connections, host = {}, limits =
|
|
|
406
432
|
} });
|
|
407
433
|
}
|
|
408
434
|
|
|
409
|
-
async function completeObservation({ provider, error, sharedStop }) {
|
|
435
|
+
async function completeObservation({ provider, error, sharedStop, threadIds }) {
|
|
436
|
+
if (threadIds && !threadIds.includes(binding.threadId)) return;
|
|
410
437
|
await updateBinding({ observationLoss: { ...binding.observationLoss, stopped: true },
|
|
411
438
|
...(sharedStop ? { executionId: "" } : {}) });
|
|
412
|
-
await observation.complete({ provider, error, sharedStop });
|
|
439
|
+
await observation.complete({ provider, error, sharedStop, threadIds });
|
|
413
440
|
observationError = null;
|
|
414
441
|
await reportFailure(error, { recovered: true });
|
|
415
442
|
current?.completion.reject(error);
|
|
@@ -469,6 +496,7 @@ export function createCodexConversationDriver({ connections, host = {}, limits =
|
|
|
469
496
|
create: onObservationLost => new CodexAppServerAgentProvider({ ...providerOptions, onObservationLost }, facilities),
|
|
470
497
|
observation: {
|
|
471
498
|
async prepare(input) {
|
|
499
|
+
if (input.threadIds && !input.threadIds.includes(binding.threadId)) return new Map();
|
|
472
500
|
observationError = input.error;
|
|
473
501
|
await reportFailure(input.error);
|
|
474
502
|
const threads = await observation.prepare(input);
|
|
@@ -476,6 +504,7 @@ export function createCodexConversationDriver({ connections, host = {}, limits =
|
|
|
476
504
|
return threads;
|
|
477
505
|
},
|
|
478
506
|
async barrier(input) {
|
|
507
|
+
if (input.threadIds && !input.threadIds.includes(binding.threadId)) return;
|
|
479
508
|
await observation.barrier(input);
|
|
480
509
|
await recordObservationBarrier(input);
|
|
481
510
|
},
|
|
@@ -514,8 +543,14 @@ export function createCodexConversationDriver({ connections, host = {}, limits =
|
|
|
514
543
|
return stopping;
|
|
515
544
|
}
|
|
516
545
|
|
|
517
|
-
async function selection(configuration, context, signal) {
|
|
518
|
-
if (!configuration.integrationId)
|
|
546
|
+
async function selection(configuration, context, signal, requireBound = false, receiptBinding) {
|
|
547
|
+
if (!configuration.integrationId) {
|
|
548
|
+
if (requireBound && !receiptBinding?.accountIdentity) throw new Error("The completed Codex response has no saved native account fingerprint.");
|
|
549
|
+
if (requireBound && binding.accountIdentity !== receiptBinding.accountIdentity) {
|
|
550
|
+
throw new Error("The completed Codex response belongs to a different native account binding.");
|
|
551
|
+
}
|
|
552
|
+
return { model: configuration.model, providerId: "openai" };
|
|
553
|
+
}
|
|
519
554
|
const connection = await connections.resolve({ context, integrationId: configuration.integrationId });
|
|
520
555
|
signal.throwIfAborted();
|
|
521
556
|
const provider = nativeAiProvider(connection.providerId);
|
|
@@ -525,6 +560,11 @@ export function createCodexConversationDriver({ connections, host = {}, limits =
|
|
|
525
560
|
if (configuration.effort && !model.variants.includes(configuration.effort)) throw new Error("The selected Codex model does not support this reasoning effort.");
|
|
526
561
|
const identity = hash([provider.id, provider.baseUrl, connection.apiKey]);
|
|
527
562
|
const previous = binding.connectionIdentities?.[provider.id];
|
|
563
|
+
const expected = receiptBinding?.connectionIdentities?.[provider.id];
|
|
564
|
+
if (requireBound && !expected) throw new Error("The completed Codex response has no saved provider account fingerprint.");
|
|
565
|
+
if (requireBound && (previous !== expected || identity !== expected)) {
|
|
566
|
+
throw new Error("The completed Codex response belongs to a different provider account binding.");
|
|
567
|
+
}
|
|
528
568
|
if (previous && previous !== identity) throw new Error("This conversation belongs to another Codex provider account. Restore that connection or start a new conversation.");
|
|
529
569
|
if (!previous) await updateBinding({ connectionIdentities: { ...binding.connectionIdentities, [provider.id]: identity } });
|
|
530
570
|
return { model: model.id, providerId: provider.id, accountIdentity: identity, config: codexProviderConfiguration(provider, connection.apiKey) };
|
|
@@ -832,6 +872,13 @@ export function createCodexConversationDriver({ connections, host = {}, limits =
|
|
|
832
872
|
await native.ensureAvailable();
|
|
833
873
|
return native;
|
|
834
874
|
}
|
|
875
|
+
}).catch(error => {
|
|
876
|
+
// A thrown control error also leaves this native owner unconfirmed.
|
|
877
|
+
if (error === current?.toolFailure && !current.signal.aborted && !error.cleanupFailed) {
|
|
878
|
+
current.toolFailureCleanup = error;
|
|
879
|
+
}
|
|
880
|
+
error.cleanupFailed = true;
|
|
881
|
+
throw error;
|
|
835
882
|
});
|
|
836
883
|
if (result.value?.ok === false) throw Object.assign(new Error(result.value.error || "Codex could not confirm that work stopped."), { cleanupFailed: true });
|
|
837
884
|
return result.value;
|
|
@@ -901,7 +948,7 @@ export function createCodexConversationDriver({ connections, host = {}, limits =
|
|
|
901
948
|
if (result.ok === false) throw new Error(result.error);
|
|
902
949
|
return goalValue(result.goal);
|
|
903
950
|
},
|
|
904
|
-
async inspectAdmission({ messageId, threadId, nativeTurnId, configuration, context, goal: command, representation }) {
|
|
951
|
+
async inspectAdmission({ messageId, threadId, nativeTurnId, configuration, context, goal: command, representation, verifyAccount = false, receiptBinding }) {
|
|
905
952
|
if (supplied) {
|
|
906
953
|
const prepared = await supplied.inspectionPreparation({ messageId, threadId }, context);
|
|
907
954
|
if (prepared.ok === false) return representation === "native" ? prepared : { accepted: false, ...prepared };
|
|
@@ -915,9 +962,13 @@ export function createCodexConversationDriver({ connections, host = {}, limits =
|
|
|
915
962
|
// cannot certify the authored delivery invented by that adapter.
|
|
916
963
|
return { accepted: false, recoveryLimitation: "This older goal-message record requires offline inspection. Its saved history and delivery evidence were preserved." };
|
|
917
964
|
}
|
|
965
|
+
if (verifyAccount === true && (!receiptBinding || threadId !== binding.threadId || receiptBinding.threadId !== binding.threadId ||
|
|
966
|
+
receiptBinding.workdir !== binding.workdir || receiptBinding.configRoot !== binding.configRoot)) {
|
|
967
|
+
throw new Error("The completed Codex response belongs to a different native conversation or host scope.");
|
|
968
|
+
}
|
|
918
969
|
if (!binding.threadId) return { accepted: false };
|
|
919
970
|
const signal = AbortSignal.timeout(30_000);
|
|
920
|
-
await connect(signal, await selection(configuration, context, signal));
|
|
971
|
+
await connect(signal, await selection(configuration, context, signal, verifyAccount === true, receiptBinding));
|
|
921
972
|
let acceptedTurn;
|
|
922
973
|
let cursor;
|
|
923
974
|
do {
|
|
@@ -973,6 +1024,7 @@ export function createCodexConversationDriver({ connections, host = {}, limits =
|
|
|
973
1024
|
onEvent, onNativeTurn, signal, completion, admission, inputReady: admission.promise, finishIfCurrent,
|
|
974
1025
|
admitted: false, dispatched: false, turnId: "" };
|
|
975
1026
|
let cancellation;
|
|
1027
|
+
let toolFailureRecovery;
|
|
976
1028
|
let problem;
|
|
977
1029
|
let delivered;
|
|
978
1030
|
let waitForTurn = false;
|
|
@@ -1007,7 +1059,15 @@ export function createCodexConversationDriver({ connections, host = {}, limits =
|
|
|
1007
1059
|
owned = false;
|
|
1008
1060
|
throw new Error("This application tool call belongs to another turn.");
|
|
1009
1061
|
}
|
|
1010
|
-
|
|
1062
|
+
let result;
|
|
1063
|
+
try {
|
|
1064
|
+
result = await tools.execute({ id: params.callId, name: params.tool, arguments: JSON.stringify(params.arguments) });
|
|
1065
|
+
} catch (error) {
|
|
1066
|
+
if (!supplied && owned && current === active && !disposed && !signal.aborted &&
|
|
1067
|
+
provider === executingProvider && provider === native &&
|
|
1068
|
+
params.threadId === binding.threadId && params.turnId === active.turnId) active.toolFailure ||= error;
|
|
1069
|
+
throw error;
|
|
1070
|
+
}
|
|
1011
1071
|
return { success: result.ok, contentItems: [{ type: "inputText", text: JSON.stringify(result) }] };
|
|
1012
1072
|
})();
|
|
1013
1073
|
toolCalls.add(operation);
|
|
@@ -1075,7 +1135,7 @@ export function createCodexConversationDriver({ connections, host = {}, limits =
|
|
|
1075
1135
|
waitForTurn = !supplied || delivered.value?.delivered === true;
|
|
1076
1136
|
if (!finishIfCurrent && waitForTurn) {
|
|
1077
1137
|
await completion.promise;
|
|
1078
|
-
await
|
|
1138
|
+
await drainNotifications();
|
|
1079
1139
|
}
|
|
1080
1140
|
} catch (error) { problem = error; }
|
|
1081
1141
|
if (finishIfCurrent) {
|
|
@@ -1092,23 +1152,41 @@ export function createCodexConversationDriver({ connections, host = {}, limits =
|
|
|
1092
1152
|
problem = undefined;
|
|
1093
1153
|
waitForTurn = true;
|
|
1094
1154
|
}
|
|
1095
|
-
if (!problem && waitForTurn) await
|
|
1155
|
+
if (!problem && waitForTurn) await drainNotifications();
|
|
1096
1156
|
} catch (error) { problem = error; }
|
|
1097
1157
|
}
|
|
1098
1158
|
signal.removeEventListener("abort", abort);
|
|
1099
1159
|
admission.reject(problem || signal.reason);
|
|
1160
|
+
const failureThreadId = binding.threadId;
|
|
1161
|
+
const failureTurnId = active.turnId;
|
|
1100
1162
|
try {
|
|
1101
1163
|
// Account invalidation already owns this stop attempt. Its failed
|
|
1102
1164
|
// proof stays available for explicit cleanup through the same owner.
|
|
1103
1165
|
if ((!supplied && problem || supplied && active.toolFailure || signal.aborted) && problem?.code !== "codex_runtime_invalidated") {
|
|
1104
1166
|
await (cancellation || interrupt());
|
|
1105
1167
|
}
|
|
1106
|
-
} catch (error) {
|
|
1168
|
+
} catch (error) {
|
|
1169
|
+
problem = error;
|
|
1170
|
+
// A direct Stop checkpoint can throw before its native completion
|
|
1171
|
+
// reaches the queue. Keep the same admitted tool failure with the
|
|
1172
|
+
// original observation-stop owner until its pending recovery joins.
|
|
1173
|
+
if (!supplied && error === active.toolFailure && current === active && !disposed && !signal.aborted &&
|
|
1174
|
+
executingProvider && executingProvider === native && binding.threadId === failureThreadId &&
|
|
1175
|
+
active.turnId === failureTurnId && error.code !== "codex_runtime_invalidated") {
|
|
1176
|
+
toolFailureRecovery = executingProvider.failObservation(error);
|
|
1177
|
+
}
|
|
1178
|
+
}
|
|
1107
1179
|
try {
|
|
1108
|
-
await
|
|
1180
|
+
await drainNotifications();
|
|
1109
1181
|
// The original queue starts observation recovery without awaiting
|
|
1110
1182
|
// it. Join only this provider's existing attempt before settling.
|
|
1111
1183
|
if (!supplied) await providerOwner.pendingRecovery(providerKey, executingProvider);
|
|
1184
|
+
if (toolFailureRecovery) {
|
|
1185
|
+
await toolFailureRecovery;
|
|
1186
|
+
if (problem === active.toolFailureCleanup && current === active && !disposed && !signal.aborted &&
|
|
1187
|
+
executingProvider === native && binding.threadId === failureThreadId && active.turnId === failureTurnId &&
|
|
1188
|
+
!observationError && binding.observationLoss?.stopped === true) delete problem.cleanupFailed;
|
|
1189
|
+
}
|
|
1112
1190
|
} catch (error) { problem = error; }
|
|
1113
1191
|
finally {
|
|
1114
1192
|
for (const owned of toolRegistrations.values()) owned.registration.release();
|
|
@@ -234,7 +234,7 @@ export function createOpenCodeConversationDriver({ connections, host = {}, limit
|
|
|
234
234
|
maxArgumentBytes: active.tools.maximumArgumentBytes, execute(input, signal) {
|
|
235
235
|
const turn = supplied.owner.turns.get(acquired.key);
|
|
236
236
|
const target = supplied.owner.processes.get(acquired.key);
|
|
237
|
-
|
|
237
|
+
const execute = () => executeOpenCodeApplicationTool({ active: current, turn, target,
|
|
238
238
|
sessionId: target?.upstreamSessionId,
|
|
239
239
|
assertCurrent(throwFailure = true, authored) {
|
|
240
240
|
const owned = !disposed && current === active && active.turn === turn && turn?.active &&
|
|
@@ -244,6 +244,7 @@ export function createOpenCodeConversationDriver({ connections, host = {}, limit
|
|
|
244
244
|
return owned;
|
|
245
245
|
}
|
|
246
246
|
}, input, signal);
|
|
247
|
+
return typeof acquired.runInContext === "function" ? acquired.runInContext(execute) : execute();
|
|
247
248
|
}
|
|
248
249
|
});
|
|
249
250
|
}
|
|
@@ -417,23 +418,39 @@ export function createOpenCodeConversationDriver({ connections, host = {}, limit
|
|
|
417
418
|
}).finally(() => { stopping = null; });
|
|
418
419
|
return stopping;
|
|
419
420
|
}
|
|
420
|
-
async function resolvePreparation(configuration, context, signal) {
|
|
421
|
+
async function resolvePreparation(configuration, context, signal, requireBound = false, receiptBinding, authoredPreparation = false) {
|
|
421
422
|
const connection = await connections.resolve({ context, integrationId: configuration.integrationId });
|
|
422
423
|
signal.throwIfAborted();
|
|
423
424
|
validateConnectionModel(configuration, connection);
|
|
424
425
|
const identity = hash([connection.providerId, connection.baseURL || "", connection.apiKey]);
|
|
425
|
-
if (
|
|
426
|
+
if (requireBound && !receiptBinding?.accountIdentity) throw new Error("The completed OpenCode response has no saved account fingerprint.");
|
|
427
|
+
if (requireBound && (binding.accountIdentity !== receiptBinding.accountIdentity || identity !== receiptBinding.accountIdentity)) {
|
|
428
|
+
throw new Error("The completed OpenCode response belongs to a different account binding.");
|
|
429
|
+
}
|
|
430
|
+
// The existing managed host reauthorizes its own connection on every preparation.
|
|
431
|
+
// A key change can rotate an authored run only inside the same native storage/runtime scope.
|
|
432
|
+
const preparedFacilities = host.opencode ? await host.opencode({ connection, context }) : facilities;
|
|
433
|
+
signal.throwIfAborted();
|
|
434
|
+
if (host.opencode && (
|
|
435
|
+
binding.databasePath && binding.databasePath !== preparedFacilities.databasePath ||
|
|
436
|
+
binding.runtimeDirectory && binding.runtimeDirectory !== preparedFacilities.runtimeDirectory ||
|
|
437
|
+
facilities && facilities.runtime !== preparedFacilities.runtime
|
|
438
|
+
)) {
|
|
439
|
+
throw new Error("This OpenCode conversation belongs to another native history store or runtime. Restore its original host configuration.");
|
|
440
|
+
}
|
|
441
|
+
const allowCredentialRefresh = authoredPreparation && host.opencode && preparedFacilities.allowCredentialRefresh === true;
|
|
442
|
+
if (binding.accountIdentity && identity !== binding.accountIdentity && !allowCredentialRefresh) {
|
|
426
443
|
throw new Error("This conversation belongs to another OpenCode account. Restore that connection or start a new conversation.");
|
|
427
444
|
}
|
|
428
|
-
if (
|
|
445
|
+
if (binding.accountIdentity !== identity) await updateBinding({ accountIdentity: identity });
|
|
429
446
|
const model = { providerID: connection.providerId, id: connection.model };
|
|
430
447
|
const agent = openCodeConversationAgent({ nativeTools, tools: Boolean(current?.tools) });
|
|
431
448
|
if (current) current.agent = agent;
|
|
432
449
|
if (binding.sessionId && !binding.databasePath) {
|
|
433
450
|
throw new Error("This OpenCode conversation uses the earlier private native history layout. Run the offline native-history upgrade before continuing; no history was changed.");
|
|
434
451
|
}
|
|
435
|
-
if (!facilities) {
|
|
436
|
-
if (host.opencode) facilities =
|
|
452
|
+
if (!facilities || host.opencode) {
|
|
453
|
+
if (host.opencode) facilities = preparedFacilities;
|
|
437
454
|
else {
|
|
438
455
|
const runtimeDirectory = path.join(stateDirectory, "shared", hash([identity, host.commands?.opencode || "opencode"]));
|
|
439
456
|
const selected = { modelProviderId: connection.providerId, canonicalUrl: connection.baseURL || "",
|
|
@@ -649,9 +666,14 @@ export function createOpenCodeConversationDriver({ connections, host = {}, limit
|
|
|
649
666
|
}
|
|
650
667
|
|
|
651
668
|
return Object.freeze({
|
|
652
|
-
async inspectAdmission({ messageId, configuration, context }) {
|
|
669
|
+
async inspectAdmission({ messageId, threadId, configuration, context, verifyAccount = false, receiptBinding }) {
|
|
670
|
+
if (verifyAccount === true && (!receiptBinding || threadId !== binding.sessionId || receiptBinding.sessionId !== binding.sessionId ||
|
|
671
|
+
receiptBinding.workdir !== binding.workdir || receiptBinding.directory !== binding.directory ||
|
|
672
|
+
receiptBinding.databasePath !== binding.databasePath || receiptBinding.runtimeDirectory !== binding.runtimeDirectory)) {
|
|
673
|
+
throw new Error("The completed OpenCode response belongs to a different native conversation or host scope.");
|
|
674
|
+
}
|
|
653
675
|
if (!binding.sessionId) return { accepted: false, messages: [] };
|
|
654
|
-
const prepared = await prepare(await resolvePreparation(configuration, context, AbortSignal.timeout(120_000)));
|
|
676
|
+
const prepared = await prepare(await resolvePreparation(configuration, context, AbortSignal.timeout(120_000), verifyAccount === true, receiptBinding));
|
|
655
677
|
await ensureOpenCodeSession(prepared.target, prepared.session);
|
|
656
678
|
const id = nativeMessageId(messageId);
|
|
657
679
|
const { accepted = false, messages = [] } = await inspectOpenCodeMessageAdmission(native.server.client, binding.sessionId, id) || {};
|
|
@@ -671,7 +693,7 @@ export function createOpenCodeConversationDriver({ connections, host = {}, limit
|
|
|
671
693
|
let problem;
|
|
672
694
|
try {
|
|
673
695
|
active.resolved = await resolvePreparation(command.configuration, command.context,
|
|
674
|
-
AbortSignal.any([command.signal, AbortSignal.timeout(120_000)]));
|
|
696
|
+
AbortSignal.any([command.signal, AbortSignal.timeout(120_000)]), false, undefined, true);
|
|
675
697
|
let dispatchFailure;
|
|
676
698
|
try { await send(command); }
|
|
677
699
|
catch (error) { dispatchFailure = error; }
|