happy-imou-cloud 2.1.49 → 2.1.51
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/dist/AcpBackend-CqO3D07V.mjs +2619 -0
- package/dist/AcpBackend-XPiTd6ph.cjs +2621 -0
- package/dist/{BaseReasoningProcessor-Dn9NcoHz.cjs → BaseReasoningProcessor-BD9tiwep.cjs} +1 -144
- package/dist/{BaseReasoningProcessor-CAVeOdyo.mjs → BaseReasoningProcessor-CjlayL2f.mjs} +2 -144
- package/dist/ConversationHistory-Bl2doTA-.cjs +780 -0
- package/dist/ConversationHistory-CI5bBfuA.mjs +771 -0
- package/dist/{ProviderSelectionHandler-BJJc7qOR.cjs → ProviderSelectionHandler-C7GE5QjX.cjs} +6 -6
- package/dist/{ProviderSelectionHandler-DIYidT13.mjs → ProviderSelectionHandler-uQ8jzdzr.mjs} +2 -2
- package/dist/RuntimeShell-BDt42io_.mjs +252 -0
- package/dist/RuntimeShell-D_Te12wq.cjs +258 -0
- package/dist/bootstrapManagedProviderSession-Bln-TwyB.cjs +147 -0
- package/dist/bootstrapManagedProviderSession-D2Z6YU3n.mjs +145 -0
- package/dist/claude-BKNT-2fG.cjs +1080 -0
- package/dist/claude-CnN5WCWj.mjs +1073 -0
- package/dist/codex-DLGP8WF6.mjs +577 -0
- package/dist/codex-Fv2eali8.cjs +582 -0
- package/dist/{command-VcH4hbhi.cjs → command-BWPlJyCN.cjs} +16 -8
- package/dist/{command-CzfRRhVe.mjs → command-CELwsYoG.mjs} +15 -7
- package/dist/config-CFL0Gkqt.cjs +184 -0
- package/dist/config-ChSPe7p9.mjs +174 -0
- package/dist/createDefaultRuntimeShell-BXu3vCvT.cjs +33 -0
- package/dist/createDefaultRuntimeShell-DOg6g3-G.mjs +31 -0
- package/dist/cursor-Blq1cHdr.cjs +91 -0
- package/dist/cursor-CwPNSy_A.mjs +88 -0
- package/dist/future-Dq4Ha1Dn.cjs +24 -0
- package/dist/future-xRdLl3vf.mjs +22 -0
- package/dist/{index-xa1kwZoj.cjs → index-B_JYgMUS.cjs} +189 -5352
- package/dist/{index-7Z93BoVn.mjs → index-CX-F_fuk.mjs} +177 -5331
- package/dist/index.cjs +2 -2
- package/dist/index.mjs +2 -2
- package/dist/installFatalProcessHandlers-0vaw9MAz.mjs +55 -0
- package/dist/installFatalProcessHandlers-CyURn5Bp.cjs +57 -0
- package/dist/launch-BoCCEd5p.mjs +63 -0
- package/dist/launch-wZA5BcvS.cjs +66 -0
- package/dist/lib.cjs +2 -3
- package/dist/lib.d.cts +20 -17
- package/dist/lib.d.mts +20 -17
- package/dist/lib.mjs +1 -2
- package/dist/resolveCommand-B3BGyBE2.mjs +189 -0
- package/dist/resolveCommand-DYMd9PNC.cjs +193 -0
- package/dist/{runClaude-zCwRhpOw.mjs → runClaude-Be0myF9k.mjs} +8 -5
- package/dist/{runClaude-BBGNmGj6.cjs → runClaude-DZJt5er7.cjs} +46 -43
- package/dist/{runCodex-BbgLVjb9.mjs → runCodex-BSnyN4m7.mjs} +226 -117
- package/dist/{runCodex-jUU6U2tZ.cjs → runCodex-DTCcGRue.cjs} +269 -160
- package/dist/runCursor-Bn1PuwJy.cjs +506 -0
- package/dist/runCursor-M6dQ6bGF.mjs +504 -0
- package/dist/{runGemini-DcwNsudA.mjs → runGemini-BNm4vYKA.mjs} +279 -5
- package/dist/{runGemini-C0NT8MHK.cjs → runGemini-Bn3lFhz6.cjs} +309 -35
- package/dist/{registerKillSessionHandler-DLDg2EES.mjs → sessionControl-1bT_7OI6.mjs} +1643 -2405
- package/dist/{registerKillSessionHandler-CfCya6si.cjs → sessionControl-flKnQrx0.cjs} +1647 -2417
- package/dist/{api-DnqaNvyV.mjs → types-B5vtxa38.mjs} +55 -5
- package/dist/{api-D7nAeZi7.cjs → types-CttABk32.cjs} +55 -4
- package/package.json +2 -2
- package/dist/types-CiliQpqS.mjs +0 -52
- package/dist/types-DVk3crez.cjs +0 -54
|
@@ -0,0 +1,1080 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var node_crypto = require('node:crypto');
|
|
4
|
+
var path = require('node:path');
|
|
5
|
+
var node_child_process = require('node:child_process');
|
|
6
|
+
var node_readline = require('node:readline');
|
|
7
|
+
var fs = require('node:fs');
|
|
8
|
+
var index = require('./index-B_JYgMUS.cjs');
|
|
9
|
+
var persistence = require('./types-CttABk32.cjs');
|
|
10
|
+
var future = require('./future-Dq4Ha1Dn.cjs');
|
|
11
|
+
var RuntimeShell = require('./RuntimeShell-D_Te12wq.cjs');
|
|
12
|
+
|
|
13
|
+
class Stream {
|
|
14
|
+
constructor(returned) {
|
|
15
|
+
this.returned = returned;
|
|
16
|
+
}
|
|
17
|
+
queue = [];
|
|
18
|
+
readResolve;
|
|
19
|
+
readReject;
|
|
20
|
+
isDone = false;
|
|
21
|
+
hasError;
|
|
22
|
+
started = false;
|
|
23
|
+
/**
|
|
24
|
+
* Implements async iterable protocol
|
|
25
|
+
*/
|
|
26
|
+
[Symbol.asyncIterator]() {
|
|
27
|
+
if (this.started) {
|
|
28
|
+
throw new Error("Stream can only be iterated once");
|
|
29
|
+
}
|
|
30
|
+
this.started = true;
|
|
31
|
+
return this;
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Gets the next value from the stream
|
|
35
|
+
*/
|
|
36
|
+
async next() {
|
|
37
|
+
if (this.queue.length > 0) {
|
|
38
|
+
return Promise.resolve({
|
|
39
|
+
done: false,
|
|
40
|
+
value: this.queue.shift()
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
if (this.isDone) {
|
|
44
|
+
return Promise.resolve({ done: true, value: void 0 });
|
|
45
|
+
}
|
|
46
|
+
if (this.hasError) {
|
|
47
|
+
return Promise.reject(this.hasError);
|
|
48
|
+
}
|
|
49
|
+
return new Promise((resolve, reject) => {
|
|
50
|
+
this.readResolve = resolve;
|
|
51
|
+
this.readReject = reject;
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* Adds a value to the stream
|
|
56
|
+
*/
|
|
57
|
+
enqueue(value) {
|
|
58
|
+
if (this.readResolve) {
|
|
59
|
+
const resolve = this.readResolve;
|
|
60
|
+
this.readResolve = void 0;
|
|
61
|
+
this.readReject = void 0;
|
|
62
|
+
resolve({ done: false, value });
|
|
63
|
+
} else {
|
|
64
|
+
this.queue.push(value);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* Marks the stream as complete
|
|
69
|
+
*/
|
|
70
|
+
done() {
|
|
71
|
+
this.isDone = true;
|
|
72
|
+
if (this.readResolve) {
|
|
73
|
+
const resolve = this.readResolve;
|
|
74
|
+
this.readResolve = void 0;
|
|
75
|
+
this.readReject = void 0;
|
|
76
|
+
resolve({ done: true, value: void 0 });
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* Propagates an error through the stream
|
|
81
|
+
*/
|
|
82
|
+
error(error) {
|
|
83
|
+
this.hasError = error;
|
|
84
|
+
if (this.readReject) {
|
|
85
|
+
const reject = this.readReject;
|
|
86
|
+
this.readResolve = void 0;
|
|
87
|
+
this.readReject = void 0;
|
|
88
|
+
reject(error);
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
/**
|
|
92
|
+
* Implements async iterator cleanup
|
|
93
|
+
*/
|
|
94
|
+
async return() {
|
|
95
|
+
this.isDone = true;
|
|
96
|
+
if (this.returned) {
|
|
97
|
+
this.returned();
|
|
98
|
+
}
|
|
99
|
+
return Promise.resolve({ done: true, value: void 0 });
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
class AbortError extends Error {
|
|
104
|
+
constructor(message) {
|
|
105
|
+
super(message);
|
|
106
|
+
this.name = "AbortError";
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
class Query {
|
|
111
|
+
constructor(childStdin, childStdout, processExitPromise, canCallTool) {
|
|
112
|
+
this.childStdin = childStdin;
|
|
113
|
+
this.childStdout = childStdout;
|
|
114
|
+
this.processExitPromise = processExitPromise;
|
|
115
|
+
this.canCallTool = canCallTool;
|
|
116
|
+
this.readMessages();
|
|
117
|
+
this.sdkMessages = this.readSdkMessages();
|
|
118
|
+
}
|
|
119
|
+
pendingControlResponses = /* @__PURE__ */ new Map();
|
|
120
|
+
cancelControllers = /* @__PURE__ */ new Map();
|
|
121
|
+
controlTasks = /* @__PURE__ */ new Set();
|
|
122
|
+
sdkMessages;
|
|
123
|
+
inputStream = new Stream();
|
|
124
|
+
canCallTool;
|
|
125
|
+
/**
|
|
126
|
+
* Set an error on the stream
|
|
127
|
+
*/
|
|
128
|
+
setError(error) {
|
|
129
|
+
this.inputStream.error(error);
|
|
130
|
+
}
|
|
131
|
+
/**
|
|
132
|
+
* AsyncIterableIterator implementation
|
|
133
|
+
*/
|
|
134
|
+
next(...args) {
|
|
135
|
+
return this.sdkMessages.next(...args);
|
|
136
|
+
}
|
|
137
|
+
return(value) {
|
|
138
|
+
if (this.sdkMessages.return) {
|
|
139
|
+
return this.sdkMessages.return(value);
|
|
140
|
+
}
|
|
141
|
+
return Promise.resolve({ done: true, value: void 0 });
|
|
142
|
+
}
|
|
143
|
+
throw(e) {
|
|
144
|
+
if (this.sdkMessages.throw) {
|
|
145
|
+
return this.sdkMessages.throw(e);
|
|
146
|
+
}
|
|
147
|
+
return Promise.reject(e);
|
|
148
|
+
}
|
|
149
|
+
[Symbol.asyncIterator]() {
|
|
150
|
+
return this.sdkMessages;
|
|
151
|
+
}
|
|
152
|
+
/**
|
|
153
|
+
* Read messages from Claude process stdout
|
|
154
|
+
*/
|
|
155
|
+
async readMessages() {
|
|
156
|
+
const rl = node_readline.createInterface({ input: this.childStdout });
|
|
157
|
+
try {
|
|
158
|
+
for await (const line of rl) {
|
|
159
|
+
if (line.trim()) {
|
|
160
|
+
try {
|
|
161
|
+
const message = JSON.parse(line);
|
|
162
|
+
if (message.type === "control_response") {
|
|
163
|
+
const controlResponse = message;
|
|
164
|
+
const handler = this.pendingControlResponses.get(controlResponse.response.request_id);
|
|
165
|
+
if (handler) {
|
|
166
|
+
handler(controlResponse.response);
|
|
167
|
+
}
|
|
168
|
+
continue;
|
|
169
|
+
} else if (message.type === "control_request") {
|
|
170
|
+
const controlTask = this.handleControlRequest(message).catch((error) => {
|
|
171
|
+
persistence.logger.debug("[ClaudeSDK] Failed to handle control request:", error);
|
|
172
|
+
}).finally(() => {
|
|
173
|
+
this.controlTasks.delete(controlTask);
|
|
174
|
+
});
|
|
175
|
+
this.controlTasks.add(controlTask);
|
|
176
|
+
continue;
|
|
177
|
+
} else if (message.type === "control_cancel_request") {
|
|
178
|
+
this.handleControlCancelRequest(message);
|
|
179
|
+
continue;
|
|
180
|
+
}
|
|
181
|
+
this.inputStream.enqueue(message);
|
|
182
|
+
} catch (e) {
|
|
183
|
+
persistence.logger.debug(line);
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
await this.processExitPromise;
|
|
188
|
+
} catch (error) {
|
|
189
|
+
this.inputStream.error(error);
|
|
190
|
+
} finally {
|
|
191
|
+
this.cleanupControllers();
|
|
192
|
+
if (this.controlTasks.size > 0) {
|
|
193
|
+
await Promise.allSettled(Array.from(this.controlTasks));
|
|
194
|
+
}
|
|
195
|
+
this.inputStream.done();
|
|
196
|
+
rl.close();
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
/**
|
|
200
|
+
* Async generator for SDK messages
|
|
201
|
+
*/
|
|
202
|
+
async *readSdkMessages() {
|
|
203
|
+
for await (const message of this.inputStream) {
|
|
204
|
+
yield message;
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
/**
|
|
208
|
+
* Send interrupt request to Claude
|
|
209
|
+
*/
|
|
210
|
+
async interrupt() {
|
|
211
|
+
if (!this.childStdin) {
|
|
212
|
+
throw new Error("Interrupt requires --input-format stream-json");
|
|
213
|
+
}
|
|
214
|
+
await this.request({
|
|
215
|
+
subtype: "interrupt"
|
|
216
|
+
}, this.childStdin);
|
|
217
|
+
}
|
|
218
|
+
/**
|
|
219
|
+
* Send control request to Claude process
|
|
220
|
+
*/
|
|
221
|
+
request(request, childStdin) {
|
|
222
|
+
const requestId = Math.random().toString(36).substring(2, 15);
|
|
223
|
+
const sdkRequest = {
|
|
224
|
+
request_id: requestId,
|
|
225
|
+
type: "control_request",
|
|
226
|
+
request
|
|
227
|
+
};
|
|
228
|
+
return new Promise((resolve, reject) => {
|
|
229
|
+
this.pendingControlResponses.set(requestId, (response) => {
|
|
230
|
+
if (response.subtype === "success") {
|
|
231
|
+
resolve(response);
|
|
232
|
+
} else {
|
|
233
|
+
reject(new Error(response.error));
|
|
234
|
+
}
|
|
235
|
+
});
|
|
236
|
+
childStdin.write(JSON.stringify(sdkRequest) + "\n");
|
|
237
|
+
});
|
|
238
|
+
}
|
|
239
|
+
/**
|
|
240
|
+
* Handle incoming control requests for tool permissions
|
|
241
|
+
* Replicates the exact logic from the SDK's handleControlRequest method
|
|
242
|
+
*/
|
|
243
|
+
async handleControlRequest(request) {
|
|
244
|
+
if (!this.childStdin) {
|
|
245
|
+
index.logDebug("Cannot handle control request - no stdin available");
|
|
246
|
+
return;
|
|
247
|
+
}
|
|
248
|
+
const controller = new AbortController();
|
|
249
|
+
this.cancelControllers.set(request.request_id, controller);
|
|
250
|
+
try {
|
|
251
|
+
const response = await this.processControlRequest(request, controller.signal);
|
|
252
|
+
const controlResponse = {
|
|
253
|
+
type: "control_response",
|
|
254
|
+
response: {
|
|
255
|
+
subtype: "success",
|
|
256
|
+
request_id: request.request_id,
|
|
257
|
+
response
|
|
258
|
+
}
|
|
259
|
+
};
|
|
260
|
+
this.writeControlResponse(controlResponse);
|
|
261
|
+
} catch (error) {
|
|
262
|
+
const controlErrorResponse = {
|
|
263
|
+
type: "control_response",
|
|
264
|
+
response: {
|
|
265
|
+
subtype: "error",
|
|
266
|
+
request_id: request.request_id,
|
|
267
|
+
error: error instanceof Error ? error.message : String(error)
|
|
268
|
+
}
|
|
269
|
+
};
|
|
270
|
+
this.writeControlResponse(controlErrorResponse);
|
|
271
|
+
} finally {
|
|
272
|
+
this.cancelControllers.delete(request.request_id);
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
writeControlResponse(response) {
|
|
276
|
+
if (!this.childStdin || this.childStdin.destroyed || !this.childStdin.writable) {
|
|
277
|
+
return;
|
|
278
|
+
}
|
|
279
|
+
try {
|
|
280
|
+
this.childStdin.write(JSON.stringify(response) + "\n");
|
|
281
|
+
} catch (error) {
|
|
282
|
+
persistence.logger.debug("[ClaudeSDK] Failed to write control response:", error);
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
/**
|
|
286
|
+
* Handle control cancel requests
|
|
287
|
+
* Replicates the exact logic from the SDK's handleControlCancelRequest method
|
|
288
|
+
*/
|
|
289
|
+
handleControlCancelRequest(request) {
|
|
290
|
+
const controller = this.cancelControllers.get(request.request_id);
|
|
291
|
+
if (controller) {
|
|
292
|
+
controller.abort();
|
|
293
|
+
this.cancelControllers.delete(request.request_id);
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
/**
|
|
297
|
+
* Process control requests based on subtype
|
|
298
|
+
* Replicates the exact logic from the SDK's processControlRequest method
|
|
299
|
+
*/
|
|
300
|
+
async processControlRequest(request, signal) {
|
|
301
|
+
if (request.request.subtype === "can_use_tool") {
|
|
302
|
+
if (!this.canCallTool) {
|
|
303
|
+
throw new Error("canCallTool callback is not provided.");
|
|
304
|
+
}
|
|
305
|
+
return this.canCallTool(request.request.tool_name, request.request.input, {
|
|
306
|
+
signal,
|
|
307
|
+
requestId: request.request_id
|
|
308
|
+
});
|
|
309
|
+
}
|
|
310
|
+
throw new Error("Unsupported control request subtype: " + request.request.subtype);
|
|
311
|
+
}
|
|
312
|
+
/**
|
|
313
|
+
* Cleanup method to abort all pending control requests
|
|
314
|
+
*/
|
|
315
|
+
cleanupControllers() {
|
|
316
|
+
for (const [requestId, controller] of this.cancelControllers.entries()) {
|
|
317
|
+
controller.abort();
|
|
318
|
+
this.cancelControllers.delete(requestId);
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
function query(config) {
|
|
323
|
+
const {
|
|
324
|
+
prompt,
|
|
325
|
+
options: {
|
|
326
|
+
allowedTools = [],
|
|
327
|
+
appendSystemPrompt,
|
|
328
|
+
customSystemPrompt,
|
|
329
|
+
cwd,
|
|
330
|
+
disallowedTools = [],
|
|
331
|
+
env,
|
|
332
|
+
executable = "node",
|
|
333
|
+
executableArgs = [],
|
|
334
|
+
maxTurns,
|
|
335
|
+
mcpServers,
|
|
336
|
+
pathToClaudeCodeExecutable = index.getDefaultClaudeCodePath(),
|
|
337
|
+
permissionMode,
|
|
338
|
+
continue: continueConversation,
|
|
339
|
+
resume,
|
|
340
|
+
model,
|
|
341
|
+
fallbackModel,
|
|
342
|
+
strictMcpConfig,
|
|
343
|
+
canCallTool,
|
|
344
|
+
settingsPath,
|
|
345
|
+
extraArgs = []
|
|
346
|
+
} = {}
|
|
347
|
+
} = config;
|
|
348
|
+
if (!process.env.CLAUDE_CODE_ENTRYPOINT) {
|
|
349
|
+
process.env.CLAUDE_CODE_ENTRYPOINT = "sdk-ts";
|
|
350
|
+
}
|
|
351
|
+
const args = [...extraArgs, "--output-format", "stream-json", "--verbose"];
|
|
352
|
+
if (customSystemPrompt) args.push("--system-prompt", customSystemPrompt);
|
|
353
|
+
if (appendSystemPrompt) args.push("--append-system-prompt", appendSystemPrompt);
|
|
354
|
+
if (maxTurns) args.push("--max-turns", maxTurns.toString());
|
|
355
|
+
if (model) args.push("--model", model);
|
|
356
|
+
if (canCallTool) {
|
|
357
|
+
if (typeof prompt === "string") {
|
|
358
|
+
throw new Error("canCallTool callback requires --input-format stream-json. Please set prompt as an AsyncIterable.");
|
|
359
|
+
}
|
|
360
|
+
args.push("--permission-prompt-tool", "stdio");
|
|
361
|
+
}
|
|
362
|
+
if (continueConversation) args.push("--continue");
|
|
363
|
+
if (resume) args.push("--resume", resume);
|
|
364
|
+
if (allowedTools.length > 0) args.push("--allowedTools", allowedTools.join(","));
|
|
365
|
+
if (disallowedTools.length > 0) args.push("--disallowedTools", disallowedTools.join(","));
|
|
366
|
+
if (mcpServers && Object.keys(mcpServers).length > 0) {
|
|
367
|
+
args.push("--mcp-config", JSON.stringify({ mcpServers }));
|
|
368
|
+
}
|
|
369
|
+
if (strictMcpConfig) args.push("--strict-mcp-config");
|
|
370
|
+
if (permissionMode) args.push("--permission-mode", permissionMode);
|
|
371
|
+
if (settingsPath) args.push("--settings", settingsPath);
|
|
372
|
+
if (fallbackModel) {
|
|
373
|
+
if (model && fallbackModel === model) {
|
|
374
|
+
throw new Error("Fallback model cannot be the same as the main model. Please specify a different model for fallbackModel option.");
|
|
375
|
+
}
|
|
376
|
+
args.push("--fallback-model", fallbackModel);
|
|
377
|
+
}
|
|
378
|
+
if (typeof prompt === "string") {
|
|
379
|
+
args.push("--print", prompt.trim());
|
|
380
|
+
} else {
|
|
381
|
+
args.push("--input-format", "stream-json");
|
|
382
|
+
}
|
|
383
|
+
const isJsFile = pathToClaudeCodeExecutable.endsWith(".js") || pathToClaudeCodeExecutable.endsWith(".cjs");
|
|
384
|
+
const isCommandOnly = pathToClaudeCodeExecutable === "claude";
|
|
385
|
+
if (!isCommandOnly && !fs.existsSync(pathToClaudeCodeExecutable)) {
|
|
386
|
+
throw new ReferenceError(`Claude Code executable not found at ${pathToClaudeCodeExecutable}. Is options.pathToClaudeCodeExecutable set?`);
|
|
387
|
+
}
|
|
388
|
+
const spawnCommand = isJsFile ? executable : pathToClaudeCodeExecutable;
|
|
389
|
+
const spawnArgs = isJsFile ? [...executableArgs, pathToClaudeCodeExecutable, ...args] : args;
|
|
390
|
+
const spawnEnv = {
|
|
391
|
+
...isCommandOnly ? index.getCleanEnv() : process.env,
|
|
392
|
+
...env
|
|
393
|
+
};
|
|
394
|
+
index.logDebug(`Spawning Claude Code process: ${spawnCommand} ${spawnArgs.join(" ")} (using ${isCommandOnly ? "clean" : "normal"} env)`);
|
|
395
|
+
const child = node_child_process.spawn(spawnCommand, spawnArgs, {
|
|
396
|
+
cwd,
|
|
397
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
398
|
+
signal: config.options?.abort,
|
|
399
|
+
env: spawnEnv,
|
|
400
|
+
// Use shell on Windows for global binaries and command-only mode
|
|
401
|
+
shell: !isJsFile && process.platform === "win32",
|
|
402
|
+
windowsHide: true
|
|
403
|
+
});
|
|
404
|
+
let childStdin = null;
|
|
405
|
+
if (typeof prompt === "string") {
|
|
406
|
+
child.stdin.end();
|
|
407
|
+
} else {
|
|
408
|
+
index.streamToStdin(prompt, child.stdin, config.options?.abort);
|
|
409
|
+
childStdin = child.stdin;
|
|
410
|
+
}
|
|
411
|
+
if (process.env.DEBUG) {
|
|
412
|
+
child.stderr.on("data", (data) => {
|
|
413
|
+
console.error("Claude Code stderr:", data.toString());
|
|
414
|
+
});
|
|
415
|
+
}
|
|
416
|
+
const cleanup = () => {
|
|
417
|
+
if (!child.killed) {
|
|
418
|
+
child.kill("SIGTERM");
|
|
419
|
+
}
|
|
420
|
+
};
|
|
421
|
+
config.options?.abort?.addEventListener("abort", cleanup);
|
|
422
|
+
process.on("exit", cleanup);
|
|
423
|
+
const processExitPromise = new Promise((resolve) => {
|
|
424
|
+
child.on("close", (code) => {
|
|
425
|
+
if (config.options?.abort?.aborted) {
|
|
426
|
+
query2.setError(new AbortError("Claude Code process aborted by user"));
|
|
427
|
+
}
|
|
428
|
+
if (code !== 0) {
|
|
429
|
+
query2.setError(new Error(`Claude Code process exited with code ${code}`));
|
|
430
|
+
} else {
|
|
431
|
+
resolve();
|
|
432
|
+
}
|
|
433
|
+
});
|
|
434
|
+
});
|
|
435
|
+
const query2 = new Query(childStdin, child.stdout, processExitPromise, canCallTool);
|
|
436
|
+
child.on("error", (error) => {
|
|
437
|
+
if (config.options?.abort?.aborted) {
|
|
438
|
+
query2.setError(new AbortError("Claude Code process aborted by user"));
|
|
439
|
+
} else {
|
|
440
|
+
query2.setError(new Error(`Failed to spawn Claude Code process: ${error.message}`));
|
|
441
|
+
}
|
|
442
|
+
});
|
|
443
|
+
processExitPromise.finally(() => {
|
|
444
|
+
cleanup();
|
|
445
|
+
config.options?.abort?.removeEventListener("abort", cleanup);
|
|
446
|
+
if (process.env.CLAUDE_SDK_MCP_SERVERS) {
|
|
447
|
+
delete process.env.CLAUDE_SDK_MCP_SERVERS;
|
|
448
|
+
}
|
|
449
|
+
});
|
|
450
|
+
return query2;
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
class PushableAsyncIterable {
|
|
454
|
+
queue = [];
|
|
455
|
+
waiters = [];
|
|
456
|
+
isDone = false;
|
|
457
|
+
error = null;
|
|
458
|
+
started = false;
|
|
459
|
+
constructor() {
|
|
460
|
+
}
|
|
461
|
+
/**
|
|
462
|
+
* Push a value to the iterable
|
|
463
|
+
*/
|
|
464
|
+
push(value) {
|
|
465
|
+
if (this.isDone) {
|
|
466
|
+
throw new Error("Cannot push to completed iterable");
|
|
467
|
+
}
|
|
468
|
+
if (this.error) {
|
|
469
|
+
throw this.error;
|
|
470
|
+
}
|
|
471
|
+
const waiter = this.waiters.shift();
|
|
472
|
+
if (waiter) {
|
|
473
|
+
waiter.resolve({ done: false, value });
|
|
474
|
+
} else {
|
|
475
|
+
this.queue.push(value);
|
|
476
|
+
}
|
|
477
|
+
}
|
|
478
|
+
/**
|
|
479
|
+
* Mark the iterable as complete
|
|
480
|
+
*/
|
|
481
|
+
end() {
|
|
482
|
+
if (this.isDone) {
|
|
483
|
+
return;
|
|
484
|
+
}
|
|
485
|
+
this.isDone = true;
|
|
486
|
+
this.cleanup();
|
|
487
|
+
}
|
|
488
|
+
/**
|
|
489
|
+
* Set an error on the iterable
|
|
490
|
+
*/
|
|
491
|
+
setError(err) {
|
|
492
|
+
if (this.isDone) {
|
|
493
|
+
return;
|
|
494
|
+
}
|
|
495
|
+
this.error = err;
|
|
496
|
+
this.isDone = true;
|
|
497
|
+
this.cleanup();
|
|
498
|
+
}
|
|
499
|
+
/**
|
|
500
|
+
* Cleanup waiting consumers
|
|
501
|
+
*/
|
|
502
|
+
cleanup() {
|
|
503
|
+
while (this.waiters.length > 0) {
|
|
504
|
+
const waiter = this.waiters.shift();
|
|
505
|
+
if (this.error) {
|
|
506
|
+
waiter.reject(this.error);
|
|
507
|
+
} else {
|
|
508
|
+
waiter.resolve({ done: true, value: void 0 });
|
|
509
|
+
}
|
|
510
|
+
}
|
|
511
|
+
}
|
|
512
|
+
/**
|
|
513
|
+
* AsyncIterableIterator implementation
|
|
514
|
+
*/
|
|
515
|
+
async next() {
|
|
516
|
+
if (this.queue.length > 0) {
|
|
517
|
+
return { done: false, value: this.queue.shift() };
|
|
518
|
+
}
|
|
519
|
+
if (this.isDone) {
|
|
520
|
+
if (this.error) {
|
|
521
|
+
throw this.error;
|
|
522
|
+
}
|
|
523
|
+
return { done: true, value: void 0 };
|
|
524
|
+
}
|
|
525
|
+
return new Promise((resolve, reject) => {
|
|
526
|
+
this.waiters.push({ resolve, reject });
|
|
527
|
+
});
|
|
528
|
+
}
|
|
529
|
+
/**
|
|
530
|
+
* AsyncIterableIterator return implementation
|
|
531
|
+
*/
|
|
532
|
+
async return(_value) {
|
|
533
|
+
this.end();
|
|
534
|
+
return { done: true, value: void 0 };
|
|
535
|
+
}
|
|
536
|
+
/**
|
|
537
|
+
* AsyncIterableIterator throw implementation
|
|
538
|
+
*/
|
|
539
|
+
async throw(e) {
|
|
540
|
+
this.setError(e instanceof Error ? e : new Error(String(e)));
|
|
541
|
+
throw this.error;
|
|
542
|
+
}
|
|
543
|
+
/**
|
|
544
|
+
* Make this iterable
|
|
545
|
+
*/
|
|
546
|
+
[Symbol.asyncIterator]() {
|
|
547
|
+
if (this.started) {
|
|
548
|
+
throw new Error("PushableAsyncIterable can only be iterated once");
|
|
549
|
+
}
|
|
550
|
+
this.started = true;
|
|
551
|
+
return this;
|
|
552
|
+
}
|
|
553
|
+
/**
|
|
554
|
+
* Check if the iterable is done
|
|
555
|
+
*/
|
|
556
|
+
get done() {
|
|
557
|
+
return this.isDone;
|
|
558
|
+
}
|
|
559
|
+
/**
|
|
560
|
+
* Check if the iterable has an error
|
|
561
|
+
*/
|
|
562
|
+
get hasError() {
|
|
563
|
+
return this.error !== null;
|
|
564
|
+
}
|
|
565
|
+
/**
|
|
566
|
+
* Get the current queue size
|
|
567
|
+
*/
|
|
568
|
+
get queueSize() {
|
|
569
|
+
return this.queue.length;
|
|
570
|
+
}
|
|
571
|
+
/**
|
|
572
|
+
* Get the number of waiting consumers
|
|
573
|
+
*/
|
|
574
|
+
get waiterCount() {
|
|
575
|
+
return this.waiters.length;
|
|
576
|
+
}
|
|
577
|
+
}
|
|
578
|
+
|
|
579
|
+
function normalizeClaudeBackendError(error) {
|
|
580
|
+
const record = typeof error === "object" && error !== null ? error : null;
|
|
581
|
+
const text = RuntimeShell.formatDisplayMessage(error).trim();
|
|
582
|
+
const stderrText = record ? RuntimeShell.formatDisplayMessage(record.stderr).trim() : "";
|
|
583
|
+
const detailText = record ? RuntimeShell.formatDisplayMessage(record.detail).trim() : "";
|
|
584
|
+
const searchable = [text, stderrText, detailText].filter(Boolean).join("\n").trim();
|
|
585
|
+
return searchable || "Claude runtime backend exited unexpectedly";
|
|
586
|
+
}
|
|
587
|
+
function isRecord(value) {
|
|
588
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
589
|
+
}
|
|
590
|
+
function extractClaudeToolResultText(value) {
|
|
591
|
+
if (typeof value === "string") {
|
|
592
|
+
return value.length > 0 ? value : null;
|
|
593
|
+
}
|
|
594
|
+
if (Array.isArray(value)) {
|
|
595
|
+
const parts = value.map((item) => extractClaudeToolResultText(item)).filter((item) => Boolean(item));
|
|
596
|
+
return parts.length > 0 ? parts.join("") : null;
|
|
597
|
+
}
|
|
598
|
+
if (!isRecord(value)) {
|
|
599
|
+
return null;
|
|
600
|
+
}
|
|
601
|
+
if (typeof value.text === "string" && value.text.length > 0) {
|
|
602
|
+
return value.text;
|
|
603
|
+
}
|
|
604
|
+
if ("content" in value) {
|
|
605
|
+
return extractClaudeToolResultText(value.content);
|
|
606
|
+
}
|
|
607
|
+
return null;
|
|
608
|
+
}
|
|
609
|
+
function normalizeClaudeToolResultContent(value) {
|
|
610
|
+
if (typeof value === "string") {
|
|
611
|
+
return value;
|
|
612
|
+
}
|
|
613
|
+
const extractedText = extractClaudeToolResultText(value);
|
|
614
|
+
if (extractedText) {
|
|
615
|
+
return extractedText;
|
|
616
|
+
}
|
|
617
|
+
return value;
|
|
618
|
+
}
|
|
619
|
+
function createTimeoutError(timeoutMs) {
|
|
620
|
+
return new Error(`Claude response did not complete within ${timeoutMs}ms`);
|
|
621
|
+
}
|
|
622
|
+
const CLAUDE_RESPONSE_PROGRESS_POLL_MS = 1e3;
|
|
623
|
+
function normalizePermissionResult(input, decision) {
|
|
624
|
+
if (decision === "approved" || decision === "approved_for_session") {
|
|
625
|
+
return {
|
|
626
|
+
behavior: "allow",
|
|
627
|
+
updatedInput: isRecord(input) ? input : {}
|
|
628
|
+
};
|
|
629
|
+
}
|
|
630
|
+
return {
|
|
631
|
+
behavior: "deny",
|
|
632
|
+
message: decision === "denied" ? "The user denied this tool request." : "The tool request was aborted."
|
|
633
|
+
};
|
|
634
|
+
}
|
|
635
|
+
function getClaudeExecutablePath(command) {
|
|
636
|
+
if (command === "claude") {
|
|
637
|
+
return path.resolve(path.join(index.projectPath(), "scripts", "claude_remote_launcher.cjs"));
|
|
638
|
+
}
|
|
639
|
+
return command;
|
|
640
|
+
}
|
|
641
|
+
class ClaudeCodeBackend {
|
|
642
|
+
constructor(options) {
|
|
643
|
+
this.options = options;
|
|
644
|
+
}
|
|
645
|
+
listeners = /* @__PURE__ */ new Set();
|
|
646
|
+
toolNamesByCallId = /* @__PURE__ */ new Map();
|
|
647
|
+
runtimeSessionId = node_crypto.randomUUID();
|
|
648
|
+
messageStream = null;
|
|
649
|
+
queryInstance = null;
|
|
650
|
+
runtimeAbortController = null;
|
|
651
|
+
consumeTask = null;
|
|
652
|
+
activeResponse = null;
|
|
653
|
+
responseCompletionOutcome = null;
|
|
654
|
+
responseLastProgressAt = null;
|
|
655
|
+
disposed = false;
|
|
656
|
+
lastStatus = null;
|
|
657
|
+
onMessage(handler) {
|
|
658
|
+
this.listeners.add(handler);
|
|
659
|
+
}
|
|
660
|
+
offMessage(handler) {
|
|
661
|
+
this.listeners.delete(handler);
|
|
662
|
+
}
|
|
663
|
+
async startSession(initialPrompt) {
|
|
664
|
+
if (initialPrompt) {
|
|
665
|
+
await this.sendPrompt(this.runtimeSessionId, initialPrompt);
|
|
666
|
+
}
|
|
667
|
+
return { sessionId: this.runtimeSessionId };
|
|
668
|
+
}
|
|
669
|
+
async sendPrompt(_sessionId, prompt) {
|
|
670
|
+
if (this.disposed) {
|
|
671
|
+
throw new Error("Claude runtime backend has already been disposed");
|
|
672
|
+
}
|
|
673
|
+
if (this.activeResponse) {
|
|
674
|
+
throw new Error("Claude runtime backend is already processing a prompt");
|
|
675
|
+
}
|
|
676
|
+
await this.ensureQueryStarted();
|
|
677
|
+
this.responseCompletionOutcome = null;
|
|
678
|
+
this.activeResponse = new future.Future();
|
|
679
|
+
this.responseLastProgressAt = Date.now();
|
|
680
|
+
void this.activeResponse.promise.catch(() => {
|
|
681
|
+
});
|
|
682
|
+
this.emitStatus("running");
|
|
683
|
+
this.messageStream.push({
|
|
684
|
+
type: "user",
|
|
685
|
+
message: {
|
|
686
|
+
role: "user",
|
|
687
|
+
content: prompt
|
|
688
|
+
}
|
|
689
|
+
});
|
|
690
|
+
}
|
|
691
|
+
async cancel(_sessionId) {
|
|
692
|
+
if (!this.queryInstance) {
|
|
693
|
+
return;
|
|
694
|
+
}
|
|
695
|
+
try {
|
|
696
|
+
await this.queryInstance.interrupt();
|
|
697
|
+
} catch (error) {
|
|
698
|
+
persistence.logger.debug("[ClaudeRuntimeBackend] Error interrupting Claude query:", error);
|
|
699
|
+
}
|
|
700
|
+
}
|
|
701
|
+
async waitForResponseComplete(timeoutMs = 10 * 6e4) {
|
|
702
|
+
if (this.responseCompletionOutcome) {
|
|
703
|
+
const outcome = this.responseCompletionOutcome;
|
|
704
|
+
if (outcome.kind === "rejected") {
|
|
705
|
+
throw outcome.error;
|
|
706
|
+
}
|
|
707
|
+
return;
|
|
708
|
+
}
|
|
709
|
+
if (!this.activeResponse) {
|
|
710
|
+
return;
|
|
711
|
+
}
|
|
712
|
+
while (this.activeResponse) {
|
|
713
|
+
const elapsedSinceProgress = Date.now() - (this.responseLastProgressAt ?? Date.now());
|
|
714
|
+
const remainingMs = timeoutMs - elapsedSinceProgress;
|
|
715
|
+
if (remainingMs <= 0) {
|
|
716
|
+
const timeoutError = createTimeoutError(timeoutMs);
|
|
717
|
+
this.rejectActiveResponse(timeoutError);
|
|
718
|
+
throw timeoutError;
|
|
719
|
+
}
|
|
720
|
+
await Promise.race([
|
|
721
|
+
this.activeResponse.promise,
|
|
722
|
+
new Promise((resolve2) => {
|
|
723
|
+
setTimeout(resolve2, Math.min(CLAUDE_RESPONSE_PROGRESS_POLL_MS, remainingMs));
|
|
724
|
+
})
|
|
725
|
+
]);
|
|
726
|
+
}
|
|
727
|
+
const completionOutcome = this.responseCompletionOutcome;
|
|
728
|
+
const rejectedError = completionOutcome && typeof completionOutcome === "object" && "error" in completionOutcome ? completionOutcome.error : null;
|
|
729
|
+
if (rejectedError) {
|
|
730
|
+
throw rejectedError;
|
|
731
|
+
}
|
|
732
|
+
}
|
|
733
|
+
async dispose() {
|
|
734
|
+
if (this.disposed) {
|
|
735
|
+
return;
|
|
736
|
+
}
|
|
737
|
+
this.disposed = true;
|
|
738
|
+
const activeQuery = this.queryInstance;
|
|
739
|
+
this.rejectActiveResponse(new Error("Claude runtime backend disposed"));
|
|
740
|
+
this.messageStream?.end();
|
|
741
|
+
this.runtimeAbortController?.abort();
|
|
742
|
+
if (activeQuery && typeof activeQuery.return === "function") {
|
|
743
|
+
try {
|
|
744
|
+
await activeQuery.return();
|
|
745
|
+
} catch (error) {
|
|
746
|
+
persistence.logger.debug("[ClaudeRuntimeBackend] Error closing Claude query iterator:", error);
|
|
747
|
+
}
|
|
748
|
+
}
|
|
749
|
+
try {
|
|
750
|
+
await this.consumeTask;
|
|
751
|
+
} catch (error) {
|
|
752
|
+
persistence.logger.debug("[ClaudeRuntimeBackend] Error while disposing backend:", error);
|
|
753
|
+
} finally {
|
|
754
|
+
this.queryInstance = null;
|
|
755
|
+
this.messageStream = null;
|
|
756
|
+
this.runtimeAbortController = null;
|
|
757
|
+
this.consumeTask = null;
|
|
758
|
+
this.toolNamesByCallId.clear();
|
|
759
|
+
this.emitStatus("stopped");
|
|
760
|
+
}
|
|
761
|
+
}
|
|
762
|
+
emit(message) {
|
|
763
|
+
for (const listener of this.listeners) {
|
|
764
|
+
listener(message);
|
|
765
|
+
}
|
|
766
|
+
}
|
|
767
|
+
emitStatus(status, detail) {
|
|
768
|
+
if (this.lastStatus?.status === status && this.lastStatus.detail === detail) {
|
|
769
|
+
return;
|
|
770
|
+
}
|
|
771
|
+
const message = {
|
|
772
|
+
type: "status",
|
|
773
|
+
status,
|
|
774
|
+
...detail ? { detail } : {}
|
|
775
|
+
};
|
|
776
|
+
this.lastStatus = message;
|
|
777
|
+
this.emit(message);
|
|
778
|
+
}
|
|
779
|
+
async ensureQueryStarted() {
|
|
780
|
+
if (this.queryInstance) {
|
|
781
|
+
return;
|
|
782
|
+
}
|
|
783
|
+
this.emitStatus("starting");
|
|
784
|
+
this.runtimeAbortController = new AbortController();
|
|
785
|
+
this.messageStream = new PushableAsyncIterable();
|
|
786
|
+
this.queryInstance = query({
|
|
787
|
+
prompt: this.messageStream,
|
|
788
|
+
options: {
|
|
789
|
+
cwd: this.options.cwd,
|
|
790
|
+
env: this.options.env,
|
|
791
|
+
allowedTools: this.options.allowedTools,
|
|
792
|
+
appendSystemPrompt: this.options.appendSystemPrompt,
|
|
793
|
+
customSystemPrompt: this.options.customSystemPrompt,
|
|
794
|
+
disallowedTools: this.options.disallowedTools,
|
|
795
|
+
extraArgs: this.options.args,
|
|
796
|
+
fallbackModel: this.options.fallbackModel,
|
|
797
|
+
mcpServers: this.options.mcpServers,
|
|
798
|
+
model: this.options.model,
|
|
799
|
+
pathToClaudeCodeExecutable: getClaudeExecutablePath(this.options.command),
|
|
800
|
+
permissionMode: this.options.permissionMode,
|
|
801
|
+
settingsPath: this.options.settingsPath,
|
|
802
|
+
canCallTool: this.options.permissionHandler ? async (toolName, input, requestOptions) => {
|
|
803
|
+
const requestId = requestOptions.requestId ?? node_crypto.randomUUID();
|
|
804
|
+
this.emit({
|
|
805
|
+
type: "permission-request",
|
|
806
|
+
id: requestId,
|
|
807
|
+
reason: `Claude wants to use ${toolName}`,
|
|
808
|
+
payload: {
|
|
809
|
+
toolName,
|
|
810
|
+
input
|
|
811
|
+
}
|
|
812
|
+
});
|
|
813
|
+
const decision = await this.options.permissionHandler.handleToolCall(requestId, toolName, input);
|
|
814
|
+
this.emit({
|
|
815
|
+
type: "permission-response",
|
|
816
|
+
id: requestId,
|
|
817
|
+
approved: decision.decision === "approved" || decision.decision === "approved_for_session"
|
|
818
|
+
});
|
|
819
|
+
return normalizePermissionResult(input, decision.decision);
|
|
820
|
+
} : void 0,
|
|
821
|
+
abort: this.runtimeAbortController.signal
|
|
822
|
+
}
|
|
823
|
+
});
|
|
824
|
+
this.consumeTask = this.consumeMessages(this.queryInstance);
|
|
825
|
+
}
|
|
826
|
+
async consumeMessages(activeQuery) {
|
|
827
|
+
try {
|
|
828
|
+
for await (const message of activeQuery) {
|
|
829
|
+
this.handleMessage(message);
|
|
830
|
+
}
|
|
831
|
+
} catch (error) {
|
|
832
|
+
const aborted = this.runtimeAbortController?.signal.aborted === true;
|
|
833
|
+
if (!aborted) {
|
|
834
|
+
const detail = normalizeClaudeBackendError(error);
|
|
835
|
+
this.emitStatus("error", detail);
|
|
836
|
+
this.rejectActiveResponse(new Error(detail));
|
|
837
|
+
}
|
|
838
|
+
} finally {
|
|
839
|
+
if (!this.disposed && this.activeResponse) {
|
|
840
|
+
const detail = "Claude runtime backend stopped before completing the response";
|
|
841
|
+
this.emitStatus("error", detail);
|
|
842
|
+
this.rejectActiveResponse(new Error(detail));
|
|
843
|
+
}
|
|
844
|
+
if (!this.disposed) {
|
|
845
|
+
this.queryInstance = null;
|
|
846
|
+
this.messageStream = null;
|
|
847
|
+
this.runtimeAbortController = null;
|
|
848
|
+
this.consumeTask = null;
|
|
849
|
+
this.emitStatus("stopped");
|
|
850
|
+
}
|
|
851
|
+
}
|
|
852
|
+
}
|
|
853
|
+
handleMessage(message) {
|
|
854
|
+
this.markResponseProgress();
|
|
855
|
+
switch (message.type) {
|
|
856
|
+
case "system":
|
|
857
|
+
this.handleSystemMessage(message);
|
|
858
|
+
return;
|
|
859
|
+
case "assistant":
|
|
860
|
+
this.handleAssistantMessage(message);
|
|
861
|
+
return;
|
|
862
|
+
case "user":
|
|
863
|
+
this.handleUserMessage(message);
|
|
864
|
+
return;
|
|
865
|
+
case "result":
|
|
866
|
+
this.handleResultMessage(message);
|
|
867
|
+
return;
|
|
868
|
+
default:
|
|
869
|
+
return;
|
|
870
|
+
}
|
|
871
|
+
}
|
|
872
|
+
handleSystemMessage(message) {
|
|
873
|
+
if (message.subtype === "init") {
|
|
874
|
+
this.emitStatus("running");
|
|
875
|
+
this.emit({
|
|
876
|
+
type: "event",
|
|
877
|
+
name: "session-init",
|
|
878
|
+
payload: {
|
|
879
|
+
sessionId: message.session_id,
|
|
880
|
+
cwd: message.cwd,
|
|
881
|
+
model: message.model,
|
|
882
|
+
tools: message.tools
|
|
883
|
+
}
|
|
884
|
+
});
|
|
885
|
+
}
|
|
886
|
+
}
|
|
887
|
+
handleAssistantMessage(message) {
|
|
888
|
+
for (const block of message.message.content ?? []) {
|
|
889
|
+
if (block.type === "text" && typeof block.text === "string" && block.text.length > 0) {
|
|
890
|
+
this.emit({
|
|
891
|
+
type: "model-output",
|
|
892
|
+
textDelta: block.text
|
|
893
|
+
});
|
|
894
|
+
continue;
|
|
895
|
+
}
|
|
896
|
+
if (block.type === "thinking") {
|
|
897
|
+
const thinkingText = isRecord(block) && typeof block.thinking === "string" ? block.thinking : "";
|
|
898
|
+
if (thinkingText) {
|
|
899
|
+
this.emit({
|
|
900
|
+
type: "event",
|
|
901
|
+
name: "thinking",
|
|
902
|
+
payload: { text: thinkingText }
|
|
903
|
+
});
|
|
904
|
+
}
|
|
905
|
+
continue;
|
|
906
|
+
}
|
|
907
|
+
if (block.type === "tool_use" && block.id && block.name) {
|
|
908
|
+
this.toolNamesByCallId.set(block.id, block.name);
|
|
909
|
+
this.emit({
|
|
910
|
+
type: "tool-call",
|
|
911
|
+
toolName: block.name,
|
|
912
|
+
args: isRecord(block.input) ? block.input : {},
|
|
913
|
+
callId: block.id
|
|
914
|
+
});
|
|
915
|
+
}
|
|
916
|
+
}
|
|
917
|
+
}
|
|
918
|
+
handleUserMessage(message) {
|
|
919
|
+
const content = Array.isArray(message.message.content) ? message.message.content : [];
|
|
920
|
+
for (const block of content) {
|
|
921
|
+
if (block.type !== "tool_result" || !block.tool_use_id) {
|
|
922
|
+
continue;
|
|
923
|
+
}
|
|
924
|
+
const toolName = this.toolNamesByCallId.get(block.tool_use_id) ?? "unknown";
|
|
925
|
+
this.emit({
|
|
926
|
+
type: "tool-result",
|
|
927
|
+
toolName,
|
|
928
|
+
result: normalizeClaudeToolResultContent(block.content),
|
|
929
|
+
callId: block.tool_use_id
|
|
930
|
+
});
|
|
931
|
+
if (toolName === "Bash" && isRecord(message.tool_use_result)) {
|
|
932
|
+
const toolResult = message.tool_use_result;
|
|
933
|
+
const stdout = RuntimeShell.formatDisplayMessage(toolResult.stdout).trim();
|
|
934
|
+
const stderr = RuntimeShell.formatDisplayMessage(toolResult.stderr).trim();
|
|
935
|
+
const combined = [stdout, stderr].filter(Boolean).join("\n");
|
|
936
|
+
if (combined) {
|
|
937
|
+
this.emit({
|
|
938
|
+
type: "terminal-output",
|
|
939
|
+
data: combined,
|
|
940
|
+
callId: block.tool_use_id
|
|
941
|
+
});
|
|
942
|
+
}
|
|
943
|
+
}
|
|
944
|
+
}
|
|
945
|
+
}
|
|
946
|
+
handleResultMessage(message) {
|
|
947
|
+
if (message.usage) {
|
|
948
|
+
this.emit({
|
|
949
|
+
type: "token-count",
|
|
950
|
+
...message.usage,
|
|
951
|
+
duration_ms: message.duration_ms,
|
|
952
|
+
duration_api_ms: message.duration_api_ms,
|
|
953
|
+
total_cost_usd: message.total_cost_usd,
|
|
954
|
+
session_id: message.session_id
|
|
955
|
+
});
|
|
956
|
+
}
|
|
957
|
+
if (message.is_error) {
|
|
958
|
+
const detail = typeof message.result === "string" && message.result.trim().length > 0 ? message.result : message.subtype;
|
|
959
|
+
this.emitStatus("error", detail);
|
|
960
|
+
this.rejectActiveResponse(new Error(detail));
|
|
961
|
+
return;
|
|
962
|
+
}
|
|
963
|
+
this.emitStatus("idle");
|
|
964
|
+
this.resolveActiveResponse();
|
|
965
|
+
}
|
|
966
|
+
resolveActiveResponse() {
|
|
967
|
+
this.responseCompletionOutcome = { kind: "resolved" };
|
|
968
|
+
this.responseLastProgressAt = null;
|
|
969
|
+
if (!this.activeResponse) {
|
|
970
|
+
return;
|
|
971
|
+
}
|
|
972
|
+
const active = this.activeResponse;
|
|
973
|
+
this.activeResponse = null;
|
|
974
|
+
active.resolve(void 0);
|
|
975
|
+
}
|
|
976
|
+
rejectActiveResponse(error) {
|
|
977
|
+
this.responseCompletionOutcome = { kind: "rejected", error };
|
|
978
|
+
this.responseLastProgressAt = null;
|
|
979
|
+
if (!this.activeResponse) {
|
|
980
|
+
return;
|
|
981
|
+
}
|
|
982
|
+
const active = this.activeResponse;
|
|
983
|
+
this.activeResponse = null;
|
|
984
|
+
active.reject(error);
|
|
985
|
+
}
|
|
986
|
+
markResponseProgress() {
|
|
987
|
+
if (!this.activeResponse) {
|
|
988
|
+
return;
|
|
989
|
+
}
|
|
990
|
+
this.responseLastProgressAt = Date.now();
|
|
991
|
+
}
|
|
992
|
+
}
|
|
993
|
+
|
|
994
|
+
function mapToClaudeMode(mode) {
|
|
995
|
+
const codexToClaudeMap = {
|
|
996
|
+
"yolo": "bypassPermissions",
|
|
997
|
+
"safe-yolo": "default",
|
|
998
|
+
"read-only": "default"
|
|
999
|
+
};
|
|
1000
|
+
return codexToClaudeMap[mode] ?? mode;
|
|
1001
|
+
}
|
|
1002
|
+
|
|
1003
|
+
function defaultClaudeCommand() {
|
|
1004
|
+
return process.env.HAPPY_CLAUDE_COMMAND ?? process.env.HAPPY_CLAUDE_ACP_COMMAND ?? "claude";
|
|
1005
|
+
}
|
|
1006
|
+
function appendStringFlag(args, flag, value) {
|
|
1007
|
+
if (value && value.trim().length > 0) {
|
|
1008
|
+
args.push(flag, value);
|
|
1009
|
+
}
|
|
1010
|
+
}
|
|
1011
|
+
function appendListFlag(args, flag, values) {
|
|
1012
|
+
if (values && values.length > 0) {
|
|
1013
|
+
args.push(flag, values.join(","));
|
|
1014
|
+
}
|
|
1015
|
+
}
|
|
1016
|
+
function normalizeClaudePassthroughArgs(options) {
|
|
1017
|
+
const sourceArgs = options.args ?? options.baseArgs ?? [];
|
|
1018
|
+
return sourceArgs.filter((arg) => arg !== "--experimental-acp");
|
|
1019
|
+
}
|
|
1020
|
+
function resolveClaudeCliArgs(options) {
|
|
1021
|
+
const args = [
|
|
1022
|
+
"--output-format",
|
|
1023
|
+
"stream-json",
|
|
1024
|
+
"--verbose",
|
|
1025
|
+
"--input-format",
|
|
1026
|
+
"stream-json",
|
|
1027
|
+
...normalizeClaudePassthroughArgs(options)
|
|
1028
|
+
];
|
|
1029
|
+
if (options.permissionHandler) {
|
|
1030
|
+
args.push("--permission-prompt-tool", "stdio");
|
|
1031
|
+
}
|
|
1032
|
+
appendStringFlag(args, "--system-prompt", options.customSystemPrompt);
|
|
1033
|
+
appendStringFlag(args, "--append-system-prompt", options.appendSystemPrompt);
|
|
1034
|
+
appendStringFlag(args, "--model", options.model);
|
|
1035
|
+
appendStringFlag(args, "--fallback-model", options.fallbackModel);
|
|
1036
|
+
appendListFlag(args, "--allowedTools", options.allowedTools);
|
|
1037
|
+
appendListFlag(args, "--disallowedTools", options.disallowedTools);
|
|
1038
|
+
appendStringFlag(args, "--settings", options.settingsPath);
|
|
1039
|
+
if (options.permissionMode) {
|
|
1040
|
+
args.push("--permission-mode", mapToClaudeMode(options.permissionMode));
|
|
1041
|
+
}
|
|
1042
|
+
return args;
|
|
1043
|
+
}
|
|
1044
|
+
function createClaudeBackend(options) {
|
|
1045
|
+
const command = options.command ?? defaultClaudeCommand();
|
|
1046
|
+
const args = resolveClaudeCliArgs(options);
|
|
1047
|
+
return {
|
|
1048
|
+
backend: new ClaudeCodeBackend({
|
|
1049
|
+
cwd: options.cwd,
|
|
1050
|
+
env: {
|
|
1051
|
+
...options.env,
|
|
1052
|
+
NODE_ENV: "production"
|
|
1053
|
+
},
|
|
1054
|
+
command,
|
|
1055
|
+
args: normalizeClaudePassthroughArgs(options),
|
|
1056
|
+
model: options.model,
|
|
1057
|
+
fallbackModel: options.fallbackModel,
|
|
1058
|
+
permissionMode: options.permissionMode ? mapToClaudeMode(options.permissionMode) : void 0,
|
|
1059
|
+
customSystemPrompt: options.customSystemPrompt,
|
|
1060
|
+
appendSystemPrompt: options.appendSystemPrompt,
|
|
1061
|
+
allowedTools: options.allowedTools,
|
|
1062
|
+
disallowedTools: options.disallowedTools,
|
|
1063
|
+
settingsPath: options.settingsPath,
|
|
1064
|
+
mcpServers: options.mcpServers,
|
|
1065
|
+
permissionHandler: options.permissionHandler
|
|
1066
|
+
}),
|
|
1067
|
+
command,
|
|
1068
|
+
args
|
|
1069
|
+
};
|
|
1070
|
+
}
|
|
1071
|
+
function registerClaudeAgent() {
|
|
1072
|
+
RuntimeShell.agentRegistry.register("claude", (opts) => createClaudeBackend(opts).backend);
|
|
1073
|
+
}
|
|
1074
|
+
|
|
1075
|
+
exports.AbortError = AbortError;
|
|
1076
|
+
exports.PushableAsyncIterable = PushableAsyncIterable;
|
|
1077
|
+
exports.createClaudeBackend = createClaudeBackend;
|
|
1078
|
+
exports.mapToClaudeMode = mapToClaudeMode;
|
|
1079
|
+
exports.query = query;
|
|
1080
|
+
exports.registerClaudeAgent = registerClaudeAgent;
|