@jskit-ai/assistant-core 0.2.7 → 0.2.9
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/package.json +18 -7
- package/src/client/browser.js +21 -0
- package/src/client/conversation/AssistantAttachmentQueue.vue +25 -108
- package/src/client/conversation/AssistantComposerActions.vue +1 -1
- package/src/client/conversation/AssistantConversationElement.vue +14 -52
- package/src/client/conversation/AssistantGoalControl.vue +13 -10
- package/src/client/conversation/AssistantPromptInput.vue +6 -91
- package/src/client/conversation/AssistantTranscript.css +505 -0
- package/src/client/conversation/AssistantTranscript.vue +24 -1197
- package/src/client/conversation/attachmentFiles.js +158 -0
- package/src/client/conversation/attachmentQueueItems.js +121 -0
- package/src/client/conversation/attachmentUploadRows.js +265 -0
- package/src/client/conversation/conversationDisplay.js +54 -0
- package/src/client/conversation/messageDelivery.js +13 -9
- package/src/client/conversation/retainedConversation.js +14 -3
- package/src/client/conversation/submitText.js +13 -2
- package/src/client/conversation/transcriptEntries.js +225 -0
- package/src/client/conversation/useAssistantAttachments.js +86 -388
- package/src/client/conversation/useAssistantQuestions.js +16 -3
- package/src/client/conversation/useAssistantSuggestions.js +16 -2
- package/src/client/conversation/useScrollToBottom.js +17 -19
- package/src/client/conversation/useTextareaAutoGrow.js +115 -0
- package/src/client/conversation/useTranscriptScrollFollow.js +525 -0
- package/src/client/lib/assistantApi.js +192 -161
- package/src/client/types.js +110 -0
- package/src/server/conversation/attachments.js +21 -6
- package/src/server/conversation/boundedToolLoop.js +1 -0
- package/src/server/conversation/claudeClient.js +71 -20
- package/src/server/conversation/claudeCommandRun.js +191 -0
- package/src/server/conversation/claudeHistory.js +7 -0
- package/src/server/conversation/claudeNativeTurn.js +708 -0
- package/src/server/conversation/claudeProcess.js +186 -112
- package/src/server/conversation/claudeProcessStart.js +214 -0
- package/src/server/conversation/claudePromptDelivery.js +183 -0
- package/src/server/conversation/claudeSessionCleanup.js +248 -0
- package/src/server/conversation/claudeTools.js +15 -3
- package/src/server/conversation/claudeTurn.js +212 -1388
- package/src/server/conversation/codexAccountReader.js +168 -0
- package/src/server/conversation/codexAuthPreflight.js +5 -4
- package/src/server/conversation/codexClient.js +18 -6
- package/src/server/conversation/codexConfiguration.js +8 -844
- package/src/server/conversation/codexConversationStops.js +377 -0
- package/src/server/conversation/codexConversationStore.js +119 -85
- package/src/server/conversation/codexConversationTurns.js +378 -0
- package/src/server/conversation/codexConversations.js +134 -866
- package/src/server/conversation/codexDetachedConversation.js +203 -0
- package/src/server/conversation/codexDetachedTurn.js +145 -104
- package/src/server/conversation/codexEvents.js +5 -200
- package/src/server/conversation/codexGoalCommands.js +14 -4
- package/src/server/conversation/codexHelperExecution.js +57 -57
- package/src/server/conversation/codexHelperThreadLedger.js +30 -288
- package/src/server/conversation/codexHelperThreadRecord.js +304 -0
- package/src/server/conversation/codexHelperThreadRecovery.js +54 -76
- package/src/server/conversation/codexHelperThreads.js +94 -93
- package/src/server/conversation/codexHistoryAdapter.js +13 -8
- package/src/server/conversation/codexIsolation.js +672 -0
- package/src/server/conversation/codexLiveItems.js +379 -0
- package/src/server/conversation/codexMessageCommands.js +68 -286
- package/src/server/conversation/codexMessageDelivery.js +46 -37
- package/src/server/conversation/codexModelCatalog.js +86 -7
- package/src/server/conversation/codexNotificationObserver.js +81 -88
- package/src/server/conversation/codexNotificationQueue.js +21 -10
- package/src/server/conversation/codexObservationControl.js +105 -89
- package/src/server/conversation/codexOutput.js +84 -419
- package/src/server/conversation/codexProcess.js +29 -1053
- package/src/server/conversation/codexProcessLeader.js +1 -0
- package/src/server/conversation/codexPromptDispatch.js +241 -0
- package/src/server/conversation/codexProvider.js +147 -1807
- package/src/server/conversation/codexProviderAuth.js +348 -0
- package/src/server/conversation/codexProviderInvalidation.js +371 -0
- package/src/server/conversation/codexProviderInventories.js +437 -0
- package/src/server/conversation/codexProviderOwner.js +149 -957
- package/src/server/conversation/codexProviderRelease.js +465 -0
- package/src/server/conversation/codexProviderSelection.js +26 -15
- package/src/server/conversation/codexProviderThreads.js +698 -0
- package/src/server/conversation/codexProviderTurns.js +453 -0
- package/src/server/conversation/codexReasoning.js +32 -23
- package/src/server/conversation/codexRenewalCommands.js +18 -6
- package/src/server/conversation/codexRenewalThreads.js +7 -22
- package/src/server/conversation/codexResultSettlement.js +70 -88
- package/src/server/conversation/codexRunJournal.js +91 -97
- package/src/server/conversation/codexRuntime.js +426 -0
- package/src/server/conversation/codexRuntimeInventory.js +113 -0
- package/src/server/conversation/codexRuntimeLifecycle.js +39 -13
- package/src/server/conversation/codexRuntimeStart.js +431 -0
- package/src/server/conversation/codexRuntimeStop.js +324 -0
- package/src/server/conversation/codexSessionCleanup.js +25 -10
- package/src/server/conversation/codexSessionReconciliation.js +21 -7
- package/src/server/conversation/codexThreadReadiness.js +49 -52
- package/src/server/conversation/codexThreadSnapshot.js +210 -0
- package/src/server/conversation/codexThreadStatus.js +48 -62
- package/src/server/conversation/codexTurn.js +171 -55
- package/src/server/conversation/codexTurnActivity.js +210 -0
- package/src/server/conversation/codexTurnControl.js +44 -48
- package/src/server/conversation/codexTurnEndings.js +610 -0
- package/src/server/conversation/codexTurnRecovery.js +97 -848
- package/src/server/conversation/codexTurnState.js +138 -111
- package/src/server/conversation/completedResponses.js +227 -0
- package/src/server/conversation/configuration.js +2 -2
- package/src/server/conversation/continuity.js +43 -6
- package/src/server/conversation/conversationDelivery.js +394 -0
- package/src/server/conversation/conversationEntries.js +365 -0
- package/src/server/conversation/conversationEvents.js +192 -0
- package/src/server/conversation/conversationGoals.js +187 -0
- package/src/server/conversation/conversationHandles.js +237 -0
- package/src/server/conversation/conversationReads.js +206 -0
- package/src/server/conversation/conversationRefusals.js +58 -0
- package/src/server/conversation/conversationReplacement.js +254 -0
- package/src/server/conversation/conversationSending.js +273 -0
- package/src/server/conversation/conversationStopping.js +121 -0
- package/src/server/conversation/conversationTurnDelivery.js +413 -0
- package/src/server/conversation/conversationTurns.js +511 -0
- package/src/server/conversation/deliveryTiming.js +25 -3
- package/src/server/conversation/fileStorage.js +11 -6
- package/src/server/conversation/hookBridge.js +14 -3
- package/src/server/conversation/index.js +1 -1
- package/src/server/conversation/localExecution.js +82 -625
- package/src/server/conversation/nativeCommandCallbacks.js +10 -0
- package/src/server/conversation/nativeConversationLifecycle.js +379 -0
- package/src/server/conversation/nativeHistoryExport.js +27 -18
- package/src/server/conversation/normalize.js +9 -0
- package/src/server/conversation/openCodeClient.js +295 -366
- package/src/server/conversation/openCodeNativeHistory.js +141 -0
- package/src/server/conversation/openCodeNativeSession.js +79 -0
- package/src/server/conversation/openCodeProcess.js +109 -53
- package/src/server/conversation/openCodeProcessOwner.js +565 -0
- package/src/server/conversation/openCodeRuntime.js +111 -1814
- package/src/server/conversation/openCodeSessionTurns.js +547 -0
- package/src/server/conversation/openCodeTemporaryConversations.js +394 -0
- package/src/server/conversation/openCodeTools.js +28 -14
- package/src/server/conversation/openCodeTurn.js +80 -74
- package/src/server/conversation/openCodeTurnMonitor.js +388 -0
- package/src/server/conversation/processIdentity.js +582 -0
- package/src/server/conversation/provider.js +21 -6
- package/src/server/conversation/providers/api.js +268 -107
- package/src/server/conversation/providers/claude.js +29 -6
- package/src/server/conversation/providers/claudeDriver.js +325 -250
- package/src/server/conversation/providers/codex.js +69 -33
- package/src/server/conversation/providers/codexDriver.js +503 -1081
- package/src/server/conversation/providers/codexDriverConnection.js +331 -0
- package/src/server/conversation/providers/codexDriverTurns.js +464 -0
- package/src/server/conversation/providers/opencode.js +30 -4
- package/src/server/conversation/providers/opencodeDriver.js +321 -659
- package/src/server/conversation/providers/opencodeStandalone.js +475 -0
- package/src/server/conversation/providers/scoped.js +64 -19
- package/src/server/conversation/runtime.js +171 -2863
- package/src/server/conversation/storage.js +91 -50
- package/src/server/conversation/streams.js +15 -2
- package/src/server/conversation/structuredOutput.js +7 -11
- package/src/server/conversation/systemPrompt.js +13 -1
- package/src/server/conversation/tools.js +47 -15
- package/src/server/conversation/transcript.js +49 -20
- package/src/server/hosts/claudeTurn.js +2 -4
- package/src/server/hosts/codexConfiguration.js +6 -8
- package/src/server/hosts/codexEvents.js +4 -2
- package/src/server/hosts/codexProcess.js +1 -1
- package/src/server/hosts/codexProvider.js +11 -9
- package/src/server/hosts/codexTurn.js +3 -3
- package/src/server/hosts/openCodeClient.js +4 -2
- package/src/server/index.js +0 -2
- package/src/server/lib/aiConnectionClient.js +35 -25
- package/src/server/lib/ndjson.js +1 -0
- package/src/server/lib/providers/anthropicClient.js +15 -6
- package/src/server/lib/providers/common.js +10 -6
- package/src/server/lib/providers/openAiCompatibleClient.js +40 -23
- package/src/server/lib/serviceToolCatalog.js +29 -283
- package/src/server/lib/serviceToolEntries.js +270 -0
- package/src/server/types.js +78 -0
- package/src/shared/assistantResource.js +0 -23
- package/src/shared/conversation/answerChoiceSugar.js +2 -14
- package/src/shared/conversation/messageInputSugar.js +18 -0
- package/src/shared/conversation/numberedQuestionSugar.js +6 -16
- package/src/shared/conversation/pagination.js +1 -1
- package/src/shared/conversation/questions.js +1 -0
- package/src/shared/conversation/streaming.js +1 -0
- package/src/shared/index.js +0 -1
- package/src/testing/conversationStorageContract.js +20 -0
- package/test/aiConnectionClient.test.js +42 -1
- package/test/apiConversationDriver.test.js +217 -38
- package/test/assistantBoundedToolLoop.test.js +1 -1
- package/test/claudeConversationSession.test.js +15 -11
- package/test/claudeInstructionResume.test.js +1 -1
- package/test/claudeProcess.test.js +9 -9
- package/test/claudeTurn.test.js +21 -8
- package/test/codexAccountChange.test.js +5 -3
- package/test/codexConversationSession.test.js +22 -21
- package/test/codexEvents.test.js +3 -1
- package/test/codexHelperDeletion.test.js +3 -7
- package/test/codexIsolation.test.js +6 -6
- package/test/codexModelCatalog.test.js +6 -7
- package/test/codexProcess.test.js +3 -3
- package/test/codexThreadSelection.test.js +4 -1
- package/test/codexTurn.test.js +29 -20
- package/test/conversationApiFacilities.test.js +11 -9
- package/test/conversationAttachments.test.js +3 -3
- package/test/conversationConfiguration.test.js +4 -4
- package/test/conversationNativeStorage.test.js +11 -10
- package/test/conversationReplacement.test.js +13 -13
- package/test/conversationRuntime.test.js +9 -5
- package/test/conversationSession.test.js +78 -22
- package/test/conversationTools.test.js +47 -26
- package/test/fileConversationStorage.test.js +10 -3
- package/test/fixtures/codexHelperThreadLedger.js +3 -3
- package/test/frameworkActions.test.js +33 -18
- package/test/localConversationExecution.test.js +4 -4
- package/test/localExecution.test.js +1 -1
- package/test/openCodeConnectionChange.test.js +3 -2
- package/test/openCodeRuntime.test.js +8 -7
- package/test/openCodeTools.test.js +1 -1
- package/test/opencodeConversationSession.test.js +4 -3
- package/test/serviceToolCatalog.test.js +3 -8
- package/src/server/lib/assistantCompletion.js +0 -190
- package/src/server/lib/assistantToolLoop.js +0 -259
|
@@ -1498,11 +1498,11 @@ test("concurrent final common-runtime closes stop the shared Codex service once"
|
|
|
1498
1498
|
|
|
1499
1499
|
for (const loseFirst of [true, false]) test(`shared Codex fallback records both common-runtime barriers before stop (first: ${loseFirst})`, async t => {
|
|
1500
1500
|
const providers = new Map();
|
|
1501
|
-
const
|
|
1502
|
-
t.mock.method(CodexAppServerAgentProvider.prototype, "
|
|
1503
|
-
const
|
|
1504
|
-
providers.set(thread.id, this);
|
|
1505
|
-
return
|
|
1501
|
+
const runRequest = CodexAppServerAgentProvider.prototype.runRequest;
|
|
1502
|
+
t.mock.method(CodexAppServerAgentProvider.prototype, "runRequest", async function (operation, reason) {
|
|
1503
|
+
const result = await runRequest.call(this, operation, reason);
|
|
1504
|
+
if (reason === "codex-app-server-thread-start") providers.set(result.thread.id, this);
|
|
1505
|
+
return result;
|
|
1506
1506
|
});
|
|
1507
1507
|
const execution = createLocalConversationExecution();
|
|
1508
1508
|
const barriers = [];
|
|
@@ -1654,10 +1654,10 @@ for (const stage of ["preparation", "completion"]) test(`account invalidation re
|
|
|
1654
1654
|
test("failed stopped-state persistence retains Codex recovery across reopening without starting work", async t => {
|
|
1655
1655
|
let source;
|
|
1656
1656
|
let rejectStopped = false;
|
|
1657
|
-
const
|
|
1658
|
-
t.mock.method(CodexAppServerAgentProvider.prototype, "
|
|
1659
|
-
source = this;
|
|
1660
|
-
return
|
|
1657
|
+
const runRequest = CodexAppServerAgentProvider.prototype.runRequest;
|
|
1658
|
+
t.mock.method(CodexAppServerAgentProvider.prototype, "runRequest", async function (operation, reason) {
|
|
1659
|
+
if (reason === "codex-app-server-thread-start") source = this;
|
|
1660
|
+
return runRequest.call(this, operation, reason);
|
|
1661
1661
|
});
|
|
1662
1662
|
const f = await fixture(t, { checkpoints: true, storage: disk => ({
|
|
1663
1663
|
read: disk.read,
|
|
@@ -1858,7 +1858,7 @@ function applicationActions(execute) {
|
|
|
1858
1858
|
const actions = createActionCatalogue();
|
|
1859
1859
|
actions.register({ contributorId: "test.tools", domain: "numbers", actions: [{
|
|
1860
1860
|
id: "numbers.read", version: 1, kind: "query", channels: ["automation"], surfaces: ["app"],
|
|
1861
|
-
permission: { require: "all", permissions: ["numbers.read"] },
|
|
1861
|
+
permission: { require: "all", permissions: ["numbers.read"] },
|
|
1862
1862
|
input: { schema: createSchema({}), mode: "replace" },
|
|
1863
1863
|
output: { schema: createSchema({ value: { type: "number", required: true } }), mode: "replace" }, execute
|
|
1864
1864
|
}] });
|
|
@@ -2344,7 +2344,7 @@ test("Codex retries retain their frozen local file manifest and cannot retarget
|
|
|
2344
2344
|
test("Codex goal controls reject local attachments before native work", async t => {
|
|
2345
2345
|
const f = await fixture(t, { attachments: { resolve: nativeAttachmentResolver } });
|
|
2346
2346
|
await assert.rejects(f.conversation.updateGoal({ action: "set", messageId: "local-goal", objective: "Inspect the files",
|
|
2347
|
-
attachmentIds: ["image", "file"], expectedSegmentId: (await f.conversation.read()).segmentId }), { code: "
|
|
2347
|
+
attachmentIds: ["image", "file"], expectedSegmentId: (await f.conversation.read()).segmentId }), { code: "conversation_goal_attachments_unsupported" });
|
|
2348
2348
|
assert.equal((await f.conversation.read()).conversationLog.length, 0);
|
|
2349
2349
|
await assert.rejects(readFile(path.join(f.directory, "trace.jsonl")), { code: "ENOENT" });
|
|
2350
2350
|
});
|
|
@@ -2464,6 +2464,7 @@ test("held lifecycle catalogue work does not block another owner's runtime stop
|
|
|
2464
2464
|
}) });
|
|
2465
2465
|
const acquiredProvider = other.createProvider({ providerKey: "b-acquire", providerOptions: acquireOptions, create: () => ({
|
|
2466
2466
|
async ensureRuntime() { events.push("b-acquire"); return {}; },
|
|
2467
|
+
ensureAvailable() { return this.ensureRuntime(); },
|
|
2467
2468
|
async stopRuntime() { return { stopped: true }; }, close() {}
|
|
2468
2469
|
}) });
|
|
2469
2470
|
const catalogue = first.readModelCatalog({ prepareProviderOptions: async () => firstOptions,
|
|
@@ -2667,19 +2668,19 @@ test("held lifecycle catalogue rejection preserves a newer same-runtime tail and
|
|
|
2667
2668
|
}
|
|
2668
2669
|
});
|
|
2669
2670
|
|
|
2670
|
-
//
|
|
2671
|
-
//
|
|
2672
|
-
//
|
|
2671
|
+
// The bound-host contract on the native app-server fixture: schemas, tool-call
|
|
2672
|
+
// framing, transcript store and run owner. Identity, authority and readiness
|
|
2673
|
+
// composition belongs to the bound application.
|
|
2673
2674
|
import { createCodexAppServerRunOwner, codexAppServerTurnState } from "../src/server/conversation/codexTurn.js";
|
|
2674
|
-
import { codexApplicationToolConfiguration, ensureCodexAppServerThread } from "../src/server/conversation/
|
|
2675
|
+
import { codexApplicationToolConfiguration, ensureCodexAppServerThread } from "../src/server/conversation/codexProviderThreads.js";
|
|
2675
2676
|
import { createServiceToolCatalog } from "../src/server/lib/serviceToolCatalog.js";
|
|
2676
2677
|
|
|
2677
2678
|
async function boundCodexToolsFixture(t, options = {}) {
|
|
2678
2679
|
let sharedProvider;
|
|
2679
|
-
const
|
|
2680
|
-
t.mock.method(CodexAppServerAgentProvider.prototype, "
|
|
2681
|
-
sharedProvider ||= this;
|
|
2682
|
-
return
|
|
2680
|
+
const runRequest = CodexAppServerAgentProvider.prototype.runRequest;
|
|
2681
|
+
t.mock.method(CodexAppServerAgentProvider.prototype, "runRequest", async function (operation, reason) {
|
|
2682
|
+
if (reason === "codex-app-server-thread-start") sharedProvider ||= this;
|
|
2683
|
+
return runRequest.call(this, operation, reason);
|
|
2683
2684
|
});
|
|
2684
2685
|
const cleanups = [];
|
|
2685
2686
|
let boundRuntime, owner;
|
|
@@ -2886,7 +2887,7 @@ test("bound Codex refuses stale turns, foreign threads and retired handlers with
|
|
|
2886
2887
|
});
|
|
2887
2888
|
|
|
2888
2889
|
test("shared Codex thread registrations retain ownership tokens and Helper request precedence", async t => {
|
|
2889
|
-
const provider = new CodexAppServerAgentProvider();
|
|
2890
|
+
const provider = new CodexAppServerAgentProvider({}, {});
|
|
2890
2891
|
const calls = [];
|
|
2891
2892
|
let originalClientRequest;
|
|
2892
2893
|
provider.client = { setRequestHandler(callback) { originalClientRequest = callback; }, close() {} };
|
|
@@ -3259,7 +3260,7 @@ for (const ending of ["steer", "close"]) test(`transient native completion decli
|
|
|
3259
3260
|
const closing = runtime.close();
|
|
3260
3261
|
releaseAuthorization.resolve();
|
|
3261
3262
|
await closing;
|
|
3262
|
-
await assert.rejects(conversation.read(), { code: "
|
|
3263
|
+
await assert.rejects(conversation.read(), { code: "conversation_handle_closed" });
|
|
3263
3264
|
}
|
|
3264
3265
|
assert.equal(events.some(event => event.type === "message" && event.status === "complete" &&
|
|
3265
3266
|
event.role === "commentary" && event.text === "Checking the numbers."), false,
|
package/test/codexEvents.test.js
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import assert from "node:assert/strict";
|
|
2
2
|
import test from "node:test";
|
|
3
|
-
import { codexAppServerNotificationError
|
|
3
|
+
import { codexAppServerNotificationError } from "../src/server/conversation/codexEvents.js";
|
|
4
|
+
import { codexAppServerProviderThreadAssistantSegments } from "../src/server/conversation/codexThreadSnapshot.js";
|
|
5
|
+
import { codexAppServerProviderTurnError } from "../src/server/conversation/codexThreadSnapshot.js";
|
|
4
6
|
|
|
5
7
|
test("phase-less Codex progress before tools does not become a final answer", () => {
|
|
6
8
|
const items = [
|
|
@@ -1,17 +1,13 @@
|
|
|
1
1
|
import assert from "node:assert/strict";
|
|
2
2
|
import test from "node:test";
|
|
3
|
-
import {
|
|
4
|
-
codexAppServerThreadHasReadableHistory,
|
|
5
|
-
deleteCodexAppServerThread,
|
|
6
|
-
deleteCodexAppServerHelperThread
|
|
7
|
-
} from "../src/server/conversation/codexProvider.js";
|
|
3
|
+
import { codexAppServerThreadHasReadableHistory, deleteCodexAppServerThread, deleteCodexAppServerHelperThread } from "../src/server/conversation/codexProviderThreads.js";
|
|
8
4
|
|
|
9
5
|
function invalidRequest(method) {
|
|
10
6
|
return Object.assign(new Error(`${method} rejected`), { code: -32600, method });
|
|
11
7
|
}
|
|
12
8
|
|
|
13
|
-
//
|
|
14
|
-
//
|
|
9
|
+
// These cases cover the native protocol branch of helper deletion; durable
|
|
10
|
+
// receipts, authority and retries belong to the application's lifecycle tests.
|
|
15
11
|
test("Helper deletion awaits the exact active-turn interruption before confirming deletion", async () => {
|
|
16
12
|
const calls = [];
|
|
17
13
|
const started = Promise.withResolvers();
|
|
@@ -4,10 +4,10 @@ import {
|
|
|
4
4
|
createCodexAppServerIsolation,
|
|
5
5
|
codexToolFreeConfiguration,
|
|
6
6
|
readCodexToolFreeConfiguration
|
|
7
|
-
} from "../src/server/conversation/
|
|
7
|
+
} from "../src/server/conversation/codexIsolation.js";
|
|
8
8
|
|
|
9
|
-
//
|
|
10
|
-
//
|
|
9
|
+
// Helper isolation at the native boundary. Profile, schema, prompt and
|
|
10
|
+
// request-settings assertions belong to the application's helper tests.
|
|
11
11
|
const MINIMUM_CODEX_VERSION = "0.151.0";
|
|
12
12
|
const POLICY_UNENFORCEABLE = "codex_policy_unenforceable";
|
|
13
13
|
const HELPER_DISABLED_FEATURES = Object.freeze([
|
|
@@ -104,8 +104,8 @@ function helperInventoryProvider({
|
|
|
104
104
|
};
|
|
105
105
|
}
|
|
106
106
|
|
|
107
|
-
// The
|
|
108
|
-
//
|
|
107
|
+
// The application's prepare callback supplies already authorized policy
|
|
108
|
+
// values. These fixtures exercise only the native ownership boundary.
|
|
109
109
|
async function prepareHelper({ provider }) {
|
|
110
110
|
const enforcement = await isolation.inspect(provider, { effort: "low", summary: "none" });
|
|
111
111
|
return { enforcement, settings: { config: enforcement.config, cwd: enforcement.executionCwd } };
|
|
@@ -557,7 +557,7 @@ test("Codex helper safely reapplies isolation when resuming a controller-owned t
|
|
|
557
557
|
assert.equal(calls.some(([method]) => method === "deleteThread"), false);
|
|
558
558
|
});
|
|
559
559
|
|
|
560
|
-
//
|
|
560
|
+
// Ownership-boundary checks.
|
|
561
561
|
test("helper inventory rejects a reconnect at either sequential read boundary", async () => {
|
|
562
562
|
for (const changedAt of ["readConfig", "listHooks"]) {
|
|
563
563
|
const provider = helperInventoryProvider();
|
|
@@ -111,8 +111,8 @@ function liveCatalogFixture() {
|
|
|
111
111
|
return { calls, captures, provider, readCatalog };
|
|
112
112
|
}
|
|
113
113
|
|
|
114
|
-
//
|
|
115
|
-
//
|
|
114
|
+
// Helper model discovery per connection generation. Host acquisition,
|
|
115
|
+
// restoration and application integration belong to the application's tests.
|
|
116
116
|
test("helper model discovery uses one live provider catalog per connection generation", async () => {
|
|
117
117
|
const { calls, provider, readCatalog } = liveCatalogFixture();
|
|
118
118
|
const first = await readCatalog(provider);
|
|
@@ -217,10 +217,10 @@ test("invalid and unavailable native catalogs retain the host's error prefix wit
|
|
|
217
217
|
|
|
218
218
|
test("native connection generation keeps the production method priority and falsy normalization", () => {
|
|
219
219
|
assert.equal(codexAppServerProviderConnectionGeneration({ currentConnectionGeneration: () => " first ", connectionGeneration: "second" }), "first");
|
|
220
|
-
assert.equal(codexAppServerProviderConnectionGeneration({ connectionGeneration: () => 2 }), "2");
|
|
221
|
-
assert.equal(codexAppServerProviderConnectionGeneration({ connectionGeneration: " direct " }), "direct");
|
|
220
|
+
assert.equal(codexAppServerProviderConnectionGeneration({ currentConnectionGeneration: () => 2, connectionGeneration: () => 2 }), "2");
|
|
221
|
+
assert.equal(codexAppServerProviderConnectionGeneration({ currentConnectionGeneration: () => " direct ", connectionGeneration: " direct " }), "direct");
|
|
222
222
|
for (const value of [0, false, null, undefined]) {
|
|
223
|
-
assert.equal(codexAppServerProviderConnectionGeneration({ connectionGeneration: value }), "");
|
|
223
|
+
assert.equal(codexAppServerProviderConnectionGeneration({ currentConnectionGeneration: () => value, connectionGeneration: value }), "");
|
|
224
224
|
}
|
|
225
225
|
});
|
|
226
226
|
|
|
@@ -281,8 +281,7 @@ function chatCatalogFixture(t, { resources = null } = {}) {
|
|
|
281
281
|
return { owner, captures, readCatalog, retainProvider };
|
|
282
282
|
}
|
|
283
283
|
|
|
284
|
-
//
|
|
285
|
-
// controller fixture becomes the existing native provider owner.
|
|
284
|
+
// Chat model discovery through a native provider owner.
|
|
286
285
|
test("chat model discovery reuses a resident provider without closing it", async t => {
|
|
287
286
|
const { captures, readCatalog, retainProvider } = chatCatalogFixture(t);
|
|
288
287
|
await retainProvider();
|
|
@@ -4,7 +4,7 @@ import { tmpdir } from "node:os";
|
|
|
4
4
|
import { join } from "node:path";
|
|
5
5
|
import { test } from "node:test";
|
|
6
6
|
import { assertCodexAuthPreflightReady } from "../src/server/conversation/codexAuthPreflight.js";
|
|
7
|
-
import { createCodexAppServerRuntime } from "../src/server/conversation/
|
|
7
|
+
import { createCodexAppServerRuntime } from "../src/server/conversation/codexRuntime.js";
|
|
8
8
|
|
|
9
9
|
/** Starts a runtime whose execution host records the launch and reports failure. */
|
|
10
10
|
async function launchRequest(t, configuration, executionMode = "interactive") {
|
|
@@ -21,7 +21,7 @@ async function launchRequest(t, configuration, executionMode = "interactive") {
|
|
|
21
21
|
}
|
|
22
22
|
}
|
|
23
23
|
});
|
|
24
|
-
await assert.rejects(runtime.start({ codexCommand: process.execPath, executionMode, runtimeDir }), /recorded launch/);
|
|
24
|
+
await assert.rejects(runtime.start({ codexCommand: process.execPath, env: process.env, executionMode, runtimeDir }), /recorded launch/);
|
|
25
25
|
return request;
|
|
26
26
|
}
|
|
27
27
|
|
|
@@ -65,7 +65,7 @@ test("a non-octal umask is refused before it reaches the startup script", () =>
|
|
|
65
65
|
test("the Codex sign-in check names itself to the execution host", async () => {
|
|
66
66
|
let request;
|
|
67
67
|
const execution = { async run(value) { request = value; return { ok: true, exitCode: 0, output: "{}" }; } };
|
|
68
|
-
await assertCodexAuthPreflightReady({ codexCommand: process.execPath }, { execution });
|
|
68
|
+
await assertCodexAuthPreflightReady({ codexCommand: process.execPath, env: process.env }, { execution });
|
|
69
69
|
assert.equal(request.mode, "capture");
|
|
70
70
|
assert.deepEqual(request.args.slice(-2), ["debug", "models"]);
|
|
71
71
|
assert.deepEqual(request.execution, { label: "Checking the Codex sign-in", operationId: "codex-auth-preflight" });
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import assert from "node:assert/strict";
|
|
2
2
|
import test from "node:test";
|
|
3
|
-
import { ensureCodexAppServerThread } from "../src/server/conversation/
|
|
3
|
+
import { ensureCodexAppServerThread } from "../src/server/conversation/codexProviderThreads.js";
|
|
4
4
|
|
|
5
5
|
test("retained Codex selection awaits observation before resume and identity publication before return", async () => {
|
|
6
6
|
const calls = [];
|
|
@@ -67,6 +67,7 @@ test("selection failures preserve the original error and never fall back to a ne
|
|
|
67
67
|
};
|
|
68
68
|
await assert.rejects(ensureCodexAppServerThread({
|
|
69
69
|
provider: {
|
|
70
|
+
async ensureAvailable() { return {}; },
|
|
70
71
|
async ensureRuntime() { step("runtime"); return {}; },
|
|
71
72
|
async readGoal() { step("goal"); return { goal: { status: "active" } }; },
|
|
72
73
|
async setGoalStatus() { step("pause"); },
|
|
@@ -93,6 +94,7 @@ test("new selection starts once and rejects a missing native ID before publishin
|
|
|
93
94
|
const settings = { model: "selected-model", sessionStartSource: "startup" };
|
|
94
95
|
const selecting = ensureCodexAppServerThread({
|
|
95
96
|
provider: {
|
|
97
|
+
async ensureAvailable() { return {}; },
|
|
96
98
|
async ensureRuntime() { calls.push("runtime"); return {}; },
|
|
97
99
|
async startThread(input) { calls.push("start"); assert.equal(input, settings); return { id }; },
|
|
98
100
|
async resumeThread() { assert.fail("There is no saved thread to resume"); },
|
|
@@ -117,6 +119,7 @@ test("selection reads the saved pause marker again after resume and retains the
|
|
|
117
119
|
let written;
|
|
118
120
|
const selected = await ensureCodexAppServerThread({
|
|
119
121
|
provider: {
|
|
122
|
+
async ensureAvailable() { return {}; },
|
|
120
123
|
async ensureRuntime() { return {}; },
|
|
121
124
|
async readGoal() { return { goal: { status: "paused" } }; },
|
|
122
125
|
async setGoalStatus() { assert.fail("An already paused goal stays paused"); },
|
package/test/codexTurn.test.js
CHANGED
|
@@ -7,7 +7,8 @@ import test from "node:test";
|
|
|
7
7
|
import { createMemoryConversationStorage } from "../src/server/conversation/memoryStorage.js";
|
|
8
8
|
import { createConversationTranscript } from "../src/server/conversation/transcript.js";
|
|
9
9
|
import { createConversationStreams } from "../src/server/conversation/streams.js";
|
|
10
|
-
import { sendCodexAppServerPrompt,
|
|
10
|
+
import { sendCodexAppServerPrompt, codexAppServerDeliveredTurn } from "../src/server/conversation/codexProviderTurns.js";
|
|
11
|
+
import { CodexAppServerAgentProvider } from "../src/server/conversation/codexProvider.js";
|
|
11
12
|
import { createCodexHelperThreadLedgerOwner } from "../src/server/conversation/codexHelperThreadLedger.js";
|
|
12
13
|
import { createCodexSessionReconciliation } from "../src/server/conversation/codexSessionReconciliation.js";
|
|
13
14
|
import { createCodexSessionCleanup } from "../src/server/conversation/codexSessionCleanup.js";
|
|
@@ -21,7 +22,7 @@ import {
|
|
|
21
22
|
CODEX_APP_SERVER_RUN_STATE
|
|
22
23
|
} from "../src/server/conversation/codexTurn.js";
|
|
23
24
|
|
|
24
|
-
//
|
|
25
|
+
// A new chat turn claims its client message id through the run journal, which keeps the outer turn id.
|
|
25
26
|
test("a new chat turn claims its client message id and preserves the stable outer identity", async () => {
|
|
26
27
|
const messageSource = await readFile(new URL(
|
|
27
28
|
"../src/server/conversation/codexMessageCommands.js", import.meta.url
|
|
@@ -34,9 +35,9 @@ test("a new chat turn claims its client message id and preserves the stable oute
|
|
|
34
35
|
assert.match(journalSource, /outerTurnId: normalizedOuterTurnId/u);
|
|
35
36
|
});
|
|
36
37
|
|
|
37
|
-
//
|
|
38
|
-
// handover, saved identity and structured acknowledgement
|
|
39
|
-
//
|
|
38
|
+
// These cases exercise the native renewal wait sequence. Application retry,
|
|
39
|
+
// accepted handover, saved identity and structured acknowledgement belong to
|
|
40
|
+
// the application's renewal tests.
|
|
40
41
|
function turn(id = "turn-1", status = "inProgress", text = "") {
|
|
41
42
|
return {
|
|
42
43
|
id,
|
|
@@ -342,7 +343,7 @@ function runFixture(initial = {}) {
|
|
|
342
343
|
test("native run claims retain the original lock/read/write/read sequence and clear former goal ownership", async () => {
|
|
343
344
|
const f = runFixture({ state: "completed", providerGoalStatus: "active", providerGoalThreadId: "old-thread",
|
|
344
345
|
providerGoalUpdatedAt: "old-goal-time", pendingUserMessageClientIds: ["old-message"] });
|
|
345
|
-
const owner = createCodexAppServerRunOwner();
|
|
346
|
+
const owner = createCodexAppServerRunOwner({});
|
|
346
347
|
const result = await owner.claimTurnStart(f.runtime, " session-1 ", " request-2 ");
|
|
347
348
|
assert.equal(result.claimed, true);
|
|
348
349
|
const run = result.session.agentRuns[0];
|
|
@@ -362,7 +363,7 @@ test("native run claims retain the original lock/read/write/read sequence and cl
|
|
|
362
363
|
|
|
363
364
|
test("the moved native run guard preserves Stop when an earlier provider read resolves", async () => {
|
|
364
365
|
const f = runFixture();
|
|
365
|
-
const owner = createCodexAppServerRunOwner();
|
|
366
|
+
const owner = createCodexAppServerRunOwner({});
|
|
366
367
|
const observedRun = f.read();
|
|
367
368
|
await owner.writeAgentRun(f.runtime, "session-1", { runState: CODEX_APP_SERVER_RUN_STATE.INTERRUPTED,
|
|
368
369
|
status: "interrupted", threadId: "thread-1", turnId: "turn-1", publishReason: "codex-app-server-turn-interrupted" });
|
|
@@ -386,7 +387,7 @@ test("native recovery requires the exact stopped revision and keeps its original
|
|
|
386
387
|
for (const state of ["failed", "interrupted"]) {
|
|
387
388
|
const f = runFixture({ state, providerStatus: state });
|
|
388
389
|
const observedRun = f.read();
|
|
389
|
-
const result = await createCodexAppServerRunOwner().writeAgentRun(f.runtime, "session-1", { observedRun,
|
|
390
|
+
const result = await createCodexAppServerRunOwner({}).writeAgentRun(f.runtime, "session-1", { observedRun,
|
|
390
391
|
runState: CODEX_APP_SERVER_RUN_STATE.ACTIVE, status: "inProgress", threadId: "thread-1", turnId: "turn-1",
|
|
391
392
|
updatedAt: "2026-10-01T01:00:00.000Z" });
|
|
392
393
|
assert.equal(result.wrote, true);
|
|
@@ -422,7 +423,7 @@ test("durable processed results and observation loss prevent revival of an exact
|
|
|
422
423
|
|
|
423
424
|
test("native phase changes require the current exact turn and retain its active start", async () => {
|
|
424
425
|
const f = runFixture({ providerPhase: "reasoning" });
|
|
425
|
-
const owner = createCodexAppServerRunOwner();
|
|
426
|
+
const owner = createCodexAppServerRunOwner({});
|
|
426
427
|
for (const turnId of ["", "foreign-turn"]) {
|
|
427
428
|
assert.equal((await owner.writeAgentRun(f.runtime, "session-1", { phase: "compacting",
|
|
428
429
|
runState: CODEX_APP_SERVER_RUN_STATE.ACTIVE, status: "inProgress", threadId: "thread-1", turnId })).wrote, false);
|
|
@@ -437,7 +438,7 @@ test("native phase changes require the current exact turn and retain its active
|
|
|
437
438
|
test("a settling write for a predecessor turn cannot end the active successor run", async () => {
|
|
438
439
|
for (const state of ["active", "finalizing", "starting"]) {
|
|
439
440
|
const f = runFixture({ state, providerTurnId: "successor" });
|
|
440
|
-
const owner = createCodexAppServerRunOwner();
|
|
441
|
+
const owner = createCodexAppServerRunOwner({});
|
|
441
442
|
const successor = f.read();
|
|
442
443
|
for (const runState of [CODEX_APP_SERVER_RUN_STATE.COMPLETED, CODEX_APP_SERVER_RUN_STATE.FAILED,
|
|
443
444
|
CODEX_APP_SERVER_RUN_STATE.INTERRUPTED]) {
|
|
@@ -540,7 +541,7 @@ test("the run owner's session closures are the ones its runtime lifecycle drains
|
|
|
540
541
|
});
|
|
541
542
|
|
|
542
543
|
test("the run owner exposes only operations its consumers use, not facet recovery state", () => {
|
|
543
|
-
const owner = createCodexAppServerRunOwner();
|
|
544
|
+
const owner = createCodexAppServerRunOwner({});
|
|
544
545
|
for (const internal of ["completedTurns", "automaticHookThreads", "conversationTurnStarts", "reasoningPersistQueues",
|
|
545
546
|
"threadReconciliations", "reasoningTurns", "finalAssistantResults", "mirroredTerminalItems", "clearActiveTimer",
|
|
546
547
|
"clearFinalizingTimer", "adoptSuccessorTurn", "markTurnActive", "markTurnFinalizing", "checkManagedConnection"]) {
|
|
@@ -770,7 +771,9 @@ async function outputFixture({ persistCommentary } = {}) {
|
|
|
770
771
|
nextCursor: offset + 1 < f.history.length ? String(offset + 1) : null };
|
|
771
772
|
},
|
|
772
773
|
async resumeThread() { throw new Error("History recovery must not resume native work"); },
|
|
773
|
-
async startTurn() { throw new Error("History reads must not resend the prompt"); }
|
|
774
|
+
async startTurn() { throw new Error("History reads must not resend the prompt"); },
|
|
775
|
+
isControlProbeTurn: () => false,
|
|
776
|
+
interruptionOutcome: () => ""
|
|
774
777
|
};
|
|
775
778
|
f.store = store;
|
|
776
779
|
f.provider = provider;
|
|
@@ -1486,11 +1489,12 @@ test("original goal command preserves a failed cancellation for retry without re
|
|
|
1486
1489
|
assert.equal(f.publications.length, 0);
|
|
1487
1490
|
});
|
|
1488
1491
|
|
|
1489
|
-
//
|
|
1490
|
-
//
|
|
1491
|
-
//
|
|
1492
|
+
// These bounded cases cover the dispatch and receipt boundary: supplied
|
|
1493
|
+
// settings, Error identity and result projection. Admission and Git fixtures
|
|
1494
|
+
// belong to the application's start and steer tests.
|
|
1492
1495
|
function commandThreadFixture(f, providerAlreadyAvailable) {
|
|
1493
1496
|
Object.assign(f.provider, {
|
|
1497
|
+
async ensureAvailable() { return {}; },
|
|
1494
1498
|
async ensureRuntime() { return {}; },
|
|
1495
1499
|
async resumeThread(threadId) { return { id: threadId }; }
|
|
1496
1500
|
});
|
|
@@ -1719,7 +1723,8 @@ test("original dispatch start waits for a persisted receipt and ignores its late
|
|
|
1719
1723
|
const receipt = Promise.withResolvers();
|
|
1720
1724
|
f.run = { ...f.run, state: "starting", providerTurnId: "", outerTurnId: messageId };
|
|
1721
1725
|
f.owner.pendingUserMessages.set(`${context.promptDeliveryKey}\0${messageId}`, {
|
|
1722
|
-
text: context.displayMessage, turnMetadata: context.turnMetadata, receipt
|
|
1726
|
+
text: context.displayMessage, turnMetadata: context.turnMetadata, receipt,
|
|
1727
|
+
onNativeReceipt() {}, onReceiptPersisted() {}
|
|
1723
1728
|
});
|
|
1724
1729
|
let rpcReplies = 0;
|
|
1725
1730
|
let returned = false;
|
|
@@ -1804,7 +1809,8 @@ test("original dispatch steering persists the authored receipt while its exact n
|
|
|
1804
1809
|
const acknowledgement = Promise.withResolvers();
|
|
1805
1810
|
f.owner.pendingUserMessages.set(`${context.promptDeliveryKey}\0${context.messageId}`, {
|
|
1806
1811
|
text: context.displayMessage, turnMetadata: context.turnMetadata,
|
|
1807
|
-
attachments: [{ id: "diagram", name: "Diagram" }], receipt
|
|
1812
|
+
attachments: [{ id: "diagram", name: "Diagram" }], receipt,
|
|
1813
|
+
onNativeReceipt() {}, onReceiptPersisted() {}
|
|
1808
1814
|
});
|
|
1809
1815
|
let rpcReplies = 0;
|
|
1810
1816
|
f.provider.steerTurn = async (threadId, turnId, input, params) => {
|
|
@@ -2204,7 +2210,8 @@ test("original authorized input supports an attachment-only receipt through the
|
|
|
2204
2210
|
const acknowledgement = Promise.withResolvers();
|
|
2205
2211
|
f.run = { ...f.run, state: "starting", providerTurnId: "" };
|
|
2206
2212
|
f.owner.pendingUserMessages.set(`${context.promptDeliveryKey}\0${context.messageId}`, {
|
|
2207
|
-
text: "", attachments, turnMetadata: context.turnMetadata, receipt
|
|
2213
|
+
text: "", attachments, turnMetadata: context.turnMetadata, receipt,
|
|
2214
|
+
onNativeReceipt() {}, onReceiptPersisted() {}
|
|
2208
2215
|
});
|
|
2209
2216
|
f.provider.sendTurn = async (threadId, input, params) => {
|
|
2210
2217
|
assert.equal(threadId, f.threadId);
|
|
@@ -3124,7 +3131,8 @@ test("native receipt keeps admitted application data with the original authored
|
|
|
3124
3131
|
f.run = { ...f.run, state: "starting", providerTurnId: "", pendingUserMessageClientIds: [messageId] };
|
|
3125
3132
|
const receipt = Promise.withResolvers();
|
|
3126
3133
|
f.owner.pendingUserMessages.set(`project:${f.sessionId}\0${messageId}`, {
|
|
3127
|
-
text: "These are my actual words.", attachments: [], authoredInput: { messageId, data }, receipt
|
|
3134
|
+
text: "These are my actual words.", attachments: [], authoredInput: { messageId, data }, receipt,
|
|
3135
|
+
onNativeReceipt() {}, onReceiptPersisted() {}
|
|
3128
3136
|
});
|
|
3129
3137
|
f.observe();
|
|
3130
3138
|
f.emit(f.receipt(messageId, "Prepared model context must not replace authored words"));
|
|
@@ -3169,7 +3177,8 @@ test("native receipt does not borrow application data from another authored iden
|
|
|
3169
3177
|
const receipt = Promise.withResolvers();
|
|
3170
3178
|
f.owner.pendingUserMessages.set(`project:${f.sessionId}\0${messageId}`, {
|
|
3171
3179
|
text: "Unassociated actual words.", attachments: [],
|
|
3172
|
-
authoredInput: { messageId: "other-message", data: { association: "foreign" } }, receipt
|
|
3180
|
+
authoredInput: { messageId: "other-message", data: { association: "foreign" } }, receipt,
|
|
3181
|
+
onNativeReceipt() {}, onReceiptPersisted() {}
|
|
3173
3182
|
});
|
|
3174
3183
|
f.observe();
|
|
3175
3184
|
f.emit(f.receipt(messageId));
|
|
@@ -3,6 +3,7 @@ import test from "node:test";
|
|
|
3
3
|
import { mkdtemp, readFile, readdir, rm } from "node:fs/promises";
|
|
4
4
|
import { tmpdir } from "node:os";
|
|
5
5
|
import { join } from "node:path";
|
|
6
|
+
import { createOpenAICompatible } from "@ai-sdk/openai-compatible";
|
|
6
7
|
import { createConversationRuntime, createFileConversationStorage, createMemoryConversationStorage } from "../src/server/conversation/index.js";
|
|
7
8
|
|
|
8
9
|
test("API configuration rejects unsupported structured output before resolving an account or sending", async t => {
|
|
@@ -36,13 +37,13 @@ test("an existing API client keeps opaque receipts path-safe and selected histor
|
|
|
36
37
|
apiClientFactory: { resolveClient({ context: actor, configuration }) {
|
|
37
38
|
assert.equal(actor, context);
|
|
38
39
|
assert.equal(configuration.integrationId, undefined, "Existing environment clients need no fabricated integration ID");
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
40
|
+
// An existing client supplies its AI SDK model; this one speaks the OpenAI chat format.
|
|
41
|
+
return { enabled: true, defaultModel: "existing-model", model: createOpenAICompatible({ name: "existing", baseURL: "http://test.invalid/v1",
|
|
42
|
+
fetch: async (_url, request) => {
|
|
43
|
+
requests.push(JSON.parse(request.body).messages);
|
|
44
|
+
return new Response(`data: ${JSON.stringify({ choices: [{ index: 0, delta: { content: " A complete answer. " } }] })}\n\n` +
|
|
45
|
+
`data: ${JSON.stringify({ choices: [{ index: 0, delta: {}, finish_reason: "stop" }] })}\n\n`, { headers: { "content-type": "text/event-stream" } });
|
|
46
|
+
} })("existing-model") };
|
|
46
47
|
} },
|
|
47
48
|
attachments: { async resolve({ attachmentIds, context: actor, conversationId }) {
|
|
48
49
|
assert.equal(actor, context);
|
|
@@ -59,7 +60,7 @@ test("an existing API client keeps opaque receipts path-safe and selected histor
|
|
|
59
60
|
assert.equal((await conversation.send({ messageId, text: "Current question", attachmentIds: ["current"] })).messageId, messageId);
|
|
60
61
|
const first = await conversation.wait();
|
|
61
62
|
assert.equal(first.conversationLog[0].user.messageId, messageId);
|
|
62
|
-
assert.equal(first.conversationLog[0].assistant.text, "
|
|
63
|
+
assert.equal(first.conversationLog[0].assistant.text, "A complete answer.", "The engine's reply has no whitespace at its ends");
|
|
63
64
|
assert.deepEqual(resolvedAttachments, [["current"], ["historical"]]);
|
|
64
65
|
assert.deepEqual(requests[0][1], { role: "user", content: [
|
|
65
66
|
{ type: "text", text: "Selected prior context" }, { type: "text", text: "Authorized historical bytes" }
|
|
@@ -83,7 +84,8 @@ test("an existing API client keeps opaque receipts path-safe and selected histor
|
|
|
83
84
|
context.history = [];
|
|
84
85
|
await conversation.send({ messageId: "empty-context", text: "No prior model context" });
|
|
85
86
|
const last = await conversation.wait();
|
|
86
|
-
assert.
|
|
87
|
+
assert.match(requests[2][0].content, /^Existing instructions\n\nCurrent state, from the system\./u);
|
|
88
|
+
assert.deepEqual(requests[2].slice(1).map(message => message.content), ["No prior model context"]);
|
|
87
89
|
assert.equal(last.conversationLog.length, 3, "Explicitly empty model context never removes saved conversation history");
|
|
88
90
|
await assert.rejects(conversation.send({ messageId: "x".repeat(129), text: "Must not dispatch" }), { code: "conversation_invalid_message" });
|
|
89
91
|
assert.equal(requests.length, 3);
|
|
@@ -2,7 +2,7 @@ import { useAssistantAttachments } from "../src/client/conversation/useAssistant
|
|
|
2
2
|
import assert from "node:assert/strict";
|
|
3
3
|
import test from "node:test";
|
|
4
4
|
import { createConversationRuntime, createMemoryConversationStorage } from "../src/server/conversation/index.js";
|
|
5
|
-
import { codexLocalImageInput, codexTurnInput } from "../src/server/conversation/
|
|
5
|
+
import { codexLocalImageInput, codexTurnInput } from "../src/server/conversation/codexProviderTurns.js";
|
|
6
6
|
import { conversationAttachmentIds, createConversationAttachmentReader } from "../src/server/conversation/attachments.js";
|
|
7
7
|
import { attachmentIdsField } from "../src/shared/index.js";
|
|
8
8
|
import { ASSISTANT_ATTACHMENT_MAX_ITEMS as CLIENT_ATTACHMENT_MAX_ITEMS } from "../src/client/conversation/useAssistantAttachments.js";
|
|
@@ -154,7 +154,7 @@ test("authorized image bytes reach the API once and only safe receipts enter sto
|
|
|
154
154
|
assert.doesNotMatch(JSON.stringify(state), /privatePath|private\/upload|iVBOR/);
|
|
155
155
|
assert.equal((await f.conversation.send(input)).duplicate, true);
|
|
156
156
|
assert.equal(f.reads.length, 1, "An accepted retry returns its receipt without rereading files or resending");
|
|
157
|
-
await assert.rejects(f.conversation.send({ ...input, attachmentIds: ["different"] }), { code: "
|
|
157
|
+
await assert.rejects(f.conversation.send({ ...input, attachmentIds: ["different"] }), { code: "conversation_message_id_content_conflict" });
|
|
158
158
|
});
|
|
159
159
|
|
|
160
160
|
test("API history and continuity reauthorize files after reopen and renewal", async t => {
|
|
@@ -214,7 +214,7 @@ test("invalid IDs and caller-supplied attachment content do not reach the resolv
|
|
|
214
214
|
for (const attachmentIds of [null, "picture", [""], ["picture", "picture"], [{}], Array.from({ length: 11 }, (_, i) => String(i))]) {
|
|
215
215
|
await assert.rejects(f.conversation.send({ ...input, attachmentIds }), { code: "conversation_invalid_attachments" });
|
|
216
216
|
}
|
|
217
|
-
await assert.rejects(f.conversation.send({ ...input, attachments: [resolution()] }), { code: "
|
|
217
|
+
await assert.rejects(f.conversation.send({ ...input, attachments: [resolution()] }), { code: "conversation_attachment_ids_only" });
|
|
218
218
|
assert.equal(f.reads.length, 0);
|
|
219
219
|
assert.equal(f.requests.length, 0);
|
|
220
220
|
});
|
|
@@ -128,7 +128,7 @@ test("runtime normalizes destination settings and retains file-backed selection
|
|
|
128
128
|
conversation = await second.open({ id: "one" });
|
|
129
129
|
assert.equal((await conversation.select(selection)).duplicate, true);
|
|
130
130
|
await assert.rejects(conversation.select({ ...selection, configuration: { ...selection.configuration, model: "changed-model" } }),
|
|
131
|
-
{ code: "
|
|
131
|
+
{ code: "conversation_replacement_operation_conflict" });
|
|
132
132
|
const replacement = { operationId: "api", expectedSegmentId: selected.segmentId, reason: "engine-change", engine: "api",
|
|
133
133
|
configuration: { systemPrompt: "API instructions.", integrationId: undefined, model: undefined, effort: undefined } };
|
|
134
134
|
await conversation.replace(replacement);
|
|
@@ -140,7 +140,7 @@ test("runtime normalizes destination settings and retains file-backed selection
|
|
|
140
140
|
conversation = await f.runtime({ defaultIntegrationId: "later-default" }).open({ id: "one" });
|
|
141
141
|
assert.equal((await conversation.replace(replacement)).duplicate, true);
|
|
142
142
|
await assert.rejects(conversation.replace({ ...replacement, configuration: { ...replacement.configuration, integrationId: "changed" } }),
|
|
143
|
-
{ code: "
|
|
143
|
+
{ code: "conversation_replacement_operation_conflict" });
|
|
144
144
|
assert.deepEqual((await conversation.read()).configuration, { systemPrompt: "API instructions.", integrationId: "next-default" });
|
|
145
145
|
assert.deepEqual((await conversation.read()).conversationLog, []);
|
|
146
146
|
});
|
|
@@ -166,7 +166,7 @@ test("file-backed pending replacement keeps its admitted configuration across a
|
|
|
166
166
|
await first.close();
|
|
167
167
|
const resumed = await f.runtime({ defaultIntegrationId: "later-default" }).open({ id: "one" });
|
|
168
168
|
await assert.rejects(resumed.replace({ ...input, configuration: { ...input.configuration, systemPrompt: "Different instructions." } }),
|
|
169
|
-
{ code: "
|
|
169
|
+
{ code: "conversation_replacement_unfinished" });
|
|
170
170
|
assert.deepEqual(await f.storage.read("one", transaction => transaction.readMetadata()), saved);
|
|
171
171
|
await resumed.replace(input);
|
|
172
172
|
assert.deepEqual((await resumed.read()).configuration, { systemPrompt: "Replacement instructions.", integrationId: "assistant" });
|
|
@@ -197,5 +197,5 @@ test("file-backed OpenCode selections keep compatible bindings and reserve a fre
|
|
|
197
197
|
assert.equal((await reopened.select(selection)).duplicate, true);
|
|
198
198
|
assert.deepEqual(await f.storage.read("open-code", tx => tx.readMetadata()), saved);
|
|
199
199
|
await assert.rejects(reopened.select({ ...selection, configuration: { ...selection.configuration, integrationId: "different" } }),
|
|
200
|
-
{ code: "
|
|
200
|
+
{ code: "conversation_replacement_operation_conflict" });
|
|
201
201
|
});
|
|
@@ -6,7 +6,8 @@ import { randomUUID } from "node:crypto";
|
|
|
6
6
|
import { nativeConversationStoragePolicy } from "../src/server/conversation/nativeStorage.js";
|
|
7
7
|
import { createCodexAppServerProviderOwner } from "../src/server/conversation/codexProviderOwner.js";
|
|
8
8
|
import { createCodexConversationDriver } from "../src/server/conversation/providers/codexDriver.js";
|
|
9
|
-
import { createOpenCodeSharedRuntime
|
|
9
|
+
import { createOpenCodeSharedRuntime } from "../src/server/conversation/openCodeRuntime.js";
|
|
10
|
+
import { openCodeServerForDirectory } from "../src/server/conversation/openCodeNativeSession.js";
|
|
10
11
|
import { createOpenCodeConversationDriver } from "../src/server/conversation/providers/opencodeDriver.js";
|
|
11
12
|
import { eventually } from "./fixtures/eventually.js";
|
|
12
13
|
|
|
@@ -274,30 +275,30 @@ test("Codex native existence checks a home whose thread previews exceed any byte
|
|
|
274
275
|
[nativeThread("wanted", { previewBytes: 70 * 1024 })]
|
|
275
276
|
];
|
|
276
277
|
const { inventory } = codexThreadListing(pages);
|
|
277
|
-
const {
|
|
278
|
-
assert.equal(await
|
|
279
|
-
assert.equal(await
|
|
278
|
+
const { createCodexProviderInventories } = await import("../src/server/conversation/codexProviderInventories.js");
|
|
279
|
+
assert.equal(await createCodexProviderInventories({ provider: inventory }).nativeThreadExists("wanted"), true);
|
|
280
|
+
assert.equal(await createCodexProviderInventories({ provider: inventory }).nativeThreadExists("missing"), false);
|
|
280
281
|
});
|
|
281
282
|
|
|
282
283
|
test("Codex native existence searches more threads than an inventory retains", async () => {
|
|
283
284
|
const pages = Array.from({ length: 12 }, (_, page) =>
|
|
284
285
|
Array.from({ length: 100 }, (_, index) => nativeThread(`thread-${page}-${index}`)));
|
|
285
286
|
const { inventory, requests } = codexThreadListing(pages);
|
|
286
|
-
const {
|
|
287
|
-
assert.equal(await
|
|
287
|
+
const { createCodexProviderInventories } = await import("../src/server/conversation/codexProviderInventories.js");
|
|
288
|
+
assert.equal(await createCodexProviderInventories({ provider: inventory }).nativeThreadExists("thread-3-7"), true);
|
|
288
289
|
assert.equal(requests.length, 4, "the search stops at the page that lists the thread");
|
|
289
|
-
assert.equal(await
|
|
290
|
+
assert.equal(await createCodexProviderInventories({ provider: inventory }).nativeThreadExists("thread-12-0"), false);
|
|
290
291
|
});
|
|
291
292
|
|
|
292
293
|
test("a refused Codex inventory entry names the check and the thread", async () => {
|
|
293
|
-
const {
|
|
294
|
+
const { createCodexProviderInventories } = await import("../src/server/conversation/codexProviderInventories.js");
|
|
294
295
|
const outside = codexThreadListing([[nativeThread("thread-a"), nativeThread("thread-b", { cwd: "/elsewhere" })]]);
|
|
295
|
-
await assert.rejects(
|
|
296
|
+
await assert.rejects(createCodexProviderInventories({ provider: outside.inventory }).listNativeThreadsForCwd("/source"), (error) => {
|
|
296
297
|
assert.equal(error.code, "vibe64_codex_retirement_inventory_invalid");
|
|
297
298
|
assert.match(error.message, /thread thread-b .*outside the requested directory/u);
|
|
298
299
|
return true;
|
|
299
300
|
});
|
|
300
301
|
const unnamed = codexThreadListing([[{ cwd: "/source" }]]);
|
|
301
|
-
await assert.rejects(
|
|
302
|
+
await assert.rejects(createCodexProviderInventories({ provider: unnamed.inventory }).listNativeThreadsForCwd("/source"),
|
|
302
303
|
/listed a thread without an id/u);
|
|
303
304
|
});
|