happy-coder 0.1.14 → 0.2.1
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/index.cjs +678 -113
- package/dist/index.mjs +677 -112
- package/dist/lib.cjs +1 -1
- package/dist/lib.d.cts +21 -3
- package/dist/lib.d.mts +21 -3
- package/dist/lib.mjs +1 -1
- package/dist/types-CkPUFpfr.cjs +885 -0
- package/dist/types-DNu8okOb.mjs +874 -0
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -1,16 +1,15 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
3
|
var chalk = require('chalk');
|
|
4
|
-
var types = require('./types-
|
|
4
|
+
var types = require('./types-CkPUFpfr.cjs');
|
|
5
5
|
var node_crypto = require('node:crypto');
|
|
6
|
-
var
|
|
6
|
+
var node_child_process = require('node:child_process');
|
|
7
|
+
var node_readline = require('node:readline');
|
|
7
8
|
var node_fs = require('node:fs');
|
|
8
9
|
var node_path = require('node:path');
|
|
10
|
+
var node_url = require('node:url');
|
|
9
11
|
var os = require('node:os');
|
|
10
12
|
var promises = require('fs/promises');
|
|
11
|
-
var node_child_process = require('node:child_process');
|
|
12
|
-
var node_readline = require('node:readline');
|
|
13
|
-
var node_url = require('node:url');
|
|
14
13
|
var promises$1 = require('node:fs/promises');
|
|
15
14
|
var mcp_js = require('@modelcontextprotocol/sdk/server/mcp.js');
|
|
16
15
|
var node_http = require('node:http');
|
|
@@ -50,6 +49,343 @@ function _interopNamespaceDefault(e) {
|
|
|
50
49
|
|
|
51
50
|
var z__namespace = /*#__PURE__*/_interopNamespaceDefault(z);
|
|
52
51
|
|
|
52
|
+
class Stream {
|
|
53
|
+
constructor(returned) {
|
|
54
|
+
this.returned = returned;
|
|
55
|
+
}
|
|
56
|
+
queue = [];
|
|
57
|
+
readResolve;
|
|
58
|
+
readReject;
|
|
59
|
+
isDone = false;
|
|
60
|
+
hasError;
|
|
61
|
+
started = false;
|
|
62
|
+
/**
|
|
63
|
+
* Implements async iterable protocol
|
|
64
|
+
*/
|
|
65
|
+
[Symbol.asyncIterator]() {
|
|
66
|
+
if (this.started) {
|
|
67
|
+
throw new Error("Stream can only be iterated once");
|
|
68
|
+
}
|
|
69
|
+
this.started = true;
|
|
70
|
+
return this;
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* Gets the next value from the stream
|
|
74
|
+
*/
|
|
75
|
+
async next() {
|
|
76
|
+
if (this.queue.length > 0) {
|
|
77
|
+
return Promise.resolve({
|
|
78
|
+
done: false,
|
|
79
|
+
value: this.queue.shift()
|
|
80
|
+
});
|
|
81
|
+
}
|
|
82
|
+
if (this.isDone) {
|
|
83
|
+
return Promise.resolve({ done: true, value: void 0 });
|
|
84
|
+
}
|
|
85
|
+
if (this.hasError) {
|
|
86
|
+
return Promise.reject(this.hasError);
|
|
87
|
+
}
|
|
88
|
+
return new Promise((resolve, reject) => {
|
|
89
|
+
this.readResolve = resolve;
|
|
90
|
+
this.readReject = reject;
|
|
91
|
+
});
|
|
92
|
+
}
|
|
93
|
+
/**
|
|
94
|
+
* Adds a value to the stream
|
|
95
|
+
*/
|
|
96
|
+
enqueue(value) {
|
|
97
|
+
if (this.readResolve) {
|
|
98
|
+
const resolve = this.readResolve;
|
|
99
|
+
this.readResolve = void 0;
|
|
100
|
+
this.readReject = void 0;
|
|
101
|
+
resolve({ done: false, value });
|
|
102
|
+
} else {
|
|
103
|
+
this.queue.push(value);
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
/**
|
|
107
|
+
* Marks the stream as complete
|
|
108
|
+
*/
|
|
109
|
+
done() {
|
|
110
|
+
this.isDone = true;
|
|
111
|
+
if (this.readResolve) {
|
|
112
|
+
const resolve = this.readResolve;
|
|
113
|
+
this.readResolve = void 0;
|
|
114
|
+
this.readReject = void 0;
|
|
115
|
+
resolve({ done: true, value: void 0 });
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
/**
|
|
119
|
+
* Propagates an error through the stream
|
|
120
|
+
*/
|
|
121
|
+
error(error) {
|
|
122
|
+
this.hasError = error;
|
|
123
|
+
if (this.readReject) {
|
|
124
|
+
const reject = this.readReject;
|
|
125
|
+
this.readResolve = void 0;
|
|
126
|
+
this.readReject = void 0;
|
|
127
|
+
reject(error);
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
/**
|
|
131
|
+
* Implements async iterator cleanup
|
|
132
|
+
*/
|
|
133
|
+
async return() {
|
|
134
|
+
this.isDone = true;
|
|
135
|
+
if (this.returned) {
|
|
136
|
+
this.returned();
|
|
137
|
+
}
|
|
138
|
+
return Promise.resolve({ done: true, value: void 0 });
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
class AbortError extends Error {
|
|
143
|
+
constructor(message) {
|
|
144
|
+
super(message);
|
|
145
|
+
this.name = "AbortError";
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
const __filename$1 = node_url.fileURLToPath((typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('index.cjs', document.baseURI).href)));
|
|
150
|
+
const __dirname$3 = node_path.join(__filename$1, "..");
|
|
151
|
+
function getDefaultClaudeCodePath() {
|
|
152
|
+
return node_path.join(__dirname$3, "..", "..", "..", "node_modules", "@anthropic-ai", "claude-code", "cli.js");
|
|
153
|
+
}
|
|
154
|
+
function logDebug(message) {
|
|
155
|
+
if (process.env.DEBUG) {
|
|
156
|
+
types.logger.debug(message);
|
|
157
|
+
console.log(message);
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
async function streamToStdin(stream, stdin, abortController) {
|
|
161
|
+
for await (const message of stream) {
|
|
162
|
+
if (abortController.signal.aborted) break;
|
|
163
|
+
stdin.write(JSON.stringify(message) + "\n");
|
|
164
|
+
}
|
|
165
|
+
stdin.end();
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
class Query {
|
|
169
|
+
constructor(childStdin, childStdout, processExitPromise) {
|
|
170
|
+
this.childStdin = childStdin;
|
|
171
|
+
this.childStdout = childStdout;
|
|
172
|
+
this.processExitPromise = processExitPromise;
|
|
173
|
+
this.readMessages();
|
|
174
|
+
this.sdkMessages = this.readSdkMessages();
|
|
175
|
+
}
|
|
176
|
+
pendingControlResponses = /* @__PURE__ */ new Map();
|
|
177
|
+
sdkMessages;
|
|
178
|
+
inputStream = new Stream();
|
|
179
|
+
/**
|
|
180
|
+
* Set an error on the stream
|
|
181
|
+
*/
|
|
182
|
+
setError(error) {
|
|
183
|
+
this.inputStream.error(error);
|
|
184
|
+
}
|
|
185
|
+
/**
|
|
186
|
+
* AsyncIterableIterator implementation
|
|
187
|
+
*/
|
|
188
|
+
next(...args) {
|
|
189
|
+
return this.sdkMessages.next(...args);
|
|
190
|
+
}
|
|
191
|
+
return(value) {
|
|
192
|
+
if (this.sdkMessages.return) {
|
|
193
|
+
return this.sdkMessages.return(value);
|
|
194
|
+
}
|
|
195
|
+
return Promise.resolve({ done: true, value: void 0 });
|
|
196
|
+
}
|
|
197
|
+
throw(e) {
|
|
198
|
+
if (this.sdkMessages.throw) {
|
|
199
|
+
return this.sdkMessages.throw(e);
|
|
200
|
+
}
|
|
201
|
+
return Promise.reject(e);
|
|
202
|
+
}
|
|
203
|
+
[Symbol.asyncIterator]() {
|
|
204
|
+
return this.sdkMessages;
|
|
205
|
+
}
|
|
206
|
+
/**
|
|
207
|
+
* Read messages from Claude process stdout
|
|
208
|
+
*/
|
|
209
|
+
async readMessages() {
|
|
210
|
+
const rl = node_readline.createInterface({ input: this.childStdout });
|
|
211
|
+
try {
|
|
212
|
+
for await (const line of rl) {
|
|
213
|
+
if (line.trim()) {
|
|
214
|
+
const message = JSON.parse(line);
|
|
215
|
+
if (message.type === "control_response") {
|
|
216
|
+
const controlResponse = message;
|
|
217
|
+
const handler = this.pendingControlResponses.get(controlResponse.response.request_id);
|
|
218
|
+
if (handler) {
|
|
219
|
+
handler(controlResponse.response);
|
|
220
|
+
}
|
|
221
|
+
continue;
|
|
222
|
+
}
|
|
223
|
+
this.inputStream.enqueue(message);
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
await this.processExitPromise;
|
|
227
|
+
} catch (error) {
|
|
228
|
+
this.inputStream.error(error);
|
|
229
|
+
} finally {
|
|
230
|
+
this.inputStream.done();
|
|
231
|
+
rl.close();
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
/**
|
|
235
|
+
* Async generator for SDK messages
|
|
236
|
+
*/
|
|
237
|
+
async *readSdkMessages() {
|
|
238
|
+
for await (const message of this.inputStream) {
|
|
239
|
+
yield message;
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
/**
|
|
243
|
+
* Send interrupt request to Claude
|
|
244
|
+
*/
|
|
245
|
+
async interrupt() {
|
|
246
|
+
if (!this.childStdin) {
|
|
247
|
+
throw new Error("Interrupt requires --input-format stream-json");
|
|
248
|
+
}
|
|
249
|
+
await this.request({
|
|
250
|
+
subtype: "interrupt"
|
|
251
|
+
}, this.childStdin);
|
|
252
|
+
}
|
|
253
|
+
/**
|
|
254
|
+
* Send control request to Claude process
|
|
255
|
+
*/
|
|
256
|
+
request(request, childStdin) {
|
|
257
|
+
const requestId = Math.random().toString(36).substring(2, 15);
|
|
258
|
+
const sdkRequest = {
|
|
259
|
+
request_id: requestId,
|
|
260
|
+
type: "control_request",
|
|
261
|
+
request
|
|
262
|
+
};
|
|
263
|
+
return new Promise((resolve, reject) => {
|
|
264
|
+
this.pendingControlResponses.set(requestId, (response) => {
|
|
265
|
+
if (response.subtype === "success") {
|
|
266
|
+
resolve(response);
|
|
267
|
+
} else {
|
|
268
|
+
reject(new Error(response.error));
|
|
269
|
+
}
|
|
270
|
+
});
|
|
271
|
+
childStdin.write(JSON.stringify(sdkRequest) + "\n");
|
|
272
|
+
});
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
function query(config) {
|
|
276
|
+
const {
|
|
277
|
+
prompt,
|
|
278
|
+
abortController = config.abortController || new AbortController(),
|
|
279
|
+
options: {
|
|
280
|
+
allowedTools = [],
|
|
281
|
+
appendSystemPrompt,
|
|
282
|
+
customSystemPrompt,
|
|
283
|
+
cwd,
|
|
284
|
+
disallowedTools = [],
|
|
285
|
+
executable = "node",
|
|
286
|
+
executableArgs = [],
|
|
287
|
+
maxTurns,
|
|
288
|
+
mcpServers,
|
|
289
|
+
pathToClaudeCodeExecutable = getDefaultClaudeCodePath(),
|
|
290
|
+
permissionMode = "default",
|
|
291
|
+
permissionPromptToolName,
|
|
292
|
+
continue: continueConversation,
|
|
293
|
+
resume,
|
|
294
|
+
model,
|
|
295
|
+
fallbackModel,
|
|
296
|
+
strictMcpConfig
|
|
297
|
+
} = {}
|
|
298
|
+
} = config;
|
|
299
|
+
if (!process.env.CLAUDE_CODE_ENTRYPOINT) {
|
|
300
|
+
process.env.CLAUDE_CODE_ENTRYPOINT = "sdk-ts";
|
|
301
|
+
}
|
|
302
|
+
const args = ["--output-format", "stream-json", "--verbose"];
|
|
303
|
+
if (customSystemPrompt) args.push("--system-prompt", customSystemPrompt);
|
|
304
|
+
if (appendSystemPrompt) args.push("--append-system-prompt", appendSystemPrompt);
|
|
305
|
+
if (maxTurns) args.push("--max-turns", maxTurns.toString());
|
|
306
|
+
if (model) args.push("--model", model);
|
|
307
|
+
if (permissionPromptToolName) args.push("--permission-prompt-tool", permissionPromptToolName);
|
|
308
|
+
if (continueConversation) args.push("--continue");
|
|
309
|
+
if (resume) args.push("--resume", resume);
|
|
310
|
+
if (allowedTools.length > 0) args.push("--allowedTools", allowedTools.join(","));
|
|
311
|
+
if (disallowedTools.length > 0) args.push("--disallowedTools", disallowedTools.join(","));
|
|
312
|
+
if (mcpServers && Object.keys(mcpServers).length > 0) {
|
|
313
|
+
args.push("--mcp-config", JSON.stringify({ mcpServers }));
|
|
314
|
+
}
|
|
315
|
+
if (strictMcpConfig) args.push("--strict-mcp-config");
|
|
316
|
+
if (permissionMode) args.push("--permission-mode", permissionMode);
|
|
317
|
+
if (fallbackModel) {
|
|
318
|
+
if (model && fallbackModel === model) {
|
|
319
|
+
throw new Error("Fallback model cannot be the same as the main model. Please specify a different model for fallbackModel option.");
|
|
320
|
+
}
|
|
321
|
+
args.push("--fallback-model", fallbackModel);
|
|
322
|
+
}
|
|
323
|
+
if (typeof prompt === "string") {
|
|
324
|
+
args.push("--print", prompt.trim());
|
|
325
|
+
} else {
|
|
326
|
+
args.push("--input-format", "stream-json");
|
|
327
|
+
}
|
|
328
|
+
if (!node_fs.existsSync(pathToClaudeCodeExecutable)) {
|
|
329
|
+
throw new ReferenceError(`Claude Code executable not found at ${pathToClaudeCodeExecutable}. Is options.pathToClaudeCodeExecutable set?`);
|
|
330
|
+
}
|
|
331
|
+
logDebug(`Spawning Claude Code process: ${executable} ${[...executableArgs, pathToClaudeCodeExecutable, ...args].join(" ")}`);
|
|
332
|
+
const child = node_child_process.spawn(executable, [...executableArgs, pathToClaudeCodeExecutable, ...args], {
|
|
333
|
+
cwd,
|
|
334
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
335
|
+
signal: abortController.signal,
|
|
336
|
+
env: {
|
|
337
|
+
...process.env
|
|
338
|
+
}
|
|
339
|
+
});
|
|
340
|
+
let childStdin = null;
|
|
341
|
+
if (typeof prompt === "string") {
|
|
342
|
+
child.stdin.end();
|
|
343
|
+
} else {
|
|
344
|
+
streamToStdin(prompt, child.stdin, abortController);
|
|
345
|
+
childStdin = child.stdin;
|
|
346
|
+
}
|
|
347
|
+
if (process.env.DEBUG) {
|
|
348
|
+
child.stderr.on("data", (data) => {
|
|
349
|
+
console.error("Claude Code stderr:", data.toString());
|
|
350
|
+
});
|
|
351
|
+
}
|
|
352
|
+
const cleanup = () => {
|
|
353
|
+
if (!child.killed) {
|
|
354
|
+
child.kill("SIGTERM");
|
|
355
|
+
}
|
|
356
|
+
};
|
|
357
|
+
abortController.signal.addEventListener("abort", cleanup);
|
|
358
|
+
process.on("exit", cleanup);
|
|
359
|
+
const processExitPromise = new Promise((resolve) => {
|
|
360
|
+
child.on("close", (code) => {
|
|
361
|
+
if (abortController.signal.aborted) {
|
|
362
|
+
query2.setError(new AbortError("Claude Code process aborted by user"));
|
|
363
|
+
}
|
|
364
|
+
if (code !== 0) {
|
|
365
|
+
query2.setError(new Error(`Claude Code process exited with code ${code}`));
|
|
366
|
+
} else {
|
|
367
|
+
resolve();
|
|
368
|
+
}
|
|
369
|
+
});
|
|
370
|
+
});
|
|
371
|
+
const query2 = new Query(childStdin, child.stdout, processExitPromise);
|
|
372
|
+
child.on("error", (error) => {
|
|
373
|
+
if (abortController.signal.aborted) {
|
|
374
|
+
query2.setError(new AbortError("Claude Code process aborted by user"));
|
|
375
|
+
} else {
|
|
376
|
+
query2.setError(new Error(`Failed to spawn Claude Code process: ${error.message}`));
|
|
377
|
+
}
|
|
378
|
+
});
|
|
379
|
+
processExitPromise.finally(() => {
|
|
380
|
+
cleanup();
|
|
381
|
+
abortController.signal.removeEventListener("abort", cleanup);
|
|
382
|
+
if (process.env.CLAUDE_SDK_MCP_SERVERS) {
|
|
383
|
+
delete process.env.CLAUDE_SDK_MCP_SERVERS;
|
|
384
|
+
}
|
|
385
|
+
});
|
|
386
|
+
return query2;
|
|
387
|
+
}
|
|
388
|
+
|
|
53
389
|
function formatClaudeMessage(message, onAssistantResult) {
|
|
54
390
|
types.logger.debugLargeJson("[CLAUDE] Message from non interactive & remote mode:", message);
|
|
55
391
|
switch (message.type) {
|
|
@@ -159,9 +495,8 @@ function formatClaudeMessage(message, onAssistantResult) {
|
|
|
159
495
|
break;
|
|
160
496
|
}
|
|
161
497
|
default: {
|
|
162
|
-
const exhaustiveCheck = message;
|
|
163
498
|
if (process.env.DEBUG) {
|
|
164
|
-
console.log(chalk.gray(`[Unknown message type]`)
|
|
499
|
+
console.log(chalk.gray(`[Unknown message type: ${message.type}]`));
|
|
165
500
|
}
|
|
166
501
|
}
|
|
167
502
|
}
|
|
@@ -207,6 +542,19 @@ async function awaitFileExist(file, timeout = 1e4) {
|
|
|
207
542
|
return false;
|
|
208
543
|
}
|
|
209
544
|
|
|
545
|
+
function deepEqual(a, b) {
|
|
546
|
+
if (a === b) return true;
|
|
547
|
+
if (a == null || b == null) return false;
|
|
548
|
+
if (typeof a !== "object" || typeof b !== "object") return false;
|
|
549
|
+
const keysA = Object.keys(a);
|
|
550
|
+
const keysB = Object.keys(b);
|
|
551
|
+
if (keysA.length !== keysB.length) return false;
|
|
552
|
+
for (const key of keysA) {
|
|
553
|
+
if (!keysB.includes(key)) return false;
|
|
554
|
+
if (!deepEqual(a[key], b[key])) return false;
|
|
555
|
+
}
|
|
556
|
+
return true;
|
|
557
|
+
}
|
|
210
558
|
async function claudeRemote(opts) {
|
|
211
559
|
let startFrom = opts.sessionId;
|
|
212
560
|
if (opts.sessionId && !claudeCheckSession(opts.sessionId, opts.path)) {
|
|
@@ -223,6 +571,7 @@ async function claudeRemote(opts) {
|
|
|
223
571
|
resume: startFrom ?? void 0,
|
|
224
572
|
mcpServers: opts.mcpServers,
|
|
225
573
|
permissionPromptToolName: opts.permissionPromptToolName,
|
|
574
|
+
permissionMode: opts.permissionMode,
|
|
226
575
|
executable: "node",
|
|
227
576
|
abortController
|
|
228
577
|
};
|
|
@@ -237,7 +586,7 @@ async function claudeRemote(opts) {
|
|
|
237
586
|
if (response) {
|
|
238
587
|
(async () => {
|
|
239
588
|
try {
|
|
240
|
-
|
|
589
|
+
await response.interrupt();
|
|
241
590
|
} catch (e) {
|
|
242
591
|
}
|
|
243
592
|
abortController.abort();
|
|
@@ -247,10 +596,9 @@ async function claudeRemote(opts) {
|
|
|
247
596
|
}
|
|
248
597
|
}
|
|
249
598
|
});
|
|
250
|
-
types.logger.debug(`[claudeRemote] Starting query with
|
|
251
|
-
response =
|
|
252
|
-
prompt: opts.
|
|
253
|
-
abortController,
|
|
599
|
+
types.logger.debug(`[claudeRemote] Starting query with permission mode: ${opts.permissionMode}`);
|
|
600
|
+
response = query({
|
|
601
|
+
prompt: opts.message,
|
|
254
602
|
options: sdkOptions
|
|
255
603
|
});
|
|
256
604
|
if (opts.interruptController) {
|
|
@@ -270,21 +618,76 @@ async function claudeRemote(opts) {
|
|
|
270
618
|
}
|
|
271
619
|
}
|
|
272
620
|
};
|
|
621
|
+
const toolCalls = [];
|
|
622
|
+
const resolveToolCallId = (name, args) => {
|
|
623
|
+
for (let i = toolCalls.length - 1; i >= 0; i--) {
|
|
624
|
+
const call = toolCalls[i];
|
|
625
|
+
if (call.name === name && deepEqual(call.input, args)) {
|
|
626
|
+
if (call.used) {
|
|
627
|
+
types.logger.debug("[claudeRemote] Warning: Permission request matched an already-used tool call");
|
|
628
|
+
return null;
|
|
629
|
+
}
|
|
630
|
+
call.used = true;
|
|
631
|
+
types.logger.debug(`[claudeRemote] Resolved tool call ID: ${call.id} for ${name}`);
|
|
632
|
+
return call.id;
|
|
633
|
+
}
|
|
634
|
+
}
|
|
635
|
+
types.logger.debug(`[claudeRemote] No matching tool call found for permission request: ${name}`);
|
|
636
|
+
return null;
|
|
637
|
+
};
|
|
638
|
+
if (opts.onToolCallResolver) {
|
|
639
|
+
opts.onToolCallResolver(resolveToolCallId);
|
|
640
|
+
}
|
|
273
641
|
try {
|
|
274
642
|
types.logger.debug(`[claudeRemote] Starting to iterate over response`);
|
|
275
643
|
for await (const message of response) {
|
|
276
644
|
types.logger.debugLargeJson(`[claudeRemote] Message ${message.type}`, message);
|
|
277
645
|
formatClaudeMessage(message, opts.onAssistantResult);
|
|
646
|
+
if (message.type === "assistant") {
|
|
647
|
+
const assistantMsg = message;
|
|
648
|
+
if (assistantMsg.message && assistantMsg.message.content) {
|
|
649
|
+
for (const block of assistantMsg.message.content) {
|
|
650
|
+
if (block.type === "tool_use") {
|
|
651
|
+
toolCalls.push({
|
|
652
|
+
id: block.id,
|
|
653
|
+
name: block.name,
|
|
654
|
+
input: block.input,
|
|
655
|
+
used: false
|
|
656
|
+
});
|
|
657
|
+
types.logger.debug(`[claudeRemote] Tracked tool call: ${block.id} - ${block.name}`);
|
|
658
|
+
}
|
|
659
|
+
}
|
|
660
|
+
}
|
|
661
|
+
}
|
|
662
|
+
if (message.type === "user") {
|
|
663
|
+
const userMsg = message;
|
|
664
|
+
if (userMsg.message && userMsg.message.content && Array.isArray(userMsg.message.content)) {
|
|
665
|
+
for (const block of userMsg.message.content) {
|
|
666
|
+
if (block.type === "tool_result" && block.tool_use_id) {
|
|
667
|
+
const toolCall = toolCalls.find((tc) => tc.id === block.tool_use_id);
|
|
668
|
+
if (toolCall && !toolCall.used) {
|
|
669
|
+
toolCall.used = true;
|
|
670
|
+
types.logger.debug(`[claudeRemote] Tool completed execution, marked as used: ${block.tool_use_id}`);
|
|
671
|
+
}
|
|
672
|
+
}
|
|
673
|
+
}
|
|
674
|
+
}
|
|
675
|
+
}
|
|
278
676
|
if (message.type === "system" && message.subtype === "init") {
|
|
279
677
|
updateThinking(true);
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
678
|
+
const systemInit = message;
|
|
679
|
+
if (systemInit.session_id) {
|
|
680
|
+
types.logger.debug(`[claudeRemote] Waiting for session file to be written to disk: ${systemInit.session_id}`);
|
|
681
|
+
const projectDir = getProjectPath(opts.path);
|
|
682
|
+
const found = await awaitFileExist(node_path.join(projectDir, `${systemInit.session_id}.jsonl`));
|
|
683
|
+
types.logger.debug(`[claudeRemote] Session file found: ${systemInit.session_id} ${found}`);
|
|
684
|
+
opts.onSessionFound(systemInit.session_id);
|
|
685
|
+
}
|
|
285
686
|
}
|
|
286
687
|
if (message.type === "result") {
|
|
287
688
|
updateThinking(false);
|
|
689
|
+
types.logger.debug("[claudeRemote] Result received, exiting claudeRemote");
|
|
690
|
+
break;
|
|
288
691
|
}
|
|
289
692
|
}
|
|
290
693
|
types.logger.debug(`[claudeRemote] Finished iterating over response`);
|
|
@@ -292,13 +695,17 @@ async function claudeRemote(opts) {
|
|
|
292
695
|
if (abortController.signal.aborted) {
|
|
293
696
|
types.logger.debug(`[claudeRemote] Aborted`);
|
|
294
697
|
}
|
|
295
|
-
if (e instanceof
|
|
698
|
+
if (e instanceof AbortError) {
|
|
296
699
|
types.logger.debug(`[claudeRemote] Aborted`);
|
|
297
700
|
} else {
|
|
298
701
|
throw e;
|
|
299
702
|
}
|
|
300
703
|
} finally {
|
|
301
704
|
updateThinking(false);
|
|
705
|
+
toolCalls.length = 0;
|
|
706
|
+
if (opts.onToolCallResolver) {
|
|
707
|
+
opts.onToolCallResolver(null);
|
|
708
|
+
}
|
|
302
709
|
if (opts.interruptController) {
|
|
303
710
|
opts.interruptController.unregister();
|
|
304
711
|
}
|
|
@@ -446,58 +853,69 @@ async function claudeLocal(opts) {
|
|
|
446
853
|
return resolvedSessionId;
|
|
447
854
|
}
|
|
448
855
|
|
|
449
|
-
class
|
|
856
|
+
class MessageQueue2 {
|
|
857
|
+
constructor(modeHasher) {
|
|
858
|
+
this.modeHasher = modeHasher;
|
|
859
|
+
types.logger.debug(`[MessageQueue2] Initialized`);
|
|
860
|
+
}
|
|
450
861
|
queue = [];
|
|
451
|
-
|
|
862
|
+
waiter = null;
|
|
452
863
|
closed = false;
|
|
453
|
-
closePromise;
|
|
454
|
-
closeResolve;
|
|
455
|
-
constructor() {
|
|
456
|
-
this.closePromise = new Promise((resolve) => {
|
|
457
|
-
this.closeResolve = resolve;
|
|
458
|
-
});
|
|
459
|
-
}
|
|
460
864
|
/**
|
|
461
|
-
* Push a message to the queue
|
|
865
|
+
* Push a message to the queue with a mode.
|
|
462
866
|
*/
|
|
463
|
-
push(message) {
|
|
867
|
+
push(message, mode) {
|
|
464
868
|
if (this.closed) {
|
|
465
869
|
throw new Error("Cannot push to closed queue");
|
|
466
870
|
}
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
871
|
+
const modeHash = this.modeHasher(mode);
|
|
872
|
+
types.logger.debug(`[MessageQueue2] push() called with mode hash: ${modeHash}`);
|
|
873
|
+
this.queue.push({
|
|
874
|
+
message,
|
|
875
|
+
mode,
|
|
876
|
+
modeHash
|
|
877
|
+
});
|
|
878
|
+
if (this.waiter) {
|
|
879
|
+
types.logger.debug(`[MessageQueue2] Notifying waiter`);
|
|
880
|
+
const waiter = this.waiter;
|
|
881
|
+
this.waiter = null;
|
|
882
|
+
waiter(true);
|
|
883
|
+
}
|
|
884
|
+
types.logger.debug(`[MessageQueue2] push() completed. Queue size: ${this.queue.length}`);
|
|
885
|
+
}
|
|
886
|
+
/**
|
|
887
|
+
* Push a message to the beginning of the queue with a mode.
|
|
888
|
+
*/
|
|
889
|
+
unshift(message, mode) {
|
|
890
|
+
if (this.closed) {
|
|
891
|
+
throw new Error("Cannot unshift to closed queue");
|
|
892
|
+
}
|
|
893
|
+
const modeHash = this.modeHasher(mode);
|
|
894
|
+
types.logger.debug(`[MessageQueue2] unshift() called with mode hash: ${modeHash}`);
|
|
895
|
+
this.queue.unshift({
|
|
896
|
+
message,
|
|
897
|
+
mode,
|
|
898
|
+
modeHash
|
|
899
|
+
});
|
|
900
|
+
if (this.waiter) {
|
|
901
|
+
types.logger.debug(`[MessageQueue2] Notifying waiter`);
|
|
902
|
+
const waiter = this.waiter;
|
|
903
|
+
this.waiter = null;
|
|
904
|
+
waiter(true);
|
|
491
905
|
}
|
|
492
|
-
types.logger.debug(`[
|
|
906
|
+
types.logger.debug(`[MessageQueue2] unshift() completed. Queue size: ${this.queue.length}`);
|
|
493
907
|
}
|
|
494
908
|
/**
|
|
495
909
|
* Close the queue - no more messages can be pushed
|
|
496
910
|
*/
|
|
497
911
|
close() {
|
|
498
|
-
types.logger.debug(`[
|
|
912
|
+
types.logger.debug(`[MessageQueue2] close() called`);
|
|
499
913
|
this.closed = true;
|
|
500
|
-
this.
|
|
914
|
+
if (this.waiter) {
|
|
915
|
+
const waiter = this.waiter;
|
|
916
|
+
this.waiter = null;
|
|
917
|
+
waiter(false);
|
|
918
|
+
}
|
|
501
919
|
}
|
|
502
920
|
/**
|
|
503
921
|
* Check if the queue is closed
|
|
@@ -512,56 +930,91 @@ class MessageQueue {
|
|
|
512
930
|
return this.queue.length;
|
|
513
931
|
}
|
|
514
932
|
/**
|
|
515
|
-
*
|
|
933
|
+
* Wait for messages and return all messages with the same mode as a single string
|
|
934
|
+
* Returns { message: string, mode: T } or null if aborted/closed
|
|
516
935
|
*/
|
|
517
|
-
async
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
936
|
+
async waitForMessagesAndGetAsString(abortSignal) {
|
|
937
|
+
if (this.queue.length > 0) {
|
|
938
|
+
return this.collectBatch();
|
|
939
|
+
}
|
|
940
|
+
if (this.closed || abortSignal?.aborted) {
|
|
941
|
+
return null;
|
|
942
|
+
}
|
|
943
|
+
const hasMessages = await this.waitForMessages(abortSignal);
|
|
944
|
+
if (!hasMessages) {
|
|
945
|
+
return null;
|
|
946
|
+
}
|
|
947
|
+
return this.collectBatch();
|
|
948
|
+
}
|
|
949
|
+
/**
|
|
950
|
+
* Collect a batch of messages with the same mode
|
|
951
|
+
*/
|
|
952
|
+
collectBatch() {
|
|
953
|
+
if (this.queue.length === 0) {
|
|
954
|
+
return null;
|
|
955
|
+
}
|
|
956
|
+
const firstItem = this.queue[0];
|
|
957
|
+
const sameModeMessages = [];
|
|
958
|
+
let mode = firstItem.mode;
|
|
959
|
+
const targetModeHash = firstItem.modeHash;
|
|
960
|
+
while (this.queue.length > 0 && this.queue[0].modeHash === targetModeHash) {
|
|
961
|
+
const item = this.queue.shift();
|
|
962
|
+
sameModeMessages.push(item.message);
|
|
538
963
|
}
|
|
964
|
+
const combinedMessage = sameModeMessages.join("\n");
|
|
965
|
+
types.logger.debug(`[MessageQueue2] Collected batch of ${sameModeMessages.length} messages with mode hash: ${targetModeHash}`);
|
|
966
|
+
return {
|
|
967
|
+
message: combinedMessage,
|
|
968
|
+
mode
|
|
969
|
+
};
|
|
539
970
|
}
|
|
540
971
|
/**
|
|
541
|
-
* Wait for
|
|
972
|
+
* Wait for messages to arrive
|
|
542
973
|
*/
|
|
543
|
-
|
|
974
|
+
waitForMessages(abortSignal) {
|
|
544
975
|
return new Promise((resolve) => {
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
976
|
+
let abortHandler = null;
|
|
977
|
+
if (abortSignal) {
|
|
978
|
+
abortHandler = () => {
|
|
979
|
+
types.logger.debug("[MessageQueue2] Wait aborted");
|
|
980
|
+
if (this.waiter === waiterFunc) {
|
|
981
|
+
this.waiter = null;
|
|
982
|
+
}
|
|
983
|
+
resolve(false);
|
|
984
|
+
};
|
|
985
|
+
abortSignal.addEventListener("abort", abortHandler);
|
|
986
|
+
}
|
|
987
|
+
const waiterFunc = (hasMessages) => {
|
|
988
|
+
if (abortHandler && abortSignal) {
|
|
989
|
+
abortSignal.removeEventListener("abort", abortHandler);
|
|
990
|
+
}
|
|
991
|
+
resolve(hasMessages);
|
|
992
|
+
};
|
|
993
|
+
if (this.queue.length > 0) {
|
|
994
|
+
if (abortHandler && abortSignal) {
|
|
995
|
+
abortSignal.removeEventListener("abort", abortHandler);
|
|
996
|
+
}
|
|
997
|
+
resolve(true);
|
|
548
998
|
return;
|
|
549
999
|
}
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
this.closePromise?.then(() => {
|
|
554
|
-
const index = this.waiters.indexOf(waiter);
|
|
555
|
-
if (index !== -1) {
|
|
556
|
-
this.waiters.splice(index, 1);
|
|
557
|
-
types.logger.debug(`[MessageQueue] waitForNext() waiter removed due to close. Remaining waiters: ${this.waiters.length}`);
|
|
558
|
-
resolve(void 0);
|
|
1000
|
+
if (this.closed || abortSignal?.aborted) {
|
|
1001
|
+
if (abortHandler && abortSignal) {
|
|
1002
|
+
abortSignal.removeEventListener("abort", abortHandler);
|
|
559
1003
|
}
|
|
560
|
-
|
|
1004
|
+
resolve(false);
|
|
1005
|
+
return;
|
|
1006
|
+
}
|
|
1007
|
+
this.waiter = waiterFunc;
|
|
1008
|
+
types.logger.debug("[MessageQueue2] Waiting for messages...");
|
|
561
1009
|
});
|
|
562
1010
|
}
|
|
563
1011
|
}
|
|
564
1012
|
|
|
1013
|
+
var MessageQueue2$1 = /*#__PURE__*/Object.freeze({
|
|
1014
|
+
__proto__: null,
|
|
1015
|
+
MessageQueue2: MessageQueue2
|
|
1016
|
+
});
|
|
1017
|
+
|
|
565
1018
|
class InvalidateSync {
|
|
566
1019
|
_invalidated = false;
|
|
567
1020
|
_invalidatedDouble = false;
|
|
@@ -656,6 +1109,39 @@ function startFileWatcher(file, onFileChange) {
|
|
|
656
1109
|
};
|
|
657
1110
|
}
|
|
658
1111
|
|
|
1112
|
+
const PLAN_FAKE_REJECT = `User approved plan, but you need to be restarted. STOP IMMEDIATELY TO SWITCH FROM PLAN MODE. DO NOT REPLY TO THIS MESSAGE.`;
|
|
1113
|
+
const PLAN_FAKE_RESTART = `PlEaZe Continue with plan.`;
|
|
1114
|
+
|
|
1115
|
+
function hackToolResponse(message) {
|
|
1116
|
+
console.debug("hackToolResponse", JSON.stringify(message, null, 2));
|
|
1117
|
+
if (message.type === "user" && message.message?.role === "user" && message.message?.content && Array.isArray(message.message.content)) {
|
|
1118
|
+
let modified = false;
|
|
1119
|
+
const hackedContent = message.message.content.map((item) => {
|
|
1120
|
+
if (item.type === "tool_result" && item.is_error === true) {
|
|
1121
|
+
if (item.content === PLAN_FAKE_REJECT) {
|
|
1122
|
+
types.logger.debug(`[SESSION_SCANNER] Hacking exit_plan_mode tool_result: flipping is_error from true to false`);
|
|
1123
|
+
modified = true;
|
|
1124
|
+
return {
|
|
1125
|
+
...item,
|
|
1126
|
+
is_error: false,
|
|
1127
|
+
content: "Plan approved"
|
|
1128
|
+
};
|
|
1129
|
+
}
|
|
1130
|
+
}
|
|
1131
|
+
return item;
|
|
1132
|
+
});
|
|
1133
|
+
if (modified) {
|
|
1134
|
+
return {
|
|
1135
|
+
...message,
|
|
1136
|
+
message: {
|
|
1137
|
+
...message.message,
|
|
1138
|
+
content: hackedContent
|
|
1139
|
+
}
|
|
1140
|
+
};
|
|
1141
|
+
}
|
|
1142
|
+
}
|
|
1143
|
+
return message;
|
|
1144
|
+
}
|
|
659
1145
|
function createSessionScanner(opts) {
|
|
660
1146
|
const projectDir = getProjectPath(opts.workingDirectory);
|
|
661
1147
|
let finishedSessions = /* @__PURE__ */ new Set();
|
|
@@ -708,7 +1194,8 @@ function createSessionScanner(opts) {
|
|
|
708
1194
|
continue;
|
|
709
1195
|
}
|
|
710
1196
|
}
|
|
711
|
-
|
|
1197
|
+
const hackedMessage = hackToolResponse(message);
|
|
1198
|
+
opts.onMessage(hackedMessage);
|
|
712
1199
|
} catch (e) {
|
|
713
1200
|
types.logger.debug(`[SESSION_SCANNER] Error processing message: ${e}`);
|
|
714
1201
|
continue;
|
|
@@ -808,10 +1295,15 @@ function sortKeys(value) {
|
|
|
808
1295
|
|
|
809
1296
|
async function loop(opts) {
|
|
810
1297
|
let mode = opts.startingMode ?? "local";
|
|
811
|
-
let
|
|
1298
|
+
let currentPermissionMode = opts.permissionMode ?? "default";
|
|
1299
|
+
types.logger.debug(`[loop] Starting with permission mode: ${currentPermissionMode}`);
|
|
1300
|
+
let currentMessageQueue = opts.messageQueue || new MessageQueue2(
|
|
1301
|
+
(mode2) => mode2
|
|
1302
|
+
// Simple string hasher since modes are already strings
|
|
1303
|
+
);
|
|
812
1304
|
let sessionId = null;
|
|
813
1305
|
let onMessage = null;
|
|
814
|
-
const sessionScanner = createSessionScanner({
|
|
1306
|
+
const sessionScanner = opts.sessionScanner || createSessionScanner({
|
|
815
1307
|
workingDirectory: opts.path,
|
|
816
1308
|
onMessage: (message) => {
|
|
817
1309
|
opts.session.sendClaudeSessionMessage(message);
|
|
@@ -819,7 +1311,20 @@ async function loop(opts) {
|
|
|
819
1311
|
});
|
|
820
1312
|
opts.session.onUserMessage((message) => {
|
|
821
1313
|
sessionScanner.onRemoteUserMessageForDeduplication(message.content.text);
|
|
822
|
-
|
|
1314
|
+
let messagePermissionMode = currentPermissionMode;
|
|
1315
|
+
if (message.meta?.permissionMode) {
|
|
1316
|
+
const validModes = ["default", "acceptEdits", "bypassPermissions", "plan"];
|
|
1317
|
+
if (validModes.includes(message.meta.permissionMode)) {
|
|
1318
|
+
messagePermissionMode = message.meta.permissionMode;
|
|
1319
|
+
currentPermissionMode = messagePermissionMode;
|
|
1320
|
+
types.logger.debug(`[loop] Permission mode updated from user message to: ${currentPermissionMode}`);
|
|
1321
|
+
} else {
|
|
1322
|
+
types.logger.info(`[loop] Invalid permission mode received: ${message.meta.permissionMode}`);
|
|
1323
|
+
}
|
|
1324
|
+
} else {
|
|
1325
|
+
types.logger.debug(`[loop] User message received with no permission mode override, using current: ${currentPermissionMode}`);
|
|
1326
|
+
}
|
|
1327
|
+
currentMessageQueue.push(message.content.text, messagePermissionMode);
|
|
823
1328
|
types.logger.debugLargeJson("User message pushed to queue:", message);
|
|
824
1329
|
if (onMessage) {
|
|
825
1330
|
onMessage();
|
|
@@ -830,6 +1335,7 @@ async function loop(opts) {
|
|
|
830
1335
|
sessionScanner.onNewSession(newSessionId);
|
|
831
1336
|
};
|
|
832
1337
|
while (true) {
|
|
1338
|
+
types.logger.debug(`[loop] Starting loop iteration, queue size: ${currentMessageQueue.size()}, mode: ${mode}`);
|
|
833
1339
|
if (currentMessageQueue.size() > 0) {
|
|
834
1340
|
if (mode !== "remote") {
|
|
835
1341
|
mode = "remote";
|
|
@@ -837,7 +1343,6 @@ async function loop(opts) {
|
|
|
837
1343
|
opts.onModeChange(mode);
|
|
838
1344
|
}
|
|
839
1345
|
}
|
|
840
|
-
continue;
|
|
841
1346
|
}
|
|
842
1347
|
if (mode === "local") {
|
|
843
1348
|
let abortedOutside = false;
|
|
@@ -904,15 +1409,16 @@ async function loop(opts) {
|
|
|
904
1409
|
}
|
|
905
1410
|
}
|
|
906
1411
|
if (mode === "remote") {
|
|
1412
|
+
console.log("Starting remote mode...");
|
|
907
1413
|
types.logger.debug("Starting " + sessionId);
|
|
908
1414
|
const remoteAbortController = new AbortController();
|
|
909
1415
|
opts.session.setHandler("abort", () => {
|
|
910
|
-
if (!remoteAbortController.signal.aborted) {
|
|
1416
|
+
if (remoteAbortController && !remoteAbortController.signal.aborted) {
|
|
911
1417
|
remoteAbortController.abort();
|
|
912
1418
|
}
|
|
913
1419
|
});
|
|
914
1420
|
const abortHandler = () => {
|
|
915
|
-
if (!remoteAbortController.signal.aborted) {
|
|
1421
|
+
if (remoteAbortController && !remoteAbortController.signal.aborted) {
|
|
916
1422
|
if (mode !== "local") {
|
|
917
1423
|
mode = "local";
|
|
918
1424
|
if (opts.onModeChange) {
|
|
@@ -934,22 +1440,35 @@ async function loop(opts) {
|
|
|
934
1440
|
process.stdin.on("data", abortHandler);
|
|
935
1441
|
try {
|
|
936
1442
|
types.logger.debug(`Starting claudeRemote with messages: ${currentMessageQueue.size()}`);
|
|
1443
|
+
types.logger.debug("[loop] Waiting for messages before starting claudeRemote...");
|
|
1444
|
+
const messageData = await currentMessageQueue.waitForMessagesAndGetAsString(remoteAbortController.signal);
|
|
1445
|
+
if (!messageData) {
|
|
1446
|
+
console.log("[LOOP] No message received (queue closed or aborted), continuing loop");
|
|
1447
|
+
types.logger.debug("[loop] No message received (queue closed or aborted), skipping remote mode");
|
|
1448
|
+
continue;
|
|
1449
|
+
}
|
|
1450
|
+
currentPermissionMode = messageData.mode;
|
|
1451
|
+
types.logger.debug(`[loop] Using permission mode from queue: ${currentPermissionMode}`);
|
|
937
1452
|
if (opts.onProcessStart) {
|
|
938
1453
|
opts.onProcessStart("remote");
|
|
939
1454
|
}
|
|
1455
|
+
opts.session.sendSessionEvent({ type: "permission-mode-changed", mode: currentPermissionMode });
|
|
1456
|
+
types.logger.debug(`[loop] Sent permission-mode-changed event to app: ${currentPermissionMode}`);
|
|
940
1457
|
await claudeRemote({
|
|
941
1458
|
abort: remoteAbortController.signal,
|
|
942
1459
|
sessionId,
|
|
943
1460
|
path: opts.path,
|
|
944
1461
|
mcpServers: opts.mcpServers,
|
|
945
1462
|
permissionPromptToolName: opts.permissionPromptToolName,
|
|
1463
|
+
permissionMode: currentPermissionMode,
|
|
946
1464
|
onSessionFound,
|
|
947
1465
|
onThinkingChange: opts.onThinkingChange,
|
|
948
|
-
|
|
1466
|
+
message: messageData.message,
|
|
949
1467
|
onAssistantResult: opts.onAssistantResult,
|
|
950
1468
|
interruptController: opts.interruptController,
|
|
951
1469
|
claudeEnvVars: opts.claudeEnvVars,
|
|
952
|
-
claudeArgs: opts.claudeArgs
|
|
1470
|
+
claudeArgs: opts.claudeArgs,
|
|
1471
|
+
onToolCallResolver: opts.onToolCallResolver
|
|
953
1472
|
});
|
|
954
1473
|
} catch (e) {
|
|
955
1474
|
if (!remoteAbortController.signal.aborted) {
|
|
@@ -963,8 +1482,6 @@ async function loop(opts) {
|
|
|
963
1482
|
if (process.stdin.isTTY) {
|
|
964
1483
|
process.stdin.setRawMode(false);
|
|
965
1484
|
}
|
|
966
|
-
currentMessageQueue.close();
|
|
967
|
-
currentMessageQueue = new MessageQueue();
|
|
968
1485
|
}
|
|
969
1486
|
if (mode !== "remote") {
|
|
970
1487
|
console.log("Switching back to good old claude...");
|
|
@@ -1073,7 +1590,7 @@ class InterruptController {
|
|
|
1073
1590
|
}
|
|
1074
1591
|
}
|
|
1075
1592
|
|
|
1076
|
-
var version = "0.1
|
|
1593
|
+
var version = "0.2.1";
|
|
1077
1594
|
var packageJson = {
|
|
1078
1595
|
version: version};
|
|
1079
1596
|
|
|
@@ -1133,6 +1650,7 @@ function registerHandlers(session, interruptController, permissionCallbacks, onS
|
|
|
1133
1650
|
if (!request) return currentState;
|
|
1134
1651
|
let r = { ...currentState.requests };
|
|
1135
1652
|
delete r[id];
|
|
1653
|
+
const isExitPlanModeSuccess = request.tool === "exit_plan_mode" && !message.approved && message.reason === PLAN_FAKE_REJECT;
|
|
1136
1654
|
return {
|
|
1137
1655
|
...currentState,
|
|
1138
1656
|
requests: r,
|
|
@@ -1141,8 +1659,8 @@ function registerHandlers(session, interruptController, permissionCallbacks, onS
|
|
|
1141
1659
|
[id]: {
|
|
1142
1660
|
...request,
|
|
1143
1661
|
completedAt: Date.now(),
|
|
1144
|
-
status: message.approved ? "approved" : "denied",
|
|
1145
|
-
reason: message.reason
|
|
1662
|
+
status: isExitPlanModeSuccess ? "approved" : message.approved ? "approved" : "denied",
|
|
1663
|
+
reason: isExitPlanModeSuccess ? "Plan approved" : message.reason
|
|
1146
1664
|
}
|
|
1147
1665
|
}
|
|
1148
1666
|
};
|
|
@@ -1440,11 +1958,53 @@ async function start(credentials, options = {}) {
|
|
|
1440
1958
|
types.logger.infoDeveloper(`Session: ${response.id}`);
|
|
1441
1959
|
types.logger.infoDeveloper(`Logs: ${logPath}`);
|
|
1442
1960
|
const interruptController = new InterruptController();
|
|
1961
|
+
const { MessageQueue2 } = await Promise.resolve().then(function () { return MessageQueue2$1; });
|
|
1962
|
+
const messageQueue = new MessageQueue2(
|
|
1963
|
+
(mode2) => mode2
|
|
1964
|
+
// Simple string hasher since modes are already strings
|
|
1965
|
+
);
|
|
1443
1966
|
let requests = /* @__PURE__ */ new Map();
|
|
1967
|
+
let toolCallResolver = null;
|
|
1968
|
+
const sessionScanner = createSessionScanner({
|
|
1969
|
+
workingDirectory,
|
|
1970
|
+
onMessage: (message) => {
|
|
1971
|
+
session.sendClaudeSessionMessage(message);
|
|
1972
|
+
}
|
|
1973
|
+
});
|
|
1444
1974
|
const permissionServer = await startPermissionServerV2(async (request) => {
|
|
1445
|
-
|
|
1975
|
+
if (!toolCallResolver) {
|
|
1976
|
+
const error = `Tool call resolver not available for permission request: ${request.name}`;
|
|
1977
|
+
types.logger.info(`ERROR: ${error}`);
|
|
1978
|
+
throw new Error(error);
|
|
1979
|
+
}
|
|
1980
|
+
const toolCallId = toolCallResolver(request.name, request.arguments);
|
|
1981
|
+
if (!toolCallId) {
|
|
1982
|
+
const error = `Could not resolve tool call ID for permission request: ${request.name}`;
|
|
1983
|
+
types.logger.info(`ERROR: ${error}`);
|
|
1984
|
+
throw new Error(error);
|
|
1985
|
+
}
|
|
1986
|
+
const id = toolCallId;
|
|
1987
|
+
types.logger.debug(`Using tool call ID as permission request ID: ${id} for ${request.name}`);
|
|
1446
1988
|
let promise = new Promise((resolve) => {
|
|
1447
|
-
|
|
1989
|
+
if (request.name === "exit_plan_mode") {
|
|
1990
|
+
const wrappedResolve = (response2) => {
|
|
1991
|
+
if (response2.approved) {
|
|
1992
|
+
types.logger.debug("[HACK] exit_plan_mode approved - injecting approval message and denying");
|
|
1993
|
+
sessionScanner.onRemoteUserMessageForDeduplication(PLAN_FAKE_RESTART);
|
|
1994
|
+
messageQueue.unshift(PLAN_FAKE_RESTART, "default");
|
|
1995
|
+
types.logger.debug(`[HACK] Message queue size after unshift: ${messageQueue.size()}`);
|
|
1996
|
+
resolve({
|
|
1997
|
+
approved: false,
|
|
1998
|
+
reason: PLAN_FAKE_REJECT
|
|
1999
|
+
});
|
|
2000
|
+
} else {
|
|
2001
|
+
resolve(response2);
|
|
2002
|
+
}
|
|
2003
|
+
};
|
|
2004
|
+
requests.set(id, wrappedResolve);
|
|
2005
|
+
} else {
|
|
2006
|
+
requests.set(id, resolve);
|
|
2007
|
+
}
|
|
1448
2008
|
});
|
|
1449
2009
|
let timeout = setTimeout(async () => {
|
|
1450
2010
|
types.logger.debug("Permission timeout - attempting to interrupt Claude");
|
|
@@ -1528,12 +2088,15 @@ async function start(credentials, options = {}) {
|
|
|
1528
2088
|
model: options.model,
|
|
1529
2089
|
permissionMode: options.permissionMode,
|
|
1530
2090
|
startingMode: options.startingMode,
|
|
2091
|
+
messageQueue,
|
|
2092
|
+
sessionScanner,
|
|
1531
2093
|
onModeChange: (newMode) => {
|
|
1532
2094
|
mode = newMode;
|
|
1533
2095
|
session.sendSessionEvent({ type: "switch", mode: newMode });
|
|
1534
2096
|
session.keepAlive(thinking, mode);
|
|
1535
2097
|
if (newMode === "local") {
|
|
1536
2098
|
types.logger.debug("Switching to local mode - clearing pending permission requests");
|
|
2099
|
+
toolCallResolver = null;
|
|
1537
2100
|
for (const [id, resolve] of requests) {
|
|
1538
2101
|
types.logger.debug(`Rejecting pending permission request: ${id}`);
|
|
1539
2102
|
resolve({ approved: false, reason: "Session switched to local mode" });
|
|
@@ -1600,6 +2163,9 @@ async function start(credentials, options = {}) {
|
|
|
1600
2163
|
onThinkingChange: (newThinking) => {
|
|
1601
2164
|
thinking = newThinking;
|
|
1602
2165
|
session.keepAlive(thinking, mode);
|
|
2166
|
+
},
|
|
2167
|
+
onToolCallResolver: (resolver) => {
|
|
2168
|
+
toolCallResolver = resolver;
|
|
1603
2169
|
}
|
|
1604
2170
|
});
|
|
1605
2171
|
clearInterval(pingInterval);
|
|
@@ -1860,7 +2426,6 @@ class ApiDaemonSession extends node_events.EventEmitter {
|
|
|
1860
2426
|
startKeepAlive() {
|
|
1861
2427
|
this.stopKeepAlive();
|
|
1862
2428
|
this.keepAliveInterval = setInterval(() => {
|
|
1863
|
-
types.logger.daemonDebug("Sending keep-alive ping");
|
|
1864
2429
|
this.socket.volatile.emit("machine-alive", {
|
|
1865
2430
|
time: Date.now()
|
|
1866
2431
|
});
|
|
@@ -2286,7 +2851,7 @@ Currently only supported on macOS.
|
|
|
2286
2851
|
} else if (arg === "-m" || arg === "--model") {
|
|
2287
2852
|
options.model = args[++i];
|
|
2288
2853
|
} else if (arg === "-p" || arg === "--permission-mode") {
|
|
2289
|
-
options.permissionMode = z.z.enum(["
|
|
2854
|
+
options.permissionMode = z.z.enum(["default", "acceptEdits", "bypassPermissions", "plan"]).parse(args[++i]);
|
|
2290
2855
|
} else if (arg === "--local") ; else if (arg === "--happy-starting-mode") {
|
|
2291
2856
|
options.startingMode = z.z.enum(["local", "remote"]).parse(args[++i]);
|
|
2292
2857
|
} else if (arg === "--claude-env") {
|
|
@@ -2322,7 +2887,7 @@ ${chalk.bold("Options:")}
|
|
|
2322
2887
|
-h, --help Show this help message
|
|
2323
2888
|
-v, --version Show version
|
|
2324
2889
|
-m, --model <model> Claude model to use (default: sonnet)
|
|
2325
|
-
-p, --permission-mode Permission mode:
|
|
2890
|
+
-p, --permission-mode Permission mode: default, acceptEdits, bypassPermissions, or plan
|
|
2326
2891
|
--auth, --login Force re-authentication
|
|
2327
2892
|
--claude-env KEY=VALUE Set environment variable for Claude Code
|
|
2328
2893
|
--claude-arg ARG Pass additional argument to Claude CLI
|
|
@@ -2394,7 +2959,7 @@ ${chalk.bold("Examples:")}
|
|
|
2394
2959
|
await writeSettings(settings);
|
|
2395
2960
|
}
|
|
2396
2961
|
if (settings.daemonAutoStartWhenRunningHappy) {
|
|
2397
|
-
console.
|
|
2962
|
+
console.debug("Starting Happy background service...");
|
|
2398
2963
|
if (!await isDaemonRunning()) {
|
|
2399
2964
|
const happyPath = process.argv[1];
|
|
2400
2965
|
const isBuiltBinary = happyPath.endsWith("/bin/happy") || happyPath.endsWith("\\bin\\happy");
|