@jskit-ai/assistant-core 0.2.7 → 0.2.9
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/examples/conversation/package.json +5 -5
- package/package.json +18 -7
- package/src/client/browser.js +21 -0
- package/src/client/conversation/AssistantAttachmentQueue.vue +25 -108
- package/src/client/conversation/AssistantComposerActions.vue +1 -1
- package/src/client/conversation/AssistantConversationElement.vue +14 -52
- package/src/client/conversation/AssistantGoalControl.vue +13 -10
- package/src/client/conversation/AssistantPromptInput.vue +6 -91
- package/src/client/conversation/AssistantTranscript.css +505 -0
- package/src/client/conversation/AssistantTranscript.vue +24 -1197
- package/src/client/conversation/attachmentFiles.js +158 -0
- package/src/client/conversation/attachmentQueueItems.js +121 -0
- package/src/client/conversation/attachmentUploadRows.js +265 -0
- package/src/client/conversation/conversationDisplay.js +54 -0
- package/src/client/conversation/messageDelivery.js +13 -9
- package/src/client/conversation/retainedConversation.js +14 -3
- package/src/client/conversation/submitText.js +13 -2
- package/src/client/conversation/transcriptEntries.js +225 -0
- package/src/client/conversation/useAssistantAttachments.js +86 -388
- package/src/client/conversation/useAssistantQuestions.js +16 -3
- package/src/client/conversation/useAssistantSuggestions.js +16 -2
- package/src/client/conversation/useScrollToBottom.js +17 -19
- package/src/client/conversation/useTextareaAutoGrow.js +115 -0
- package/src/client/conversation/useTranscriptScrollFollow.js +525 -0
- package/src/client/lib/assistantApi.js +192 -161
- package/src/client/types.js +110 -0
- package/src/server/conversation/attachments.js +21 -6
- package/src/server/conversation/boundedToolLoop.js +1 -0
- package/src/server/conversation/claudeClient.js +71 -20
- package/src/server/conversation/claudeCommandRun.js +191 -0
- package/src/server/conversation/claudeHistory.js +7 -0
- package/src/server/conversation/claudeNativeTurn.js +708 -0
- package/src/server/conversation/claudeProcess.js +186 -112
- package/src/server/conversation/claudeProcessStart.js +214 -0
- package/src/server/conversation/claudePromptDelivery.js +183 -0
- package/src/server/conversation/claudeSessionCleanup.js +248 -0
- package/src/server/conversation/claudeTools.js +15 -3
- package/src/server/conversation/claudeTurn.js +212 -1388
- package/src/server/conversation/codexAccountReader.js +168 -0
- package/src/server/conversation/codexAuthPreflight.js +5 -4
- package/src/server/conversation/codexClient.js +18 -6
- package/src/server/conversation/codexConfiguration.js +8 -844
- package/src/server/conversation/codexConversationStops.js +377 -0
- package/src/server/conversation/codexConversationStore.js +119 -85
- package/src/server/conversation/codexConversationTurns.js +378 -0
- package/src/server/conversation/codexConversations.js +134 -866
- package/src/server/conversation/codexDetachedConversation.js +203 -0
- package/src/server/conversation/codexDetachedTurn.js +145 -104
- package/src/server/conversation/codexEvents.js +5 -200
- package/src/server/conversation/codexGoalCommands.js +14 -4
- package/src/server/conversation/codexHelperExecution.js +57 -57
- package/src/server/conversation/codexHelperThreadLedger.js +30 -288
- package/src/server/conversation/codexHelperThreadRecord.js +304 -0
- package/src/server/conversation/codexHelperThreadRecovery.js +54 -76
- package/src/server/conversation/codexHelperThreads.js +94 -93
- package/src/server/conversation/codexHistoryAdapter.js +13 -8
- package/src/server/conversation/codexIsolation.js +672 -0
- package/src/server/conversation/codexLiveItems.js +379 -0
- package/src/server/conversation/codexMessageCommands.js +68 -286
- package/src/server/conversation/codexMessageDelivery.js +46 -37
- package/src/server/conversation/codexModelCatalog.js +86 -7
- package/src/server/conversation/codexNotificationObserver.js +81 -88
- package/src/server/conversation/codexNotificationQueue.js +21 -10
- package/src/server/conversation/codexObservationControl.js +105 -89
- package/src/server/conversation/codexOutput.js +84 -419
- package/src/server/conversation/codexProcess.js +29 -1053
- package/src/server/conversation/codexProcessLeader.js +1 -0
- package/src/server/conversation/codexPromptDispatch.js +241 -0
- package/src/server/conversation/codexProvider.js +147 -1807
- package/src/server/conversation/codexProviderAuth.js +348 -0
- package/src/server/conversation/codexProviderInvalidation.js +371 -0
- package/src/server/conversation/codexProviderInventories.js +437 -0
- package/src/server/conversation/codexProviderOwner.js +149 -957
- package/src/server/conversation/codexProviderRelease.js +465 -0
- package/src/server/conversation/codexProviderSelection.js +26 -15
- package/src/server/conversation/codexProviderThreads.js +698 -0
- package/src/server/conversation/codexProviderTurns.js +453 -0
- package/src/server/conversation/codexReasoning.js +32 -23
- package/src/server/conversation/codexRenewalCommands.js +18 -6
- package/src/server/conversation/codexRenewalThreads.js +7 -22
- package/src/server/conversation/codexResultSettlement.js +70 -88
- package/src/server/conversation/codexRunJournal.js +91 -97
- package/src/server/conversation/codexRuntime.js +426 -0
- package/src/server/conversation/codexRuntimeInventory.js +113 -0
- package/src/server/conversation/codexRuntimeLifecycle.js +39 -13
- package/src/server/conversation/codexRuntimeStart.js +431 -0
- package/src/server/conversation/codexRuntimeStop.js +324 -0
- package/src/server/conversation/codexSessionCleanup.js +25 -10
- package/src/server/conversation/codexSessionReconciliation.js +21 -7
- package/src/server/conversation/codexThreadReadiness.js +49 -52
- package/src/server/conversation/codexThreadSnapshot.js +210 -0
- package/src/server/conversation/codexThreadStatus.js +48 -62
- package/src/server/conversation/codexTurn.js +171 -55
- package/src/server/conversation/codexTurnActivity.js +210 -0
- package/src/server/conversation/codexTurnControl.js +44 -48
- package/src/server/conversation/codexTurnEndings.js +610 -0
- package/src/server/conversation/codexTurnRecovery.js +97 -848
- package/src/server/conversation/codexTurnState.js +138 -111
- package/src/server/conversation/completedResponses.js +227 -0
- package/src/server/conversation/configuration.js +2 -2
- package/src/server/conversation/continuity.js +43 -6
- package/src/server/conversation/conversationDelivery.js +394 -0
- package/src/server/conversation/conversationEntries.js +365 -0
- package/src/server/conversation/conversationEvents.js +192 -0
- package/src/server/conversation/conversationGoals.js +187 -0
- package/src/server/conversation/conversationHandles.js +237 -0
- package/src/server/conversation/conversationReads.js +206 -0
- package/src/server/conversation/conversationRefusals.js +58 -0
- package/src/server/conversation/conversationReplacement.js +254 -0
- package/src/server/conversation/conversationSending.js +273 -0
- package/src/server/conversation/conversationStopping.js +121 -0
- package/src/server/conversation/conversationTurnDelivery.js +413 -0
- package/src/server/conversation/conversationTurns.js +511 -0
- package/src/server/conversation/deliveryTiming.js +25 -3
- package/src/server/conversation/fileStorage.js +11 -6
- package/src/server/conversation/hookBridge.js +14 -3
- package/src/server/conversation/index.js +1 -1
- package/src/server/conversation/localExecution.js +82 -625
- package/src/server/conversation/nativeCommandCallbacks.js +10 -0
- package/src/server/conversation/nativeConversationLifecycle.js +379 -0
- package/src/server/conversation/nativeHistoryExport.js +27 -18
- package/src/server/conversation/normalize.js +9 -0
- package/src/server/conversation/openCodeClient.js +295 -366
- package/src/server/conversation/openCodeNativeHistory.js +141 -0
- package/src/server/conversation/openCodeNativeSession.js +79 -0
- package/src/server/conversation/openCodeProcess.js +109 -53
- package/src/server/conversation/openCodeProcessOwner.js +565 -0
- package/src/server/conversation/openCodeRuntime.js +111 -1814
- package/src/server/conversation/openCodeSessionTurns.js +547 -0
- package/src/server/conversation/openCodeTemporaryConversations.js +394 -0
- package/src/server/conversation/openCodeTools.js +28 -14
- package/src/server/conversation/openCodeTurn.js +80 -74
- package/src/server/conversation/openCodeTurnMonitor.js +388 -0
- package/src/server/conversation/processIdentity.js +582 -0
- package/src/server/conversation/provider.js +21 -6
- package/src/server/conversation/providers/api.js +268 -107
- package/src/server/conversation/providers/claude.js +29 -6
- package/src/server/conversation/providers/claudeDriver.js +325 -250
- package/src/server/conversation/providers/codex.js +69 -33
- package/src/server/conversation/providers/codexDriver.js +503 -1081
- package/src/server/conversation/providers/codexDriverConnection.js +331 -0
- package/src/server/conversation/providers/codexDriverTurns.js +464 -0
- package/src/server/conversation/providers/opencode.js +30 -4
- package/src/server/conversation/providers/opencodeDriver.js +321 -659
- package/src/server/conversation/providers/opencodeStandalone.js +475 -0
- package/src/server/conversation/providers/scoped.js +64 -19
- package/src/server/conversation/runtime.js +171 -2863
- package/src/server/conversation/storage.js +91 -50
- package/src/server/conversation/streams.js +15 -2
- package/src/server/conversation/structuredOutput.js +7 -11
- package/src/server/conversation/systemPrompt.js +13 -1
- package/src/server/conversation/tools.js +47 -15
- package/src/server/conversation/transcript.js +49 -20
- package/src/server/hosts/claudeTurn.js +2 -4
- package/src/server/hosts/codexConfiguration.js +6 -8
- package/src/server/hosts/codexEvents.js +4 -2
- package/src/server/hosts/codexProcess.js +1 -1
- package/src/server/hosts/codexProvider.js +11 -9
- package/src/server/hosts/codexTurn.js +3 -3
- package/src/server/hosts/openCodeClient.js +4 -2
- package/src/server/index.js +0 -2
- package/src/server/lib/aiConnectionClient.js +35 -25
- package/src/server/lib/ndjson.js +1 -0
- package/src/server/lib/providers/anthropicClient.js +15 -6
- package/src/server/lib/providers/common.js +10 -6
- package/src/server/lib/providers/openAiCompatibleClient.js +40 -23
- package/src/server/lib/serviceToolCatalog.js +29 -283
- package/src/server/lib/serviceToolEntries.js +270 -0
- package/src/server/types.js +78 -0
- package/src/shared/assistantResource.js +0 -23
- package/src/shared/conversation/answerChoiceSugar.js +2 -14
- package/src/shared/conversation/messageInputSugar.js +18 -0
- package/src/shared/conversation/numberedQuestionSugar.js +6 -16
- package/src/shared/conversation/pagination.js +1 -1
- package/src/shared/conversation/questions.js +1 -0
- package/src/shared/conversation/streaming.js +1 -0
- package/src/shared/index.js +0 -1
- package/src/testing/conversationStorageContract.js +20 -0
- package/test/aiConnectionClient.test.js +42 -1
- package/test/apiConversationDriver.test.js +217 -38
- package/test/assistantBoundedToolLoop.test.js +1 -1
- package/test/claudeConversationSession.test.js +15 -11
- package/test/claudeInstructionResume.test.js +1 -1
- package/test/claudeProcess.test.js +9 -9
- package/test/claudeTurn.test.js +21 -8
- package/test/codexAccountChange.test.js +5 -3
- package/test/codexConversationSession.test.js +22 -21
- package/test/codexEvents.test.js +3 -1
- package/test/codexHelperDeletion.test.js +3 -7
- package/test/codexIsolation.test.js +6 -6
- package/test/codexModelCatalog.test.js +6 -7
- package/test/codexProcess.test.js +3 -3
- package/test/codexThreadSelection.test.js +4 -1
- package/test/codexTurn.test.js +29 -20
- package/test/conversationApiFacilities.test.js +11 -9
- package/test/conversationAttachments.test.js +3 -3
- package/test/conversationConfiguration.test.js +4 -4
- package/test/conversationNativeStorage.test.js +11 -10
- package/test/conversationReplacement.test.js +13 -13
- package/test/conversationRuntime.test.js +9 -5
- package/test/conversationSession.test.js +78 -22
- package/test/conversationTools.test.js +47 -26
- package/test/fileConversationStorage.test.js +10 -3
- package/test/fixtures/codexHelperThreadLedger.js +3 -3
- package/test/frameworkActions.test.js +33 -18
- package/test/localConversationExecution.test.js +4 -4
- package/test/localExecution.test.js +1 -1
- package/test/openCodeConnectionChange.test.js +3 -2
- package/test/openCodeRuntime.test.js +8 -7
- package/test/openCodeTools.test.js +1 -1
- package/test/opencodeConversationSession.test.js +4 -3
- package/test/serviceToolCatalog.test.js +3 -8
- package/src/server/lib/assistantCompletion.js +0 -190
- package/src/server/lib/assistantToolLoop.js +0 -259
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
// What a native command does when its caller leaves out one of its optional callbacks.
|
|
2
|
+
|
|
3
|
+
/** A command whose caller records no delivery timing for a receipt: the native acknowledgement or its persisted copy. */
|
|
4
|
+
export function ignoreReceipt() {}
|
|
5
|
+
|
|
6
|
+
/** A command whose caller has nothing to do before its prompt is sent. */
|
|
7
|
+
export function ignorePromptSending() {}
|
|
8
|
+
|
|
9
|
+
/** A command whose caller keeps no record of a rejected prompt. */
|
|
10
|
+
export function ignorePromptRejection() {}
|
|
@@ -0,0 +1,379 @@
|
|
|
1
|
+
import { failure } from "./conversationRefusals.js";
|
|
2
|
+
import { serial } from "./conversationEvents.js";
|
|
3
|
+
|
|
4
|
+
// readable-check: allow method-probe - input validation: a host preparation that is a promise is refused, since these operations prepare synchronously
|
|
5
|
+
const isThenable = value => typeof value.then === "function";
|
|
6
|
+
|
|
7
|
+
// readable-check: allow method-probe - optional operation: each native lifecycle method is optional in the conversation driver contract, and an engine without it refuses
|
|
8
|
+
const implementsOperation = (driver, name) => typeof driver[name] === "function";
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Native operations that use the configured host and its engine's driver directly, without a
|
|
12
|
+
* retained conversation handle: activity, renewal, settings, readiness, creation, detached
|
|
13
|
+
* runs, project and runtime lifecycle, session reconciliation, native history storage and
|
|
14
|
+
* the cleanup of a conversation that is not open.
|
|
15
|
+
* @param {object} dependencies
|
|
16
|
+
* @param {import("../types.js").ConversationAuthorize} dependencies.authorize
|
|
17
|
+
* @param {import("../types.js").ConversationHost | undefined} dependencies.defaultHost
|
|
18
|
+
* @param {boolean} dependencies.hostPrepares Whether the configured host prepares native conversations.
|
|
19
|
+
* @param {import("./provider.js").ConversationProviderFactory} dependencies.providers
|
|
20
|
+
* @param {import("./conversationEntries.js").ConversationEntries} dependencies.entries
|
|
21
|
+
* @param {import("./conversationEvents.js").ConversationEvents} dependencies.events
|
|
22
|
+
* @param {import("./conversationStopping.js").ConversationStopping} dependencies.stopping
|
|
23
|
+
* @param {() => void} dependencies.assertRuntimeOpen Refuses once the runtime has closed.
|
|
24
|
+
*/
|
|
25
|
+
function createNativeConversationLifecycle({ authorize, defaultHost, hostPrepares, providers, entries, events, stopping, assertRuntimeOpen }) {
|
|
26
|
+
// Activity can still be inspected while cleanup retries after runtime closure.
|
|
27
|
+
// Use the configured owner without retaining or creating a Main conversation.
|
|
28
|
+
async function inspectNativeTemporaryActivity({ id, context }) {
|
|
29
|
+
if (!await authorize({ context, conversationId: id, operation: "inspectTemporaryActivity" })) {
|
|
30
|
+
throw failure("This conversation is not available to this identity.", "conversation_forbidden", 403);
|
|
31
|
+
}
|
|
32
|
+
if (!hostPrepares) throw new TypeError("Native activity requires a configured conversation host.");
|
|
33
|
+
const prepared = await defaultHost.conversation({ id, context, operation: "inspectTemporaryActivity" });
|
|
34
|
+
if (prepared?.sessionId !== id || !["codex", "claude", "opencode"].includes(prepared.engine) ||
|
|
35
|
+
!prepared.native || !Object.hasOwn(prepared, "context")) {
|
|
36
|
+
throw new TypeError("Native activity requires its authorized host preparation.");
|
|
37
|
+
}
|
|
38
|
+
return providers.createDriver(prepared.engine, defaultHost).inspectTemporaryActivity(prepared);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// Renewal cleanup keeps its existing trusted server authority after closure.
|
|
42
|
+
// Neither proof release opens a retained conversation or admits inference.
|
|
43
|
+
async function releaseNativeRenewalPredecessorProcessExitProof({ id, context, input = {} }) {
|
|
44
|
+
if (!hostPrepares) {
|
|
45
|
+
throw new TypeError("Native renewal proof release requires a configured conversation host.");
|
|
46
|
+
}
|
|
47
|
+
const prepared = defaultHost.conversation({ id, context, input, operation: "releaseRenewalPredecessorProcessExitProof" });
|
|
48
|
+
if (!prepared || isThenable(prepared) || prepared.sessionId !== id ||
|
|
49
|
+
!["codex", "claude", "opencode"].includes(prepared.engine) || !prepared.native ||
|
|
50
|
+
!Object.hasOwn(prepared, "input") || !Object.hasOwn(prepared, "context")) {
|
|
51
|
+
throw new TypeError("Native renewal proof release requires synchronous configured host preparation.");
|
|
52
|
+
}
|
|
53
|
+
const driver = providers.createDriver(prepared.engine, defaultHost);
|
|
54
|
+
if (!implementsOperation(driver, "releaseRenewalPredecessorProcessExitProof")) {
|
|
55
|
+
throw new TypeError("The selected conversation driver does not implement native renewal proof release.");
|
|
56
|
+
}
|
|
57
|
+
return driver.releaseRenewalPredecessorProcessExitProof(prepared);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
async function releaseNativeRenewalSuccessorProcessExitProof({ id, context, input = {} }) {
|
|
61
|
+
if (!hostPrepares) {
|
|
62
|
+
throw new TypeError("Native renewal proof release requires a configured conversation host.");
|
|
63
|
+
}
|
|
64
|
+
const prepared = defaultHost.conversation({ id, context, input, operation: "releaseRenewalSuccessorProcessExitProof" });
|
|
65
|
+
if (!prepared || isThenable(prepared) || prepared.sessionId !== id ||
|
|
66
|
+
!["codex", "claude", "opencode"].includes(prepared.engine) || !prepared.native ||
|
|
67
|
+
!Object.hasOwn(prepared, "input") || !Object.hasOwn(prepared, "context")) {
|
|
68
|
+
throw new TypeError("Native renewal proof release requires synchronous configured host preparation.");
|
|
69
|
+
}
|
|
70
|
+
const driver = providers.createDriver(prepared.engine, defaultHost);
|
|
71
|
+
if (!implementsOperation(driver, "releaseRenewalSuccessorProcessExitProof")) {
|
|
72
|
+
throw new TypeError("The selected conversation driver does not implement native renewal proof release.");
|
|
73
|
+
}
|
|
74
|
+
return driver.releaseRenewalSuccessorProcessExitProof(prepared);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// Renewal retains the application's existing session and private successor transaction.
|
|
78
|
+
async function generateNativeRenewalHandover({ id, context, input = {} }) {
|
|
79
|
+
if (typeof id !== "string" || !id.trim()) throw new TypeError("A parent conversation or scope id is required.");
|
|
80
|
+
await events.access(context, id, "generateRenewalHandover");
|
|
81
|
+
if (!hostPrepares) {
|
|
82
|
+
throw new TypeError("Native renewal requires a configured conversation host.");
|
|
83
|
+
}
|
|
84
|
+
const prepared = await defaultHost.conversation({ id, context, input, operation: "generateRenewalHandover" });
|
|
85
|
+
assertRuntimeOpen();
|
|
86
|
+
if (prepared?.sessionId !== id || !["codex", "claude", "opencode"].includes(prepared.engine) || !prepared.native ||
|
|
87
|
+
!Object.hasOwn(prepared, "input") || !Object.hasOwn(prepared, "context")) {
|
|
88
|
+
throw new TypeError("Native renewal requires its authorized host preparation.");
|
|
89
|
+
}
|
|
90
|
+
return providers.createDriver(prepared.engine, defaultHost).generateRenewalHandover(prepared);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// Renewal retains the application's existing session and private successor transaction.
|
|
94
|
+
async function seedNativeRenewalHandover({ id, context, input = {} }) {
|
|
95
|
+
if (typeof id !== "string" || !id.trim()) throw new TypeError("A parent conversation or scope id is required.");
|
|
96
|
+
await events.access(context, id, "seedRenewalHandover");
|
|
97
|
+
if (!hostPrepares) {
|
|
98
|
+
throw new TypeError("Native renewal requires a configured conversation host.");
|
|
99
|
+
}
|
|
100
|
+
const prepared = await defaultHost.conversation({ id, context, input, operation: "seedRenewalHandover" });
|
|
101
|
+
assertRuntimeOpen();
|
|
102
|
+
if (prepared?.sessionId !== id || !["codex", "claude", "opencode"].includes(prepared.engine) || !prepared.native ||
|
|
103
|
+
!Object.hasOwn(prepared, "input") || !Object.hasOwn(prepared, "context")) {
|
|
104
|
+
throw new TypeError("Native renewal requires its authorized host preparation.");
|
|
105
|
+
}
|
|
106
|
+
return providers.createDriver(prepared.engine, defaultHost).seedRenewalHandover(prepared);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
async function updateNativeConversationSettings({ id, context, input = {} }) {
|
|
110
|
+
if (typeof id !== "string" || !id.trim()) throw new TypeError("A conversation id is required.");
|
|
111
|
+
await events.access(context, id, "configure");
|
|
112
|
+
assertRuntimeOpen();
|
|
113
|
+
const prepared = hostPrepares ? await defaultHost.conversation({ id, context, input, operation: "updateSettings" }) : undefined;
|
|
114
|
+
if (prepared?.sessionId !== id || !prepared.native || !Object.hasOwn(prepared, "context")) {
|
|
115
|
+
throw new TypeError("Native settings require their authorized host preparation.");
|
|
116
|
+
}
|
|
117
|
+
const driver = providers.createDriver(prepared.engine, defaultHost);
|
|
118
|
+
if (!implementsOperation(driver, "updateConversationSettings")) {
|
|
119
|
+
throw failure("This provider does not support live native settings.", "conversation_live_settings_unsupported", 409, { messageCode: "assistant.conversation.liveSettingsUnsupported" });
|
|
120
|
+
}
|
|
121
|
+
assertRuntimeOpen();
|
|
122
|
+
return driver.updateConversationSettings({ ...prepared, input });
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
// Readiness uses the same authorized host and native owner without
|
|
126
|
+
// opening a retained handle or starting a conversational turn.
|
|
127
|
+
async function ensureNativeConversation({ id, context }) {
|
|
128
|
+
if (typeof id !== "string" || !id.trim()) throw new TypeError("A parent conversation or scope id is required.");
|
|
129
|
+
await events.access(context, id, "ensure");
|
|
130
|
+
if (!hostPrepares) {
|
|
131
|
+
throw new TypeError("Native conversation readiness requires a configured conversation host.");
|
|
132
|
+
}
|
|
133
|
+
const prepared = await defaultHost.conversation({ id, context, operation: "ensure" });
|
|
134
|
+
assertRuntimeOpen();
|
|
135
|
+
if (prepared?.sessionId !== id || !["codex", "claude", "opencode"].includes(prepared.engine) ||
|
|
136
|
+
!prepared.native || !Object.hasOwn(prepared, "context")) {
|
|
137
|
+
throw new TypeError("Native conversation readiness requires its authorized host preparation.");
|
|
138
|
+
}
|
|
139
|
+
const driver = providers.createDriver(prepared.engine, defaultHost);
|
|
140
|
+
if (!implementsOperation(driver, "ensureConversation")) {
|
|
141
|
+
throw new TypeError("The selected conversation driver does not implement native readiness.");
|
|
142
|
+
}
|
|
143
|
+
return driver.ensureConversation(prepared);
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
/**
|
|
147
|
+
* Advanced hosts create a native identity before a retained handle exists.
|
|
148
|
+
* Only the configured host supplies native facilities; caller input is data.
|
|
149
|
+
* @param {{ id?: string, context?: object, input?: object }} [request]
|
|
150
|
+
*/
|
|
151
|
+
async function createNativeConversation({ id, context, input = {} }) {
|
|
152
|
+
if (typeof id !== "string" || !id.trim()) throw new TypeError("A parent conversation or scope id is required.");
|
|
153
|
+
await events.access(context, id, "create");
|
|
154
|
+
if (!hostPrepares) {
|
|
155
|
+
throw new TypeError("Native conversation creation requires a configured conversation host.");
|
|
156
|
+
}
|
|
157
|
+
const prepared = await defaultHost.conversation({ id, context, input, operation: "create" });
|
|
158
|
+
assertRuntimeOpen();
|
|
159
|
+
if (prepared?.sessionId !== id || !["codex", "claude", "opencode"].includes(prepared.engine) || !prepared.native ||
|
|
160
|
+
!Object.hasOwn(prepared, "input") || !Object.hasOwn(prepared, "context")) {
|
|
161
|
+
throw new TypeError("Native conversation creation requires its authorized host preparation.");
|
|
162
|
+
}
|
|
163
|
+
return providers.createDriver(prepared.engine, defaultHost).createConversation(prepared);
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
async function runNativeDetachedConversation({ id, context, input = {}, options }) {
|
|
167
|
+
if (typeof id !== "string" || !id.trim()) throw new TypeError("A parent conversation or scope id is required.");
|
|
168
|
+
await events.access(context, id, "runDetachedConversation");
|
|
169
|
+
if (!hostPrepares) {
|
|
170
|
+
throw new TypeError("Native detached execution requires a configured conversation host.");
|
|
171
|
+
}
|
|
172
|
+
const prepared = await defaultHost.conversation({ id, context, input,
|
|
173
|
+
...(options === undefined ? {} : { options }), operation: "runDetachedConversation" });
|
|
174
|
+
assertRuntimeOpen();
|
|
175
|
+
if (prepared?.sessionId !== id || !["codex", "claude", "opencode"].includes(prepared.engine) || !prepared.native ||
|
|
176
|
+
!Object.hasOwn(prepared, "input") || !Object.hasOwn(prepared, "context") || !Object.hasOwn(prepared, "options")) {
|
|
177
|
+
throw new TypeError("Native detached execution requires its authorized host preparation.");
|
|
178
|
+
}
|
|
179
|
+
return providers.createDriver(prepared.engine, defaultHost).runDetachedConversation(prepared);
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
// Trusted server lifecycle, like close(): the application has already
|
|
183
|
+
// authorized its project operation. Never expose this through browser routes.
|
|
184
|
+
// Acquisition is synchronous and creates no retained conversation or queue.
|
|
185
|
+
async function closeNativeProject({ context, input = {} }) {
|
|
186
|
+
if (!hostPrepares) {
|
|
187
|
+
throw new TypeError("Native project cleanup requires a configured conversation host.");
|
|
188
|
+
}
|
|
189
|
+
const prepared = defaultHost.conversation({ context, input, operation: "closeProject" });
|
|
190
|
+
if (!prepared || isThenable(prepared) ||
|
|
191
|
+
!["codex", "claude", "opencode"].includes(prepared.engine) || !prepared.native ||
|
|
192
|
+
!Object.hasOwn(prepared, "input") || !Object.hasOwn(prepared, "context")) {
|
|
193
|
+
throw new TypeError("Native project cleanup requires synchronous configured host preparation.");
|
|
194
|
+
}
|
|
195
|
+
const driver = providers.createDriver(prepared.engine, defaultHost);
|
|
196
|
+
if (!implementsOperation(driver, "closeProject")) {
|
|
197
|
+
throw new TypeError("The selected conversation driver does not implement native project cleanup.");
|
|
198
|
+
}
|
|
199
|
+
return driver.closeProject(prepared);
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
// Account revocation and shutdown use this same trusted lifecycle boundary.
|
|
203
|
+
// The configured owner enters its native closing fence before its first await.
|
|
204
|
+
function invalidateNativeRuntimes({ context, input = {} }) {
|
|
205
|
+
if (!hostPrepares) {
|
|
206
|
+
throw new TypeError("Native runtime invalidation requires a configured conversation host.");
|
|
207
|
+
}
|
|
208
|
+
const prepared = defaultHost.conversation({ context, input, operation: "invalidateRuntimes" });
|
|
209
|
+
if (!prepared || isThenable(prepared) ||
|
|
210
|
+
!["codex", "claude", "opencode"].includes(prepared.engine) || !prepared.native ||
|
|
211
|
+
!Object.hasOwn(prepared, "input") || !Object.hasOwn(prepared, "context")) {
|
|
212
|
+
throw new TypeError("Native runtime invalidation requires synchronous configured host preparation.");
|
|
213
|
+
}
|
|
214
|
+
const driver = providers.createDriver(prepared.engine, defaultHost);
|
|
215
|
+
if (!implementsOperation(driver, "invalidateRuntimes")) {
|
|
216
|
+
throw new TypeError("The selected conversation driver does not implement native runtime invalidation.");
|
|
217
|
+
}
|
|
218
|
+
return driver.invalidateRuntimes(prepared);
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
// Startup reconciliation and subscription reset use the existing trusted
|
|
222
|
+
// lifecycle caller. Native owners retain their own shutdown fences and tasks.
|
|
223
|
+
function reconcileNativeSessions({ context, sessions, options = {} }) {
|
|
224
|
+
if (!hostPrepares) {
|
|
225
|
+
throw new TypeError("Native reconciliation requires a configured conversation host.");
|
|
226
|
+
}
|
|
227
|
+
const prepared = defaultHost.conversation({ context, input: sessions, options, operation: "reconcileSessions" });
|
|
228
|
+
if (!prepared || isThenable(prepared) ||
|
|
229
|
+
!["codex", "claude", "opencode"].includes(prepared.engine) || !prepared.native ||
|
|
230
|
+
!Object.hasOwn(prepared, "sessions") || !Object.hasOwn(prepared, "options") ||
|
|
231
|
+
!Object.hasOwn(prepared, "context")) {
|
|
232
|
+
throw new TypeError("Native reconciliation requires synchronous configured host preparation.");
|
|
233
|
+
}
|
|
234
|
+
const driver = providers.createDriver(prepared.engine, defaultHost);
|
|
235
|
+
if (!implementsOperation(driver, "reconcileSessions")) {
|
|
236
|
+
throw new TypeError("The selected conversation driver does not implement native reconciliation.");
|
|
237
|
+
}
|
|
238
|
+
return driver.reconcileSessions(prepared);
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
function unsubscribeNativeSessions({ context, sessions }) {
|
|
242
|
+
if (!hostPrepares) {
|
|
243
|
+
throw new TypeError("Native subscription reset requires a configured conversation host.");
|
|
244
|
+
}
|
|
245
|
+
const prepared = defaultHost.conversation({ context, input: sessions, operation: "unsubscribeSessions" });
|
|
246
|
+
if (!prepared || isThenable(prepared) ||
|
|
247
|
+
!["codex", "claude", "opencode"].includes(prepared.engine) || !prepared.native ||
|
|
248
|
+
!Object.hasOwn(prepared, "sessions") || !Object.hasOwn(prepared, "context")) {
|
|
249
|
+
throw new TypeError("Native subscription reset requires synchronous configured host preparation.");
|
|
250
|
+
}
|
|
251
|
+
const driver = providers.createDriver(prepared.engine, defaultHost);
|
|
252
|
+
if (!implementsOperation(driver, "unsubscribeSessions")) {
|
|
253
|
+
throw new TypeError("The selected conversation driver does not implement native subscription reset.");
|
|
254
|
+
}
|
|
255
|
+
return driver.unsubscribeSessions(prepared);
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
// Historical cleanup has a saved native identity but no retained scope.
|
|
259
|
+
// Recheck its original caller authority; native owners retain shutdown guards.
|
|
260
|
+
async function interruptNativeDetachedConversation({ id, context, input = {} }) {
|
|
261
|
+
if (!await authorize({ context, conversationId: id, operation: "interruptDetachedConversation" })) {
|
|
262
|
+
throw failure("This conversation is not available to this identity.", "conversation_forbidden", 403);
|
|
263
|
+
}
|
|
264
|
+
if (!hostPrepares) {
|
|
265
|
+
throw new TypeError("Historical native cleanup requires a configured conversation host.");
|
|
266
|
+
}
|
|
267
|
+
const prepared = await defaultHost.conversation({ id, context, input, operation: "interruptDetachedConversation" });
|
|
268
|
+
if (prepared?.sessionId !== id || !["codex", "claude", "opencode"].includes(prepared.engine) || !prepared.native ||
|
|
269
|
+
!Object.hasOwn(prepared, "input") || !Object.hasOwn(prepared, "context")) {
|
|
270
|
+
throw new TypeError("Historical native cleanup requires its authorized host preparation.");
|
|
271
|
+
}
|
|
272
|
+
return providers.createDriver(prepared.engine, defaultHost).interruptDetachedConversation(prepared);
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
async function deleteNativeDetachedConversation({ id, context, input = {} }) {
|
|
276
|
+
if (!await authorize({ context, conversationId: id, operation: "deleteDetachedConversation" })) {
|
|
277
|
+
throw failure("This conversation is not available to this identity.", "conversation_forbidden", 403);
|
|
278
|
+
}
|
|
279
|
+
if (!hostPrepares) {
|
|
280
|
+
throw new TypeError("Historical native cleanup requires a configured conversation host.");
|
|
281
|
+
}
|
|
282
|
+
const prepared = await defaultHost.conversation({ id, context, input, operation: "deleteDetachedConversation" });
|
|
283
|
+
if (prepared?.sessionId !== id || !["codex", "claude", "opencode"].includes(prepared.engine) || !prepared.native ||
|
|
284
|
+
!Object.hasOwn(prepared, "input") || !Object.hasOwn(prepared, "context")) {
|
|
285
|
+
throw new TypeError("Historical native cleanup requires its authorized host preparation.");
|
|
286
|
+
}
|
|
287
|
+
return providers.createDriver(prepared.engine, defaultHost).deleteDetachedConversation(prepared);
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
// Trusted server storage operations retain the saved engine and preservation
|
|
291
|
+
// policy. They neither authorize inference nor acquire a retained handle.
|
|
292
|
+
async function listNativeConversationStorage({ id, context, binding }) {
|
|
293
|
+
if (!hostPrepares) {
|
|
294
|
+
throw new TypeError("Native history storage requires a configured conversation host.");
|
|
295
|
+
}
|
|
296
|
+
const prepared = await defaultHost.conversation({ id, context, input: binding, operation: "listNativeConversationStorage" });
|
|
297
|
+
if (prepared?.sessionId !== id || prepared.binding !== binding || prepared.engine !== binding?.engineId ||
|
|
298
|
+
!["codex", "claude", "opencode"].includes(prepared.engine) || !prepared.native ||
|
|
299
|
+
!Object.hasOwn(prepared, "context")) {
|
|
300
|
+
throw new TypeError("Native history storage requires its saved binding and configured host preparation.");
|
|
301
|
+
}
|
|
302
|
+
return providers.createDriver(prepared.engine, defaultHost).listConversationStorage(prepared);
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
async function retireNativeConversationHistory({ id, context, binding }) {
|
|
306
|
+
if (!hostPrepares) {
|
|
307
|
+
throw new TypeError("Native history storage requires a configured conversation host.");
|
|
308
|
+
}
|
|
309
|
+
const prepared = await defaultHost.conversation({ id, context, input: binding, operation: "retireConversationHistory" });
|
|
310
|
+
if (prepared?.sessionId !== id || prepared.binding !== binding || prepared.engine !== binding?.engineId ||
|
|
311
|
+
!["codex", "claude", "opencode"].includes(prepared.engine) || !prepared.native ||
|
|
312
|
+
!Object.hasOwn(prepared, "context")) {
|
|
313
|
+
throw new TypeError("Native history storage requires its saved binding and configured host preparation.");
|
|
314
|
+
}
|
|
315
|
+
return providers.createDriver(prepared.engine, defaultHost).retireConversationHistory(prepared);
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
// Cleanup may retry after runtime closure. Missing or unready retained
|
|
319
|
+
// handles use the configured native owner without opening a Main record.
|
|
320
|
+
async function disposeNative({ namespace, sessionId, context, options }) {
|
|
321
|
+
const authorizeCleanup = async () => {
|
|
322
|
+
if (!await authorize({ context, conversationId: sessionId, operation: "dispose" })) {
|
|
323
|
+
throw failure("This conversation is not available to this identity.", "conversation_forbidden", 403);
|
|
324
|
+
}
|
|
325
|
+
};
|
|
326
|
+
const cleanupUnopened = async () => {
|
|
327
|
+
if (!hostPrepares) return null;
|
|
328
|
+
await authorizeCleanup();
|
|
329
|
+
const prepared = await defaultHost.conversation({ id: sessionId, context, input: options, operation: "dispose" });
|
|
330
|
+
if (prepared?.sessionId !== sessionId || prepared.namespace !== namespace ||
|
|
331
|
+
!["codex", "claude", "opencode"].includes(prepared.engine) || !prepared.native ||
|
|
332
|
+
!Object.hasOwn(prepared, "context") || !Object.hasOwn(prepared, "options")) {
|
|
333
|
+
throw new TypeError("Native cleanup requires its authorized host preparation.");
|
|
334
|
+
}
|
|
335
|
+
return { result: await providers.createDriver(prepared.engine, defaultHost).disposeNative(prepared) };
|
|
336
|
+
};
|
|
337
|
+
const entry = entries.find(namespace);
|
|
338
|
+
if (!entry || entry.disposed) return cleanupUnopened();
|
|
339
|
+
if (entry.id !== sessionId || !entry.conversation) {
|
|
340
|
+
throw failure("Native cleanup requires its original bound conversation.", "conversation_cleanup_needs_original_conversation", 409, { messageCode: "assistant.conversation.cleanupNeedsOriginalConversation" });
|
|
341
|
+
}
|
|
342
|
+
await authorizeCleanup();
|
|
343
|
+
const retained = await serial(entry, async () => {
|
|
344
|
+
if (entry.disposed) return null;
|
|
345
|
+
await authorizeCleanup();
|
|
346
|
+
if (!entry.provider) {
|
|
347
|
+
entries.retire(entry);
|
|
348
|
+
return null;
|
|
349
|
+
}
|
|
350
|
+
return { result: await stopping.disposeEntry(entry, context, options, "native") };
|
|
351
|
+
});
|
|
352
|
+
return retained || cleanupUnopened();
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
return Object.freeze({
|
|
356
|
+
inspectNativeTemporaryActivity,
|
|
357
|
+
releaseNativeRenewalPredecessorProcessExitProof,
|
|
358
|
+
releaseNativeRenewalSuccessorProcessExitProof,
|
|
359
|
+
generateNativeRenewalHandover,
|
|
360
|
+
seedNativeRenewalHandover,
|
|
361
|
+
updateNativeConversationSettings,
|
|
362
|
+
ensureNativeConversation,
|
|
363
|
+
createNativeConversation,
|
|
364
|
+
runNativeDetachedConversation,
|
|
365
|
+
closeNativeProject,
|
|
366
|
+
invalidateNativeRuntimes,
|
|
367
|
+
reconcileNativeSessions,
|
|
368
|
+
unsubscribeNativeSessions,
|
|
369
|
+
interruptNativeDetachedConversation,
|
|
370
|
+
deleteNativeDetachedConversation,
|
|
371
|
+
listNativeConversationStorage,
|
|
372
|
+
retireNativeConversationHistory,
|
|
373
|
+
disposeNative
|
|
374
|
+
});
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
/** @typedef {ReturnType<typeof createNativeConversationLifecycle>} NativeConversationLifecycle */
|
|
378
|
+
|
|
379
|
+
export { createNativeConversationLifecycle };
|
|
@@ -106,28 +106,37 @@ export function canonicalNativeHistoryJson(value) {
|
|
|
106
106
|
// Providers own record projection; this small sink owns the common byte bound,
|
|
107
107
|
// callback backpressure and exact content revision used before retirement.
|
|
108
108
|
export function createNativeHistoryExport(onRecord, { signal, maxBytes = 2 * 1024 ** 3 } = {}) {
|
|
109
|
+
// readable-check: allow method-probe - input validation: the provider passes the callback each exported record goes to
|
|
109
110
|
if (typeof onRecord !== "function") throw new TypeError("Native export requires a record callback.");
|
|
110
111
|
const digest = createHash("sha256");
|
|
111
112
|
let bytes = 0;
|
|
112
113
|
let recordCount = 0;
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
}
|
|
130
|
-
}
|
|
114
|
+
|
|
115
|
+
async function emit(record) {
|
|
116
|
+
signal?.throwIfAborted();
|
|
117
|
+
const serialized = `${canonicalNativeHistoryJson(record)}\n`;
|
|
118
|
+
bytes += Buffer.byteLength(serialized);
|
|
119
|
+
if (bytes > maxBytes) throw new Error("Native export exceeded its byte limit; history was not retired.");
|
|
120
|
+
digest.update(serialized);
|
|
121
|
+
recordCount++;
|
|
122
|
+
// An admitted host callback may still publish under the archive lock.
|
|
123
|
+
// Cancellation must await its completion; the host owns cancelling its IO.
|
|
124
|
+
await onRecord(record);
|
|
125
|
+
signal?.throwIfAborted();
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
function complete(extra = {}) {
|
|
129
|
+
signal?.throwIfAborted();
|
|
130
|
+
return { revision: digest.digest("hex"), bytes, recordCount, ...extra };
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
return Object.freeze({
|
|
134
|
+
|
|
135
|
+
emit,
|
|
136
|
+
|
|
137
|
+
complete
|
|
138
|
+
|
|
139
|
+
});
|
|
131
140
|
}
|
|
132
141
|
|
|
133
142
|
// Shared by the three native owners. The host must durably preserve customer
|
|
@@ -10,3 +10,12 @@ export function normalizeNullableText(value) {
|
|
|
10
10
|
export function jsonHash(value) {
|
|
11
11
|
return createHash("sha256").update(JSON.stringify(value)).digest("hex");
|
|
12
12
|
}
|
|
13
|
+
|
|
14
|
+
/** Whether a value is an object literal or a null-prototype record, not an array or a class instance. */
|
|
15
|
+
export function isPlainRecord(value) {
|
|
16
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
17
|
+
return false;
|
|
18
|
+
}
|
|
19
|
+
const prototype = Object.getPrototypeOf(value);
|
|
20
|
+
return prototype === Object.prototype || prototype === null;
|
|
21
|
+
}
|