@jskit-ai/assistant-core 0.2.7 → 0.2.8

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.
Files changed (214) hide show
  1. package/examples/conversation/package.json +5 -5
  2. package/package.json +18 -7
  3. package/src/client/browser.js +21 -0
  4. package/src/client/conversation/AssistantAttachmentQueue.vue +25 -108
  5. package/src/client/conversation/AssistantComposerActions.vue +1 -1
  6. package/src/client/conversation/AssistantConversationElement.vue +14 -52
  7. package/src/client/conversation/AssistantGoalControl.vue +13 -10
  8. package/src/client/conversation/AssistantPromptInput.vue +6 -91
  9. package/src/client/conversation/AssistantTranscript.css +505 -0
  10. package/src/client/conversation/AssistantTranscript.vue +24 -1197
  11. package/src/client/conversation/attachmentFiles.js +158 -0
  12. package/src/client/conversation/attachmentQueueItems.js +121 -0
  13. package/src/client/conversation/attachmentUploadRows.js +265 -0
  14. package/src/client/conversation/conversationDisplay.js +54 -0
  15. package/src/client/conversation/messageDelivery.js +13 -9
  16. package/src/client/conversation/retainedConversation.js +14 -3
  17. package/src/client/conversation/submitText.js +13 -2
  18. package/src/client/conversation/transcriptEntries.js +225 -0
  19. package/src/client/conversation/useAssistantAttachments.js +86 -388
  20. package/src/client/conversation/useAssistantQuestions.js +16 -3
  21. package/src/client/conversation/useAssistantSuggestions.js +16 -2
  22. package/src/client/conversation/useScrollToBottom.js +17 -19
  23. package/src/client/conversation/useTextareaAutoGrow.js +115 -0
  24. package/src/client/conversation/useTranscriptScrollFollow.js +525 -0
  25. package/src/client/lib/assistantApi.js +192 -161
  26. package/src/client/types.js +110 -0
  27. package/src/server/conversation/attachments.js +21 -6
  28. package/src/server/conversation/boundedToolLoop.js +1 -0
  29. package/src/server/conversation/claudeClient.js +71 -20
  30. package/src/server/conversation/claudeCommandRun.js +191 -0
  31. package/src/server/conversation/claudeHistory.js +7 -0
  32. package/src/server/conversation/claudeNativeTurn.js +708 -0
  33. package/src/server/conversation/claudeProcess.js +186 -112
  34. package/src/server/conversation/claudeProcessStart.js +214 -0
  35. package/src/server/conversation/claudePromptDelivery.js +183 -0
  36. package/src/server/conversation/claudeSessionCleanup.js +248 -0
  37. package/src/server/conversation/claudeTools.js +15 -3
  38. package/src/server/conversation/claudeTurn.js +212 -1388
  39. package/src/server/conversation/codexAccountReader.js +168 -0
  40. package/src/server/conversation/codexAuthPreflight.js +5 -4
  41. package/src/server/conversation/codexClient.js +18 -6
  42. package/src/server/conversation/codexConfiguration.js +8 -844
  43. package/src/server/conversation/codexConversationStops.js +377 -0
  44. package/src/server/conversation/codexConversationStore.js +119 -85
  45. package/src/server/conversation/codexConversationTurns.js +378 -0
  46. package/src/server/conversation/codexConversations.js +134 -866
  47. package/src/server/conversation/codexDetachedConversation.js +203 -0
  48. package/src/server/conversation/codexDetachedTurn.js +145 -104
  49. package/src/server/conversation/codexEvents.js +5 -200
  50. package/src/server/conversation/codexGoalCommands.js +14 -4
  51. package/src/server/conversation/codexHelperExecution.js +57 -57
  52. package/src/server/conversation/codexHelperThreadLedger.js +30 -288
  53. package/src/server/conversation/codexHelperThreadRecord.js +304 -0
  54. package/src/server/conversation/codexHelperThreadRecovery.js +54 -76
  55. package/src/server/conversation/codexHelperThreads.js +94 -93
  56. package/src/server/conversation/codexHistoryAdapter.js +13 -8
  57. package/src/server/conversation/codexIsolation.js +672 -0
  58. package/src/server/conversation/codexLiveItems.js +379 -0
  59. package/src/server/conversation/codexMessageCommands.js +68 -286
  60. package/src/server/conversation/codexMessageDelivery.js +46 -37
  61. package/src/server/conversation/codexModelCatalog.js +86 -7
  62. package/src/server/conversation/codexNotificationObserver.js +81 -88
  63. package/src/server/conversation/codexNotificationQueue.js +21 -10
  64. package/src/server/conversation/codexObservationControl.js +105 -89
  65. package/src/server/conversation/codexOutput.js +84 -419
  66. package/src/server/conversation/codexProcess.js +29 -1053
  67. package/src/server/conversation/codexProcessLeader.js +1 -0
  68. package/src/server/conversation/codexPromptDispatch.js +241 -0
  69. package/src/server/conversation/codexProvider.js +147 -1807
  70. package/src/server/conversation/codexProviderAuth.js +348 -0
  71. package/src/server/conversation/codexProviderInvalidation.js +371 -0
  72. package/src/server/conversation/codexProviderInventories.js +437 -0
  73. package/src/server/conversation/codexProviderOwner.js +149 -957
  74. package/src/server/conversation/codexProviderRelease.js +465 -0
  75. package/src/server/conversation/codexProviderSelection.js +26 -15
  76. package/src/server/conversation/codexProviderThreads.js +698 -0
  77. package/src/server/conversation/codexProviderTurns.js +453 -0
  78. package/src/server/conversation/codexReasoning.js +32 -23
  79. package/src/server/conversation/codexRenewalCommands.js +18 -6
  80. package/src/server/conversation/codexRenewalThreads.js +7 -22
  81. package/src/server/conversation/codexResultSettlement.js +70 -88
  82. package/src/server/conversation/codexRunJournal.js +91 -97
  83. package/src/server/conversation/codexRuntime.js +426 -0
  84. package/src/server/conversation/codexRuntimeInventory.js +113 -0
  85. package/src/server/conversation/codexRuntimeLifecycle.js +39 -13
  86. package/src/server/conversation/codexRuntimeStart.js +431 -0
  87. package/src/server/conversation/codexRuntimeStop.js +324 -0
  88. package/src/server/conversation/codexSessionCleanup.js +25 -10
  89. package/src/server/conversation/codexSessionReconciliation.js +21 -7
  90. package/src/server/conversation/codexThreadReadiness.js +49 -52
  91. package/src/server/conversation/codexThreadSnapshot.js +210 -0
  92. package/src/server/conversation/codexThreadStatus.js +48 -62
  93. package/src/server/conversation/codexTurn.js +171 -55
  94. package/src/server/conversation/codexTurnActivity.js +210 -0
  95. package/src/server/conversation/codexTurnControl.js +44 -48
  96. package/src/server/conversation/codexTurnEndings.js +610 -0
  97. package/src/server/conversation/codexTurnRecovery.js +97 -848
  98. package/src/server/conversation/codexTurnState.js +138 -111
  99. package/src/server/conversation/completedResponses.js +227 -0
  100. package/src/server/conversation/configuration.js +2 -2
  101. package/src/server/conversation/continuity.js +43 -6
  102. package/src/server/conversation/conversationDelivery.js +394 -0
  103. package/src/server/conversation/conversationEntries.js +365 -0
  104. package/src/server/conversation/conversationEvents.js +192 -0
  105. package/src/server/conversation/conversationGoals.js +187 -0
  106. package/src/server/conversation/conversationHandles.js +237 -0
  107. package/src/server/conversation/conversationReads.js +206 -0
  108. package/src/server/conversation/conversationRefusals.js +58 -0
  109. package/src/server/conversation/conversationReplacement.js +254 -0
  110. package/src/server/conversation/conversationSending.js +273 -0
  111. package/src/server/conversation/conversationStopping.js +121 -0
  112. package/src/server/conversation/conversationTurnDelivery.js +413 -0
  113. package/src/server/conversation/conversationTurns.js +511 -0
  114. package/src/server/conversation/deliveryTiming.js +25 -3
  115. package/src/server/conversation/fileStorage.js +11 -6
  116. package/src/server/conversation/hookBridge.js +14 -3
  117. package/src/server/conversation/index.js +1 -1
  118. package/src/server/conversation/localExecution.js +82 -625
  119. package/src/server/conversation/nativeCommandCallbacks.js +10 -0
  120. package/src/server/conversation/nativeConversationLifecycle.js +379 -0
  121. package/src/server/conversation/nativeHistoryExport.js +27 -18
  122. package/src/server/conversation/normalize.js +9 -0
  123. package/src/server/conversation/openCodeClient.js +295 -366
  124. package/src/server/conversation/openCodeNativeHistory.js +141 -0
  125. package/src/server/conversation/openCodeNativeSession.js +79 -0
  126. package/src/server/conversation/openCodeProcess.js +109 -53
  127. package/src/server/conversation/openCodeProcessOwner.js +565 -0
  128. package/src/server/conversation/openCodeRuntime.js +111 -1814
  129. package/src/server/conversation/openCodeSessionTurns.js +547 -0
  130. package/src/server/conversation/openCodeTemporaryConversations.js +394 -0
  131. package/src/server/conversation/openCodeTools.js +28 -14
  132. package/src/server/conversation/openCodeTurn.js +80 -74
  133. package/src/server/conversation/openCodeTurnMonitor.js +388 -0
  134. package/src/server/conversation/processIdentity.js +582 -0
  135. package/src/server/conversation/provider.js +21 -6
  136. package/src/server/conversation/providers/api.js +268 -107
  137. package/src/server/conversation/providers/claude.js +29 -6
  138. package/src/server/conversation/providers/claudeDriver.js +325 -250
  139. package/src/server/conversation/providers/codex.js +69 -33
  140. package/src/server/conversation/providers/codexDriver.js +503 -1081
  141. package/src/server/conversation/providers/codexDriverConnection.js +331 -0
  142. package/src/server/conversation/providers/codexDriverTurns.js +464 -0
  143. package/src/server/conversation/providers/opencode.js +30 -4
  144. package/src/server/conversation/providers/opencodeDriver.js +321 -659
  145. package/src/server/conversation/providers/opencodeStandalone.js +475 -0
  146. package/src/server/conversation/providers/scoped.js +64 -19
  147. package/src/server/conversation/runtime.js +171 -2863
  148. package/src/server/conversation/storage.js +91 -50
  149. package/src/server/conversation/streams.js +15 -2
  150. package/src/server/conversation/structuredOutput.js +7 -11
  151. package/src/server/conversation/systemPrompt.js +13 -1
  152. package/src/server/conversation/tools.js +47 -15
  153. package/src/server/conversation/transcript.js +49 -20
  154. package/src/server/hosts/claudeTurn.js +2 -4
  155. package/src/server/hosts/codexConfiguration.js +6 -8
  156. package/src/server/hosts/codexEvents.js +4 -2
  157. package/src/server/hosts/codexProcess.js +1 -1
  158. package/src/server/hosts/codexProvider.js +11 -9
  159. package/src/server/hosts/codexTurn.js +3 -3
  160. package/src/server/hosts/openCodeClient.js +4 -2
  161. package/src/server/index.js +0 -2
  162. package/src/server/lib/aiConnectionClient.js +35 -25
  163. package/src/server/lib/ndjson.js +1 -0
  164. package/src/server/lib/providers/anthropicClient.js +15 -6
  165. package/src/server/lib/providers/common.js +10 -6
  166. package/src/server/lib/providers/openAiCompatibleClient.js +40 -23
  167. package/src/server/lib/serviceToolCatalog.js +29 -283
  168. package/src/server/lib/serviceToolEntries.js +270 -0
  169. package/src/server/types.js +78 -0
  170. package/src/shared/assistantResource.js +0 -23
  171. package/src/shared/conversation/answerChoiceSugar.js +2 -14
  172. package/src/shared/conversation/messageInputSugar.js +18 -0
  173. package/src/shared/conversation/numberedQuestionSugar.js +6 -16
  174. package/src/shared/conversation/pagination.js +1 -1
  175. package/src/shared/conversation/questions.js +1 -0
  176. package/src/shared/conversation/streaming.js +1 -0
  177. package/src/shared/index.js +0 -1
  178. package/src/testing/conversationStorageContract.js +20 -0
  179. package/test/aiConnectionClient.test.js +42 -1
  180. package/test/apiConversationDriver.test.js +217 -38
  181. package/test/assistantBoundedToolLoop.test.js +1 -1
  182. package/test/claudeConversationSession.test.js +15 -11
  183. package/test/claudeInstructionResume.test.js +1 -1
  184. package/test/claudeProcess.test.js +9 -9
  185. package/test/claudeTurn.test.js +21 -8
  186. package/test/codexAccountChange.test.js +5 -3
  187. package/test/codexConversationSession.test.js +22 -21
  188. package/test/codexEvents.test.js +3 -1
  189. package/test/codexHelperDeletion.test.js +3 -7
  190. package/test/codexIsolation.test.js +6 -6
  191. package/test/codexModelCatalog.test.js +6 -7
  192. package/test/codexProcess.test.js +3 -3
  193. package/test/codexThreadSelection.test.js +4 -1
  194. package/test/codexTurn.test.js +29 -20
  195. package/test/conversationApiFacilities.test.js +11 -9
  196. package/test/conversationAttachments.test.js +3 -3
  197. package/test/conversationConfiguration.test.js +4 -4
  198. package/test/conversationNativeStorage.test.js +11 -10
  199. package/test/conversationReplacement.test.js +13 -13
  200. package/test/conversationRuntime.test.js +9 -5
  201. package/test/conversationSession.test.js +78 -22
  202. package/test/conversationTools.test.js +47 -26
  203. package/test/fileConversationStorage.test.js +10 -3
  204. package/test/fixtures/codexHelperThreadLedger.js +3 -3
  205. package/test/frameworkActions.test.js +33 -18
  206. package/test/localConversationExecution.test.js +4 -4
  207. package/test/localExecution.test.js +1 -1
  208. package/test/openCodeConnectionChange.test.js +3 -2
  209. package/test/openCodeRuntime.test.js +8 -7
  210. package/test/openCodeTools.test.js +1 -1
  211. package/test/opencodeConversationSession.test.js +4 -3
  212. package/test/serviceToolCatalog.test.js +3 -8
  213. package/src/server/lib/assistantCompletion.js +0 -190
  214. 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 startThread = CodexAppServerAgentProvider.prototype.startThread;
1502
- t.mock.method(CodexAppServerAgentProvider.prototype, "startThread", async function (...args) {
1503
- const thread = await startThread.apply(this, args);
1504
- providers.set(thread.id, this);
1505
- return thread;
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 startThread = CodexAppServerAgentProvider.prototype.startThread;
1658
- t.mock.method(CodexAppServerAgentProvider.prototype, "startThread", async function (...args) {
1659
- source = this;
1660
- return startThread.apply(this, args);
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"] }, idempotency: "none",
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: "conversation_unsupported" });
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
- // Bound-host contract additions retain the original native app-server fixture,
2671
- // schemas, tool-call framing, transcript store and run owner. Public Main's
2672
- // identity/authority/readiness composition is a separate adoption prerequisite.
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/codexProvider.js";
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 startThread = CodexAppServerAgentProvider.prototype.startThread;
2680
- t.mock.method(CodexAppServerAgentProvider.prototype, "startThread", async function (...args) {
2681
- sharedProvider ||= this;
2682
- return startThread.apply(this, args);
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: "conversation_closed" });
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,
@@ -1,6 +1,8 @@
1
1
  import assert from "node:assert/strict";
2
2
  import test from "node:test";
3
- import { codexAppServerNotificationError, codexAppServerProviderThreadAssistantSegments, codexAppServerProviderTurnError } from "../src/server/conversation/codexEvents.js";
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
- // The original Public lifecycle tests retain durable receipts, authority and
14
- // retry assertions. These cases cover the native protocol branch moved here.
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/codexConfiguration.js";
7
+ } from "../src/server/conversation/codexIsolation.js";
8
8
 
9
- // Carried from the original public helper bridge tests. Public profile, schema,
10
- // prompt and request-settings assertions remain in that unchanged test file.
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 original application's prepare callback now supplies already authorized
108
- // policy values. These fixtures exercise only the native ownership boundary.
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
- // Supplementary ownership-boundary checks, alongside the original assertions.
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
- // These two cases carry the original public lifecycle assertions. Their public
115
- // counterparts retain host acquisition, restoration and application integration.
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
- // Carried from the original public chat catalog tests. Only the application
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/codexProcess.js";
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/codexProvider.js";
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"); },
@@ -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, CodexAppServerAgentProvider, codexAppServerDeliveredTurn } from "../src/server/conversation/codexProvider.js";
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
- // Original stable-outer-turn source assertions follow their native owners.
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
- // The original public renewal tests retain the application retry, accepted
38
- // handover, saved identity and structured acknowledgement assertions. These
39
- // cases exercise the native wait sequence carried from that implementation.
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
- // Original start/steer assertions retain the public admission and Git fixtures.
1490
- // These bounded cases cover only the moved dispatch/receipt boundary and the
1491
- // ownership adaptations: supplied settings, Error identity and result projection.
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
- return { enabled: true, defaultModel: "existing-model", async createChatCompletionStream(input) {
40
- requests.push(structuredClone(input.messages));
41
- return (async function* () {
42
- yield { choices: [{ delta: { content: " A complete answer. " } }] };
43
- yield { choices: [{ delta: {}, finish_reason: "stop" }] };
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, " A complete answer. ", "The default API whitespace policy stays unchanged");
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.deepEqual(requests[2].map(message => message.content), ["Existing instructions", "No prior model context"]);
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/codexProvider.js";
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: "conversation_message_conflict" });
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: "conversation_unsupported" });
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: "conversation_replacement_conflict" });
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: "conversation_replacement_conflict" });
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: "conversation_replacement_pending" });
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: "conversation_replacement_conflict" });
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, openCodeServerForDirectory } from "../src/server/conversation/openCodeRuntime.js";
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 { CodexAppServerAgentProvider } = await import("../src/server/conversation/codexProvider.js");
278
- assert.equal(await CodexAppServerAgentProvider.prototype.nativeThreadExists.call(inventory, "wanted"), true);
279
- assert.equal(await CodexAppServerAgentProvider.prototype.nativeThreadExists.call(inventory, "missing"), false);
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 { CodexAppServerAgentProvider } = await import("../src/server/conversation/codexProvider.js");
287
- assert.equal(await CodexAppServerAgentProvider.prototype.nativeThreadExists.call(inventory, "thread-3-7"), true);
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 CodexAppServerAgentProvider.prototype.nativeThreadExists.call(inventory, "thread-12-0"), false);
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 { CodexAppServerAgentProvider } = await import("../src/server/conversation/codexProvider.js");
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(CodexAppServerAgentProvider.prototype.listNativeThreadsForCwd.call(outside.inventory, "/source"), (error) => {
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(CodexAppServerAgentProvider.prototype.listNativeThreadsForCwd.call(unnamed.inventory, "/source"),
302
+ await assert.rejects(createCodexProviderInventories({ provider: unnamed.inventory }).listNativeThreadsForCwd("/source"),
302
303
  /listed a thread without an id/u);
303
304
  });