@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
|
@@ -61,7 +61,31 @@ async function* readClaudeJsonFrames(stream, { maxFrameBytes = CLAUDE_JSON_MAX_F
|
|
|
61
61
|
if (length && !allowIncompleteTail) throw claudeProtocolError("Claude JSON stream ended in an incomplete frame.");
|
|
62
62
|
}
|
|
63
63
|
|
|
64
|
-
|
|
64
|
+
/** A Claude connection whose owner keeps no native events. */
|
|
65
|
+
async function ignoreClaudeFrame() {}
|
|
66
|
+
|
|
67
|
+
/** A Claude connection whose owner keeps no record of its failure. */
|
|
68
|
+
async function ignoreClaudeFailure() {}
|
|
69
|
+
|
|
70
|
+
/** A control request whose caller follows none of its stages. */
|
|
71
|
+
function reportNoProgress() {}
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* The Claude Code CLI's JSON stream: ordered native events, control requests both ways and user messages.
|
|
75
|
+
* @param {object} options
|
|
76
|
+
* @param {import("node:stream").Duplex} options.stream
|
|
77
|
+
* @param {(frame: Record<string, any>) => Promise<unknown>} [options.onEvent]
|
|
78
|
+
* @param {(request: Record<string, any>, context: { signal: AbortSignal }) => Promise<unknown>} [options.onControlRequest]
|
|
79
|
+
* @param {(error: Error) => Promise<unknown>} [options.onFailure]
|
|
80
|
+
* @param {number} [options.timeoutMs]
|
|
81
|
+
*/
|
|
82
|
+
function createClaudeJsonClient({
|
|
83
|
+
stream,
|
|
84
|
+
onEvent = ignoreClaudeFrame,
|
|
85
|
+
onControlRequest,
|
|
86
|
+
onFailure = ignoreClaudeFailure,
|
|
87
|
+
timeoutMs = 30_000
|
|
88
|
+
}) {
|
|
65
89
|
const requests = new Map();
|
|
66
90
|
const incoming = new Map();
|
|
67
91
|
let failure = null;
|
|
@@ -106,8 +130,9 @@ function createClaudeJsonClient({ stream, onEvent = async () => {}, onControlReq
|
|
|
106
130
|
async function request(request, options = {}) {
|
|
107
131
|
if (failure || closed) throw failure || claudeProtocolError("Claude connection is closed.");
|
|
108
132
|
const requestId = randomUUID();
|
|
133
|
+
const { onProgress = reportNoProgress } = options;
|
|
109
134
|
function progress(stage) {
|
|
110
|
-
try {
|
|
135
|
+
try { onProgress(stage); }
|
|
111
136
|
catch { /* Diagnostic observers cannot change protocol completion. */ }
|
|
112
137
|
}
|
|
113
138
|
let timer;
|
|
@@ -212,27 +237,53 @@ function createClaudeJsonClient({ stream, onEvent = async () => {}, onControlReq
|
|
|
212
237
|
// unhandled rejection if their failure callback itself fails.
|
|
213
238
|
void completion.catch(() => {});
|
|
214
239
|
|
|
240
|
+
/** Whether the CLI announced targeted Send now. */
|
|
241
|
+
function sendNowSupported() {
|
|
242
|
+
return supportsSendNow;
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
/** The protocol handshake, with the host's hooks. */
|
|
246
|
+
function initialize({ hooks } = {}, options) {
|
|
247
|
+
return request({ subtype: "initialize", ...(hooks ? { hooks } : {}) }, options);
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
/** Stops the current native turn. */
|
|
251
|
+
function interrupt() {
|
|
252
|
+
return request({ subtype: "interrupt" });
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
/** Settles every control request the CLI sent. */
|
|
256
|
+
function whenRequestsIdle() {
|
|
257
|
+
return Promise.all([...incoming.values()].map(pending => pending.done));
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
/** Writes one user message, optionally delivered at once with targeted Send now. */
|
|
261
|
+
async function send(message, { messageId = randomUUID(), sessionId = "", sendNow = false } = {}) {
|
|
262
|
+
// Older CLIs treat this control as a plain Stop, cancelling background
|
|
263
|
+
// workers. Require the native capability before writing either frame.
|
|
264
|
+
if (sendNow && !supportsSendNow) throw Object.assign(
|
|
265
|
+
claudeProtocolError("This Claude version does not support targeted Send now."), { delivery: "not-sent" }
|
|
266
|
+
);
|
|
267
|
+
await write({ type: "user", uuid: messageId, session_id: sessionId,
|
|
268
|
+
parent_tool_use_id: null, message: { role: "user", content: message } });
|
|
269
|
+
if (sendNow) await request({ subtype: "interrupt", send_now: true, message_uuid: messageId });
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
/** Closes the connection; pending requests fail. */
|
|
273
|
+
function close() {
|
|
274
|
+
closed = true;
|
|
275
|
+
fail(claudeProtocolError("Claude connection was closed."));
|
|
276
|
+
}
|
|
277
|
+
|
|
215
278
|
return Object.freeze({
|
|
216
279
|
completion,
|
|
217
|
-
supportsSendNow:
|
|
218
|
-
initialize
|
|
219
|
-
interrupt
|
|
280
|
+
supportsSendNow: sendNowSupported,
|
|
281
|
+
initialize,
|
|
282
|
+
interrupt,
|
|
220
283
|
request,
|
|
221
|
-
whenRequestsIdle
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
// workers. Require the native capability before writing either frame.
|
|
225
|
-
if (sendNow && !supportsSendNow) throw Object.assign(
|
|
226
|
-
claudeProtocolError("This Claude version does not support targeted Send now."), { delivery: "not-sent" }
|
|
227
|
-
);
|
|
228
|
-
await write({ type: "user", uuid: messageId, session_id: sessionId,
|
|
229
|
-
parent_tool_use_id: null, message: { role: "user", content: message } });
|
|
230
|
-
if (sendNow) await request({ subtype: "interrupt", send_now: true, message_uuid: messageId });
|
|
231
|
-
},
|
|
232
|
-
close() {
|
|
233
|
-
closed = true;
|
|
234
|
-
fail(claudeProtocolError("Claude connection was closed."));
|
|
235
|
-
}
|
|
284
|
+
whenRequestsIdle,
|
|
285
|
+
send,
|
|
286
|
+
close
|
|
236
287
|
});
|
|
237
288
|
}
|
|
238
289
|
|
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Runs an application command on a Claude conversation: its dispatch,
|
|
3
|
+
* steering, cancellation and settlement. A runtime binding runs it on the one
|
|
4
|
+
* entry it selected; otherwise each dispatch acquires its entry.
|
|
5
|
+
* @param {{
|
|
6
|
+
* bound: boolean,
|
|
7
|
+
* preparation: import("./claudeTurn.js").ClaudeConversationPreparation,
|
|
8
|
+
* send: (entry: import("./claudeTurn.js").ClaudeConversationEntry, input?: Record<string, any>, options?: Record<string, any>) => Promise<Record<string, any>>,
|
|
9
|
+
* interrupt: (entry: import("./claudeTurn.js").ClaudeConversationEntry) => Promise<Record<string, any>>
|
|
10
|
+
* }} options
|
|
11
|
+
*/
|
|
12
|
+
export function createClaudeCommandRun({ bound, preparation, send, interrupt }) {
|
|
13
|
+
async function cancel(control, { context, acquire, reportFailure, entry }) {
|
|
14
|
+
if (bound) {
|
|
15
|
+
await entry.nativeTurn.stopProcess("Work stopped.");
|
|
16
|
+
return;
|
|
17
|
+
}
|
|
18
|
+
try {
|
|
19
|
+
const value = await interrupt(await acquire(context || control.current?.context));
|
|
20
|
+
if (control.cleanupFailure) {
|
|
21
|
+
await reportFailure(control.cleanupFailure, { recovered: true });
|
|
22
|
+
control.cleanupFailure = null;
|
|
23
|
+
}
|
|
24
|
+
return value;
|
|
25
|
+
} catch (error) {
|
|
26
|
+
error.cleanupFailed = true;
|
|
27
|
+
control.cleanupFailure = error;
|
|
28
|
+
control.current?.failure.reject(error);
|
|
29
|
+
await reportFailure(error);
|
|
30
|
+
throw error;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
async function dispatch(control, command, { acquire, entry: selectedEntry, dispatch: nativeDispatch }) {
|
|
35
|
+
if (bound) {
|
|
36
|
+
await send(selectedEntry, command.input, { command: nativeDispatch });
|
|
37
|
+
return;
|
|
38
|
+
}
|
|
39
|
+
command.signal.throwIfAborted();
|
|
40
|
+
const entry = await acquire(command.context);
|
|
41
|
+
const active = control.current;
|
|
42
|
+
const applicationCommand = active && (active.tools || active.finishIfCurrent) ? { active, accept: command.accept } : null;
|
|
43
|
+
if (applicationCommand) {
|
|
44
|
+
if (entry.disposed || entry.command && entry.command !== active) throw new Error("This Claude conversation is closed or already working.");
|
|
45
|
+
if (active.entry && active.entry !== entry) throw new Error("Claude's application tools belong to another native conversation.");
|
|
46
|
+
active.entry = entry;
|
|
47
|
+
entry.command = active;
|
|
48
|
+
entry.toolSchemas = active.tools?.schemas || [];
|
|
49
|
+
}
|
|
50
|
+
const value = await send(entry, { ...command.input.nativeMessage, signal: command.input.signal, onPromptSending: command.beforeDispatch,
|
|
51
|
+
onNativeReceipt: command.onNativeReceipt, onReceiptPersisted: command.onReceiptPersisted, deliveryTiming: command.deliveryTiming },
|
|
52
|
+
{ applicationCommand });
|
|
53
|
+
const completion = value.conversationTurn ? entry.completion?.promise : null;
|
|
54
|
+
const delivered = { value, completion };
|
|
55
|
+
if (!applicationCommand && value.delivered === true && value.conversationTurn) {
|
|
56
|
+
if (control.current) control.current.delivery = delivered;
|
|
57
|
+
await command.accept({ nativeResult: { value }, conversationTurn: value.conversationTurn, nativeTurnId: value.turn?.id });
|
|
58
|
+
}
|
|
59
|
+
return delivered;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function steer(control, command, options) {
|
|
63
|
+
if (!bound) return dispatch(control, command, options).then(delivered => ({ value: delivered.value }));
|
|
64
|
+
if (!control.current?.steer || !control.current.entry.nativeTurn.read().active) throw new Error("Claude has no active turn to steer.");
|
|
65
|
+
return control.current.steer(command);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
async function run(control, command, { acquire, reportFailure, entry: selectedEntry, maximumOutput, maximumToolCalls = 32, timeoutMs } = {}) {
|
|
69
|
+
if (control.disposed || control.current) throw new Error("This Claude conversation is closed or already working.");
|
|
70
|
+
const { configuration, context, input, tools, signal, beforeDispatch, accept, onMessage, onEvent, onNativeToolUse } = command;
|
|
71
|
+
const completion = Promise.withResolvers();
|
|
72
|
+
completion.promise.catch(() => {});
|
|
73
|
+
const active = { completion, onMessage, onEvent, onNativeToolUse, signal, tools, admitted: false, nativeTools: new Map(),
|
|
74
|
+
messageId: input.messageId, goal: input.goal, nativeToolCount: 0, toolWork: new Set(), maximumToolCalls,
|
|
75
|
+
context, finishIfCurrent: command.finishIfCurrent, failure: Promise.withResolvers() };
|
|
76
|
+
let failure;
|
|
77
|
+
active.failure.promise.catch(error => { failure = error; });
|
|
78
|
+
control.current = active;
|
|
79
|
+
const ready = Promise.withResolvers();
|
|
80
|
+
ready.promise.catch(() => {});
|
|
81
|
+
active.steer = async ({ input, beforeDispatch, accept, onNativeReceipt, onReceiptPersisted, deliveryTiming }) => {
|
|
82
|
+
await ready.promise;
|
|
83
|
+
const nativeDispatch = { active, signal, beforeDispatch, accept, onNativeReceipt, onReceiptPersisted, deliveryTiming,
|
|
84
|
+
timeoutMs, steering: true, attempted: false, admitted: false };
|
|
85
|
+
try { await dispatch(control, { input, context, signal }, { acquire, entry, dispatch: nativeDispatch }); }
|
|
86
|
+
catch (error) {
|
|
87
|
+
if (nativeDispatch.attempted && !nativeDispatch.admitted && error.delivery !== "not-sent") error.delivery = "uncertain";
|
|
88
|
+
if (nativeDispatch.attempted) completion.reject(error);
|
|
89
|
+
throw error;
|
|
90
|
+
}
|
|
91
|
+
};
|
|
92
|
+
const nativeDispatch = { active, signal, beforeDispatch, accept, onNativeReceipt: command.onNativeReceipt,
|
|
93
|
+
onReceiptPersisted: command.onReceiptPersisted, deliveryTiming: command.deliveryTiming, timeoutMs, maximumOutput, attempted: false, admitted: false };
|
|
94
|
+
let entry;
|
|
95
|
+
let cancellation;
|
|
96
|
+
let problem;
|
|
97
|
+
let delivered;
|
|
98
|
+
function abort() {
|
|
99
|
+
if (cancellation) return;
|
|
100
|
+
if (bound) {
|
|
101
|
+
cancellation = entry.nativeTurn.cancel(entry.process?.client, () => entry.nativeTurn.stopProcess("Work stopped."));
|
|
102
|
+
cancellation.then(() => completion.reject(signal.reason), error => completion.reject(error));
|
|
103
|
+
} else {
|
|
104
|
+
cancellation = cancel(control, { context, acquire, reportFailure }).then(() => { throw signal.reason; });
|
|
105
|
+
cancellation.catch(active.failure.reject);
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
try {
|
|
109
|
+
if (bound) {
|
|
110
|
+
entry = selectedEntry;
|
|
111
|
+
if (entry.disposed || entry.command) throw new Error("This Claude conversation is closed or already working.");
|
|
112
|
+
active.entry = entry;
|
|
113
|
+
entry.command = active;
|
|
114
|
+
entry.completion = completion;
|
|
115
|
+
entry.toolSchemas = tools?.schemas || [];
|
|
116
|
+
active.delivery = { completion: completion.promise };
|
|
117
|
+
nativeDispatch.prepared = await preparation.configuration(entry, { configuration, context, signal, tools });
|
|
118
|
+
}
|
|
119
|
+
signal.addEventListener("abort", abort, { once: true });
|
|
120
|
+
let dispatchFailure;
|
|
121
|
+
try {
|
|
122
|
+
delivered = await dispatch(control, command, { acquire, entry, dispatch: nativeDispatch });
|
|
123
|
+
if (bound) ready.resolve();
|
|
124
|
+
} catch (error) { dispatchFailure = error; }
|
|
125
|
+
for (;;) {
|
|
126
|
+
const settling = active.delivery;
|
|
127
|
+
let result;
|
|
128
|
+
problem = dispatchFailure || failure;
|
|
129
|
+
if (!problem && settling?.completion) {
|
|
130
|
+
try { result = await Promise.race([settling.completion, active.failure.promise]); }
|
|
131
|
+
catch (error) { problem = error; }
|
|
132
|
+
}
|
|
133
|
+
if (command.finishIfCurrent && !await command.finishIfCurrent(() => active.delivery === settling)) continue;
|
|
134
|
+
if (!problem && typeof result === "function") {
|
|
135
|
+
try { result = await result(); }
|
|
136
|
+
catch (error) { problem = error; }
|
|
137
|
+
}
|
|
138
|
+
problem = dispatchFailure || failure || problem;
|
|
139
|
+
if (!problem && settling?.completion && (bound ? result.status !== "completed" : !result.ok)) {
|
|
140
|
+
// readable-check: allow patched-error - data: status is the native turn's state, not an HTTP status
|
|
141
|
+
problem = Object.assign(new Error(result.error || (bound ? "Claude interrupted this answer." : `Claude turn ${result.status}.`)), { status: result.status });
|
|
142
|
+
}
|
|
143
|
+
break;
|
|
144
|
+
}
|
|
145
|
+
} catch (error) { problem = error; }
|
|
146
|
+
ready.reject(problem || new Error("Claude work ended."));
|
|
147
|
+
signal.removeEventListener("abort", abort);
|
|
148
|
+
if (bound) {
|
|
149
|
+
try {
|
|
150
|
+
if (problem || signal.aborted) await (cancellation || entry.nativeTurn.stopProcess(problem?.message || "Work stopped."));
|
|
151
|
+
} catch (error) {
|
|
152
|
+
if (problem && error !== problem) error.cause ||= problem;
|
|
153
|
+
problem = error;
|
|
154
|
+
}
|
|
155
|
+
finally {
|
|
156
|
+
await Promise.allSettled([...active.toolWork]);
|
|
157
|
+
if (entry?.command === active) entry.command = null;
|
|
158
|
+
}
|
|
159
|
+
} else {
|
|
160
|
+
await cancellation?.catch(() => {});
|
|
161
|
+
if (active.entry) {
|
|
162
|
+
try {
|
|
163
|
+
// A failed Stop already retained custody for the caller's retry.
|
|
164
|
+
// Do not race that retry with another implicit cleanup attempt.
|
|
165
|
+
if ((problem || signal.aborted) && !problem?.cleanupFailed) {
|
|
166
|
+
await active.entry.nativeTurn.stopProcess(problem?.message || "Work stopped.");
|
|
167
|
+
}
|
|
168
|
+
} catch (error) {
|
|
169
|
+
if (problem && error !== problem) error.cause ||= problem;
|
|
170
|
+
problem = error;
|
|
171
|
+
}
|
|
172
|
+
finally {
|
|
173
|
+
await Promise.allSettled([...active.toolWork]);
|
|
174
|
+
if (active.entry.command === active) active.entry.command = null;
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
if (control.current === active) control.current = null;
|
|
179
|
+
if (problem) {
|
|
180
|
+
if (bound && nativeDispatch.attempted && !nativeDispatch.admitted && problem.delivery !== "not-sent") problem.delivery = "uncertain";
|
|
181
|
+
throw problem;
|
|
182
|
+
}
|
|
183
|
+
if (!bound) return { value: delivered.value };
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
return Object.freeze({
|
|
187
|
+
run,
|
|
188
|
+
steer,
|
|
189
|
+
cancel
|
|
190
|
+
});
|
|
191
|
+
}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
1
2
|
import { createReadStream } from "node:fs";
|
|
2
3
|
import { lstat, open, readdir, realpath, rm, stat } from "node:fs/promises";
|
|
3
4
|
import path from "node:path";
|
|
@@ -12,6 +13,12 @@ function requireClaudeSessionId(id) {
|
|
|
12
13
|
return id;
|
|
13
14
|
}
|
|
14
15
|
|
|
16
|
+
// The UUID Claude's history records for an application message id.
|
|
17
|
+
export function claudeNativeMessageId(id) {
|
|
18
|
+
const value = createHash("sha256").update(String(id)).digest("hex");
|
|
19
|
+
return `${value.slice(0, 8)}-${value.slice(8, 12)}-4${value.slice(13, 16)}-8${value.slice(17, 20)}-${value.slice(20, 32)}`;
|
|
20
|
+
}
|
|
21
|
+
|
|
15
22
|
// Claude's project directory name for a canonical working directory.
|
|
16
23
|
function claudeProjectDirectoryName(canonical) {
|
|
17
24
|
return canonical.normalize("NFC").replace(/[^a-zA-Z0-9]/gu, "-");
|