@jskit-ai/assistant-core 0.2.7 → 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.
Files changed (214) hide show
  1. package/examples/conversation/package.json +5 -5
  2. package/package.json +18 -7
  3. package/src/client/browser.js +21 -0
  4. package/src/client/conversation/AssistantAttachmentQueue.vue +25 -108
  5. package/src/client/conversation/AssistantComposerActions.vue +1 -1
  6. package/src/client/conversation/AssistantConversationElement.vue +14 -52
  7. package/src/client/conversation/AssistantGoalControl.vue +13 -10
  8. package/src/client/conversation/AssistantPromptInput.vue +6 -91
  9. package/src/client/conversation/AssistantTranscript.css +505 -0
  10. package/src/client/conversation/AssistantTranscript.vue +24 -1197
  11. package/src/client/conversation/attachmentFiles.js +158 -0
  12. package/src/client/conversation/attachmentQueueItems.js +121 -0
  13. package/src/client/conversation/attachmentUploadRows.js +265 -0
  14. package/src/client/conversation/conversationDisplay.js +54 -0
  15. package/src/client/conversation/messageDelivery.js +13 -9
  16. package/src/client/conversation/retainedConversation.js +14 -3
  17. package/src/client/conversation/submitText.js +13 -2
  18. package/src/client/conversation/transcriptEntries.js +225 -0
  19. package/src/client/conversation/useAssistantAttachments.js +86 -388
  20. package/src/client/conversation/useAssistantQuestions.js +16 -3
  21. package/src/client/conversation/useAssistantSuggestions.js +16 -2
  22. package/src/client/conversation/useScrollToBottom.js +17 -19
  23. package/src/client/conversation/useTextareaAutoGrow.js +115 -0
  24. package/src/client/conversation/useTranscriptScrollFollow.js +525 -0
  25. package/src/client/lib/assistantApi.js +192 -161
  26. package/src/client/types.js +110 -0
  27. package/src/server/conversation/attachments.js +21 -6
  28. package/src/server/conversation/boundedToolLoop.js +1 -0
  29. package/src/server/conversation/claudeClient.js +71 -20
  30. package/src/server/conversation/claudeCommandRun.js +191 -0
  31. package/src/server/conversation/claudeHistory.js +7 -0
  32. package/src/server/conversation/claudeNativeTurn.js +708 -0
  33. package/src/server/conversation/claudeProcess.js +186 -112
  34. package/src/server/conversation/claudeProcessStart.js +214 -0
  35. package/src/server/conversation/claudePromptDelivery.js +183 -0
  36. package/src/server/conversation/claudeSessionCleanup.js +248 -0
  37. package/src/server/conversation/claudeTools.js +15 -3
  38. package/src/server/conversation/claudeTurn.js +212 -1388
  39. package/src/server/conversation/codexAccountReader.js +168 -0
  40. package/src/server/conversation/codexAuthPreflight.js +5 -4
  41. package/src/server/conversation/codexClient.js +18 -6
  42. package/src/server/conversation/codexConfiguration.js +8 -844
  43. package/src/server/conversation/codexConversationStops.js +377 -0
  44. package/src/server/conversation/codexConversationStore.js +119 -85
  45. package/src/server/conversation/codexConversationTurns.js +378 -0
  46. package/src/server/conversation/codexConversations.js +134 -866
  47. package/src/server/conversation/codexDetachedConversation.js +203 -0
  48. package/src/server/conversation/codexDetachedTurn.js +145 -104
  49. package/src/server/conversation/codexEvents.js +5 -200
  50. package/src/server/conversation/codexGoalCommands.js +14 -4
  51. package/src/server/conversation/codexHelperExecution.js +57 -57
  52. package/src/server/conversation/codexHelperThreadLedger.js +30 -288
  53. package/src/server/conversation/codexHelperThreadRecord.js +304 -0
  54. package/src/server/conversation/codexHelperThreadRecovery.js +54 -76
  55. package/src/server/conversation/codexHelperThreads.js +94 -93
  56. package/src/server/conversation/codexHistoryAdapter.js +13 -8
  57. package/src/server/conversation/codexIsolation.js +672 -0
  58. package/src/server/conversation/codexLiveItems.js +379 -0
  59. package/src/server/conversation/codexMessageCommands.js +68 -286
  60. package/src/server/conversation/codexMessageDelivery.js +46 -37
  61. package/src/server/conversation/codexModelCatalog.js +86 -7
  62. package/src/server/conversation/codexNotificationObserver.js +81 -88
  63. package/src/server/conversation/codexNotificationQueue.js +21 -10
  64. package/src/server/conversation/codexObservationControl.js +105 -89
  65. package/src/server/conversation/codexOutput.js +84 -419
  66. package/src/server/conversation/codexProcess.js +29 -1053
  67. package/src/server/conversation/codexProcessLeader.js +1 -0
  68. package/src/server/conversation/codexPromptDispatch.js +241 -0
  69. package/src/server/conversation/codexProvider.js +147 -1807
  70. package/src/server/conversation/codexProviderAuth.js +348 -0
  71. package/src/server/conversation/codexProviderInvalidation.js +371 -0
  72. package/src/server/conversation/codexProviderInventories.js +437 -0
  73. package/src/server/conversation/codexProviderOwner.js +149 -957
  74. package/src/server/conversation/codexProviderRelease.js +465 -0
  75. package/src/server/conversation/codexProviderSelection.js +26 -15
  76. package/src/server/conversation/codexProviderThreads.js +698 -0
  77. package/src/server/conversation/codexProviderTurns.js +453 -0
  78. package/src/server/conversation/codexReasoning.js +32 -23
  79. package/src/server/conversation/codexRenewalCommands.js +18 -6
  80. package/src/server/conversation/codexRenewalThreads.js +7 -22
  81. package/src/server/conversation/codexResultSettlement.js +70 -88
  82. package/src/server/conversation/codexRunJournal.js +91 -97
  83. package/src/server/conversation/codexRuntime.js +426 -0
  84. package/src/server/conversation/codexRuntimeInventory.js +113 -0
  85. package/src/server/conversation/codexRuntimeLifecycle.js +39 -13
  86. package/src/server/conversation/codexRuntimeStart.js +431 -0
  87. package/src/server/conversation/codexRuntimeStop.js +324 -0
  88. package/src/server/conversation/codexSessionCleanup.js +25 -10
  89. package/src/server/conversation/codexSessionReconciliation.js +21 -7
  90. package/src/server/conversation/codexThreadReadiness.js +49 -52
  91. package/src/server/conversation/codexThreadSnapshot.js +210 -0
  92. package/src/server/conversation/codexThreadStatus.js +48 -62
  93. package/src/server/conversation/codexTurn.js +171 -55
  94. package/src/server/conversation/codexTurnActivity.js +210 -0
  95. package/src/server/conversation/codexTurnControl.js +44 -48
  96. package/src/server/conversation/codexTurnEndings.js +610 -0
  97. package/src/server/conversation/codexTurnRecovery.js +97 -848
  98. package/src/server/conversation/codexTurnState.js +138 -111
  99. package/src/server/conversation/completedResponses.js +227 -0
  100. package/src/server/conversation/configuration.js +2 -2
  101. package/src/server/conversation/continuity.js +43 -6
  102. package/src/server/conversation/conversationDelivery.js +394 -0
  103. package/src/server/conversation/conversationEntries.js +365 -0
  104. package/src/server/conversation/conversationEvents.js +192 -0
  105. package/src/server/conversation/conversationGoals.js +187 -0
  106. package/src/server/conversation/conversationHandles.js +237 -0
  107. package/src/server/conversation/conversationReads.js +206 -0
  108. package/src/server/conversation/conversationRefusals.js +58 -0
  109. package/src/server/conversation/conversationReplacement.js +254 -0
  110. package/src/server/conversation/conversationSending.js +273 -0
  111. package/src/server/conversation/conversationStopping.js +121 -0
  112. package/src/server/conversation/conversationTurnDelivery.js +413 -0
  113. package/src/server/conversation/conversationTurns.js +511 -0
  114. package/src/server/conversation/deliveryTiming.js +25 -3
  115. package/src/server/conversation/fileStorage.js +11 -6
  116. package/src/server/conversation/hookBridge.js +14 -3
  117. package/src/server/conversation/index.js +1 -1
  118. package/src/server/conversation/localExecution.js +82 -625
  119. package/src/server/conversation/nativeCommandCallbacks.js +10 -0
  120. package/src/server/conversation/nativeConversationLifecycle.js +379 -0
  121. package/src/server/conversation/nativeHistoryExport.js +27 -18
  122. package/src/server/conversation/normalize.js +9 -0
  123. package/src/server/conversation/openCodeClient.js +295 -366
  124. package/src/server/conversation/openCodeNativeHistory.js +141 -0
  125. package/src/server/conversation/openCodeNativeSession.js +79 -0
  126. package/src/server/conversation/openCodeProcess.js +109 -53
  127. package/src/server/conversation/openCodeProcessOwner.js +565 -0
  128. package/src/server/conversation/openCodeRuntime.js +111 -1814
  129. package/src/server/conversation/openCodeSessionTurns.js +547 -0
  130. package/src/server/conversation/openCodeTemporaryConversations.js +394 -0
  131. package/src/server/conversation/openCodeTools.js +28 -14
  132. package/src/server/conversation/openCodeTurn.js +80 -74
  133. package/src/server/conversation/openCodeTurnMonitor.js +388 -0
  134. package/src/server/conversation/processIdentity.js +582 -0
  135. package/src/server/conversation/provider.js +21 -6
  136. package/src/server/conversation/providers/api.js +268 -107
  137. package/src/server/conversation/providers/claude.js +29 -6
  138. package/src/server/conversation/providers/claudeDriver.js +325 -250
  139. package/src/server/conversation/providers/codex.js +69 -33
  140. package/src/server/conversation/providers/codexDriver.js +503 -1081
  141. package/src/server/conversation/providers/codexDriverConnection.js +331 -0
  142. package/src/server/conversation/providers/codexDriverTurns.js +464 -0
  143. package/src/server/conversation/providers/opencode.js +30 -4
  144. package/src/server/conversation/providers/opencodeDriver.js +321 -659
  145. package/src/server/conversation/providers/opencodeStandalone.js +475 -0
  146. package/src/server/conversation/providers/scoped.js +64 -19
  147. package/src/server/conversation/runtime.js +171 -2863
  148. package/src/server/conversation/storage.js +91 -50
  149. package/src/server/conversation/streams.js +15 -2
  150. package/src/server/conversation/structuredOutput.js +7 -11
  151. package/src/server/conversation/systemPrompt.js +13 -1
  152. package/src/server/conversation/tools.js +47 -15
  153. package/src/server/conversation/transcript.js +49 -20
  154. package/src/server/hosts/claudeTurn.js +2 -4
  155. package/src/server/hosts/codexConfiguration.js +6 -8
  156. package/src/server/hosts/codexEvents.js +4 -2
  157. package/src/server/hosts/codexProcess.js +1 -1
  158. package/src/server/hosts/codexProvider.js +11 -9
  159. package/src/server/hosts/codexTurn.js +3 -3
  160. package/src/server/hosts/openCodeClient.js +4 -2
  161. package/src/server/index.js +0 -2
  162. package/src/server/lib/aiConnectionClient.js +35 -25
  163. package/src/server/lib/ndjson.js +1 -0
  164. package/src/server/lib/providers/anthropicClient.js +15 -6
  165. package/src/server/lib/providers/common.js +10 -6
  166. package/src/server/lib/providers/openAiCompatibleClient.js +40 -23
  167. package/src/server/lib/serviceToolCatalog.js +29 -283
  168. package/src/server/lib/serviceToolEntries.js +270 -0
  169. package/src/server/types.js +78 -0
  170. package/src/shared/assistantResource.js +0 -23
  171. package/src/shared/conversation/answerChoiceSugar.js +2 -14
  172. package/src/shared/conversation/messageInputSugar.js +18 -0
  173. package/src/shared/conversation/numberedQuestionSugar.js +6 -16
  174. package/src/shared/conversation/pagination.js +1 -1
  175. package/src/shared/conversation/questions.js +1 -0
  176. package/src/shared/conversation/streaming.js +1 -0
  177. package/src/shared/index.js +0 -1
  178. package/src/testing/conversationStorageContract.js +20 -0
  179. package/test/aiConnectionClient.test.js +42 -1
  180. package/test/apiConversationDriver.test.js +217 -38
  181. package/test/assistantBoundedToolLoop.test.js +1 -1
  182. package/test/claudeConversationSession.test.js +15 -11
  183. package/test/claudeInstructionResume.test.js +1 -1
  184. package/test/claudeProcess.test.js +9 -9
  185. package/test/claudeTurn.test.js +21 -8
  186. package/test/codexAccountChange.test.js +5 -3
  187. package/test/codexConversationSession.test.js +22 -21
  188. package/test/codexEvents.test.js +3 -1
  189. package/test/codexHelperDeletion.test.js +3 -7
  190. package/test/codexIsolation.test.js +6 -6
  191. package/test/codexModelCatalog.test.js +6 -7
  192. package/test/codexProcess.test.js +3 -3
  193. package/test/codexThreadSelection.test.js +4 -1
  194. package/test/codexTurn.test.js +29 -20
  195. package/test/conversationApiFacilities.test.js +11 -9
  196. package/test/conversationAttachments.test.js +3 -3
  197. package/test/conversationConfiguration.test.js +4 -4
  198. package/test/conversationNativeStorage.test.js +11 -10
  199. package/test/conversationReplacement.test.js +13 -13
  200. package/test/conversationRuntime.test.js +9 -5
  201. package/test/conversationSession.test.js +78 -22
  202. package/test/conversationTools.test.js +47 -26
  203. package/test/fileConversationStorage.test.js +10 -3
  204. package/test/fixtures/codexHelperThreadLedger.js +3 -3
  205. package/test/frameworkActions.test.js +33 -18
  206. package/test/localConversationExecution.test.js +4 -4
  207. package/test/localExecution.test.js +1 -1
  208. package/test/openCodeConnectionChange.test.js +3 -2
  209. package/test/openCodeRuntime.test.js +8 -7
  210. package/test/openCodeTools.test.js +1 -1
  211. package/test/opencodeConversationSession.test.js +4 -3
  212. package/test/serviceToolCatalog.test.js +3 -8
  213. package/src/server/lib/assistantCompletion.js +0 -190
  214. 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
- function createClaudeJsonClient({ stream, onEvent = async () => {}, onControlRequest, onFailure = async () => {}, timeoutMs = 30_000 } = {}) {
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 { options.onProgress?.(stage); }
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: () => supportsSendNow,
218
- initialize: ({ hooks } = {}, options) => request({ subtype: "initialize", ...(hooks ? { hooks } : {}) }, options),
219
- interrupt: () => request({ subtype: "interrupt" }),
280
+ supportsSendNow: sendNowSupported,
281
+ initialize,
282
+ interrupt,
220
283
  request,
221
- whenRequestsIdle: () => Promise.all([...incoming.values()].map(pending => pending.done)),
222
- async send(message, { messageId = randomUUID(), sessionId = "", sendNow = false } = {}) {
223
- // Older CLIs treat this control as a plain Stop, cancelling background
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, "-");