@jskit-ai/assistant-core 0.2.6 → 0.2.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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 +18 -12
- 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,465 @@
|
|
|
1
|
+
import {
|
|
2
|
+
codexAppServerOwnedRuntimeKey,
|
|
3
|
+
codexAppServerRuntimeStopWasVerified,
|
|
4
|
+
directoryExists,
|
|
5
|
+
withRuntimeLifecycle
|
|
6
|
+
} from "./codexRuntimeInventory.js";
|
|
7
|
+
import { normalizeText } from "@jskit-ai/kernel/shared/support/normalize";
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Stops cached providers and releases what sessions and projects leave behind.
|
|
11
|
+
* @param {object} dependencies
|
|
12
|
+
* @param {Map<string, object>} dependencies.records
|
|
13
|
+
* @param {import("./codexProviderOwner.js").CodexAppServerProviderOwner["providers"]} dependencies.providers
|
|
14
|
+
* @param {import("./codexProviderOwner.js").CodexAppServerProviderOwner["providerOwners"]} dependencies.providerOwners
|
|
15
|
+
* @param {import("./codexProviderOwner.js").CodexAppServerProviderOwner["ownedRuntimes"]} dependencies.ownedRuntimes
|
|
16
|
+
* @param {<T>(operation: () => T, providerKey?: string, providerOptions?: object | null) => Promise<Awaited<T>>} dependencies.withLifecycle
|
|
17
|
+
* @param {(providerKey: string, providerOptions: object) => boolean} dependencies.runtimeIsShared
|
|
18
|
+
* @param {(provider: object) => void} dependencies.forgetOwnedRuntime
|
|
19
|
+
* @param {(providerKey: string, options?: object) => void} dependencies.closeProvider
|
|
20
|
+
* @param {string} dependencies.requiredStopCode
|
|
21
|
+
* @param {string} dependencies.exitUnverifiedCode
|
|
22
|
+
* @param {string} dependencies.runtimeCloseError
|
|
23
|
+
* @param {(event: string, details: object) => void} dependencies.debugLog
|
|
24
|
+
* @param {(error: unknown) => unknown} dependencies.debugError
|
|
25
|
+
*/
|
|
26
|
+
export function createCodexProviderRelease({
|
|
27
|
+
records,
|
|
28
|
+
providers,
|
|
29
|
+
providerOwners,
|
|
30
|
+
ownedRuntimes,
|
|
31
|
+
withLifecycle,
|
|
32
|
+
runtimeIsShared,
|
|
33
|
+
forgetOwnedRuntime,
|
|
34
|
+
closeProvider,
|
|
35
|
+
requiredStopCode,
|
|
36
|
+
exitUnverifiedCode,
|
|
37
|
+
runtimeCloseError,
|
|
38
|
+
debugLog,
|
|
39
|
+
debugError
|
|
40
|
+
}) {
|
|
41
|
+
async function stopCachedProviderUnlocked(providerKey = "", {
|
|
42
|
+
preserveProcessExitProof = false,
|
|
43
|
+
requireStopped = false
|
|
44
|
+
} = {}) {
|
|
45
|
+
const normalizedProviderKey = normalizeText(providerKey);
|
|
46
|
+
const provider = providers.get(normalizedProviderKey);
|
|
47
|
+
if (!provider) {
|
|
48
|
+
closeProvider(normalizedProviderKey);
|
|
49
|
+
return {
|
|
50
|
+
providerKey: normalizedProviderKey,
|
|
51
|
+
stopped: false
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
// readable-check: allow optional-call - optional operation: a driver's resources have no retire step, and a borrowed provider has no resources
|
|
55
|
+
await records.get(normalizedProviderKey)?.resources?.retire?.(provider);
|
|
56
|
+
const sharedProcessRetained = runtimeIsShared(
|
|
57
|
+
normalizedProviderKey, providerOwners.get(normalizedProviderKey)?.providerOptions
|
|
58
|
+
);
|
|
59
|
+
if (sharedProcessRetained) {
|
|
60
|
+
closeProvider(normalizedProviderKey);
|
|
61
|
+
return {
|
|
62
|
+
providerKey: normalizedProviderKey,
|
|
63
|
+
sessionDetached: true,
|
|
64
|
+
sharedProcessRetained: true,
|
|
65
|
+
stopped: false
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
// readable-check: allow method-probe - safety invariant: a provider that cannot stop would leave its process running unverified
|
|
69
|
+
if (typeof provider.stopRuntime !== "function") {
|
|
70
|
+
closeProvider(normalizedProviderKey);
|
|
71
|
+
throw new Error("Codex app-server provider must implement stopRuntime().");
|
|
72
|
+
}
|
|
73
|
+
let providerStoppedRuntime = false;
|
|
74
|
+
try {
|
|
75
|
+
const stoppedRuntime = await provider.stopRuntime({
|
|
76
|
+
preserveProcessExitProof: preserveProcessExitProof || requireStopped
|
|
77
|
+
});
|
|
78
|
+
providerStoppedRuntime = codexAppServerRuntimeStopWasVerified(stoppedRuntime);
|
|
79
|
+
if (requireStopped && !providerStoppedRuntime) {
|
|
80
|
+
const error = new Error("Codex app-server process exit could not be verified.");
|
|
81
|
+
error.code = requiredStopCode;
|
|
82
|
+
error.retryable = true;
|
|
83
|
+
throw error;
|
|
84
|
+
}
|
|
85
|
+
if (providerStoppedRuntime) {
|
|
86
|
+
forgetOwnedRuntime(provider);
|
|
87
|
+
}
|
|
88
|
+
return {
|
|
89
|
+
...(stoppedRuntime && typeof stoppedRuntime === "object" ? stoppedRuntime : {}),
|
|
90
|
+
providerKey: normalizedProviderKey,
|
|
91
|
+
stopped: providerStoppedRuntime
|
|
92
|
+
};
|
|
93
|
+
} finally {
|
|
94
|
+
closeProvider(normalizedProviderKey, {
|
|
95
|
+
closeProvider: !providerStoppedRuntime
|
|
96
|
+
});
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
function stopCachedProvider(providerKey = "", options = {}) {
|
|
101
|
+
return withLifecycle(
|
|
102
|
+
() => stopCachedProviderUnlocked(providerKey, options),
|
|
103
|
+
normalizeText(providerKey)
|
|
104
|
+
);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
function runtimeExitUnverifiedError(providerKey = "") {
|
|
108
|
+
const error = new Error("Codex app-server process exit could not be verified.");
|
|
109
|
+
error.code = exitUnverifiedCode;
|
|
110
|
+
error.providerKey = normalizeText(providerKey);
|
|
111
|
+
error.retryable = true;
|
|
112
|
+
return error;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
async function stopPreparedRuntime(context) {
|
|
116
|
+
const { runtime, parameters } = context.runtimeHost;
|
|
117
|
+
return runtime.stop(parameters);
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
async function stopProvider(providerKey, providerOptions, context = {}) {
|
|
121
|
+
const { preserveProcessExitProof = false, requireStopped = false } = context;
|
|
122
|
+
const provider = providers.get(providerKey);
|
|
123
|
+
if (!provider) {
|
|
124
|
+
if (runtimeIsShared(providerKey, providerOptions)) {
|
|
125
|
+
return {
|
|
126
|
+
providerKey,
|
|
127
|
+
sessionDetached: true,
|
|
128
|
+
sharedProcessRetained: true,
|
|
129
|
+
stopped: false
|
|
130
|
+
};
|
|
131
|
+
}
|
|
132
|
+
const stoppedRuntime = await stopPreparedRuntime(context);
|
|
133
|
+
const stopped = codexAppServerRuntimeStopWasVerified(stoppedRuntime);
|
|
134
|
+
if (requireStopped && !stopped) {
|
|
135
|
+
throw runtimeExitUnverifiedError(providerKey);
|
|
136
|
+
}
|
|
137
|
+
return {
|
|
138
|
+
...(Boolean(stoppedRuntime && typeof stoppedRuntime === "object" && !Array.isArray(stoppedRuntime)) ? stoppedRuntime : {}),
|
|
139
|
+
providerKey,
|
|
140
|
+
stopped
|
|
141
|
+
};
|
|
142
|
+
}
|
|
143
|
+
return stopCachedProvider(providerKey, {
|
|
144
|
+
preserveProcessExitProof,
|
|
145
|
+
requireStopped
|
|
146
|
+
});
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
async function stopThreadBeforeRelease(providerKey, provider, threadId) {
|
|
150
|
+
try {
|
|
151
|
+
await provider.stopThreadForObservationLoss(threadId, "");
|
|
152
|
+
} catch (error) {
|
|
153
|
+
// If other sessions retain the shared process, closing this
|
|
154
|
+
// socket alone is not proof that its native goal/work stopped.
|
|
155
|
+
const shared = runtimeIsShared(
|
|
156
|
+
providerKey, providerOwners.get(providerKey)?.providerOptions
|
|
157
|
+
);
|
|
158
|
+
if (shared) throw error;
|
|
159
|
+
// The caller's own shutdown proves process exit instead.
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
async function stopProvidersForProject({
|
|
164
|
+
preserveProcessExitProof = false,
|
|
165
|
+
projectContextRoot = "",
|
|
166
|
+
reason = "",
|
|
167
|
+
} = {}, { managed, keyFields, helperThreads, sessionKeyPrefix = "" }) {
|
|
168
|
+
const normalizedProjectContextRoot = normalizeText(projectContextRoot);
|
|
169
|
+
if (!normalizedProjectContextRoot) {
|
|
170
|
+
return {
|
|
171
|
+
failed: [],
|
|
172
|
+
ok: true,
|
|
173
|
+
projectContextRoot: normalizedProjectContextRoot,
|
|
174
|
+
providerCount: 0,
|
|
175
|
+
reason: normalizeText(reason),
|
|
176
|
+
results: [],
|
|
177
|
+
stopped: 0
|
|
178
|
+
};
|
|
179
|
+
}
|
|
180
|
+
const helperProviders = new Set(
|
|
181
|
+
helperThreads.records({
|
|
182
|
+
projectContextRoot: normalizedProjectContextRoot
|
|
183
|
+
}).map((record) => record.provider)
|
|
184
|
+
);
|
|
185
|
+
// Main threads name their project; every other provider of the project's
|
|
186
|
+
// sessions (Temporary chats, reads) is found by its session key.
|
|
187
|
+
const providerKeys = [...providers.keys()]
|
|
188
|
+
.filter((providerKey) => {
|
|
189
|
+
const current = managed.get(providerKey);
|
|
190
|
+
return normalizeText(current?.projectContext?.targetRoot) === normalizedProjectContextRoot ||
|
|
191
|
+
helperProviders.has(providers.get(providerKey)) ||
|
|
192
|
+
Boolean(sessionKeyPrefix && normalizeText(providerOwners.get(providerKey)?.sessionKey).startsWith(sessionKeyPrefix));
|
|
193
|
+
});
|
|
194
|
+
const failed = [];
|
|
195
|
+
const results = [];
|
|
196
|
+
for (const providerKey of providerKeys) {
|
|
197
|
+
try {
|
|
198
|
+
const fields = keyFields(providerKey);
|
|
199
|
+
results.push(await stopCachedProvider(providerKey, {
|
|
200
|
+
preserveProcessExitProof: Boolean(
|
|
201
|
+
preserveProcessExitProof &&
|
|
202
|
+
fields.sessionId &&
|
|
203
|
+
fields.executionMode !== "helper"
|
|
204
|
+
)
|
|
205
|
+
}));
|
|
206
|
+
} catch (error) {
|
|
207
|
+
failed.push({
|
|
208
|
+
code: normalizeText(error?.code),
|
|
209
|
+
error: normalizeText(error?.error || error?.message || error) || runtimeCloseError,
|
|
210
|
+
providerKey,
|
|
211
|
+
retryable: error?.retryable === true
|
|
212
|
+
});
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
const stopped = results.filter((result) => result.stopped).length;
|
|
216
|
+
debugLog("appServerRuntime.closeProject.done", {
|
|
217
|
+
failedCount: failed.length,
|
|
218
|
+
providerCount: providerKeys.length,
|
|
219
|
+
projectContextRoot: normalizedProjectContextRoot,
|
|
220
|
+
reason: normalizeText(reason),
|
|
221
|
+
stopped
|
|
222
|
+
});
|
|
223
|
+
return {
|
|
224
|
+
failed,
|
|
225
|
+
ok: failed.length === 0,
|
|
226
|
+
projectContextRoot: normalizedProjectContextRoot,
|
|
227
|
+
providerCount: providerKeys.length,
|
|
228
|
+
reason: normalizeText(reason),
|
|
229
|
+
results,
|
|
230
|
+
stopped
|
|
231
|
+
};
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
async function stopCachedProvidersForSession(sessionKey = "", options = {}) {
|
|
235
|
+
if (!sessionKey) {
|
|
236
|
+
return {
|
|
237
|
+
failed: [],
|
|
238
|
+
ok: true,
|
|
239
|
+
providerCount: 0,
|
|
240
|
+
results: [],
|
|
241
|
+
stopped: 0
|
|
242
|
+
};
|
|
243
|
+
}
|
|
244
|
+
const providerKeys = [...providers.keys()]
|
|
245
|
+
.filter((providerKey) => (
|
|
246
|
+
providerOwners.get(providerKey)?.sessionKey === sessionKey
|
|
247
|
+
));
|
|
248
|
+
const failed = [];
|
|
249
|
+
const results = [];
|
|
250
|
+
for (const providerKey of providerKeys) {
|
|
251
|
+
try {
|
|
252
|
+
results.push(await stopCachedProvider(providerKey, options));
|
|
253
|
+
} catch (error) {
|
|
254
|
+
failed.push({
|
|
255
|
+
code: normalizeText(error?.code),
|
|
256
|
+
error: normalizeText(error?.error || error?.message || error) || runtimeCloseError,
|
|
257
|
+
providerKey,
|
|
258
|
+
retryable: error?.retryable === true
|
|
259
|
+
});
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
return {
|
|
263
|
+
failed,
|
|
264
|
+
ok: failed.length === 0,
|
|
265
|
+
providerCount: providerKeys.length,
|
|
266
|
+
results,
|
|
267
|
+
stopped: results.filter((result) => result.stopped).length
|
|
268
|
+
};
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
function drainThreadEnvironmentTasks(sessionKey) {
|
|
272
|
+
return Promise.all([...providers.entries()]
|
|
273
|
+
.filter(([key]) => providerOwners.get(key)?.sessionKey === sessionKey)
|
|
274
|
+
.flatMap(([, provider]) => [...(provider.threadEnvironmentTasks?.values() || [])])
|
|
275
|
+
.map((task) => task.catch(() => null)));
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
async function stopPersistedRuntime({ runtime, parameters }, runtimeDir) {
|
|
279
|
+
const result = await runtime.stop(parameters);
|
|
280
|
+
|
|
281
|
+
return {
|
|
282
|
+
...result,
|
|
283
|
+
runtimeDirExists: await directoryExists(runtimeDir),
|
|
284
|
+
verifiedStopped: result?.stopped === true ||
|
|
285
|
+
result?.processExitVerified === true ||
|
|
286
|
+
result?.runtimeDirRemoved === true
|
|
287
|
+
};
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
function sessionExitProof(sessionKey, context) {
|
|
291
|
+
const { cachedProviders, persistedRuntime } = context;
|
|
292
|
+
const noRuntimeAcquired = cachedProviders.providerCount === 0 &&
|
|
293
|
+
!context.hasRuntime &&
|
|
294
|
+
!context.threadId &&
|
|
295
|
+
![...ownedRuntimes.values()].some((record) => record.sessionKey === sessionKey);
|
|
296
|
+
const cachedRuntimeExitVerified = cachedProviders.providerCount > 0 &&
|
|
297
|
+
cachedProviders.results.length === cachedProviders.providerCount &&
|
|
298
|
+
cachedProviders.results.every((result) => (
|
|
299
|
+
result?.stopped === true ||
|
|
300
|
+
(result?.sessionDetached === true && result?.sharedProcessRetained === true)
|
|
301
|
+
));
|
|
302
|
+
const persistedRuntimeExitVerified = persistedRuntime?.verifiedStopped === true ||
|
|
303
|
+
(
|
|
304
|
+
persistedRuntime?.sessionDetached === true &&
|
|
305
|
+
persistedRuntime?.sharedProcessRetained === true
|
|
306
|
+
);
|
|
307
|
+
return { noRuntimeAcquired, cachedRuntimeExitVerified, persistedRuntimeExitVerified };
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
async function unsubscribeSessionThread(sessionKey, sessionId, session) {
|
|
311
|
+
if (!sessionId) {
|
|
312
|
+
return { ok: true, sessionId, status: "notSubscribed" };
|
|
313
|
+
}
|
|
314
|
+
const { providerOptions, workdir, threadId } = session.unsubscribeParameters;
|
|
315
|
+
if (!threadId) {
|
|
316
|
+
return { ok: true, providerOptions, sessionId, status: "notSubscribed" };
|
|
317
|
+
}
|
|
318
|
+
// The existing record carries the workdir captured in its provider key.
|
|
319
|
+
// Never prepare new controls while releasing a retained connection.
|
|
320
|
+
const provider = [...records.values()].find(record =>
|
|
321
|
+
record.owner.sessionKey === sessionKey && record.owner.workdir === normalizeText(workdir))?.provider;
|
|
322
|
+
if (!provider) {
|
|
323
|
+
return { ok: true, providerOptions, sessionId, status: "notSubscribed" };
|
|
324
|
+
}
|
|
325
|
+
const result = await provider.unsubscribeThread(threadId);
|
|
326
|
+
debugLog("appServerThread.unsubscribe.done", {
|
|
327
|
+
sessionId,
|
|
328
|
+
status: normalizeText(result?.status),
|
|
329
|
+
threadId
|
|
330
|
+
});
|
|
331
|
+
return {
|
|
332
|
+
ok: true,
|
|
333
|
+
providerOptions,
|
|
334
|
+
result,
|
|
335
|
+
sessionId,
|
|
336
|
+
status: normalizeText(result?.status) || "unsubscribed",
|
|
337
|
+
threadId
|
|
338
|
+
};
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
async function stopPersistedSessionRuntime(session) {
|
|
342
|
+
const host = session.persistedRuntimeHost;
|
|
343
|
+
if (!host) return { stopped: false };
|
|
344
|
+
return stopPersistedRuntime(host, host.runtimeDir);
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
async function releasePersistedRuntimeProof(runtimeOptions, session) {
|
|
348
|
+
// Session closure already detached this participant. Its archived runtime
|
|
349
|
+
// path may now belong to the successor or another live conversation.
|
|
350
|
+
// Releasing the old proof must not stop or delete that shared process.
|
|
351
|
+
if (runtimeIsShared("", runtimeOptions)) {
|
|
352
|
+
return { sharedProcessRetained: true };
|
|
353
|
+
}
|
|
354
|
+
const existed = await directoryExists(runtimeOptions.runtimeDir);
|
|
355
|
+
if (!existed) return { existed };
|
|
356
|
+
const result = await stopPersistedSessionRuntime(session);
|
|
357
|
+
return { existed, result };
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
function releasePreparedRuntimeProof(preparation) {
|
|
361
|
+
return withLifecycle(async () => {
|
|
362
|
+
const prepared = preparation.read();
|
|
363
|
+
if (Object.hasOwn(prepared, "value")) return prepared.value;
|
|
364
|
+
return withRuntimeLifecycle(codexAppServerOwnedRuntimeKey("", prepared.runtimeOptions), async () => {
|
|
365
|
+
const evidence = await releasePersistedRuntimeProof(prepared.runtimeOptions, prepared);
|
|
366
|
+
return prepared.complete(evidence);
|
|
367
|
+
});
|
|
368
|
+
});
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
async function releaseSession(sessionKey, context) {
|
|
372
|
+
const { sessionId, runOwner, session } = context;
|
|
373
|
+
if (context.changeover) {
|
|
374
|
+
const threadId = session.threadId;
|
|
375
|
+
for (const [key, provider] of providers) {
|
|
376
|
+
if (!threadId || providerOwners.get(key)?.sessionKey !== sessionKey) continue;
|
|
377
|
+
await stopThreadBeforeRelease(key, provider, threadId);
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
let exitFailure = null;
|
|
381
|
+
try {
|
|
382
|
+
const unsubscribeResult = await unsubscribeSessionThread(sessionKey, sessionId, session);
|
|
383
|
+
session.providerOptions = unsubscribeResult?.providerOptions || session.providerOptions;
|
|
384
|
+
} catch (error) {
|
|
385
|
+
debugLog("appServerThread.unsubscribe.error", {
|
|
386
|
+
error: debugError(error),
|
|
387
|
+
sessionId
|
|
388
|
+
});
|
|
389
|
+
} finally {
|
|
390
|
+
await runOwner.notificationQueue.drain(sessionId);
|
|
391
|
+
const cachedProviders = await stopCachedProvidersForSession(sessionId ? sessionKey : "", {
|
|
392
|
+
preserveProcessExitProof: context.preserveProcessExitProof,
|
|
393
|
+
requireStopped: context.requireStopped
|
|
394
|
+
});
|
|
395
|
+
if (cachedProviders.ok === false) {
|
|
396
|
+
debugLog("appServerRuntime.closeSession.cached.error", {
|
|
397
|
+
failed: cachedProviders.failed,
|
|
398
|
+
sessionId
|
|
399
|
+
});
|
|
400
|
+
}
|
|
401
|
+
let persistedRuntime = null;
|
|
402
|
+
if (session.exists) {
|
|
403
|
+
persistedRuntime = cachedProviders.stopped > 0
|
|
404
|
+
? {
|
|
405
|
+
stopped: true,
|
|
406
|
+
verifiedStopped: true
|
|
407
|
+
}
|
|
408
|
+
: runtimeIsShared("", session.providerOptions || {})
|
|
409
|
+
? {
|
|
410
|
+
sessionDetached: true,
|
|
411
|
+
sharedProcessRetained: true,
|
|
412
|
+
stopped: false,
|
|
413
|
+
verifiedStopped: false
|
|
414
|
+
}
|
|
415
|
+
: session.hasRuntime
|
|
416
|
+
? await stopPersistedSessionRuntime(session)
|
|
417
|
+
: {
|
|
418
|
+
stopped: false,
|
|
419
|
+
verifiedStopped: false
|
|
420
|
+
};
|
|
421
|
+
debugLog("appServerRuntime.closeSession.persisted.done", {
|
|
422
|
+
removed: persistedRuntime?.removed === true,
|
|
423
|
+
runtimeDirRemoved: persistedRuntime?.runtimeDirRemoved === true,
|
|
424
|
+
sessionId,
|
|
425
|
+
stopped: persistedRuntime?.removed === true || persistedRuntime?.runtimeDirRemoved === true
|
|
426
|
+
});
|
|
427
|
+
}
|
|
428
|
+
if (session.requiresExitProof) {
|
|
429
|
+
// A fresh session with no acquired native owner needs no process proof.
|
|
430
|
+
// Losing a cache or recorded runtime does not make it a fresh session.
|
|
431
|
+
const { noRuntimeAcquired, cachedRuntimeExitVerified, persistedRuntimeExitVerified } =
|
|
432
|
+
sessionExitProof(sessionKey, {
|
|
433
|
+
cachedProviders,
|
|
434
|
+
persistedRuntime,
|
|
435
|
+
get hasRuntime() { return session.hasRuntime; },
|
|
436
|
+
get threadId() { return session.threadId; }
|
|
437
|
+
});
|
|
438
|
+
if (cachedProviders.failed.length > 0 ||
|
|
439
|
+
(!noRuntimeAcquired && !cachedRuntimeExitVerified && !persistedRuntimeExitVerified)) {
|
|
440
|
+
exitFailure = {
|
|
441
|
+
cachedFailures: cachedProviders.failed,
|
|
442
|
+
cachedRuntimeExitVerified,
|
|
443
|
+
persistedRuntime: persistedRuntime || null
|
|
444
|
+
};
|
|
445
|
+
}
|
|
446
|
+
}
|
|
447
|
+
}
|
|
448
|
+
return { exitFailure };
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
return Object.freeze({
|
|
452
|
+
stopCachedProvider,
|
|
453
|
+
stopProvider,
|
|
454
|
+
stopThreadBeforeRelease,
|
|
455
|
+
stopProvidersForProject,
|
|
456
|
+
stopCachedProvidersForSession,
|
|
457
|
+
drainThreadEnvironmentTasks,
|
|
458
|
+
stopPersistedRuntime,
|
|
459
|
+
sessionExitProof,
|
|
460
|
+
unsubscribeSessionThread,
|
|
461
|
+
releasePersistedRuntimeProof,
|
|
462
|
+
releasePreparedRuntimeProof,
|
|
463
|
+
releaseSession
|
|
464
|
+
});
|
|
465
|
+
}
|
|
@@ -1,10 +1,21 @@
|
|
|
1
|
-
import { normalizeCodexRunText } from "./codexTurnState.js";
|
|
2
1
|
import { codexAppServerProjectHookTrustConfig } from "./codexConfiguration.js";
|
|
3
2
|
import { createCodexAppServerModelCatalogCache } from "./codexModelCatalog.js";
|
|
4
3
|
import { readCodexAppServerAccountIdentity } from "./codexHelperExecution.js";
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
4
|
+
import { normalizeText } from "@jskit-ai/kernel/shared/support/normalize";
|
|
5
|
+
|
|
6
|
+
/** @typedef {ReturnType<typeof createCodexProviderSelection>} CodexProviderSelection */
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Provider acquisition and per-operation provider contexts for a run owner's
|
|
10
|
+
* managed threads, with their model catalogue and account identity reads.
|
|
11
|
+
* @param {object} dependencies
|
|
12
|
+
* @param {import("./codexProviderOwner.js").CodexProviderSessions} dependencies.providerSessions
|
|
13
|
+
* @param {(session: object) => object} dependencies.turnState
|
|
14
|
+
* @param {import("./codexRuntimeLifecycle.js").CodexHelperThreadLifecycle | null} dependencies.helperLifecycle
|
|
15
|
+
* @param {ReturnType<typeof import("./codexHelperExecution.js").createCodexHelperOwnershipError>} dependencies.helperOwnershipError
|
|
16
|
+
* @param {number} [dependencies.modelCatalogCacheMs]
|
|
17
|
+
* @param {string} [dependencies.errorPrefix]
|
|
18
|
+
*/
|
|
8
19
|
export function createCodexProviderSelection({
|
|
9
20
|
providerSessions,
|
|
10
21
|
turnState,
|
|
@@ -18,7 +29,7 @@ export function createCodexProviderSelection({
|
|
|
18
29
|
errorPrefix
|
|
19
30
|
});
|
|
20
31
|
function sessionConnection(sessionId, providerOptions) {
|
|
21
|
-
return providerSessions.context(
|
|
32
|
+
return providerSessions.context(normalizeText(sessionId), providerOptions);
|
|
22
33
|
}
|
|
23
34
|
|
|
24
35
|
function ensureSessionProvider(sessionId, providerOptions, threadId) {
|
|
@@ -34,7 +45,7 @@ export function createCodexProviderSelection({
|
|
|
34
45
|
const connection = sessionConnection(sessionId, providerOptions);
|
|
35
46
|
connection.runOwner.runtimeLifecycle.rememberManagedSession(connection.providerKey, {
|
|
36
47
|
providerOptions,
|
|
37
|
-
sessionId:
|
|
48
|
+
sessionId: normalizeText(sessionId),
|
|
38
49
|
executionRoot,
|
|
39
50
|
threadId,
|
|
40
51
|
workdir
|
|
@@ -51,7 +62,7 @@ export function createCodexProviderSelection({
|
|
|
51
62
|
managed.sessionId !== sessionId ||
|
|
52
63
|
managed.workdir !== workdir ||
|
|
53
64
|
managed.threadId !== threadId ||
|
|
54
|
-
provider?.isAvailable
|
|
65
|
+
provider?.isAvailable() !== true ||
|
|
55
66
|
provider.stale && !turnActive
|
|
56
67
|
) {
|
|
57
68
|
continue;
|
|
@@ -62,21 +73,21 @@ export function createCodexProviderSelection({
|
|
|
62
73
|
}
|
|
63
74
|
|
|
64
75
|
async function ensureCodexAppServerProviderForManagedThread(session = {}, options = {}) {
|
|
65
|
-
const normalizedSessionId =
|
|
76
|
+
const normalizedSessionId = normalizeText(session.sessionId || session.id);
|
|
66
77
|
const turn = turnState(session);
|
|
67
78
|
if (!normalizedSessionId || !turn.threadId) {
|
|
68
79
|
return null;
|
|
69
80
|
}
|
|
70
|
-
const normalizedExecutionRoot =
|
|
71
|
-
const normalizedWorkdir =
|
|
81
|
+
const normalizedExecutionRoot = normalizeText(options.executionRoot);
|
|
82
|
+
const normalizedWorkdir = normalizeText(options.workdir);
|
|
72
83
|
for (const [providerKey, managed] of providerSessions.managed.entries()) {
|
|
73
84
|
const fields = providerSessions.keyFields(providerKey);
|
|
74
85
|
if (
|
|
75
86
|
fields.sessionId !== normalizedSessionId ||
|
|
76
87
|
(normalizedExecutionRoot && fields.executionRoot !== normalizedExecutionRoot) ||
|
|
77
88
|
(normalizedWorkdir && fields.workdir !== normalizedWorkdir) ||
|
|
78
|
-
|
|
79
|
-
|
|
89
|
+
normalizeText(managed?.sessionId) !== normalizedSessionId ||
|
|
90
|
+
normalizeText(managed?.threadId) !== turn.threadId
|
|
80
91
|
) {
|
|
81
92
|
continue;
|
|
82
93
|
}
|
|
@@ -107,7 +118,7 @@ export function createCodexProviderSelection({
|
|
|
107
118
|
}
|
|
108
119
|
|
|
109
120
|
async function codexAppServerRuntimeForVisibleTerminal(rawSessionId, session, options, managedIdentity, prepareProviderOptions) {
|
|
110
|
-
const sessionId =
|
|
121
|
+
const sessionId = normalizeText(rawSessionId);
|
|
111
122
|
const activeProvider = await ensureCodexAppServerProviderForActiveTurn(session, managedIdentity);
|
|
112
123
|
const providerOptions = activeProvider?.providerOptions || await prepareProviderOptions();
|
|
113
124
|
const provider = activeProvider?.provider || await ensureSessionProvider(sessionId, providerOptions);
|
|
@@ -210,7 +221,7 @@ export function createCodexProviderSelection({
|
|
|
210
221
|
return readCodexAppServerAccountIdentity(provider, helperOwnershipError);
|
|
211
222
|
}
|
|
212
223
|
|
|
213
|
-
return {
|
|
224
|
+
return Object.freeze({
|
|
214
225
|
readExecutionProfileModelCatalog,
|
|
215
226
|
readProviderAccountIdentity,
|
|
216
227
|
managedProviderKeyForThread,
|
|
@@ -225,5 +236,5 @@ export function createCodexProviderSelection({
|
|
|
225
236
|
ensureSessionProvider,
|
|
226
237
|
borrowSessionProvider,
|
|
227
238
|
rememberManagedSession
|
|
228
|
-
};
|
|
239
|
+
});
|
|
229
240
|
}
|