@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,371 @@
|
|
|
1
|
+
import {
|
|
2
|
+
codexAppServerOwnedRuntimeKey,
|
|
3
|
+
codexAppServerOwnedRuntimes,
|
|
4
|
+
codexAppServerRuntimeStopWasVerified,
|
|
5
|
+
codexAppServerStaleRuntimes,
|
|
6
|
+
nativeRequest,
|
|
7
|
+
providerRecords,
|
|
8
|
+
runningNativeThreads,
|
|
9
|
+
withRuntimeLifecycle
|
|
10
|
+
} from "./codexRuntimeInventory.js";
|
|
11
|
+
import { normalizeText } from "@jskit-ai/kernel/shared/support/normalize";
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Retires the native runtimes an account change or a lost observation leaves untrusted.
|
|
15
|
+
* @param {object} dependencies
|
|
16
|
+
* @param {(record: object) => boolean} dependencies.inScope
|
|
17
|
+
* @param {<T>(operation: () => T, providerKey?: string, providerOptions?: object | null) => Promise<Awaited<T>>} dependencies.withLifecycle
|
|
18
|
+
* @param {() => void} dependencies.forgetModelCatalog
|
|
19
|
+
* @param {string} dependencies.exitUnverifiedCode
|
|
20
|
+
* @param {(event: string, details: object) => void} dependencies.debugLog
|
|
21
|
+
* @param {(error: unknown) => unknown} dependencies.debugError
|
|
22
|
+
*/
|
|
23
|
+
export function createCodexProviderInvalidation({ inScope, withLifecycle, forgetModelCatalog, exitUnverifiedCode, debugLog, debugError }) {
|
|
24
|
+
async function invalidateProviderThreads(modelProviderId, includeOwned, toolHomeSource) {
|
|
25
|
+
const groups = new Map();
|
|
26
|
+
const inventory = [...providerRecords()];
|
|
27
|
+
if (includeOwned) for (const record of codexAppServerOwnedRuntimes.values()) {
|
|
28
|
+
if (!inventory.some(candidate => candidate.provider === record.provider)) inventory.push(record);
|
|
29
|
+
}
|
|
30
|
+
for (const record of inventory) {
|
|
31
|
+
if (!inScope(record)) continue;
|
|
32
|
+
const key = codexAppServerOwnedRuntimeKey(record.providerKey, record.providerOptions);
|
|
33
|
+
if (!groups.has(key)) groups.set(key, []);
|
|
34
|
+
groups.get(key).push(record);
|
|
35
|
+
}
|
|
36
|
+
const results = [], failed = [];
|
|
37
|
+
for (const [runtimeKey, peers] of groups) {
|
|
38
|
+
try {
|
|
39
|
+
await withRuntimeLifecycle(runtimeKey, async () => {
|
|
40
|
+
const available = peers.filter(record => record.provider.client?.isOpen());
|
|
41
|
+
if (!available.length) {
|
|
42
|
+
if (peers.some(record => record.provider.runtime || record.provider.runtimeStopOwner ||
|
|
43
|
+
codexAppServerOwnedRuntimes.get(runtimeKey)?.provider === record.provider)) {
|
|
44
|
+
throw new Error("Codex's existing control connection is unavailable. Restore it before changing this provider connection.");
|
|
45
|
+
}
|
|
46
|
+
return;
|
|
47
|
+
}
|
|
48
|
+
const request = (record, method, params) => {
|
|
49
|
+
const client = record.provider.client;
|
|
50
|
+
if (!client?.isOpen()) throw new Error("Codex's control connection closed during provider retirement. Restore it and retry.");
|
|
51
|
+
return client.request(method, params, { signal: AbortSignal.timeout(5_000) });
|
|
52
|
+
};
|
|
53
|
+
const selectedHome = normalizeText(toolHomeSource);
|
|
54
|
+
const dedicated = Boolean(selectedHome && peers.some(record =>
|
|
55
|
+
normalizeText(record.providerOptions.toolHomeSource) === selectedHome));
|
|
56
|
+
if (dedicated && peers.some(record => normalizeText(record.providerOptions.toolHomeSource) !== selectedHome)) {
|
|
57
|
+
throw new Error("Codex's provider credential home has other native consumers. Close its native terminal/helper and retry; unrelated runtimes remain running.");
|
|
58
|
+
}
|
|
59
|
+
const inspector = available[0], candidates = new Set(), seen = new Set();
|
|
60
|
+
let cursor;
|
|
61
|
+
do {
|
|
62
|
+
const page = await request(inspector, "thread/loaded/list", { limit: 100, ...(cursor ? { cursor } : {}) });
|
|
63
|
+
if (!Array.isArray(page?.data)) throw new Error("Codex returned an invalid loaded-thread inventory. Restore its connection before retrying.");
|
|
64
|
+
for (const id of page.data) {
|
|
65
|
+
if (typeof id !== "string" || !id) throw new Error("Codex returned an invalid loaded-thread identity.");
|
|
66
|
+
candidates.add(id);
|
|
67
|
+
}
|
|
68
|
+
cursor = page.nextCursor;
|
|
69
|
+
if (cursor && (typeof cursor !== "string" || seen.has(cursor) || seen.size >= 100)) throw new Error("Codex's loaded-thread inventory did not finish.");
|
|
70
|
+
if (cursor) seen.add(cursor);
|
|
71
|
+
} while (cursor);
|
|
72
|
+
for (const { provider } of peers) for (const [id, binding] of provider.threadEnvironments || []) {
|
|
73
|
+
if (dedicated || binding.params?.modelProvider === modelProviderId) candidates.add(id);
|
|
74
|
+
}
|
|
75
|
+
const threadIds = [];
|
|
76
|
+
for (const id of candidates) {
|
|
77
|
+
const { thread } = await request(inspector, "thread/read", { threadId: id, includeTurns: false });
|
|
78
|
+
if (!thread || thread.id !== id || typeof thread.modelProvider !== "string" || !thread.modelProvider ||
|
|
79
|
+
!(typeof thread.status === "string" ? thread.status : thread.status?.type)) {
|
|
80
|
+
throw new Error("Codex returned an untrustworthy thread/provider inventory. Restore its connection before changing credentials.");
|
|
81
|
+
}
|
|
82
|
+
if (dedicated && thread.modelProvider !== modelProviderId) {
|
|
83
|
+
throw new Error("Codex's provider credential home retains another provider thread. Close its native terminal/helper and retry; unrelated threads remain running.");
|
|
84
|
+
}
|
|
85
|
+
if (thread.modelProvider === modelProviderId) threadIds.push(id);
|
|
86
|
+
}
|
|
87
|
+
if (!threadIds.length && !dedicated) return;
|
|
88
|
+
const error = Object.assign(new Error("This Codex provider connection changed. Work is stopped; use Send or Resume after reconnecting."), {
|
|
89
|
+
code: "codex_provider_connection_changed"
|
|
90
|
+
});
|
|
91
|
+
for (const { provider } of peers) for (const threadId of threadIds) provider.conversationRuntime?.invalidateThread(threadId);
|
|
92
|
+
await Promise.allSettled(peers.flatMap(({ provider }) => threadIds
|
|
93
|
+
.map(id => provider.threadEnvironmentTasks?.get(id)).filter(Boolean)));
|
|
94
|
+
// The same observation journal retains selected stop ownership. Peers
|
|
95
|
+
// with other threads must keep their admission and active work intact.
|
|
96
|
+
for (const record of available) await record.observation?.prepare({ provider: record.provider, error, threadIds });
|
|
97
|
+
for (const threadId of threadIds) {
|
|
98
|
+
await inspector.provider.stopThreadForObservationLoss(threadId);
|
|
99
|
+
for (const record of available) {
|
|
100
|
+
const detached = await request(record, "thread/unsubscribe", { threadId });
|
|
101
|
+
if (!["unsubscribed", "notSubscribed", "notLoaded"].includes(detached?.status)) throw new Error("Codex did not confirm selected-thread detachment.");
|
|
102
|
+
}
|
|
103
|
+
const { thread } = await request(inspector, "thread/read", { threadId, includeTurns: false });
|
|
104
|
+
if ((typeof thread?.status === "string" ? thread.status : thread?.status?.type) !== "notLoaded") {
|
|
105
|
+
throw new Error("Close the native Codex terminal using this provider, then retry changing its connection. Another subscriber retained the selected thread; the shared server and other conversations remain running.");
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
if (dedicated) {
|
|
109
|
+
// A curated default also lives in this exact isolated helper's
|
|
110
|
+
// native config cache. Reuse verified retirement only after proving
|
|
111
|
+
// every loaded/cached thread belongs to the selected provider.
|
|
112
|
+
const owned = codexAppServerOwnedRuntimes.get(runtimeKey);
|
|
113
|
+
if (!owned || !peers.some(record => record.provider === owned.provider)) {
|
|
114
|
+
throw new Error("Codex's dedicated provider runtime ownership cannot be verified. Restore its control connection and retry.");
|
|
115
|
+
}
|
|
116
|
+
await owned.scope.stopOwnedRuntime(owned, { cached: false, preserveProcessExitProof: true, requireVerifiedExit: true });
|
|
117
|
+
}
|
|
118
|
+
for (const record of available) await record.observation?.complete({ provider: record.provider, error, threadIds, sharedStop: dedicated });
|
|
119
|
+
if (dedicated) for (const record of peers) record.scope.closeProvider(record.providerKey);
|
|
120
|
+
results.push({ runtimeKey, threadIds, stopped: true, sharedProcessRetained: !dedicated,
|
|
121
|
+
...(dedicated ? { dedicatedRuntimeRetired: true } : {}) });
|
|
122
|
+
});
|
|
123
|
+
} catch (error) {
|
|
124
|
+
failed.push({ runtimeKey, error: error.message, retryable: true });
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
return { ok: failed.length === 0, failed, results, providerCount: results.length, stopped: results.reduce((sum, result) => sum + result.threadIds.length, 0) };
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
// A runtime with a running turn keeps the credentials it started with until
|
|
131
|
+
// that turn ends. Its active goals are paused so it starts no further turn;
|
|
132
|
+
// retireStaleRuntime stops it once it is idle. Any other runtime returns
|
|
133
|
+
// null and stops at once, like an idle one.
|
|
134
|
+
async function deferBusyRuntime(runtimeKey, peers) {
|
|
135
|
+
let running;
|
|
136
|
+
try {
|
|
137
|
+
running = await runningNativeThreads(peers);
|
|
138
|
+
} catch (error) {
|
|
139
|
+
// An unreadable runtime cannot show a running turn.
|
|
140
|
+
debugLog("appServerRuntime.staleInspection.error", { error: debugError(error), runtimeKey });
|
|
141
|
+
return null;
|
|
142
|
+
}
|
|
143
|
+
// Nor can one whose control connection closed while it was read.
|
|
144
|
+
const client = peers.find(record => record.provider.client?.isOpen())?.provider.client;
|
|
145
|
+
if (!running.length || !client) return null;
|
|
146
|
+
const pausedGoals = [];
|
|
147
|
+
for (const threadId of running) {
|
|
148
|
+
try {
|
|
149
|
+
const { goal } = await nativeRequest(client, "thread/goal/get", { threadId });
|
|
150
|
+
if (goal?.status !== "active") continue;
|
|
151
|
+
const result = await nativeRequest(client, "thread/goal/set", { threadId, status: "paused" });
|
|
152
|
+
if (result?.goal?.status !== "paused") throw new Error("Codex did not confirm that the goal was paused.");
|
|
153
|
+
pausedGoals.push(threadId);
|
|
154
|
+
} catch (error) {
|
|
155
|
+
debugLog("appServerRuntime.staleGoalPause.error", { error: debugError(error), runtimeKey, threadId });
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
for (const peer of peers) peer.provider.markStale();
|
|
159
|
+
const stale = {
|
|
160
|
+
runtimeKey, threadIds: running, pausedGoals, retiring: null,
|
|
161
|
+
providerKeys: peers.map(peer => peer.providerKey)
|
|
162
|
+
};
|
|
163
|
+
codexAppServerStaleRuntimes.set(runtimeKey, stale);
|
|
164
|
+
return stale;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
async function invalidateRuntimes({
|
|
168
|
+
deferBusy = false,
|
|
169
|
+
includeOwned = false,
|
|
170
|
+
requireVerifiedExit = false,
|
|
171
|
+
stopOwnedRuntimes = false,
|
|
172
|
+
toolHomeSource = "",
|
|
173
|
+
modelProviderId = ""
|
|
174
|
+
} = {}) {
|
|
175
|
+
if (modelProviderId) return withLifecycle(() => invalidateProviderThreads(modelProviderId, includeOwned, toolHomeSource));
|
|
176
|
+
forgetModelCatalog();
|
|
177
|
+
const normalizedToolHomeSource = normalizeText(toolHomeSource);
|
|
178
|
+
const ownedRecordsByProvider = new Map(
|
|
179
|
+
[...codexAppServerOwnedRuntimes.values()].filter(inScope).map((record) => [record.provider, record])
|
|
180
|
+
);
|
|
181
|
+
const targets = [...providerRecords()]
|
|
182
|
+
.filter(record => inScope(record) && (!normalizedToolHomeSource ||
|
|
183
|
+
normalizeText(record.providerOptions.toolHomeSource) === normalizedToolHomeSource))
|
|
184
|
+
.map(record => ({
|
|
185
|
+
...record,
|
|
186
|
+
kind: "cached",
|
|
187
|
+
record: ownedRecordsByProvider.get(record.provider) || null
|
|
188
|
+
}));
|
|
189
|
+
const targetedProviders = new Set(targets.map((target) => target.provider));
|
|
190
|
+
if (includeOwned) {
|
|
191
|
+
for (const record of codexAppServerOwnedRuntimes.values()) {
|
|
192
|
+
if (!inScope(record) || targetedProviders.has(record.provider)) {
|
|
193
|
+
continue;
|
|
194
|
+
}
|
|
195
|
+
const recordToolHomeSource = normalizeText(
|
|
196
|
+
record.providerOptions?.toolHomeSource
|
|
197
|
+
);
|
|
198
|
+
if (
|
|
199
|
+
normalizedToolHomeSource &&
|
|
200
|
+
recordToolHomeSource !== normalizedToolHomeSource
|
|
201
|
+
) {
|
|
202
|
+
continue;
|
|
203
|
+
}
|
|
204
|
+
targets.push({
|
|
205
|
+
...record,
|
|
206
|
+
kind: "owned",
|
|
207
|
+
provider: record.provider,
|
|
208
|
+
providerKey: record.providerKey,
|
|
209
|
+
record
|
|
210
|
+
});
|
|
211
|
+
targetedProviders.add(record.provider);
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
targets.sort((left, right) => Number(Boolean(left.record)) - Number(Boolean(right.record)));
|
|
215
|
+
const failed = [];
|
|
216
|
+
const results = [];
|
|
217
|
+
const deferred = [];
|
|
218
|
+
if (deferBusy) {
|
|
219
|
+
const groups = new Map();
|
|
220
|
+
for (const target of targets) {
|
|
221
|
+
const runtimeKey = codexAppServerOwnedRuntimeKey(target.providerKey, target.providerOptions);
|
|
222
|
+
if (!groups.has(runtimeKey)) groups.set(runtimeKey, []);
|
|
223
|
+
groups.get(runtimeKey).push(target);
|
|
224
|
+
}
|
|
225
|
+
for (const [runtimeKey, peers] of groups) {
|
|
226
|
+
if (codexAppServerStaleRuntimes.has(runtimeKey)) {
|
|
227
|
+
deferred.push(codexAppServerStaleRuntimes.get(runtimeKey));
|
|
228
|
+
} else {
|
|
229
|
+
const stale = await withRuntimeLifecycle(runtimeKey, () => deferBusyRuntime(runtimeKey, peers));
|
|
230
|
+
if (!stale) continue;
|
|
231
|
+
deferred.push(stale);
|
|
232
|
+
}
|
|
233
|
+
for (const peer of peers) {
|
|
234
|
+
targets.splice(targets.indexOf(peer), 1);
|
|
235
|
+
results.push({ providerKey: peer.providerKey, deferred: true, stopped: false });
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
for (const target of targets) {
|
|
240
|
+
if (!target.resources?.invalidation) continue;
|
|
241
|
+
const error = Object.assign(new Error("Codex's native runtime was invalidated."), {
|
|
242
|
+
code: "codex_runtime_invalidated"
|
|
243
|
+
});
|
|
244
|
+
target.invalidation = { error };
|
|
245
|
+
try {
|
|
246
|
+
if (await target.resources.invalidation.prepare({ provider: target.provider, error }) === false) {
|
|
247
|
+
delete target.invalidation;
|
|
248
|
+
}
|
|
249
|
+
} catch (preparationError) {
|
|
250
|
+
target.invalidation.preparationError = preparationError;
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
for (const target of targets) {
|
|
254
|
+
const preserveProcessExitProof = target.preserveProcessExitProof === true;
|
|
255
|
+
try {
|
|
256
|
+
const result = stopOwnedRuntimes && target.record
|
|
257
|
+
? await target.scope.stopOwnedRuntime({
|
|
258
|
+
...(target.record || {}),
|
|
259
|
+
provider: target.provider,
|
|
260
|
+
providerKey: target.providerKey
|
|
261
|
+
}, {
|
|
262
|
+
cached: target.kind === "cached",
|
|
263
|
+
preserveProcessExitProof,
|
|
264
|
+
requireVerifiedExit
|
|
265
|
+
})
|
|
266
|
+
: await target.scope.stopCachedProvider(target.providerKey, {
|
|
267
|
+
preserveProcessExitProof,
|
|
268
|
+
requireStopped: stopOwnedRuntimes && target.record != null
|
|
269
|
+
});
|
|
270
|
+
if (
|
|
271
|
+
requireVerifiedExit &&
|
|
272
|
+
target.record &&
|
|
273
|
+
!codexAppServerRuntimeStopWasVerified(result)
|
|
274
|
+
) {
|
|
275
|
+
throw Object.assign(new Error("Codex app-server process exit could not be verified."), {
|
|
276
|
+
code: exitUnverifiedCode, providerKey: target.providerKey, retryable: true
|
|
277
|
+
});
|
|
278
|
+
}
|
|
279
|
+
target.stopResult = result;
|
|
280
|
+
results.push(result);
|
|
281
|
+
} catch (error) {
|
|
282
|
+
target.stopError = error;
|
|
283
|
+
failed.push({
|
|
284
|
+
code: normalizeText(error?.code),
|
|
285
|
+
error: String(error?.message || "Codex app-server runtime invalidation failed."),
|
|
286
|
+
providerKey: target.providerKey,
|
|
287
|
+
retryable: error?.retryable === true
|
|
288
|
+
});
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
for (const target of targets) {
|
|
292
|
+
if (!target.invalidation) continue;
|
|
293
|
+
const runtimeKey = codexAppServerOwnedRuntimeKey(target.providerKey, target.providerOptions);
|
|
294
|
+
const peers = targets.filter(peer =>
|
|
295
|
+
codexAppServerOwnedRuntimeKey(peer.providerKey, peer.providerOptions) === runtimeKey);
|
|
296
|
+
const proof = peers.find(peer => codexAppServerRuntimeStopWasVerified(peer.stopResult));
|
|
297
|
+
const stopped = proof
|
|
298
|
+
? { status: "fulfilled", value: proof.stopResult }
|
|
299
|
+
: { status: "rejected", reason: peers.find(peer => peer.stopError)?.stopError ||
|
|
300
|
+
Object.assign(new Error("Codex app-server process exit could not be verified."), {
|
|
301
|
+
code: exitUnverifiedCode, providerKey: target.providerKey, retryable: true
|
|
302
|
+
}) };
|
|
303
|
+
try {
|
|
304
|
+
await target.resources.invalidation.complete({
|
|
305
|
+
provider: target.provider, ...target.invalidation, stopped
|
|
306
|
+
});
|
|
307
|
+
if (target.invalidation.preparationError) throw target.invalidation.preparationError;
|
|
308
|
+
if (stopped.status === "rejected") throw stopped.reason;
|
|
309
|
+
} catch (error) {
|
|
310
|
+
failed.push({
|
|
311
|
+
code: normalizeText(error?.code),
|
|
312
|
+
error: String(error?.message || "Codex app-server invalidation could not be recorded."),
|
|
313
|
+
providerKey: target.providerKey,
|
|
314
|
+
retryable: true
|
|
315
|
+
});
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
const stopped = results.filter((result) => result.stopped).length;
|
|
319
|
+
return {
|
|
320
|
+
deferred: deferred.map(({ runtimeKey, providerKeys, threadIds, pausedGoals }) =>
|
|
321
|
+
({ runtimeKey, providerKeys: [...providerKeys], threadIds: [...threadIds], pausedGoals: [...pausedGoals] })),
|
|
322
|
+
failed,
|
|
323
|
+
ok: failed.length === 0,
|
|
324
|
+
providerCount: targets.length + deferred.reduce((sum, stale) => sum + stale.providerKeys.length, 0),
|
|
325
|
+
results,
|
|
326
|
+
stopped
|
|
327
|
+
};
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
async function suspendUnobservedProvider(record, error) {
|
|
331
|
+
const targets = [record];
|
|
332
|
+
let sharedStop = false;
|
|
333
|
+
try {
|
|
334
|
+
const threads = await record.observation.prepare({ provider: record.provider, error });
|
|
335
|
+
if (!threads.size) throw new Error("The unobserved Codex thread could not be identified.");
|
|
336
|
+
for (const [threadId, turnId] of threads) {
|
|
337
|
+
await record.provider.stopThreadForObservationLoss(threadId, turnId);
|
|
338
|
+
}
|
|
339
|
+
} catch {
|
|
340
|
+
sharedStop = true;
|
|
341
|
+
const runtimeKey = codexAppServerOwnedRuntimeKey(record.providerKey, record.providerOptions);
|
|
342
|
+
for (const peer of providerRecords()) {
|
|
343
|
+
if (peer !== record && codexAppServerOwnedRuntimeKey(peer.providerKey, peer.providerOptions) === runtimeKey) {
|
|
344
|
+
peer.provider.observationFailure = error;
|
|
345
|
+
targets.push(peer);
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
// Every durable barrier is attempted before the native stop; a storage
|
|
349
|
+
// failure cannot leave work running.
|
|
350
|
+
for (const target of targets) {
|
|
351
|
+
try {
|
|
352
|
+
await target.observation.barrier({ provider: target.provider, error });
|
|
353
|
+
} catch (failure) {
|
|
354
|
+
target.scope.reportRecoveryEvent(target, "persistenceFailed", failure);
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
const owned = codexAppServerOwnedRuntimes.get(runtimeKey) || record;
|
|
358
|
+
await owned.scope.stopOwnedRuntime(owned, { preserveProcessExitProof: true, requireVerifiedExit: true });
|
|
359
|
+
}
|
|
360
|
+
for (const target of targets) {
|
|
361
|
+
// Each adapter persists, then publishes. It can release this provider
|
|
362
|
+
// only after its stopped state is durable.
|
|
363
|
+
await target.observation.complete({ provider: target.provider, error, sharedStop });
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
return Object.freeze({
|
|
368
|
+
invalidateRuntimes,
|
|
369
|
+
suspendUnobservedProvider
|
|
370
|
+
});
|
|
371
|
+
}
|