@jskit-ai/assistant-core 0.1.213 → 0.1.215

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (59) hide show
  1. package/examples/conversation/package.json +5 -5
  2. package/fixtures/responsive-assistant/ConversationFixture.vue +7 -1
  3. package/package.json +5 -5
  4. package/src/client/conversation/AssistantConversationElement.vue +5 -1
  5. package/src/client/conversation/AssistantTranscript.vue +10 -1
  6. package/src/server/conversation/agentRun.js +38 -11
  7. package/src/server/conversation/claudeHistory.js +40 -5
  8. package/src/server/conversation/claudeProcess.js +16 -2
  9. package/src/server/conversation/claudeTurn.js +75 -16
  10. package/src/server/conversation/codexConfiguration.js +1 -1
  11. package/src/server/conversation/codexHistoryAdapter.js +11 -4
  12. package/src/server/conversation/codexNotificationQueue.js +11 -2
  13. package/src/server/conversation/codexObservationControl.js +8 -8
  14. package/src/server/conversation/codexOutput.js +20 -15
  15. package/src/server/conversation/codexProvider.js +40 -3
  16. package/src/server/conversation/codexProviderOwner.js +110 -1
  17. package/src/server/conversation/codexTurn.js +3 -0
  18. package/src/server/conversation/continuity.js +24 -6
  19. package/src/server/conversation/index.js +1 -1
  20. package/src/server/conversation/nativeHistoryExport.js +97 -0
  21. package/src/server/conversation/openCodeClient.js +47 -1
  22. package/src/server/conversation/openCodeRuntime.js +132 -24
  23. package/src/server/conversation/openCodeTurn.js +65 -24
  24. package/src/server/conversation/providers/api.js +7 -0
  25. package/src/server/conversation/providers/claudeDriver.js +52 -7
  26. package/src/server/conversation/providers/codex.js +19 -2
  27. package/src/server/conversation/providers/codexDriver.js +92 -14
  28. package/src/server/conversation/providers/opencodeDriver.js +31 -9
  29. package/src/server/conversation/runtime.js +418 -64
  30. package/src/server/conversation/runtimeStateUpgrade.js +77 -2
  31. package/src/server/conversation/streams.js +25 -5
  32. package/src/server/conversation/tools.js +2 -2
  33. package/src/server/conversation/transcript.js +23 -8
  34. package/src/server/hosts/claudeHistory.js +1 -0
  35. package/src/server/hosts/claudeProcess.js +1 -0
  36. package/src/server/hosts/codexProvider.js +3 -0
  37. package/src/server/hosts/nativeHistory.js +1 -0
  38. package/src/server/hosts/openCodeClient.js +1 -0
  39. package/src/server/hosts/openCodeTurn.js +1 -0
  40. package/src/server/index.js +1 -1
  41. package/src/server/lib/assistantToolLoop.js +99 -2
  42. package/test/aiConnectionClient.test.js +57 -0
  43. package/test/assistantBoundedToolLoop.test.js +332 -0
  44. package/test/assistantScroll.browser.test.js +151 -0
  45. package/test/claudeConversationSession.test.js +1329 -3
  46. package/test/claudeProcess.test.js +101 -0
  47. package/test/claudeTurn.test.js +236 -1
  48. package/test/codexConversationSession.test.js +1257 -12
  49. package/test/codexHistoryAdapter.test.js +74 -1
  50. package/test/codexTurn.test.js +282 -1
  51. package/test/conversation.test.js +83 -0
  52. package/test/conversationAgentRun.test.js +181 -0
  53. package/test/conversationRuntime.test.js +95 -0
  54. package/test/conversationRuntimeStateUpgrade.test.js +89 -0
  55. package/test/conversationStreaming.test.js +85 -0
  56. package/test/conversationTools.test.js +112 -1
  57. package/test/openCodeClient.test.js +186 -0
  58. package/test/openCodeRuntime.test.js +78 -0
  59. package/test/opencodeConversationSession.test.js +637 -5
@@ -5,18 +5,20 @@ import { runScopedConversationTurn, readPersistentConversation, startPersistentC
5
5
  inspectPersistentConversationDelivery, stopPersistentConversation, deletePersistentConversation } from "./providers/scoped.js";
6
6
  import { isDeepStrictEqual } from "node:util";
7
7
  import { randomUUID } from "node:crypto";
8
- import { createConversationTranscript } from "./transcript.js";
8
+ import { createConversationTranscript, isCompletedEnvelopeTurn } from "./transcript.js";
9
9
  import { createConversationStreams } from "./streams.js";
10
10
  import { createReentrantConversationStorage } from "./storage.js";
11
11
  import { createCodexConversationStore } from "./agentRun.js";
12
12
  import { codexAppServerTurnStateFromAgentRun } from "./codexTurn.js";
13
- import { hasUnfinishedConversationRewind } from "./runtimeStateUpgrade.js";
13
+ import { hasUnfinishedConversationRewind, createConversationRuntimeReplacement,
14
+ releaseConversationRuntimeBinding, finishConversationRuntimeReplacement } from "./runtimeStateUpgrade.js";
14
15
  import { createServiceToolCatalog } from "../lib/serviceToolCatalog.js";
15
16
  import { createConversationTools } from "./tools.js";
17
+ import { readAssistantResponseEnvelope } from "../lib/assistantToolLoop.js";
16
18
  import { conversationAttachmentIds, createConversationAttachmentReader } from "./attachments.js";
17
19
  import { conversationAttachmentManifest } from "../../shared/conversation/attachments.js";
18
20
  import { createConversationChangeover, conversationContinuity, conversationHistoryVersions,
19
- conversationNativeMessages, conversationRequestText } from "./continuity.js";
21
+ conversationMessageIdentity, conversationMessageVersion, conversationNativeMessages, conversationRequestText } from "./continuity.js";
20
22
 
21
23
  function failure(message, code, statusCode = 409) {
22
24
  return Object.assign(new Error(message), { code, statusCode });
@@ -34,8 +36,10 @@ const sameRequest = (left, right) => left.text === right.text && (left.origin ||
34
36
 
35
37
  /** One application conversation API. Storage and authorization remain host facilities. */
36
38
  export function createConversationRuntime({ engine: defaultEngine = "api", defaultIntegrationId, storage, authorize, connections, apiClientFactory, apiHistory,
37
- actions, toolPolicy, toolCatalog, attachments, fetch, host: defaultHost, limits = {} } = {}) {
39
+ actions, toolPolicy, toolCatalog, attachments, fetch, host: defaultHost, persistCommentary = true, completedEnvelope = false, limits = {} } = {}) {
38
40
  if (typeof authorize !== "function") throw new TypeError("Conversation access requires an authorize function.");
41
+ if (typeof completedEnvelope !== "boolean") throw new TypeError("Completed envelope policy requires a server-side boolean.");
42
+ if (typeof persistCommentary !== "boolean") throw new TypeError("Conversation commentary persistence requires an explicit server-side boolean.");
39
43
  if (apiHistory !== undefined && typeof apiHistory !== "function") throw new TypeError("API request history requires a server-owned selector.");
40
44
  if (toolCatalog && (actions || toolPolicy)) throw new TypeError("Supply the existing tool catalog or actions and toolPolicy, not both.");
41
45
  storage = (storage || !defaultHost?.conversation) ? createReentrantConversationStorage(storage) : null;
@@ -46,6 +50,7 @@ export function createConversationRuntime({ engine: defaultEngine = "api", defau
46
50
  const providers = createConversationProviderFactory({ connections, apiClientFactory, fetch, limits, actions, toolCatalog });
47
51
  const { createDriver } = providers;
48
52
  const catalog = toolCatalog || (actions ? createServiceToolCatalog(actions, { ...limits, isActionAvailable: toolPolicy }) : null);
53
+ if (completedEnvelope && !catalog) throw new TypeError("Completed envelopes require an application tool catalogue.");
49
54
  const maximumToolCalls = limits.maxToolCalls ?? 32;
50
55
  if (!Number.isSafeInteger(maximumToolCalls) || maximumToolCalls < 1) throw new TypeError("Invalid application tool-call limit.");
51
56
  const maximumInput = limits.maxInputCharacters ?? 32_000;
@@ -54,6 +59,10 @@ export function createConversationRuntime({ engine: defaultEngine = "api", defau
54
59
  if (limits.maxFinalReplyCharacters !== undefined && (!Number.isSafeInteger(maximumFinalReply) || maximumFinalReply < 1)) {
55
60
  throw new TypeError("Invalid conversation final reply limit.");
56
61
  }
62
+ const maximumCompletedOutput = limits.maxOutputCharacters ?? 64_000;
63
+ if (completedEnvelope && (!Number.isSafeInteger(maximumCompletedOutput) || maximumCompletedOutput < 1)) {
64
+ throw new TypeError("Invalid completed envelope output limit.");
65
+ }
57
66
  const maximumContinuity = limits.maxContinuityCharacters ?? 128_000;
58
67
  if (!Number.isSafeInteger(maximumContinuity) || maximumContinuity < 1) throw new TypeError("Invalid conversation continuity limit.");
59
68
  const maximumAttachmentBytes = limits.maxAttachmentBytes ?? 8 * 1024 * 1024;
@@ -86,12 +95,28 @@ export function createConversationRuntime({ engine: defaultEngine = "api", defau
86
95
  return operation;
87
96
  }
88
97
 
89
- async function emit(entry, event) {
98
+ async function emit(entry, event, isCurrent) {
99
+ const { completedEnvelope: _untrustedMarker, ...payload } = event;
100
+ if (completedEnvelope && !entry.conversation) {
101
+ const request = payload.messageId && entry.active?.requests.get(payload.messageId) ||
102
+ payload.turnId && [...(entry.active?.requests.values() || [])].find(request => request.turnId === payload.turnId);
103
+ let marked = isCompletedEnvelopeTurn(payload.patch?.turn) ||
104
+ request?.completedEnvelope === true && request.input.origin === "application";
105
+ if (!marked && payload.turnId) {
106
+ try { marked = await storage.read(entry.id, async transaction => isCompletedEnvelopeTurn(await transaction.readTurn(payload.turnId))); }
107
+ catch { /* An unavailable receipt cannot establish presentation custody. */ }
108
+ }
109
+ if (!marked && payload.type === "message") marked =
110
+ [...(payload.streaming?.messages || []), ...(payload.streaming?.completedMessages || [])]
111
+ .some(message => message.messageId === payload.messageId && message.completedEnvelope === true && message.origin === "application");
112
+ if (marked) payload.completedEnvelope = true;
113
+ }
90
114
  await Promise.all([...entry.listeners].map(async subscription => {
91
115
  try { await access(subscription.context, entry.id, "subscribe", entry); }
92
116
  catch { entry.listeners.delete(subscription); return; }
117
+ if (isCurrent && !isCurrent()) return;
93
118
  // Presentation failures cannot interrupt persistence or provider work.
94
- try { Promise.resolve(subscription.listener(structuredClone({ conversationId: entry.id, ...event }))).catch(() => {}); }
119
+ try { Promise.resolve(subscription.listener(structuredClone({ conversationId: entry.id, ...payload }))).catch(() => {}); }
95
120
  catch { /* A failed observer does not own the conversation. */ }
96
121
  }));
97
122
  }
@@ -107,7 +132,7 @@ export function createConversationRuntime({ engine: defaultEngine = "api", defau
107
132
  ? entry.conversation.transcript.readConversationLog() : transcript.readConversationLog(entry.id);
108
133
 
109
134
  function readTranscriptPage(entry, query) {
110
- if (!entry.conversation) return transcript.readConversationLogPage(entry.id, query);
135
+ if (!entry.conversation) return transcript.readConversationLogPage(entry.id, { ...query, presentation: completedEnvelope });
111
136
  if (typeof entry.conversation.transcript.readConversationLogPage !== "function") {
112
137
  throw failure("This conversation does not supply paged history.", "conversation_unsupported", 400);
113
138
  }
@@ -150,16 +175,20 @@ export function createConversationRuntime({ engine: defaultEngine = "api", defau
150
175
  if (representation === "native") return metadata.runtime.nativeResult;
151
176
  const page = logical ? logical.pagination ? { conversationLog: logical.conversationLog, pagination: logical.pagination } : null
152
177
  : query ? await readTranscriptPage(entry, query) : null;
153
- const conversationLog = logical ? logical.conversationLog : page ? page.conversationLog : await readTranscript(entry);
178
+ const conversationLog = logical ? logical.conversationLog : page ? page.conversationLog
179
+ : completedEnvelope ? await transcript.readConversationLog(entry.id, { presentation: true }) : await readTranscript(entry);
154
180
  // Browsing an older page does not change the current turn's control/error state.
155
- const latest = !logical && query?.beforeTurnId ? (await readTranscriptPage(entry, { limit: 1 })).conversationLog : conversationLog;
181
+ const latest = !logical && (completedEnvelope || query?.beforeTurnId)
182
+ ? entry.conversation ? (await readTranscriptPage(entry, { limit: 1 })).conversationLog
183
+ : (await transcript.readConversationLogPage(entry.id, { limit: 1 })).conversationLog : conversationLog;
156
184
  const last = latest.at(-1)?.metadata?.runtime;
157
185
  const savedRequest = metadata.runtime.request;
158
186
  const nativeTurn = metadata.runtime.nativeTurn || (entry.driver.canonicalTranscript && metadata.runtime.binding?.codexAppServerRun
159
187
  ? codexAppServerTurnStateFromAgentRun(metadata.runtime.binding.codexAppServerRun) : null);
160
188
  const request = savedRequest && (entry.engine === "api" || savedRequest.attempted || savedRequest.inspectionOnly ||
161
189
  entry.active?.requests.has(savedRequest.messageId)) ? savedRequest : null;
162
- const pendingRequest = request ? Object.fromEntries([
190
+ const streaming = entry.conversation ? await entry.conversation.readStream() : streams.read(entry.id);
191
+ const pendingRequest = request && !(completedEnvelope && request.origin === "application" && request.completedEnvelope === true) ? Object.fromEntries([
163
192
  "messageId", "text", "origin", "data", "attachments", "at", "goal", "steering", "error"
164
193
  ].filter(name => Object.hasOwn(request, name)).map(name => [name, request[name]])) : null;
165
194
  return {
@@ -176,13 +205,19 @@ export function createConversationRuntime({ engine: defaultEngine = "api", defau
176
205
  ...(logical?.presentation ? { presentation: logical.presentation } : {}),
177
206
  conversationLog: conversationLog.filter(turn => !turn.metadata?.runtime?.supersededBy),
178
207
  ...(page ? { pagination: page.pagination } : {}),
179
- streaming: entry.conversation ? await entry.conversation.readStream() : streams.read(entry.id)
208
+ streaming: completedEnvelope && !entry.conversation ? { ...streaming,
209
+ messages: streaming.messages.filter(message => !(message.origin === "application" && message.completedEnvelope === true)) } : streaming
180
210
  };
181
211
  }
182
212
 
183
213
  async function publish(entry, event) {
184
214
  if (entry.disposed) return;
185
- if (entry.conversation?.commands && ["phase", "configuration"].includes(event.type)) {
215
+ const publicationOwner = entry.bindingOwner;
216
+ const publicationSegment = entry.segmentId;
217
+ const publicationActive = entry.active;
218
+ // A native-backed bound conversation also uses configuration as a browser
219
+ // invalidation. It has no command facade, but owns the same subscriptions.
220
+ if (entry.conversation && (event.type === "configuration" || entry.conversation.commands && event.type === "phase")) {
186
221
  return emit(entry, event);
187
222
  }
188
223
  const payload = event.payload || {};
@@ -211,6 +246,19 @@ export function createConversationRuntime({ engine: defaultEngine = "api", defau
211
246
  }
212
247
  if (payload.conversationStream) {
213
248
  const snapshot = payload.conversationStream;
249
+ for (const message of snapshot.completedMessages || []) {
250
+ if (turn?.messages?.some(saved => saved.messageId === message.messageId ||
251
+ (message.outputId && saved.outputId === message.outputId))) continue;
252
+ const request = publicationActive && [...publicationActive.requests.values()].find(request => request.turnId === message.turnId);
253
+ const isCurrent = () => !entry.disposed && !request?.sealed && entry.active === publicationActive &&
254
+ entry.bindingOwner === publicationOwner && entry.segmentId === publicationSegment;
255
+ if (!request || !isCurrent()) continue;
256
+ if (!request.admissionPublished) {
257
+ request.progress.set(message.messageId, message);
258
+ continue;
259
+ }
260
+ await emit(entry, { type: "message", ...message, streaming: snapshot }, isCurrent);
261
+ }
214
262
  for (const message of snapshot.messages) {
215
263
  await emit(entry, { type: "message", ...message, streaming: snapshot });
216
264
  }
@@ -240,6 +288,7 @@ export function createConversationRuntime({ engine: defaultEngine = "api", defau
240
288
  conversation = {
241
289
  ...entry.conversation,
242
290
  sessionId: entry.id,
291
+ persistCommentary,
243
292
  runtime: entry.conversation?.runtime || { store, getSession: store.getSession },
244
293
  publish: event => isCurrent() ? publish(entry, event) : undefined,
245
294
  checkpoint: entry.conversation?.checkpoint || (input => isCurrent() ? entry.active?.nativeCheckpoint?.(input) : null)
@@ -339,9 +388,16 @@ export function createConversationRuntime({ engine: defaultEngine = "api", defau
339
388
 
340
389
  function nativeChangeover(entry, context, request, sendMessage) {
341
390
  let observation;
391
+ const initialWindow = limits.maxInitialNativeHistoryMessages;
392
+ if (initialWindow !== undefined && (!Number.isSafeInteger(initialWindow) || initialWindow < 0)) {
393
+ throw new TypeError("Initial native history requires a non-negative message limit.");
394
+ }
342
395
  return createConversationChangeover({
343
396
  state: entry.conversation?.state || changeoverState(entry, request),
344
397
  captureContext: !entry.conversation, applicationMessages: !entry.conversation,
398
+ maximumInitialMessages: initialWindow === undefined ? undefined
399
+ : Math.max(0, initialWindow - (request?.input.origin === "user" ? 1 : 0)),
400
+ maximumInitialMessageCharacters: limits.maxInitialNativeHistoryMessageCharacters,
345
401
  presentation: entry.conversation?.presentation || { unconfirmedCode: "conversation_delivery_uncertain",
346
402
  unconfirmedMessage: "The previous submission has uncertain delivery. Retry this message to check its native receipt; it will not be resent automatically." },
347
403
  transcript: entry.conversation?.transcript || {
@@ -397,6 +453,9 @@ export function createConversationRuntime({ engine: defaultEngine = "api", defau
397
453
  }
398
454
  const metadata = await transaction.readMetadata();
399
455
  entry.engine = metadata.runtime?.engine || requestedEngine || defaultEngine;
456
+ if (completedEnvelope && !["codex", "claude", "opencode"].includes(entry.engine)) {
457
+ throw failure("Completed envelopes require a runtime-owned native conversation.", "conversation_unsupported", 400);
458
+ }
400
459
  if (configuration) configuration = normalizeConversationConfiguration(configuration, { engine: entry.engine, defaultIntegrationId });
401
460
  const driver = entry.driver = createDriver(entry.engine, entry.host);
402
461
  if (metadata.runtime) {
@@ -448,8 +507,207 @@ export function createConversationRuntime({ engine: defaultEngine = "api", defau
448
507
  return { storage: createReentrantConversationStorage(facilities.storage), prepareContext: facilities.prepareContext };
449
508
  }
450
509
 
510
+ async function saveTools(entry, transaction, request, calls) {
511
+ if (entry.conversation) {
512
+ const saved = await transaction.readTurn(request.turnId);
513
+ if (submittedMessage(saved || {})?.messageId !== request.input.messageId) {
514
+ throw new Error("The application tool receipt does not belong to this authored request.");
515
+ }
516
+ }
517
+ await transaction.updateTurnMetadata(request.turnId, { applicationTools: calls });
518
+ }
519
+
520
+
521
+ async function prepareCompletedResponse(entry, context, { messageId, turnId } = {}, { signal, toolSet } = {}) {
522
+ if (!completedEnvelope || entry.conversation || !["codex", "claude", "opencode"].includes(entry.engine)) {
523
+ throw failure("This conversation does not own completed envelopes.", "conversation_unsupported", 400);
524
+ }
525
+ if (typeof messageId !== "string" || !messageId || typeof turnId !== "string" || !turnId || !(signal instanceof AbortSignal)) {
526
+ throw new TypeError("Completed responses require their exact receipt and the application worker signal.");
527
+ }
528
+ signal.throwIfAborted();
529
+ await access(context, entry.id, "tool", entry);
530
+ const owner = entry.bindingOwner;
531
+ const segmentId = entry.segmentId;
532
+ const engine = entry.engine;
533
+ const toolCallId = `${messageId}:operation`;
534
+ if (toolCallId.length > 256) throw new TypeError("The completed response call identity exceeds its limit.");
535
+ let captured;
536
+
537
+ async function exact(transaction, { retry = false } = {}) {
538
+ if (closed || entry.disposed || entry.bindingOwner !== owner || entry.segmentId !== segmentId || entry.engine !== engine) {
539
+ throw failure("The completed response owner changed.", "conversation_tool_request_retired");
540
+ }
541
+ if (entry.active) throw failure("Wait for the native response to finish.", "conversation_busy");
542
+ if (entry.executionFailure) throw entry.executionFailure;
543
+ if (entry.storageFailure && !retry) throw entry.storageFailure;
544
+ const { runtime } = await transaction.readMetadata();
545
+ const turn = await transaction.readTurn(turnId);
546
+ const authored = submittedMessage(turn || {});
547
+ const saved = turn?.metadata?.runtime;
548
+ if (runtime?.version !== 3 || runtime.engine !== engine || runtime.segmentId !== segmentId ||
549
+ runtime.replacement || runtime.request || !nativeIdentity(runtime) || authored?.messageId !== messageId ||
550
+ authored.origin !== "application" || saved?.completedEnvelope !== true || saved.engine !== engine ||
551
+ saved.segmentId !== segmentId || saved.status !== "complete" || saved.supersededBy ||
552
+ typeof turn.assistant?.text !== "string" || !turn.assistant.text || !turn.assistant.messageId) {
553
+ throw failure("The completed response has no current verified receipt.", "conversation_completed_response_unavailable");
554
+ }
555
+ const turnIds = await transaction.listTurnIds();
556
+ for (const id of turnIds.slice(turnIds.indexOf(turnId) + 1)) {
557
+ const later = await transaction.readTurn(id);
558
+ if (later.metadata?.runtime?.segmentId === segmentId && later.metadata.runtime.completedEnvelope === true) {
559
+ throw failure("A newer completed response owns this worker.", "conversation_tool_request_retired");
560
+ }
561
+ }
562
+ // These verified process references may refresh on cold inspection;
563
+ // all native conversation, path and account custody remains exact.
564
+ const { codexAppServerRun: run, executionId, processDirectory, ...binding } = runtime.binding;
565
+ if (engine === "codex" && (!saved.nativeTurnId || run?.outerTurnId !== messageId ||
566
+ run.providerThreadId !== binding.threadId || run.providerTurnId !== saved.nativeTurnId || run.active || run.state !== "completed")) {
567
+ throw failure("The completed response has no exact native turn receipt.", "conversation_completed_response_unavailable");
568
+ }
569
+ if (engine === "claude" && typeof saved.nativeToolAttempt !== "boolean") {
570
+ throw failure("The completed Claude response has no native tool-attempt evidence.", "conversation_completed_response_unavailable");
571
+ }
572
+ const calls = turn.metadata.applicationTools || [];
573
+ if (!Array.isArray(calls) || calls.length > 1 || calls.some(call => call.id !== toolCallId)) {
574
+ throw failure("This response already belongs to a different application operation.", "conversation_tool_receipt_conflict");
575
+ }
576
+ const identity = { configuration: runtime.configuration, binding, assistant: turn.assistant,
577
+ nativeTurnId: saved.nativeTurnId, ...(engine === "claude" ? { nativeToolAttempt: saved.nativeToolAttempt } : {}) };
578
+ if (captured && !isDeepStrictEqual(captured, identity)) {
579
+ throw failure("The completed response identity changed.", "conversation_tool_request_retired");
580
+ }
581
+ return { runtime, turn, calls, identity };
582
+ }
583
+
584
+ async function inspect() {
585
+ signal.throwIfAborted();
586
+ await access(context, entry.id, "tool", entry);
587
+ const saved = await storage.read(entry.id, transaction => exact(transaction));
588
+ const observation = await entry.provider.inspectAdmission({ messageId,
589
+ threadId: nativeIdentity(saved.runtime), nativeTurnId: saved.turn.metadata.runtime.nativeTurnId,
590
+ configuration: saved.runtime.configuration, receiptBinding: saved.runtime.binding, context, verifyAccount: true });
591
+ signal.throwIfAborted();
592
+ await access(context, entry.id, "tool", entry);
593
+ if (!observation?.accepted || observation.recoveryLimitation || observation.conversationTurn &&
594
+ observation.conversationTurn.turnId !== turnId || engine === "codex" && observation.nativeTurnId !== saved.turn.metadata.runtime.nativeTurnId) {
595
+ throw failure("The native owner could not verify this response association.", "conversation_completed_response_unavailable");
596
+ }
597
+ // Canonical successful completion owns the exact final, including a
598
+ // StructuredOutput carrier whose native history may contain only commentary.
599
+ captured ||= structuredClone(saved.identity);
600
+ return storage.read(entry.id, transaction => exact(transaction));
601
+ }
602
+
603
+ const response = await inspect();
604
+ return { text: response.turn.assistant.text, toolCallId,
605
+ ...(engine === "claude" ? { nativeToolAttempt: response.turn.metadata.runtime.nativeToolAttempt } : {}),
606
+ publishReply(input) {
607
+ return serial(entry, async () => {
608
+ if (!input || typeof input !== "object" || Array.isArray(input) || Object.keys(input).some(key => key !== "turnId") ||
609
+ typeof input.turnId !== "string" || !input.turnId || input.turnId !== input.turnId.trim() || input.turnId.length > 128) {
610
+ throw new TypeError("Reply publication requires only its exact existing plain turn id.");
611
+ }
612
+ await inspect();
613
+ return storage.write(entry.id, async transaction => {
614
+ const saved = await exact(transaction);
615
+ signal.throwIfAborted();
616
+ const decoded = readAssistantResponseEnvelope(saved.turn.assistant.text);
617
+ if (decoded.kind !== "reply" || saved.turn.metadata.runtime.nativeToolAttempt === true || saved.calls.length) {
618
+ throw failure("This completed response does not own a plain reply.", "conversation_tool_receipt_conflict");
619
+ }
620
+ const text = decoded.text.trim();
621
+ const target = await transaction.readTurn(input.turnId);
622
+ if (!text || input.turnId === turnId || !target || isCompletedEnvelopeTurn(target) ||
623
+ target.metadata?.runtime?.engine || !(target.user || target.system) || saved.runtime.lastEngine !== engine ||
624
+ !saved.runtime.seen || typeof saved.runtime.seen !== "object" || Array.isArray(saved.runtime.seen)) {
625
+ throw failure("The completed reply has no current plain authored target.", "conversation_tool_receipt_conflict");
626
+ }
627
+ const assistantId = conversationMessageIdentity(input.turnId, { role: "assistant" });
628
+ const published = saved.turn.metadata.runtime.publishedReplyTurnId;
629
+ if (published !== undefined) {
630
+ if (published !== input.turnId || !target.assistant || target.assistant.text !== text ||
631
+ saved.runtime.seen[assistantId] !== conversationMessageVersion(target.assistant, { includeData: true })) {
632
+ throw failure("The published reply target or text changed.", "conversation_tool_receipt_conflict");
633
+ }
634
+ signal.throwIfAborted();
635
+ return target;
636
+ }
637
+ if (target.assistant || Object.hasOwn(saved.runtime.seen, assistantId)) {
638
+ throw failure("This plain reply target already has unassociated content or consumption.", "conversation_tool_receipt_conflict");
639
+ }
640
+ // The runtime transcript uses this same reentrant wrapper: its
641
+ // original upsert joins this draft rather than starting a second write.
642
+ const plain = await transcript.upsertConversationAssistantMessage(entry.id, { turnId: input.turnId, text });
643
+ saved.runtime.seen[assistantId] = conversationMessageVersion(plain.assistant, { includeData: true });
644
+ await transaction.updateTurnMetadata(turnId, { runtime: { ...saved.turn.metadata.runtime, publishedReplyTurnId: input.turnId } });
645
+ const metadata = await transaction.readMetadata();
646
+ await transaction.writeMetadata({ ...metadata, runtime: saved.runtime });
647
+ await exact(transaction);
648
+ signal.throwIfAborted();
649
+ return plain;
650
+ });
651
+ });
652
+ },
653
+ tools: { execute(input, options = {}) {
654
+ return serial(entry, async () => {
655
+ if (input?.id !== toolCallId) throw failure("The operation has a different completed response identity.", "conversation_tool_receipt_conflict");
656
+ const saved = await inspect();
657
+ if (saved.turn.metadata.runtime.publishedReplyTurnId !== undefined) {
658
+ throw failure("This completed response already published its plain reply.", "conversation_tool_receipt_conflict");
659
+ }
660
+ const workerSignal = options.signal && options.signal !== signal ? AbortSignal.any([signal, options.signal]) : signal;
661
+ workerSignal.throwIfAborted();
662
+ const request = { turnId, input: { messageId } };
663
+ async function persist(calls, retry = false) {
664
+ await storage.write(entry.id, async transaction => {
665
+ const current = await exact(transaction, { retry });
666
+ const previous = current.calls[0];
667
+ const next = calls[0];
668
+ if (calls.length !== 1 || next?.id !== toolCallId || previous &&
669
+ (previous.name !== next.name || previous.arguments !== next.arguments ||
670
+ previous.status !== "running" && next.status === "running")) {
671
+ throw failure("The saved response operation changed.", "conversation_tool_receipt_conflict");
672
+ }
673
+ await saveTools(entry, transaction, request, calls);
674
+ });
675
+ }
676
+ const tools = createConversationTools({ catalog, toolSet, context, signal: workerSignal, previousCalls: saved.calls,
677
+ maximumCalls: maximumToolCalls, discoveryOnly: false,
678
+ authorize: () => access(context, entry.id, "tool", entry),
679
+ prepareContext: async () => {
680
+ await storage.read(entry.id, transaction => exact(transaction));
681
+ return context;
682
+ },
683
+ async save(calls) {
684
+ try { await persist(calls); }
685
+ catch (error) {
686
+ if (calls[0]?.status !== "running") {
687
+ const snapshot = structuredClone(calls);
688
+ entry.error = "The application operation result could not be saved. Restore storage access and retry saving.";
689
+ entry.storageFailure = failure(entry.error, "conversation_storage_unavailable", 503);
690
+ entry.storageFailure.cause = error;
691
+ entry.saveProgress = async () => {
692
+ await persist(snapshot, true);
693
+ entry.storageFailure = null;
694
+ entry.saveProgress = null;
695
+ entry.error = "";
696
+ return { type: "tool", turnId, call: snapshot[0] };
697
+ };
698
+ }
699
+ throw error;
700
+ }
701
+ },
702
+ emit: event => emit(entry, { ...event, turnId }) });
703
+ return tools.execute(input, { signal: workerSignal });
704
+ });
705
+ } } };
706
+ }
707
+
451
708
  async function perform(entry, active, configuration) {
452
709
  const { driver, engine, segmentId } = entry;
710
+ const bindingOwner = entry.bindingOwner;
453
711
  const initial = active.request;
454
712
  let current = initial;
455
713
  let goalOwner = initial.input.goal ? initial : null;
@@ -462,18 +720,8 @@ export function createConversationRuntime({ engine: defaultEngine = "api", defau
462
720
  const applicationTools = entry.applicationTools;
463
721
  const toolStorage = applicationTools?.storage || storage;
464
722
 
465
- async function saveTools(transaction, request, calls) {
466
- if (entry.conversation) {
467
- const saved = await transaction.readTurn(request.turnId);
468
- if (submittedMessage(saved || {})?.messageId !== request.input.messageId) {
469
- throw new Error("The application tool receipt does not belong to this authored request.");
470
- }
471
- }
472
- await transaction.updateTurnMetadata(request.turnId, { applicationTools: calls });
473
- }
474
-
475
723
  function prepareTools(request) {
476
- if (!catalog || entry.conversation && !applicationTools) return;
724
+ if (request.completedEnvelope || !catalog || entry.conversation && !applicationTools) return;
477
725
  request.tools = createConversationTools({ catalog, context: request.context, signal: active.controller.signal,
478
726
  ...(applicationTools ? { prepareContext() {
479
727
  if (!request.accepted) throw new Error("The application tool does not belong to an admitted message.");
@@ -485,13 +733,22 @@ export function createConversationRuntime({ engine: defaultEngine = "api", defau
485
733
  if (entry.active !== active || current !== request || !request.accepted || request.finished) {
486
734
  throw failure("This application tool's admitted message is no longer current.", "conversation_tool_request_retired");
487
735
  }
488
- } }));
736
+ },
737
+ ...(entry.conversation ? { assertCompletionCurrent() {
738
+ active.controller.signal.throwIfAborted();
739
+ if (closed || entry.disposed || entry.bindingOwner !== bindingOwner || entry.segmentId !== segmentId ||
740
+ !active.finishing || active.timedOut || problem || toolFailure || entry.executionFailure || entry.storageFailure ||
741
+ current !== request || !request.accepted || entry.lastAcceptedRequest !== request ||
742
+ entry.active && entry.active !== active) {
743
+ throw failure("This native completion's admitted message is no longer current.", "conversation_tool_request_retired");
744
+ }
745
+ } } : {}) }));
489
746
  } } : {}),
490
747
  maximumCalls: maximumToolCalls, discoveryOnly: driver.toolDiscovery === true,
491
748
  authorize: () => access(request.context, entry.id, "tool", entry),
492
749
  save: calls => {
493
750
  if (!request.accepted) throw new Error("The engine requested an application tool before admitting the message.");
494
- return toolStorage.write(entry.id, transaction => saveTools(transaction, request, calls));
751
+ return toolStorage.write(entry.id, transaction => saveTools(entry, transaction, request, calls));
495
752
  },
496
753
  emit: event => emit(entry, { ...event, turnId: request.turnId })
497
754
  });
@@ -501,17 +758,21 @@ export function createConversationRuntime({ engine: defaultEngine = "api", defau
501
758
  if (request.tools) await transaction.updateTurnMetadata(request.turnId, { applicationTools: request.tools.records() });
502
759
  if (!driver.canonicalTranscript) {
503
760
  for (const message of request.progress.values()) {
761
+ if (!persistCommentary && message.role === "commentary") continue;
504
762
  if (!await transaction.hasMessage(message.messageId)) await transaction.appendMessage(request.turnId, message);
505
763
  }
506
764
  if (request.answer?.text.trim()) await transaction.replaceAssistant(request.turnId, request.answer);
507
765
  }
508
766
  const saved = driver.canonicalTranscript ? await transaction.readTurn(request.turnId) : null;
509
767
  await transaction.updateTurnMetadata(request.turnId, { runtime: { ...saved?.metadata?.runtime, status, engine, segmentId, origin: request.input.origin,
768
+ ...(request.completedEnvelope ? { completedEnvelope: true } : {}),
769
+ ...(typeof request.nativeToolAttempt === "boolean" ? { nativeToolAttempt: request.nativeToolAttempt } : {}),
510
770
  ...(request.nativeTurnId ? { nativeTurnId: request.nativeTurnId } : {}),
511
771
  ...(request.goalMessageId ? { goalMessageId: request.goalMessageId } : {}),
512
772
  ...(request.continuedBy ? { continuedBy: request.continuedBy } : {}), ...extra, ...(error ? { error } : {}) } });
513
773
  return conversationHistoryVersions([{ turnId: request.turnId,
514
- messages: [...request.progress.values(), ...(request.answer?.text.trim() ? [request.answer] : [])] }]);
774
+ messages: [...request.progress.values()].filter(message => !(message.turnId === request.turnId && message.status === "complete"))
775
+ .concat(request.answer?.text.trim() ? [request.answer] : []) }]);
515
776
  }
516
777
 
517
778
  async function publishAdmission(request, nativeResult) {
@@ -519,6 +780,20 @@ export function createConversationRuntime({ engine: defaultEngine = "api", defau
519
780
  request.admission.resolve(entry.conversation ? { receipt, nativeResult } : receipt);
520
781
  request.admissionSettled = true;
521
782
  await emit(entry, { type: "accepted", ...receipt });
783
+ if (driver.canonicalTranscript && !entry.conversation) {
784
+ // Native items can complete before common admission finishes. Their
785
+ // exact request already owns them; publish before tools join the
786
+ // existing delivery commit, without blocking the native event queue.
787
+ for (const [id, message] of request.progress) {
788
+ if (message.turnId !== request.turnId || message.status !== "complete") continue;
789
+ request.progress.delete(id);
790
+ if (request.sealed || entry.disposed || entry.active !== active ||
791
+ entry.bindingOwner !== bindingOwner || entry.segmentId !== segmentId) continue;
792
+ await emit(entry, { type: "message", ...message, streaming: streams.read(entry.id) },
793
+ () => !entry.disposed && !request.sealed && entry.active === active && entry.bindingOwner === bindingOwner && entry.segmentId === segmentId);
794
+ }
795
+ }
796
+ request.admissionPublished = true;
522
797
  await setPhase(entry, "working", request.turnId);
523
798
  if ((entry.conversation || driver.canonicalTranscript) && entry.nativePhase && entry.nativePhase !== "working") await setPhase(entry, entry.nativePhase, request.turnId);
524
799
  }
@@ -549,6 +824,9 @@ export function createConversationRuntime({ engine: defaultEngine = "api", defau
549
824
  if (submittedMessage(saved || {})?.messageId !== request.input.messageId || saved.metadata?.runtime?.segmentId !== segmentId) {
550
825
  throw new Error("The native receipt does not belong to this authored request.");
551
826
  }
827
+ if (request.completedEnvelope && saved.metadata.runtime.completedEnvelope !== true) {
828
+ throw new Error("The native receipt lost its completed-response marker.");
829
+ }
552
830
  id = saved.turnId;
553
831
  await transaction.updateTurnMetadata(id, { runtime: { ...saved.metadata.runtime,
554
832
  ...(request.goalMessageId ? { goalMessageId: request.goalMessageId } : {}),
@@ -557,6 +835,8 @@ export function createConversationRuntime({ engine: defaultEngine = "api", defau
557
835
  id = await transaction.nextTurnId();
558
836
  await transaction.appendMessage(id, { role: request.input.origin === "application" ? "system" : "user", ...request.input, at: new Date().toISOString(),
559
837
  turnMetadata: { runtime: { status: "running", engine, segmentId, origin: request.input.origin,
838
+ ...(request.completedEnvelope ? { completedEnvelope: true } : {}),
839
+ ...(typeof request.nativeToolAttempt === "boolean" ? { nativeToolAttempt: request.nativeToolAttempt } : {}),
560
840
  ...(request.goalMessageId ? { goalMessageId: request.goalMessageId } : {}),
561
841
  ...(nativeTurnId ? { nativeTurnId } : {}) } } });
562
842
  }
@@ -578,6 +858,7 @@ export function createConversationRuntime({ engine: defaultEngine = "api", defau
578
858
  request.nativeTurnId = nativeTurnId;
579
859
  if (request.input.goal) goalOwner = request;
580
860
  current = active.request = request;
861
+ if (entry.conversation) entry.lastAcceptedRequest = request;
581
862
  if (!nativeDelivery) await publishAdmission(request);
582
863
  }
583
864
 
@@ -650,7 +931,9 @@ export function createConversationRuntime({ engine: defaultEngine = "api", defau
650
931
  await setPhase(entry, "working", request.turnId);
651
932
  if (request.sealed) return;
652
933
  const messageId = `${request.turnId}:${message.id}`;
653
- if (message.role === "assistant" && message.complete && message.text.length > maximumFinalReply) {
934
+ const finalReplyLimit = completedEnvelope && request.completedEnvelope && request.input.origin === "application" && !request.input.goal
935
+ ? maximumCompletedOutput : maximumFinalReply;
936
+ if (message.role === "assistant" && message.complete && message.text.length > finalReplyLimit) {
654
937
  if (request.answer?.messageId === messageId) request.answer = null;
655
938
  throw new Error("The assistant exceeded the configured final reply limit.");
656
939
  }
@@ -659,10 +942,12 @@ export function createConversationRuntime({ engine: defaultEngine = "api", defau
659
942
  const outputId = previous?.outputId || (message.outputId ? `${outputOwner.turnId}:${message.outputId}` : "");
660
943
  const value = { messageId, ...(outputId ? { outputId } : {}),
661
944
  role: message.role, text: message.text, at: previous?.at || new Date().toISOString() };
945
+ const authorship = request.completedEnvelope ? { authorship: { messageId: request.input.messageId,
946
+ turnId: request.turnId, origin: request.input.origin, completedEnvelope: true } } : {};
662
947
  // Some native streams have temporary block identities. Only their final
663
948
  // history snapshots belong in the transcript, as in the original owner.
664
949
  if (message.transient) {
665
- streams.update(entry.id, { turnId: request.turnId, origin: request.input.origin, ...value });
950
+ streams.update(entry.id, { turnId: request.turnId, origin: request.input.origin, ...value, ...authorship });
666
951
  } else if (message.role === "assistant") {
667
952
  if (request.answer && request.answer.messageId !== messageId) {
668
953
  request.progress.set(request.answer.messageId, { ...request.answer, role: "commentary" });
@@ -670,12 +955,12 @@ export function createConversationRuntime({ engine: defaultEngine = "api", defau
670
955
  }
671
956
  request.progress.delete(messageId);
672
957
  request.answer = value;
673
- streams.update(entry.id, { turnId: request.turnId, origin: request.input.origin, ...value });
958
+ streams.update(entry.id, { turnId: request.turnId, origin: request.input.origin, ...value, ...authorship });
674
959
  } else {
675
960
  if (request.answer?.messageId === messageId) request.answer = null;
676
961
  if (message.complete) request.progress.set(messageId, value);
677
962
  if (message.complete) streams.complete(entry.id, messageId);
678
- else streams.update(entry.id, { turnId: request.turnId, origin: request.input.origin, ...value });
963
+ else streams.update(entry.id, { turnId: request.turnId, origin: request.input.origin, ...value, ...authorship });
679
964
  }
680
965
  await emit(entry, { type: "message", turnId: request.turnId, messageId, origin: request.input.origin, role: message.role,
681
966
  ...(outputId ? { outputId } : {}),
@@ -732,7 +1017,9 @@ export function createConversationRuntime({ engine: defaultEngine = "api", defau
732
1017
  throw failure("Authorized attachment paths changed after this instruction was prepared. Restore the original files or send a new message.", "conversation_attachment_manifest_changed");
733
1018
  }
734
1019
  metadata.runtime.request = { ...(pending?.messageId === request.input.messageId ? pending
735
- : { ...request.input, at: request.at, attempted: false }), attachmentManifest };
1020
+ : { ...request.input, at: request.at, attempted: false,
1021
+ ...(request.completedEnvelope ? { completedEnvelope: true, excludedMessageIds: request.excludedMessageIds } : {}),
1022
+ ...(typeof request.nativeToolAttempt === "boolean" ? { nativeToolAttempt: request.nativeToolAttempt } : {}) }), attachmentManifest };
736
1023
  await transaction.writeMetadata(metadata);
737
1024
  });
738
1025
  const admitted = Promise.withResolvers();
@@ -752,7 +1039,9 @@ export function createConversationRuntime({ engine: defaultEngine = "api", defau
752
1039
  threadId = native?.threadId;
753
1040
  if (!threadId) throw new Error("Native admission requires the saved conversation identity.");
754
1041
  await input.onPromptSending(entry.conversation ? native : { threadId, displayAttachments: files.attachments,
755
- turnMetadata: { runtime: { engine, segmentId, origin: request.input.origin } } });
1042
+ turnMetadata: { runtime: { engine, segmentId, origin: request.input.origin,
1043
+ ...(request.completedEnvelope ? { completedEnvelope: true } : {}),
1044
+ ...(typeof request.nativeToolAttempt === "boolean" ? { nativeToolAttempt: request.nativeToolAttempt } : {}) } } });
756
1045
  },
757
1046
  async accept(native) {
758
1047
  if (entry.conversation) nativeResult = native.nativeResult;
@@ -801,6 +1090,8 @@ export function createConversationRuntime({ engine: defaultEngine = "api", defau
801
1090
  const pending = metadata.runtime.request;
802
1091
  if (pending && pending.messageId !== request.input.messageId && (pending.attempted || pending.inspectionOnly)) throw new Error("The pending conversation instruction changed.");
803
1092
  metadata.runtime.request = { ...request.input, at: request.at,
1093
+ ...(request.completedEnvelope ? { completedEnvelope: true, excludedMessageIds: request.excludedMessageIds } : {}),
1094
+ ...(typeof request.nativeToolAttempt === "boolean" ? { nativeToolAttempt: request.nativeToolAttempt } : {}),
804
1095
  message: rendered.message, displayMessage: rendered.displayMessage,
805
1096
  displayAttachments, attachmentIds: rendered.attachmentIds,
806
1097
  attachmentManifest,
@@ -827,7 +1118,8 @@ export function createConversationRuntime({ engine: defaultEngine = "api", defau
827
1118
  ? await dispatchPrepared(input)
828
1119
  : await nativeChangeover(entry, request.context, request, dispatchPrepared).send({ engineId: engine,
829
1120
  messages: entry.conversation ? await entry.conversation.transcript.history() : conversationNativeMessages(history),
830
- ...(entry.conversation ? { turnMetadata: { engineId: engine } } : {}), input });
1121
+ ...(entry.conversation ? { turnMetadata: { engineId: engine } } : {}),
1122
+ ...(request.completedEnvelope ? { completedEnvelope: true, excludedMessageIds: request.excludedMessageIds } : {}), input });
831
1123
  if (!delivered?.delivered) {
832
1124
  const error = failure(delivered?.error || "Native delivery could not be confirmed.", delivered?.code || "conversation_delivery_uncertain");
833
1125
  if (entry.conversation) {
@@ -852,6 +1144,7 @@ export function createConversationRuntime({ engine: defaultEngine = "api", defau
852
1144
  }
853
1145
  if (!saved) throw new Error("The native receipt has no stored authored message.");
854
1146
  request.accepted = request.finished = true;
1147
+ if (entry.conversation) entry.lastAcceptedRequest = request;
855
1148
  request.turnId = saved.turnId;
856
1149
  request.recovered = saved.metadata?.runtime;
857
1150
  const receipt = { status: "accepted", messageId: request.input.messageId, turnId: saved.turnId,
@@ -919,7 +1212,35 @@ export function createConversationRuntime({ engine: defaultEngine = "api", defau
919
1212
  return work;
920
1213
  }
921
1214
  };
1215
+ const completedToolSet = engine === "claude" && initial.completedEnvelope && typeof initial.nativeToolAttempt === "boolean"
1216
+ ? catalog.resolveToolSet(initial.context) : null;
922
1217
  const providerInput = { configuration, context: initial.context, tools,
1218
+ ...(completedToolSet ? {
1219
+ async onNativeToolUse({ messageId, threadId, name }) {
1220
+ await initial.deliveryCommitted;
1221
+ active.controller.signal.throwIfAborted();
1222
+ if (entry.disposed || entry.active !== active || current !== initial || !initial.accepted || initial.finished || initial.sealed ||
1223
+ entry.bindingOwner !== bindingOwner || entry.segmentId !== segmentId ||
1224
+ messageId !== initial.input.messageId || threadId !== initial.nativeThreadId) {
1225
+ throw failure("The native tool observation belongs to a retired response.", "conversation_tool_request_retired");
1226
+ }
1227
+ if (!completedToolSet.byName.has(name)) return;
1228
+ await storage.write(entry.id, async transaction => {
1229
+ const metadata = await transaction.readMetadata();
1230
+ const turn = await transaction.readTurn(initial.turnId);
1231
+ const saved = turn?.metadata?.runtime;
1232
+ if (metadata.runtime.engine !== engine || metadata.runtime.segmentId !== segmentId ||
1233
+ nativeIdentity(metadata.runtime) !== threadId || submittedMessage(turn || {})?.messageId !== messageId ||
1234
+ saved?.engine !== engine || saved.segmentId !== segmentId || saved.completedEnvelope !== true ||
1235
+ typeof saved.nativeToolAttempt !== "boolean" || saved.status !== "running" || saved.supersededBy ||
1236
+ entry.active !== active || current !== initial || initial.finished || initial.sealed || active.controller.signal.aborted) {
1237
+ throw failure("The native tool observation lost its exact admitted receipt.", "conversation_tool_request_retired");
1238
+ }
1239
+ await transaction.updateTurnMetadata(initial.turnId, { runtime: { ...saved, nativeToolAttempt: true } });
1240
+ });
1241
+ initial.nativeToolAttempt = true;
1242
+ }
1243
+ } : {}),
923
1244
  ...(engine === "opencode" || entry.conversation && ["codex", "claude"].includes(engine) ? {
924
1245
  async finishIfCurrent(isCurrent) {
925
1246
  // A reserved Send may still be preparing when the native turn ends.
@@ -1020,7 +1341,7 @@ export function createConversationRuntime({ engine: defaultEngine = "api", defau
1020
1341
  if (entry.conversation) {
1021
1342
  if (applicationTools) await toolStorage.write(entry.id, async transaction => {
1022
1343
  for (const request of active.requests.values()) {
1023
- if (request.accepted && request.tools?.records().length) await saveTools(transaction, request, request.tools.records());
1344
+ if (request.accepted && request.tools?.records().length) await saveTools(entry, transaction, request, request.tools.records());
1024
1345
  }
1025
1346
  });
1026
1347
  // Only opted-in application receipts are saved above. Native output,
@@ -1073,7 +1394,7 @@ export function createConversationRuntime({ engine: defaultEngine = "api", defau
1073
1394
  await emit(entry, { type: "settled", turnId: current.turnId, status: entry.storageFailure || entry.executionFailure ? "failed" : status, error: entry.error });
1074
1395
  }
1075
1396
 
1076
- async function send(entry, context, input, origin = "user", goal, representation) {
1397
+ async function send(entry, context, input, origin = "user", goal, representation, wakeOptions) {
1077
1398
  await access(context, entry.id, goal ? "goal" : input?.steer === true ? "steer" : origin === "application" ? "wake" : "send", entry);
1078
1399
  if (entry.conversation?.commands) {
1079
1400
  if (origin !== "user" || goal) throw failure("This conversation supports its original application Send operation.", "conversation_unsupported", 400);
@@ -1082,7 +1403,7 @@ export function createConversationRuntime({ engine: defaultEngine = "api", defau
1082
1403
  if (entry.conversation?.native?.scoped) return entry.provider.sendNative({ input, context });
1083
1404
  const originalInput = representation === "native" ? input : undefined;
1084
1405
  const message = originalInput ? { ...input, text: input.displayMessage ?? input.message ?? input.text ?? "" } : input;
1085
- const admission = await serial(entry, () => admitSend(entry, context, message, origin, goal, originalInput));
1406
+ const admission = await serial(entry, () => admitSend(entry, context, message, origin, goal, originalInput, wakeOptions));
1086
1407
  const result = await (admission.receipt || admission.promise);
1087
1408
  if (!entry.conversation) return result;
1088
1409
  if (representation === "native") return result.nativeResult;
@@ -1092,10 +1413,22 @@ export function createConversationRuntime({ engine: defaultEngine = "api", defau
1092
1413
 
1093
1414
  // Called while holding the conversation queue, including Claude's existing
1094
1415
  // stop-then-Send goal commands. All commands retain the ordinary receipt path.
1095
- async function admitSend(entry, context, input, origin = "user", goal, originalInput) {
1416
+ async function admitSend(entry, context, input, origin = "user", goal, originalInput, wakeOptions) {
1096
1417
  const operation = goal ? "goal" : input?.steer === true ? "steer" : origin === "application" ? "wake" : "send";
1097
1418
  await access(context, entry.id, operation, entry);
1098
1419
  await entry.conversation?.admission(context, operation);
1420
+ let suppliedExclusions;
1421
+ if (wakeOptions !== undefined) {
1422
+ if (!completedEnvelope || entry.conversation || origin !== "application" || goal ||
1423
+ !wakeOptions || typeof wakeOptions !== "object" || Array.isArray(wakeOptions) ||
1424
+ Object.keys(wakeOptions).some(name => name !== "excludedMessageIds") ||
1425
+ !Array.isArray(wakeOptions.excludedMessageIds) ||
1426
+ wakeOptions.excludedMessageIds.some(id => typeof id !== "string" || !id.trim() || id.length > 128) ||
1427
+ new Set(wakeOptions.excludedMessageIds).size !== wakeOptions.excludedMessageIds.length) {
1428
+ throw failure("Native history exclusions require a server-owned completed application wake and exact message IDs.", "conversation_invalid_message", 400);
1429
+ }
1430
+ suppliedExclusions = [...wakeOptions.excludedMessageIds];
1431
+ }
1099
1432
  const attachmentIds = conversationAttachmentIds(input?.attachmentIds);
1100
1433
  if (!input || typeof input.messageId !== "string" || !input.messageId.trim() || input.messageId.length > 128 ||
1101
1434
  entry.engine !== "api" && !/^[\w-]{1,128}$/u.test(input.messageId) ||
@@ -1125,6 +1458,7 @@ export function createConversationRuntime({ engine: defaultEngine = "api", defau
1125
1458
  if (goal && goal.expectedSegmentId !== entry.segmentId) throw failure("The conversation changed before this goal command was sent.", "conversation_goal_changed");
1126
1459
  const pending = entry.active?.requests.get(message.messageId);
1127
1460
  if (pending && entry.engine !== "api" && !pending.admissionSettled) {
1461
+ if (suppliedExclusions && !isDeepStrictEqual(pending.excludedMessageIds || [], suppliedExclusions)) throw failure("This messageId already belongs to different native history exclusions.", "conversation_message_conflict");
1128
1462
  if (!entry.conversation && !sameRequest(pending.input, message)) throw failure("This messageId already belongs to different content.", "conversation_message_conflict");
1129
1463
  return { promise: pending.admission.promise };
1130
1464
  }
@@ -1141,6 +1475,7 @@ export function createConversationRuntime({ engine: defaultEngine = "api", defau
1141
1475
  }
1142
1476
  if (pending && !entry.conversation) {
1143
1477
  if (!sameRequest(pending.input, message)) throw failure("This messageId already belongs to different content.", "conversation_message_conflict");
1478
+ if (suppliedExclusions && !isDeepStrictEqual(pending.excludedMessageIds || [], suppliedExclusions)) throw failure("This messageId already belongs to different native history exclusions.", "conversation_message_conflict");
1144
1479
  return { promise: pending.admission.promise };
1145
1480
  }
1146
1481
  const working = entry.active;
@@ -1148,10 +1483,15 @@ export function createConversationRuntime({ engine: defaultEngine = "api", defau
1148
1483
  // No native dispatch has occurred for this request. Finish the previous
1149
1484
  // waiter before reserving the same authored request on the ordinary path.
1150
1485
  await working.done;
1151
- return admitSend(entry, context, input, origin, goal, originalInput);
1486
+ return admitSend(entry, context, input, origin, goal, originalInput, wakeOptions);
1152
1487
  }
1153
1488
  const nativeSteering = !working && (nativeState.nativeTurn?.active || entry.driver.canonicalTranscript && nativeState.binding?.codexAppServerRun &&
1154
1489
  codexAppServerTurnStateFromAgentRun(nativeState.binding.codexAppServerRun).active);
1490
+ if (completedEnvelope && entry.engine === "claude" &&
1491
+ (working?.request.completedEnvelope || nativePending?.completedEnvelope === true || origin === "application" && !goal) &&
1492
+ (working || input.steer || nativeSteering)) {
1493
+ throw failure("Completed Claude responses cannot share a native steering turn. Wait for the response or stop it.", "conversation_not_steerable");
1494
+ }
1155
1495
  if (working && !input.steer && !goal && !entry.conversation) throw failure("Wait for this answer or use steer to send another instruction.", "conversation_busy");
1156
1496
  if (input.steer && (!working && !nativeSteering || working?.finishing) && !(nativePending?.attempted || nativePending?.inspectionOnly)) throw failure("There is no active native turn to steer. Send a new message.", "conversation_not_steerable");
1157
1497
  if (goal && working?.finishing) throw failure("Wait for the current turn to finish saving before starting the goal.", "conversation_busy");
@@ -1163,6 +1503,9 @@ export function createConversationRuntime({ engine: defaultEngine = "api", defau
1163
1503
  if (working && working.steering === barrier?.promise) working.steering = null;
1164
1504
  };
1165
1505
  let configuration;
1506
+ let markedResponse = false;
1507
+ let nativeToolAttempt;
1508
+ let excludedMessageIds;
1166
1509
  let requestAt = new Date().toISOString();
1167
1510
  try { configuration = entry.conversation ? (await readState(entry)).configuration : await storage.write(entry.id, async transaction => {
1168
1511
  const metadata = await transaction.readMetadata();
@@ -1171,17 +1514,31 @@ export function createConversationRuntime({ engine: defaultEngine = "api", defau
1171
1514
  throw failure("This message has uncertain delivery in a retained conversation. Select that engine and check its receipt before submitting it again.", "conversation_delivery_uncertain");
1172
1515
  }
1173
1516
  if (metadata.runtime.request) {
1517
+ if (suppliedExclusions && metadata.runtime.request.messageId !== message.messageId) {
1518
+ throw failure("Check delivery of the previous instruction before preparing another completed response.", "conversation_delivery_uncertain");
1519
+ }
1174
1520
  if (entry.engine === "api") throw failure("The previous submission has uncertain delivery. It will not be resent automatically.", "conversation_delivery_uncertain");
1175
1521
  if (metadata.runtime.request.messageId === message.messageId) {
1176
1522
  if (!sameRequest(metadata.runtime.request, message)) throw failure("This messageId already belongs to different content.", "conversation_message_conflict");
1523
+ if (suppliedExclusions && !isDeepStrictEqual(metadata.runtime.request.excludedMessageIds || [], suppliedExclusions)) {
1524
+ throw failure("This messageId already belongs to different native history exclusions.", "conversation_message_conflict");
1525
+ }
1177
1526
  requestAt = metadata.runtime.request.at;
1178
1527
  }
1179
1528
  } else metadata.runtime.request = { ...message, at: requestAt,
1529
+ ...(completedEnvelope && origin === "application" && !goal ? { completedEnvelope: true, excludedMessageIds: suppliedExclusions || [],
1530
+ ...(entry.engine === "claude" ? { nativeToolAttempt: false } : {}) } : {}),
1180
1531
  ...(entry.engine !== "api" ? { attempted: false } : {}), ...(input.steer ? { steering: true } : {}) };
1532
+ markedResponse = origin === "application" && !goal && metadata.runtime.request?.messageId === message.messageId &&
1533
+ metadata.runtime.request.completedEnvelope === true;
1534
+ if (markedResponse) excludedMessageIds = structuredClone(metadata.runtime.request.excludedMessageIds || []);
1535
+ if (entry.engine === "claude" && markedResponse && typeof metadata.runtime.request.nativeToolAttempt === "boolean") nativeToolAttempt = metadata.runtime.request.nativeToolAttempt;
1181
1536
  await transaction.writeMetadata(metadata);
1182
1537
  return metadata.runtime.configuration;
1183
1538
  }); } catch (error) { release(); throw error; }
1184
- const request = { input: message, originalInput, operation, context, at: requestAt, steering: Boolean(working || input.steer && nativeSteering),
1539
+ const request = { input: message, originalInput, operation, context, at: requestAt,
1540
+ ...(markedResponse ? { completedEnvelope: true, excludedMessageIds } : {}),
1541
+ ...(typeof nativeToolAttempt === "boolean" ? { nativeToolAttempt } : {}), steering: Boolean(working || input.steer && nativeSteering),
1185
1542
  admission: Promise.withResolvers(), progress: new Map() };
1186
1543
  request.admission.promise.catch(() => {});
1187
1544
  if (working) {
@@ -1457,7 +1814,9 @@ export function createConversationRuntime({ engine: defaultEngine = "api", defau
1457
1814
  await transaction.appendMessage(turnId, { role: input.origin === "application" ? "system" : "user", origin: input.origin || "user",
1458
1815
  messageId: id, text: input.text, attachments: input.attachments, at: input.at,
1459
1816
  ...(input.data !== undefined ? { data: input.data } : {}), ...(input.goal ? { goal: input.goal } : {}),
1460
- turnMetadata: { runtime: { engine: entry.engine, segmentId: entry.segmentId, origin: input.origin || "user" } } });
1817
+ turnMetadata: { runtime: { engine: entry.engine, segmentId: entry.segmentId, origin: input.origin || "user",
1818
+ ...(input.completedEnvelope === true ? { completedEnvelope: true } : {}),
1819
+ ...(typeof input.nativeToolAttempt === "boolean" ? { nativeToolAttempt: input.nativeToolAttempt } : {}) } } });
1461
1820
  turn = await transaction.readTurn(turnId);
1462
1821
  history.push(turn);
1463
1822
  }
@@ -1465,6 +1824,7 @@ export function createConversationRuntime({ engine: defaultEngine = "api", defau
1465
1824
  if (runtime.nativeTurnId && native.nativeTurnId && runtime.nativeTurnId !== native.nativeTurnId) throw new Error("Native history changed the accepted turn's identity.");
1466
1825
  const replies = [];
1467
1826
  for (const message of native.messages || []) {
1827
+ if (!persistCommentary && message.role === "commentary") continue;
1468
1828
  if (message.role === "assistant") replies.push(message.text);
1469
1829
  else {
1470
1830
  const messageId = `${turn.turnId}:${message.id}`;
@@ -1476,7 +1836,9 @@ export function createConversationRuntime({ engine: defaultEngine = "api", defau
1476
1836
  const nativeReplies = (native.messages || []).filter(message => message.role === "assistant");
1477
1837
  const outputId = nativeReplies.length === 1 && nativeReplies[0].outputId;
1478
1838
  const replyText = replies.join("\n\n");
1479
- const oversizedReply = replyText.length > maximumFinalReply;
1839
+ const finalReplyLimit = completedEnvelope && isCompletedEnvelopeTurn(turn) && !submittedMessage(turn)?.goal
1840
+ ? maximumCompletedOutput : maximumFinalReply;
1841
+ const oversizedReply = replyText.length > finalReplyLimit;
1480
1842
  if (replies.length && !oversizedReply) await transaction.replaceAssistant(turn.turnId, { messageId: turn.assistant?.messageId || `${turn.turnId}:assistant`,
1481
1843
  ...(!turn.assistant?.outputId && outputId ? { outputId: `${turn.turnId}:${outputId}` } : {}),
1482
1844
  role: "assistant", text: replyText, at: submittedMessage(turn).at });
@@ -1555,6 +1917,7 @@ export function createConversationRuntime({ engine: defaultEngine = "api", defau
1555
1917
 
1556
1918
  async function replace(entry, context, input, selection = false, representation) {
1557
1919
  const operation = selection ? "select" : "replace";
1920
+ if (completedEnvelope && input?.engine === "api") throw failure("Completed envelopes require a native engine.", "conversation_unsupported", 400);
1558
1921
  await access(context, entry.id, operation, entry);
1559
1922
  if (entry.conversation?.commands) {
1560
1923
  if (!selection) throw failure("This conversation retains its original replacement policy.", "conversation_unsupported", 400);
@@ -1667,12 +2030,10 @@ export function createConversationRuntime({ engine: defaultEngine = "api", defau
1667
2030
  const continuity = destination ? { text: destination.continuity, attachments: destination.continuityAttachments || [] }
1668
2031
  : engine === "api" ? conversationContinuity({ history, briefing: input.briefing, maximumCharacters: maximumContinuity })
1669
2032
  : { text: undefined, attachments: [] };
1670
- pending = { request, engine, configuration: structuredClone(configuration),
2033
+ pending = createConversationRuntimeReplacement({ request, engine, configuration: structuredClone(configuration),
1671
2034
  reason: selection ? state.engine === engine ? "model-change" : "engine-change" : input.reason,
1672
- segmentId: destination?.segmentId || randomUUID(),
1673
- continuity: continuity.text, continuityAttachments: continuity.attachments,
1674
- seen: destination ? destination.seen : engine === "api" ? conversationHistoryVersions(history) : {},
1675
- ...(destination ? { binding: destination.binding || null, submission: destination.request } : {}) };
2035
+ segmentId: destination?.segmentId || randomUUID(), continuity,
2036
+ seen: destination ? destination.seen : engine === "api" ? conversationHistoryVersions(history) : {}, destination });
1676
2037
  }
1677
2038
 
1678
2039
  function saveReplacement() {
@@ -1720,25 +2081,10 @@ export function createConversationRuntime({ engine: defaultEngine = "api", defau
1720
2081
  if (replacement && nativeIdentity(state) !== replacement.previous.conversationId) {
1721
2082
  throw failure("The native predecessor identity changed before its binding was released.", "conversation_replacement_conflict");
1722
2083
  }
1723
- state.predecessors.push({ segmentId: state.segmentId, engine: state.engine,
1724
- configuration: state.configuration, binding: state.binding, seen: state.seen,
1725
- continuity: state.continuity, continuityAttachments: state.continuityAttachments, request: state.request,
1726
- successorId: pending.segmentId, replacement: request,
1727
- ...(pending.preparedAt ? { preparedAt: pending.preparedAt } : {}) });
1728
- Object.assign(state, { engine, configuration: pending.configuration, binding: pending.binding,
1729
- segmentId: pending.segmentId, continuity: pending.continuity, continuityAttachments: pending.continuityAttachments, seen: pending.seen });
1730
- if (pending.submission) state.request = pending.submission;
1731
- else delete state.request;
2084
+ releaseConversationRuntimeBinding(state, pending, request, engine);
1732
2085
  },
1733
2086
  async write(value) {
1734
- if (value) {
1735
- if (engine !== "api") state.seen = value.engines[engine].seen;
1736
- state.lastEngine = value.lastEngine;
1737
- } else if (engine !== "api" &&
1738
- (!selection || input.retireNative === true || engine === "opencode" && !pending.binding.sessionId)) {
1739
- state.lastEngine = "";
1740
- }
1741
- delete state.replacement;
2087
+ finishConversationRuntimeReplacement(state, { engine, selection, retireNative: input.retireNative, pending, value });
1742
2088
  await transaction.writeMetadata(metadata);
1743
2089
  }
1744
2090
  });
@@ -1978,6 +2324,12 @@ export function createConversationRuntime({ engine: defaultEngine = "api", defau
1978
2324
  await access(context, id, "open");
1979
2325
  const selectedHost = host || defaultHost;
1980
2326
  const conversation = selectedHost?.conversation ? await selectedHost.conversation({ id, context }) : null;
2327
+ if (completedEnvelope && (conversation || representation !== "canonical")) {
2328
+ throw failure("Completed envelopes require runtime-owned canonical storage.", "conversation_unsupported", 400);
2329
+ }
2330
+ if (conversation && !persistCommentary) {
2331
+ throw new TypeError("Transient commentary requires a runtime-owned transcript; supplied conversations retain their host's policy.");
2332
+ }
1981
2333
  const scoped = conversation?.native?.scoped;
1982
2334
  const commands = conversation?.commands;
1983
2335
  const invalidClaudeOwner = conversation?.engine === "claude" && (!conversation.native?.owner ||
@@ -2046,7 +2398,9 @@ export function createConversationRuntime({ engine: defaultEngine = "api", defau
2046
2398
  return result;
2047
2399
  },
2048
2400
  send: input => send(entry, context, input, "user", undefined, representation),
2049
- wake: input => send(entry, context, input, "application", undefined, representation),
2401
+ wake: (input, options) => send(entry, context, input, "application", undefined, representation, options),
2402
+ ...(completedEnvelope ? { prepareCompletedResponse: (input, options) =>
2403
+ serial(entry, () => prepareCompletedResponse(entry, context, input, options)) } : {}),
2050
2404
  readGoal: () => serial(entry, () => readGoal(entry, context, representation)),
2051
2405
  updateGoal: input => updateGoal(entry, context, input, representation),
2052
2406
  inspectDelivery({ messageId, threadId } = {}) {