happy-imou-cloud 1.1.7 → 2.0.0

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 (36) hide show
  1. package/dist/{setupOfflineReconnection-ndObLZk0.mjs → BaseReasoningProcessor-BKLRCKTU.mjs} +133 -90
  2. package/dist/{setupOfflineReconnection-obypStdD.cjs → BaseReasoningProcessor-BRCQXCZY.cjs} +134 -90
  3. package/dist/{types-BXyraW9R.mjs → api-BGXYX0yH.mjs} +198 -170
  4. package/dist/{types-BSTmyv9d.cjs → api-D7OK-mML.cjs} +219 -192
  5. package/dist/command-CnLtKtP-.mjs +51 -0
  6. package/dist/command-G85giEAF.cjs +54 -0
  7. package/dist/future-Dq4Ha1Dn.cjs +24 -0
  8. package/dist/future-xRdLl3vf.mjs +22 -0
  9. package/dist/{index-DVI4b0mv.cjs → index-B_wlQBy2.cjs} +5493 -7142
  10. package/dist/{index-CUmYqKWt.mjs → index-C7Y0R-MI.mjs} +5482 -7143
  11. package/dist/index.cjs +19 -21
  12. package/dist/index.mjs +19 -21
  13. package/dist/lib.cjs +3 -2
  14. package/dist/lib.d.cts +17 -0
  15. package/dist/lib.d.mts +17 -0
  16. package/dist/lib.mjs +2 -1
  17. package/dist/{persistence-BGsuPqaO.mjs → persistence-BA_unuca.mjs} +8 -4
  18. package/dist/{persistence-BRH9F6RS.cjs → persistence-DHgf1CTG.cjs} +10 -6
  19. package/dist/registerKillSessionHandler-C2-yHm1V.mjs +428 -0
  20. package/dist/registerKillSessionHandler-CLREXN11.cjs +433 -0
  21. package/dist/runClaude-CwAitpX-.cjs +3274 -0
  22. package/dist/runClaude-uNC5Eym4.mjs +3271 -0
  23. package/dist/runCodex-B-05E-YZ.mjs +1846 -0
  24. package/dist/runCodex-Cm0VTqw_.cjs +1848 -0
  25. package/dist/{runGemini-C3dDtGOV.cjs → runGemini-CLWjwDYS.cjs} +25 -1366
  26. package/dist/{runGemini-B-EK_BJQ.mjs → runGemini-_biXvQAH.mjs} +12 -1353
  27. package/dist/types-CiliQpqS.mjs +52 -0
  28. package/dist/types-DVk3crez.cjs +54 -0
  29. package/package.json +13 -12
  30. package/scripts/devtools/README.md +9 -0
  31. package/scripts/devtools/generate-mock-credentials.ts +94 -0
  32. package/scripts/release-smoke.mjs +62 -0
  33. package/dist/config-BQNrtwRY.cjs +0 -183
  34. package/dist/config-Dn99YH37.mjs +0 -173
  35. package/dist/runCodex-Cez8cuIh.cjs +0 -1143
  36. package/dist/runCodex-X0BfjcZH.mjs +0 -1140
@@ -0,0 +1,3274 @@
1
+ 'use strict';
2
+
3
+ var os = require('node:os');
4
+ var node_crypto = require('node:crypto');
5
+ var api = require('./api-D7OK-mML.cjs');
6
+ var index = require('./index-B_wlQBy2.cjs');
7
+ var future = require('./future-Dq4Ha1Dn.cjs');
8
+ var types = require('./types-DVk3crez.cjs');
9
+ var path = require('node:path');
10
+ var promises = require('node:fs/promises');
11
+ var fs = require('fs/promises');
12
+ var ink = require('ink');
13
+ var registerKillSessionHandler = require('./registerKillSessionHandler-CLREXN11.cjs');
14
+ var React = require('react');
15
+ var node_child_process = require('node:child_process');
16
+ var node_readline = require('node:readline');
17
+ var fs$1 = require('node:fs');
18
+ var node_url = require('node:url');
19
+ require('axios');
20
+ require('node:events');
21
+ require('socket.io-client');
22
+ require('tweetnacl');
23
+ require('expo-server-sdk');
24
+ require('chalk');
25
+ var node_util = require('node:util');
26
+ var persistence = require('./persistence-DHgf1CTG.cjs');
27
+ var node_http = require('node:http');
28
+ require('fs');
29
+ require('zod');
30
+ require('child_process');
31
+ require('util');
32
+ require('crypto');
33
+ require('path');
34
+ require('os');
35
+ require('tmp');
36
+ require('qrcode-terminal');
37
+ require('node:module');
38
+ require('open');
39
+ require('url');
40
+ require('ps-list');
41
+ require('cross-spawn');
42
+ require('fastify');
43
+ require('fastify-type-provider-zod');
44
+ require('http');
45
+ require('@agentclientprotocol/sdk');
46
+
47
+ var _documentCurrentScript = typeof document !== 'undefined' ? document.currentScript : null;
48
+ class Session {
49
+ path;
50
+ logPath;
51
+ api;
52
+ client;
53
+ queue;
54
+ claudeEnvVars;
55
+ claudeArgs;
56
+ // Made mutable to allow filtering
57
+ mcpServers;
58
+ allowedTools;
59
+ _onModeChange;
60
+ /** Path to temporary settings file with SessionStart hook (required for session tracking) */
61
+ hookSettingsPath;
62
+ /** JavaScript runtime to use for spawning Claude Code (default: 'node') */
63
+ jsRuntime;
64
+ sessionId;
65
+ mode = "local";
66
+ thinking = false;
67
+ /** Callbacks to be notified when session ID is found/changed */
68
+ sessionFoundCallbacks = [];
69
+ /** Keep alive interval reference for cleanup */
70
+ keepAliveInterval;
71
+ constructor(opts) {
72
+ this.path = opts.path;
73
+ this.api = opts.api;
74
+ this.client = opts.client;
75
+ this.logPath = opts.logPath;
76
+ this.sessionId = opts.sessionId;
77
+ this.queue = opts.messageQueue;
78
+ this.claudeEnvVars = opts.claudeEnvVars;
79
+ this.claudeArgs = opts.claudeArgs;
80
+ this.mcpServers = opts.mcpServers;
81
+ this.allowedTools = opts.allowedTools;
82
+ this._onModeChange = opts.onModeChange;
83
+ this.hookSettingsPath = opts.hookSettingsPath;
84
+ this.jsRuntime = opts.jsRuntime ?? "node";
85
+ this.client.keepAlive(this.thinking, this.mode);
86
+ this.keepAliveInterval = setInterval(() => {
87
+ this.client.keepAlive(this.thinking, this.mode);
88
+ }, 2e3);
89
+ }
90
+ /**
91
+ * Cleanup resources (call when session is no longer needed)
92
+ */
93
+ cleanup = () => {
94
+ clearInterval(this.keepAliveInterval);
95
+ this.sessionFoundCallbacks = [];
96
+ api.logger.debug("[Session] Cleaned up resources");
97
+ };
98
+ onThinkingChange = (thinking) => {
99
+ this.thinking = thinking;
100
+ this.client.keepAlive(thinking, this.mode);
101
+ };
102
+ onModeChange = (mode) => {
103
+ this.mode = mode;
104
+ this.client.keepAlive(this.thinking, mode);
105
+ this._onModeChange(mode);
106
+ };
107
+ /**
108
+ * Called when Claude session ID is discovered or changed.
109
+ *
110
+ * This is triggered by the SessionStart hook when:
111
+ * - Claude starts a new session (fresh start)
112
+ * - Claude resumes a session (--continue, --resume flags)
113
+ * - Claude forks a session (/compact, double-escape fork)
114
+ *
115
+ * Updates internal state, syncs to API metadata, and notifies
116
+ * all registered callbacks (e.g., SessionScanner) about the change.
117
+ */
118
+ onSessionFound = (sessionId) => {
119
+ this.sessionId = sessionId;
120
+ this.client.updateMetadata((metadata) => ({
121
+ ...metadata,
122
+ claudeSessionId: sessionId
123
+ }));
124
+ api.logger.debug(`[Session] Claude Code session ID ${sessionId} added to metadata`);
125
+ for (const callback of this.sessionFoundCallbacks) {
126
+ callback(sessionId);
127
+ }
128
+ };
129
+ /**
130
+ * Register a callback to be notified when session ID is found/changed
131
+ */
132
+ addSessionFoundCallback = (callback) => {
133
+ this.sessionFoundCallbacks.push(callback);
134
+ };
135
+ /**
136
+ * Remove a session found callback
137
+ */
138
+ removeSessionFoundCallback = (callback) => {
139
+ const index = this.sessionFoundCallbacks.indexOf(callback);
140
+ if (index !== -1) {
141
+ this.sessionFoundCallbacks.splice(index, 1);
142
+ }
143
+ };
144
+ /**
145
+ * Clear the current session ID (used by /clear command)
146
+ */
147
+ clearSessionId = () => {
148
+ this.sessionId = null;
149
+ api.logger.debug("[Session] Session ID cleared");
150
+ };
151
+ /**
152
+ * Consume one-time Claude flags from claudeArgs after Claude spawn
153
+ * Handles: --resume (with or without session ID), --continue
154
+ */
155
+ consumeOneTimeFlags = () => {
156
+ if (!this.claudeArgs) return;
157
+ const filteredArgs = [];
158
+ for (let i = 0; i < this.claudeArgs.length; i++) {
159
+ const arg = this.claudeArgs[i];
160
+ if (arg === "--continue") {
161
+ api.logger.debug("[Session] Consumed --continue flag");
162
+ continue;
163
+ }
164
+ if (arg === "--resume") {
165
+ if (i + 1 < this.claudeArgs.length) {
166
+ const nextArg = this.claudeArgs[i + 1];
167
+ if (!nextArg.startsWith("-") && nextArg.includes("-")) {
168
+ i++;
169
+ api.logger.debug(`[Session] Consumed --resume flag with session ID: ${nextArg}`);
170
+ } else {
171
+ api.logger.debug("[Session] Consumed --resume flag (no session ID)");
172
+ }
173
+ } else {
174
+ api.logger.debug("[Session] Consumed --resume flag (no session ID)");
175
+ }
176
+ continue;
177
+ }
178
+ filteredArgs.push(arg);
179
+ }
180
+ this.claudeArgs = filteredArgs.length > 0 ? filteredArgs : void 0;
181
+ api.logger.debug(`[Session] Consumed one-time flags, remaining args:`, this.claudeArgs);
182
+ };
183
+ }
184
+
185
+ class InvalidateSync {
186
+ _invalidated = false;
187
+ _invalidatedDouble = false;
188
+ _stopped = false;
189
+ _command;
190
+ _pendings = [];
191
+ constructor(command) {
192
+ this._command = command;
193
+ }
194
+ invalidate() {
195
+ if (this._stopped) {
196
+ return;
197
+ }
198
+ if (!this._invalidated) {
199
+ this._invalidated = true;
200
+ this._invalidatedDouble = false;
201
+ this._doSync();
202
+ } else {
203
+ if (!this._invalidatedDouble) {
204
+ this._invalidatedDouble = true;
205
+ }
206
+ }
207
+ }
208
+ async invalidateAndAwait() {
209
+ if (this._stopped) {
210
+ return;
211
+ }
212
+ await new Promise((resolve) => {
213
+ this._pendings.push(resolve);
214
+ this.invalidate();
215
+ });
216
+ }
217
+ stop() {
218
+ if (this._stopped) {
219
+ return;
220
+ }
221
+ this._notifyPendings();
222
+ this._stopped = true;
223
+ }
224
+ _notifyPendings = () => {
225
+ for (let pending of this._pendings) {
226
+ pending();
227
+ }
228
+ this._pendings = [];
229
+ };
230
+ _doSync = async () => {
231
+ await api.backoff(async () => {
232
+ if (this._stopped) {
233
+ return;
234
+ }
235
+ await this._command();
236
+ });
237
+ if (this._stopped) {
238
+ this._notifyPendings();
239
+ return;
240
+ }
241
+ if (this._invalidatedDouble) {
242
+ this._invalidatedDouble = false;
243
+ this._doSync();
244
+ } else {
245
+ this._invalidated = false;
246
+ this._notifyPendings();
247
+ }
248
+ };
249
+ }
250
+
251
+ function startFileWatcher(file, onFileChange) {
252
+ const abortController = new AbortController();
253
+ void (async () => {
254
+ while (true) {
255
+ try {
256
+ api.logger.debug(`[FILE_WATCHER] Starting watcher for ${file}`);
257
+ const watcher = fs.watch(file, { persistent: true, signal: abortController.signal });
258
+ for await (const event of watcher) {
259
+ if (abortController.signal.aborted) {
260
+ return;
261
+ }
262
+ api.logger.debug(`[FILE_WATCHER] File changed: ${file}`);
263
+ onFileChange(file);
264
+ }
265
+ } catch (e) {
266
+ if (abortController.signal.aborted) {
267
+ return;
268
+ }
269
+ api.logger.debug(`[FILE_WATCHER] Watch error: ${e.message}, restarting watcher in a second`);
270
+ await api.delay(1e3);
271
+ }
272
+ }
273
+ })();
274
+ return () => {
275
+ abortController.abort();
276
+ };
277
+ }
278
+
279
+ const INTERNAL_CLAUDE_EVENT_TYPES = /* @__PURE__ */ new Set([
280
+ "file-history-snapshot",
281
+ "change",
282
+ "queue-operation"
283
+ ]);
284
+ async function createSessionScanner(opts) {
285
+ const projectDir = index.getProjectPath(opts.workingDirectory);
286
+ let finishedSessions = /* @__PURE__ */ new Set();
287
+ let pendingSessions = /* @__PURE__ */ new Set();
288
+ let currentSessionId = null;
289
+ let watchers = /* @__PURE__ */ new Map();
290
+ let processedMessageKeys = /* @__PURE__ */ new Set();
291
+ if (opts.sessionId) {
292
+ let messages = await readSessionLog(projectDir, opts.sessionId);
293
+ api.logger.debug(`[SESSION_SCANNER] Marking ${messages.length} existing messages as processed from session ${opts.sessionId}`);
294
+ for (let m of messages) {
295
+ processedMessageKeys.add(messageKey(m));
296
+ }
297
+ currentSessionId = opts.sessionId;
298
+ }
299
+ const sync = new InvalidateSync(async () => {
300
+ let sessions = [];
301
+ for (let p of pendingSessions) {
302
+ sessions.push(p);
303
+ }
304
+ if (currentSessionId && !pendingSessions.has(currentSessionId)) {
305
+ sessions.push(currentSessionId);
306
+ }
307
+ for (let [sessionId] of watchers) {
308
+ if (!sessions.includes(sessionId)) {
309
+ sessions.push(sessionId);
310
+ }
311
+ }
312
+ for (let session of sessions) {
313
+ const sessionMessages = await readSessionLog(projectDir, session);
314
+ let skipped = 0;
315
+ let sent = 0;
316
+ for (let file of sessionMessages) {
317
+ let key = messageKey(file);
318
+ if (processedMessageKeys.has(key)) {
319
+ skipped++;
320
+ continue;
321
+ }
322
+ processedMessageKeys.add(key);
323
+ api.logger.debug(`[SESSION_SCANNER] Sending new message: type=${file.type}, uuid=${file.type === "summary" ? file.leafUuid : file.uuid}`);
324
+ opts.onMessage(file);
325
+ sent++;
326
+ }
327
+ if (sessionMessages.length > 0) {
328
+ api.logger.debug(`[SESSION_SCANNER] Session ${session}: found=${sessionMessages.length}, skipped=${skipped}, sent=${sent}`);
329
+ }
330
+ }
331
+ for (let p of sessions) {
332
+ if (pendingSessions.has(p)) {
333
+ pendingSessions.delete(p);
334
+ finishedSessions.add(p);
335
+ }
336
+ }
337
+ for (let p of sessions) {
338
+ if (!watchers.has(p)) {
339
+ api.logger.debug(`[SESSION_SCANNER] Starting watcher for session: ${p}`);
340
+ watchers.set(p, startFileWatcher(path.join(projectDir, `${p}.jsonl`), () => {
341
+ sync.invalidate();
342
+ }));
343
+ }
344
+ }
345
+ });
346
+ await sync.invalidateAndAwait();
347
+ const intervalId = setInterval(() => {
348
+ sync.invalidate();
349
+ }, 3e3);
350
+ return {
351
+ cleanup: async () => {
352
+ clearInterval(intervalId);
353
+ for (let w of watchers.values()) {
354
+ w();
355
+ }
356
+ watchers.clear();
357
+ await sync.invalidateAndAwait();
358
+ sync.stop();
359
+ },
360
+ onNewSession: (sessionId) => {
361
+ if (currentSessionId === sessionId) {
362
+ api.logger.debug(`[SESSION_SCANNER] New session: ${sessionId} is the same as the current session, skipping`);
363
+ return;
364
+ }
365
+ if (finishedSessions.has(sessionId)) {
366
+ api.logger.debug(`[SESSION_SCANNER] New session: ${sessionId} is already finished, skipping`);
367
+ return;
368
+ }
369
+ if (pendingSessions.has(sessionId)) {
370
+ api.logger.debug(`[SESSION_SCANNER] New session: ${sessionId} is already pending, skipping`);
371
+ return;
372
+ }
373
+ if (currentSessionId) {
374
+ pendingSessions.add(currentSessionId);
375
+ }
376
+ api.logger.debug(`[SESSION_SCANNER] New session: ${sessionId}`);
377
+ currentSessionId = sessionId;
378
+ sync.invalidate();
379
+ }
380
+ };
381
+ }
382
+ function messageKey(message) {
383
+ if (message.type === "user") {
384
+ return message.uuid;
385
+ } else if (message.type === "assistant") {
386
+ return message.uuid;
387
+ } else if (message.type === "summary") {
388
+ return "summary: " + message.leafUuid + ": " + message.summary;
389
+ } else if (message.type === "system") {
390
+ return message.uuid;
391
+ } else {
392
+ throw Error();
393
+ }
394
+ }
395
+ async function readSessionLog(projectDir, sessionId) {
396
+ const expectedSessionFile = path.join(projectDir, `${sessionId}.jsonl`);
397
+ api.logger.debug(`[SESSION_SCANNER] Reading session file: ${expectedSessionFile}`);
398
+ let file;
399
+ try {
400
+ file = await promises.readFile(expectedSessionFile, "utf-8");
401
+ } catch (error) {
402
+ api.logger.debug(`[SESSION_SCANNER] Session file not found: ${expectedSessionFile}`);
403
+ return [];
404
+ }
405
+ let lines = file.split("\n");
406
+ let messages = [];
407
+ for (let l of lines) {
408
+ try {
409
+ if (l.trim() === "") {
410
+ continue;
411
+ }
412
+ let message = JSON.parse(l);
413
+ if (message.type && INTERNAL_CLAUDE_EVENT_TYPES.has(message.type)) {
414
+ continue;
415
+ }
416
+ let parsed = types.RawJSONLinesSchema.safeParse(message);
417
+ if (!parsed.success) {
418
+ continue;
419
+ }
420
+ messages.push(parsed.data);
421
+ } catch (e) {
422
+ api.logger.debug(`[SESSION_SCANNER] Error processing message: ${e}`);
423
+ continue;
424
+ }
425
+ }
426
+ return messages;
427
+ }
428
+
429
+ async function claudeLocalLauncher(session) {
430
+ const scanner = await createSessionScanner({
431
+ sessionId: session.sessionId,
432
+ workingDirectory: session.path,
433
+ onMessage: (message) => {
434
+ if (message.type !== "summary") {
435
+ session.client.sendClaudeSessionMessage(message);
436
+ }
437
+ }
438
+ });
439
+ const scannerSessionCallback = (sessionId) => {
440
+ scanner.onNewSession(sessionId);
441
+ };
442
+ session.addSessionFoundCallback(scannerSessionCallback);
443
+ let exitReason = null;
444
+ const processAbortController = new AbortController();
445
+ let exutFuture = new future.Future();
446
+ try {
447
+ async function abort() {
448
+ if (!processAbortController.signal.aborted) {
449
+ processAbortController.abort();
450
+ }
451
+ await exutFuture.promise;
452
+ }
453
+ async function doAbort() {
454
+ api.logger.debug("[local]: doAbort");
455
+ if (!exitReason) {
456
+ exitReason = { type: "switch" };
457
+ }
458
+ session.queue.reset();
459
+ await abort();
460
+ }
461
+ async function doSwitch() {
462
+ api.logger.debug("[local]: doSwitch");
463
+ if (!exitReason) {
464
+ exitReason = { type: "switch" };
465
+ }
466
+ await abort();
467
+ }
468
+ session.client.rpcHandlerManager.registerHandler("abort", doAbort);
469
+ session.client.rpcHandlerManager.registerHandler("switch", doSwitch);
470
+ session.queue.setOnMessage((message, mode) => {
471
+ doSwitch();
472
+ });
473
+ if (session.queue.size() > 0) {
474
+ return { type: "switch" };
475
+ }
476
+ const handleSessionStart = (sessionId) => {
477
+ session.onSessionFound(sessionId);
478
+ scanner.onNewSession(sessionId);
479
+ };
480
+ while (true) {
481
+ if (exitReason) {
482
+ return exitReason;
483
+ }
484
+ api.logger.debug("[local]: launch");
485
+ try {
486
+ await index.claudeLocal({
487
+ path: session.path,
488
+ sessionId: session.sessionId,
489
+ onSessionFound: handleSessionStart,
490
+ onThinkingChange: session.onThinkingChange,
491
+ abort: processAbortController.signal,
492
+ claudeEnvVars: session.claudeEnvVars,
493
+ claudeArgs: session.claudeArgs,
494
+ mcpServers: session.mcpServers,
495
+ allowedTools: session.allowedTools,
496
+ hookSettingsPath: session.hookSettingsPath
497
+ });
498
+ session.consumeOneTimeFlags();
499
+ if (!exitReason) {
500
+ exitReason = { type: "exit", code: 0 };
501
+ break;
502
+ }
503
+ } catch (e) {
504
+ api.logger.debug("[local]: launch error", e);
505
+ if (e instanceof index.ExitCodeError && !exitReason) {
506
+ exitReason = { type: "exit", code: e.exitCode };
507
+ break;
508
+ }
509
+ if (!exitReason) {
510
+ session.client.sendSessionEvent({ type: "message", message: "Process exited unexpectedly" });
511
+ continue;
512
+ } else {
513
+ break;
514
+ }
515
+ }
516
+ api.logger.debug("[local]: launch done");
517
+ }
518
+ } finally {
519
+ exutFuture.resolve(void 0);
520
+ session.client.rpcHandlerManager.registerHandler("abort", async () => {
521
+ });
522
+ session.client.rpcHandlerManager.registerHandler("switch", async () => {
523
+ });
524
+ session.queue.setOnMessage(null);
525
+ session.removeSessionFoundCallback(scannerSessionCallback);
526
+ await scanner.cleanup();
527
+ }
528
+ return exitReason || { type: "exit", code: 0 };
529
+ }
530
+
531
+ const RemoteModeDisplay = ({ messageBuffer, logPath, onExit, onSwitchToLocal }) => {
532
+ const [messages, setMessages] = React.useState([]);
533
+ const [confirmationMode, setConfirmationMode] = React.useState(null);
534
+ const [actionInProgress, setActionInProgress] = React.useState(null);
535
+ const confirmationTimeoutRef = React.useRef(null);
536
+ const { stdout } = ink.useStdout();
537
+ const terminalWidth = stdout.columns || 80;
538
+ const terminalHeight = stdout.rows || 24;
539
+ React.useEffect(() => {
540
+ setMessages(messageBuffer.getMessages());
541
+ const unsubscribe = messageBuffer.onUpdate((newMessages) => {
542
+ setMessages(newMessages);
543
+ });
544
+ return () => {
545
+ unsubscribe();
546
+ if (confirmationTimeoutRef.current) {
547
+ clearTimeout(confirmationTimeoutRef.current);
548
+ }
549
+ };
550
+ }, [messageBuffer]);
551
+ const resetConfirmation = React.useCallback(() => {
552
+ setConfirmationMode(null);
553
+ if (confirmationTimeoutRef.current) {
554
+ clearTimeout(confirmationTimeoutRef.current);
555
+ confirmationTimeoutRef.current = null;
556
+ }
557
+ }, []);
558
+ const setConfirmationWithTimeout = React.useCallback((mode) => {
559
+ setConfirmationMode(mode);
560
+ if (confirmationTimeoutRef.current) {
561
+ clearTimeout(confirmationTimeoutRef.current);
562
+ }
563
+ confirmationTimeoutRef.current = setTimeout(() => {
564
+ resetConfirmation();
565
+ }, 15e3);
566
+ }, [resetConfirmation]);
567
+ ink.useInput(React.useCallback(async (input, key) => {
568
+ if (actionInProgress) return;
569
+ if (key.ctrl && input === "c") {
570
+ if (confirmationMode === "exit") {
571
+ resetConfirmation();
572
+ setActionInProgress("exiting");
573
+ await new Promise((resolve) => setTimeout(resolve, 100));
574
+ onExit?.();
575
+ } else {
576
+ setConfirmationWithTimeout("exit");
577
+ }
578
+ return;
579
+ }
580
+ if (input === " ") {
581
+ if (confirmationMode === "switch") {
582
+ resetConfirmation();
583
+ setActionInProgress("switching");
584
+ await new Promise((resolve) => setTimeout(resolve, 100));
585
+ onSwitchToLocal?.();
586
+ } else {
587
+ setConfirmationWithTimeout("switch");
588
+ }
589
+ return;
590
+ }
591
+ if (confirmationMode) {
592
+ resetConfirmation();
593
+ }
594
+ }, [confirmationMode, actionInProgress, onExit, onSwitchToLocal, setConfirmationWithTimeout, resetConfirmation]));
595
+ const getMessageColor = (type) => {
596
+ switch (type) {
597
+ case "user":
598
+ return "magenta";
599
+ case "assistant":
600
+ return "cyan";
601
+ case "system":
602
+ return "blue";
603
+ case "tool":
604
+ return "yellow";
605
+ case "result":
606
+ return "green";
607
+ case "status":
608
+ return "gray";
609
+ default:
610
+ return "white";
611
+ }
612
+ };
613
+ const formatMessage = (msg) => {
614
+ const lines = msg.content.split("\n");
615
+ const maxLineLength = terminalWidth - 10;
616
+ return lines.map((line) => {
617
+ if (line.length <= maxLineLength) return line;
618
+ const chunks = [];
619
+ for (let i = 0; i < line.length; i += maxLineLength) {
620
+ chunks.push(line.slice(i, i + maxLineLength));
621
+ }
622
+ return chunks.join("\n");
623
+ }).join("\n");
624
+ };
625
+ return /* @__PURE__ */ React.createElement(ink.Box, { flexDirection: "column", width: terminalWidth, height: terminalHeight }, /* @__PURE__ */ React.createElement(
626
+ ink.Box,
627
+ {
628
+ flexDirection: "column",
629
+ width: terminalWidth,
630
+ height: terminalHeight - 4,
631
+ borderStyle: "round",
632
+ borderColor: "gray",
633
+ paddingX: 1,
634
+ overflow: "hidden"
635
+ },
636
+ /* @__PURE__ */ React.createElement(ink.Box, { flexDirection: "column", marginBottom: 1 }, /* @__PURE__ */ React.createElement(ink.Text, { color: "gray", bold: true }, "\u{1F4E1} Remote Mode - Claude Messages"), /* @__PURE__ */ React.createElement(ink.Text, { color: "gray", dimColor: true }, "\u2500".repeat(Math.min(terminalWidth - 4, 60)))),
637
+ /* @__PURE__ */ React.createElement(ink.Box, { flexDirection: "column", height: terminalHeight - 10, overflow: "hidden" }, messages.length === 0 ? /* @__PURE__ */ React.createElement(ink.Text, { color: "gray", dimColor: true }, "Waiting for messages...") : (
638
+ // Show only the last messages that fit in the available space
639
+ messages.slice(-Math.max(1, terminalHeight - 10)).map((msg) => /* @__PURE__ */ React.createElement(ink.Box, { key: msg.id, flexDirection: "column", marginBottom: 1 }, /* @__PURE__ */ React.createElement(ink.Text, { color: getMessageColor(msg.type), dimColor: true }, formatMessage(msg))))
640
+ ))
641
+ ), /* @__PURE__ */ React.createElement(
642
+ ink.Box,
643
+ {
644
+ width: terminalWidth,
645
+ borderStyle: "round",
646
+ borderColor: actionInProgress ? "gray" : confirmationMode === "exit" ? "red" : confirmationMode === "switch" ? "yellow" : "green",
647
+ paddingX: 2,
648
+ justifyContent: "center",
649
+ alignItems: "center",
650
+ flexDirection: "column"
651
+ },
652
+ /* @__PURE__ */ React.createElement(ink.Box, { flexDirection: "column", alignItems: "center" }, actionInProgress === "exiting" ? /* @__PURE__ */ React.createElement(ink.Text, { color: "gray", bold: true }, "Exiting...") : actionInProgress === "switching" ? /* @__PURE__ */ React.createElement(ink.Text, { color: "gray", bold: true }, "Switching to local mode...") : confirmationMode === "exit" ? /* @__PURE__ */ React.createElement(ink.Text, { color: "red", bold: true }, "\u26A0\uFE0F Press Ctrl-C again to exit completely") : confirmationMode === "switch" ? /* @__PURE__ */ React.createElement(ink.Text, { color: "yellow", bold: true }, "\u23F8\uFE0F Press space again to switch to local mode") : /* @__PURE__ */ React.createElement(React.Fragment, null, /* @__PURE__ */ React.createElement(ink.Text, { color: "green", bold: true }, "\u{1F4F1} Press space to switch to local mode \u2022 Ctrl-C to exit")), process.env.DEBUG && logPath && /* @__PURE__ */ React.createElement(ink.Text, { color: "gray", dimColor: true }, "Debug logs: ", logPath))
653
+ ));
654
+ };
655
+
656
+ class Stream {
657
+ constructor(returned) {
658
+ this.returned = returned;
659
+ }
660
+ queue = [];
661
+ readResolve;
662
+ readReject;
663
+ isDone = false;
664
+ hasError;
665
+ started = false;
666
+ /**
667
+ * Implements async iterable protocol
668
+ */
669
+ [Symbol.asyncIterator]() {
670
+ if (this.started) {
671
+ throw new Error("Stream can only be iterated once");
672
+ }
673
+ this.started = true;
674
+ return this;
675
+ }
676
+ /**
677
+ * Gets the next value from the stream
678
+ */
679
+ async next() {
680
+ if (this.queue.length > 0) {
681
+ return Promise.resolve({
682
+ done: false,
683
+ value: this.queue.shift()
684
+ });
685
+ }
686
+ if (this.isDone) {
687
+ return Promise.resolve({ done: true, value: void 0 });
688
+ }
689
+ if (this.hasError) {
690
+ return Promise.reject(this.hasError);
691
+ }
692
+ return new Promise((resolve, reject) => {
693
+ this.readResolve = resolve;
694
+ this.readReject = reject;
695
+ });
696
+ }
697
+ /**
698
+ * Adds a value to the stream
699
+ */
700
+ enqueue(value) {
701
+ if (this.readResolve) {
702
+ const resolve = this.readResolve;
703
+ this.readResolve = void 0;
704
+ this.readReject = void 0;
705
+ resolve({ done: false, value });
706
+ } else {
707
+ this.queue.push(value);
708
+ }
709
+ }
710
+ /**
711
+ * Marks the stream as complete
712
+ */
713
+ done() {
714
+ this.isDone = true;
715
+ if (this.readResolve) {
716
+ const resolve = this.readResolve;
717
+ this.readResolve = void 0;
718
+ this.readReject = void 0;
719
+ resolve({ done: true, value: void 0 });
720
+ }
721
+ }
722
+ /**
723
+ * Propagates an error through the stream
724
+ */
725
+ error(error) {
726
+ this.hasError = error;
727
+ if (this.readReject) {
728
+ const reject = this.readReject;
729
+ this.readResolve = void 0;
730
+ this.readReject = void 0;
731
+ reject(error);
732
+ }
733
+ }
734
+ /**
735
+ * Implements async iterator cleanup
736
+ */
737
+ async return() {
738
+ this.isDone = true;
739
+ if (this.returned) {
740
+ this.returned();
741
+ }
742
+ return Promise.resolve({ done: true, value: void 0 });
743
+ }
744
+ }
745
+
746
+ class AbortError extends Error {
747
+ constructor(message) {
748
+ super(message);
749
+ this.name = "AbortError";
750
+ }
751
+ }
752
+
753
+ const __filename$1 = node_url.fileURLToPath((typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('runClaude-CwAitpX-.cjs', document.baseURI).href)));
754
+ const __dirname$1 = path.join(__filename$1, "..");
755
+ function getGlobalClaudeVersion() {
756
+ try {
757
+ const cleanEnv = getCleanEnv();
758
+ const output = node_child_process.execSync("claude --version", {
759
+ encoding: "utf8",
760
+ stdio: ["pipe", "pipe", "pipe"],
761
+ cwd: os.homedir(),
762
+ env: cleanEnv
763
+ }).trim();
764
+ const match = output.match(/(\d+\.\d+\.\d+)/);
765
+ api.logger.debug(`[Claude SDK] Global claude --version output: ${output}`);
766
+ return match ? match[1] : null;
767
+ } catch {
768
+ return null;
769
+ }
770
+ }
771
+ function getCleanEnv() {
772
+ const env = { ...process.env };
773
+ const cwd = process.cwd();
774
+ const pathSep = process.platform === "win32" ? ";" : ":";
775
+ const pathKey = process.platform === "win32" ? "Path" : "PATH";
776
+ const actualPathKey = Object.keys(env).find((k) => k.toLowerCase() === "path") || pathKey;
777
+ if (env[actualPathKey]) {
778
+ const cleanPath = env[actualPathKey].split(pathSep).filter((p) => {
779
+ const normalizedP = p.replace(/\\/g, "/").toLowerCase();
780
+ const normalizedCwd = cwd.replace(/\\/g, "/").toLowerCase();
781
+ return !normalizedP.startsWith(normalizedCwd);
782
+ }).join(pathSep);
783
+ env[actualPathKey] = cleanPath;
784
+ api.logger.debug(`[Claude SDK] Cleaned PATH, removed local paths from: ${cwd}`);
785
+ }
786
+ if (index.isBun()) {
787
+ Object.keys(env).forEach((key) => {
788
+ if (key.startsWith("BUN_")) {
789
+ delete env[key];
790
+ }
791
+ });
792
+ api.logger.debug("[Claude SDK] Removed Bun-specific environment variables for Node.js compatibility");
793
+ }
794
+ return env;
795
+ }
796
+ function findGlobalClaudePath() {
797
+ const homeDir = os.homedir();
798
+ const cleanEnv = getCleanEnv();
799
+ try {
800
+ node_child_process.execSync("claude --version", {
801
+ encoding: "utf8",
802
+ stdio: ["pipe", "pipe", "pipe"],
803
+ cwd: homeDir,
804
+ env: cleanEnv
805
+ });
806
+ api.logger.debug("[Claude SDK] Global claude command available (checked with clean PATH)");
807
+ return "claude";
808
+ } catch {
809
+ }
810
+ if (process.platform !== "win32") {
811
+ try {
812
+ const result = node_child_process.execSync("which claude", {
813
+ encoding: "utf8",
814
+ stdio: ["pipe", "pipe", "pipe"],
815
+ cwd: homeDir,
816
+ env: cleanEnv
817
+ }).trim();
818
+ if (result && fs$1.existsSync(result)) {
819
+ api.logger.debug(`[Claude SDK] Found global claude path via which: ${result}`);
820
+ return result;
821
+ }
822
+ } catch {
823
+ }
824
+ }
825
+ return null;
826
+ }
827
+ function getDefaultClaudeCodePath() {
828
+ const nodeModulesPath = path.join(__dirname$1, "..", "..", "..", "node_modules", "@anthropic-ai", "claude-code", "cli.js");
829
+ if (process.env.HAPPY_CLAUDE_PATH) {
830
+ api.logger.debug(`[Claude SDK] Using HAPPY_CLAUDE_PATH: ${process.env.HAPPY_CLAUDE_PATH}`);
831
+ return process.env.HAPPY_CLAUDE_PATH;
832
+ }
833
+ if (process.env.HAPPY_USE_BUNDLED_CLAUDE === "1") {
834
+ api.logger.debug(`[Claude SDK] Forced bundled version: ${nodeModulesPath}`);
835
+ return nodeModulesPath;
836
+ }
837
+ const globalPath = findGlobalClaudePath();
838
+ if (!globalPath) {
839
+ api.logger.debug(`[Claude SDK] No global claude found, using bundled: ${nodeModulesPath}`);
840
+ return nodeModulesPath;
841
+ }
842
+ const globalVersion = getGlobalClaudeVersion();
843
+ api.logger.debug(`[Claude SDK] Global version: ${globalVersion || "unknown"}`);
844
+ if (!globalVersion) {
845
+ api.logger.debug(`[Claude SDK] Cannot compare versions, using global: ${globalPath}`);
846
+ return globalPath;
847
+ }
848
+ return globalPath;
849
+ }
850
+ function logDebug(message) {
851
+ if (process.env.DEBUG) {
852
+ api.logger.debug(message);
853
+ console.log(message);
854
+ }
855
+ }
856
+ async function streamToStdin(stream, stdin, abort) {
857
+ for await (const message of stream) {
858
+ if (abort?.aborted) break;
859
+ stdin.write(JSON.stringify(message) + "\n");
860
+ }
861
+ stdin.end();
862
+ }
863
+
864
+ class Query {
865
+ constructor(childStdin, childStdout, processExitPromise, canCallTool) {
866
+ this.childStdin = childStdin;
867
+ this.childStdout = childStdout;
868
+ this.processExitPromise = processExitPromise;
869
+ this.canCallTool = canCallTool;
870
+ this.readMessages();
871
+ this.sdkMessages = this.readSdkMessages();
872
+ }
873
+ pendingControlResponses = /* @__PURE__ */ new Map();
874
+ cancelControllers = /* @__PURE__ */ new Map();
875
+ sdkMessages;
876
+ inputStream = new Stream();
877
+ canCallTool;
878
+ /**
879
+ * Set an error on the stream
880
+ */
881
+ setError(error) {
882
+ this.inputStream.error(error);
883
+ }
884
+ /**
885
+ * AsyncIterableIterator implementation
886
+ */
887
+ next(...args) {
888
+ return this.sdkMessages.next(...args);
889
+ }
890
+ return(value) {
891
+ if (this.sdkMessages.return) {
892
+ return this.sdkMessages.return(value);
893
+ }
894
+ return Promise.resolve({ done: true, value: void 0 });
895
+ }
896
+ throw(e) {
897
+ if (this.sdkMessages.throw) {
898
+ return this.sdkMessages.throw(e);
899
+ }
900
+ return Promise.reject(e);
901
+ }
902
+ [Symbol.asyncIterator]() {
903
+ return this.sdkMessages;
904
+ }
905
+ /**
906
+ * Read messages from Claude process stdout
907
+ */
908
+ async readMessages() {
909
+ const rl = node_readline.createInterface({ input: this.childStdout });
910
+ try {
911
+ for await (const line of rl) {
912
+ if (line.trim()) {
913
+ try {
914
+ const message = JSON.parse(line);
915
+ if (message.type === "control_response") {
916
+ const controlResponse = message;
917
+ const handler = this.pendingControlResponses.get(controlResponse.response.request_id);
918
+ if (handler) {
919
+ handler(controlResponse.response);
920
+ }
921
+ continue;
922
+ } else if (message.type === "control_request") {
923
+ await this.handleControlRequest(message);
924
+ continue;
925
+ } else if (message.type === "control_cancel_request") {
926
+ this.handleControlCancelRequest(message);
927
+ continue;
928
+ }
929
+ this.inputStream.enqueue(message);
930
+ } catch (e) {
931
+ api.logger.debug(line);
932
+ }
933
+ }
934
+ }
935
+ await this.processExitPromise;
936
+ } catch (error) {
937
+ this.inputStream.error(error);
938
+ } finally {
939
+ this.inputStream.done();
940
+ this.cleanupControllers();
941
+ rl.close();
942
+ }
943
+ }
944
+ /**
945
+ * Async generator for SDK messages
946
+ */
947
+ async *readSdkMessages() {
948
+ for await (const message of this.inputStream) {
949
+ yield message;
950
+ }
951
+ }
952
+ /**
953
+ * Send interrupt request to Claude
954
+ */
955
+ async interrupt() {
956
+ if (!this.childStdin) {
957
+ throw new Error("Interrupt requires --input-format stream-json");
958
+ }
959
+ await this.request({
960
+ subtype: "interrupt"
961
+ }, this.childStdin);
962
+ }
963
+ /**
964
+ * Send control request to Claude process
965
+ */
966
+ request(request, childStdin) {
967
+ const requestId = Math.random().toString(36).substring(2, 15);
968
+ const sdkRequest = {
969
+ request_id: requestId,
970
+ type: "control_request",
971
+ request
972
+ };
973
+ return new Promise((resolve, reject) => {
974
+ this.pendingControlResponses.set(requestId, (response) => {
975
+ if (response.subtype === "success") {
976
+ resolve(response);
977
+ } else {
978
+ reject(new Error(response.error));
979
+ }
980
+ });
981
+ childStdin.write(JSON.stringify(sdkRequest) + "\n");
982
+ });
983
+ }
984
+ /**
985
+ * Handle incoming control requests for tool permissions
986
+ * Replicates the exact logic from the SDK's handleControlRequest method
987
+ */
988
+ async handleControlRequest(request) {
989
+ if (!this.childStdin) {
990
+ logDebug("Cannot handle control request - no stdin available");
991
+ return;
992
+ }
993
+ const controller = new AbortController();
994
+ this.cancelControllers.set(request.request_id, controller);
995
+ try {
996
+ const response = await this.processControlRequest(request, controller.signal);
997
+ const controlResponse = {
998
+ type: "control_response",
999
+ response: {
1000
+ subtype: "success",
1001
+ request_id: request.request_id,
1002
+ response
1003
+ }
1004
+ };
1005
+ this.childStdin.write(JSON.stringify(controlResponse) + "\n");
1006
+ } catch (error) {
1007
+ const controlErrorResponse = {
1008
+ type: "control_response",
1009
+ response: {
1010
+ subtype: "error",
1011
+ request_id: request.request_id,
1012
+ error: error instanceof Error ? error.message : String(error)
1013
+ }
1014
+ };
1015
+ this.childStdin.write(JSON.stringify(controlErrorResponse) + "\n");
1016
+ } finally {
1017
+ this.cancelControllers.delete(request.request_id);
1018
+ }
1019
+ }
1020
+ /**
1021
+ * Handle control cancel requests
1022
+ * Replicates the exact logic from the SDK's handleControlCancelRequest method
1023
+ */
1024
+ handleControlCancelRequest(request) {
1025
+ const controller = this.cancelControllers.get(request.request_id);
1026
+ if (controller) {
1027
+ controller.abort();
1028
+ this.cancelControllers.delete(request.request_id);
1029
+ }
1030
+ }
1031
+ /**
1032
+ * Process control requests based on subtype
1033
+ * Replicates the exact logic from the SDK's processControlRequest method
1034
+ */
1035
+ async processControlRequest(request, signal) {
1036
+ if (request.request.subtype === "can_use_tool") {
1037
+ if (!this.canCallTool) {
1038
+ throw new Error("canCallTool callback is not provided.");
1039
+ }
1040
+ return this.canCallTool(request.request.tool_name, request.request.input, {
1041
+ signal
1042
+ });
1043
+ }
1044
+ throw new Error("Unsupported control request subtype: " + request.request.subtype);
1045
+ }
1046
+ /**
1047
+ * Cleanup method to abort all pending control requests
1048
+ */
1049
+ cleanupControllers() {
1050
+ for (const [requestId, controller] of this.cancelControllers.entries()) {
1051
+ controller.abort();
1052
+ this.cancelControllers.delete(requestId);
1053
+ }
1054
+ }
1055
+ }
1056
+ function query(config) {
1057
+ const {
1058
+ prompt,
1059
+ options: {
1060
+ allowedTools = [],
1061
+ appendSystemPrompt,
1062
+ customSystemPrompt,
1063
+ cwd,
1064
+ disallowedTools = [],
1065
+ executable = "node",
1066
+ executableArgs = [],
1067
+ maxTurns,
1068
+ mcpServers,
1069
+ pathToClaudeCodeExecutable = getDefaultClaudeCodePath(),
1070
+ permissionMode = "default",
1071
+ continue: continueConversation,
1072
+ resume,
1073
+ model,
1074
+ fallbackModel,
1075
+ strictMcpConfig,
1076
+ canCallTool,
1077
+ settingsPath
1078
+ } = {}
1079
+ } = config;
1080
+ if (!process.env.CLAUDE_CODE_ENTRYPOINT) {
1081
+ process.env.CLAUDE_CODE_ENTRYPOINT = "sdk-ts";
1082
+ }
1083
+ const args = ["--output-format", "stream-json", "--verbose"];
1084
+ if (customSystemPrompt) args.push("--system-prompt", customSystemPrompt);
1085
+ if (appendSystemPrompt) args.push("--append-system-prompt", appendSystemPrompt);
1086
+ if (maxTurns) args.push("--max-turns", maxTurns.toString());
1087
+ if (model) args.push("--model", model);
1088
+ if (canCallTool) {
1089
+ if (typeof prompt === "string") {
1090
+ throw new Error("canCallTool callback requires --input-format stream-json. Please set prompt as an AsyncIterable.");
1091
+ }
1092
+ args.push("--permission-prompt-tool", "stdio");
1093
+ }
1094
+ if (continueConversation) args.push("--continue");
1095
+ if (resume) args.push("--resume", resume);
1096
+ if (allowedTools.length > 0) args.push("--allowedTools", allowedTools.join(","));
1097
+ if (disallowedTools.length > 0) args.push("--disallowedTools", disallowedTools.join(","));
1098
+ if (mcpServers && Object.keys(mcpServers).length > 0) {
1099
+ args.push("--mcp-config", JSON.stringify({ mcpServers }));
1100
+ }
1101
+ if (strictMcpConfig) args.push("--strict-mcp-config");
1102
+ if (permissionMode) args.push("--permission-mode", permissionMode);
1103
+ if (settingsPath) args.push("--settings", settingsPath);
1104
+ if (fallbackModel) {
1105
+ if (model && fallbackModel === model) {
1106
+ throw new Error("Fallback model cannot be the same as the main model. Please specify a different model for fallbackModel option.");
1107
+ }
1108
+ args.push("--fallback-model", fallbackModel);
1109
+ }
1110
+ if (typeof prompt === "string") {
1111
+ args.push("--print", prompt.trim());
1112
+ } else {
1113
+ args.push("--input-format", "stream-json");
1114
+ }
1115
+ const isJsFile = pathToClaudeCodeExecutable.endsWith(".js") || pathToClaudeCodeExecutable.endsWith(".cjs");
1116
+ const isCommandOnly = pathToClaudeCodeExecutable === "claude";
1117
+ if (!isCommandOnly && !fs$1.existsSync(pathToClaudeCodeExecutable)) {
1118
+ throw new ReferenceError(`Claude Code executable not found at ${pathToClaudeCodeExecutable}. Is options.pathToClaudeCodeExecutable set?`);
1119
+ }
1120
+ const spawnCommand = isJsFile ? executable : pathToClaudeCodeExecutable;
1121
+ const spawnArgs = isJsFile ? [...executableArgs, pathToClaudeCodeExecutable, ...args] : args;
1122
+ const spawnEnv = isCommandOnly ? getCleanEnv() : process.env;
1123
+ logDebug(`Spawning Claude Code process: ${spawnCommand} ${spawnArgs.join(" ")} (using ${isCommandOnly ? "clean" : "normal"} env)`);
1124
+ const child = node_child_process.spawn(spawnCommand, spawnArgs, {
1125
+ cwd,
1126
+ stdio: ["pipe", "pipe", "pipe"],
1127
+ signal: config.options?.abort,
1128
+ env: spawnEnv,
1129
+ // Use shell on Windows for global binaries and command-only mode
1130
+ shell: !isJsFile && process.platform === "win32"
1131
+ });
1132
+ let childStdin = null;
1133
+ if (typeof prompt === "string") {
1134
+ child.stdin.end();
1135
+ } else {
1136
+ streamToStdin(prompt, child.stdin, config.options?.abort);
1137
+ childStdin = child.stdin;
1138
+ }
1139
+ if (process.env.DEBUG) {
1140
+ child.stderr.on("data", (data) => {
1141
+ console.error("Claude Code stderr:", data.toString());
1142
+ });
1143
+ }
1144
+ const cleanup = () => {
1145
+ if (!child.killed) {
1146
+ child.kill("SIGTERM");
1147
+ }
1148
+ };
1149
+ config.options?.abort?.addEventListener("abort", cleanup);
1150
+ process.on("exit", cleanup);
1151
+ const processExitPromise = new Promise((resolve) => {
1152
+ child.on("close", (code) => {
1153
+ if (config.options?.abort?.aborted) {
1154
+ query2.setError(new AbortError("Claude Code process aborted by user"));
1155
+ }
1156
+ if (code !== 0) {
1157
+ query2.setError(new Error(`Claude Code process exited with code ${code}`));
1158
+ } else {
1159
+ resolve();
1160
+ }
1161
+ });
1162
+ });
1163
+ const query2 = new Query(childStdin, child.stdout, processExitPromise, canCallTool);
1164
+ child.on("error", (error) => {
1165
+ if (config.options?.abort?.aborted) {
1166
+ query2.setError(new AbortError("Claude Code process aborted by user"));
1167
+ } else {
1168
+ query2.setError(new Error(`Failed to spawn Claude Code process: ${error.message}`));
1169
+ }
1170
+ });
1171
+ processExitPromise.finally(() => {
1172
+ cleanup();
1173
+ config.options?.abort?.removeEventListener("abort", cleanup);
1174
+ if (process.env.CLAUDE_SDK_MCP_SERVERS) {
1175
+ delete process.env.CLAUDE_SDK_MCP_SERVERS;
1176
+ }
1177
+ });
1178
+ return query2;
1179
+ }
1180
+
1181
+ function mapToClaudeMode(mode) {
1182
+ const codexToClaudeMap = {
1183
+ "yolo": "bypassPermissions",
1184
+ "safe-yolo": "default",
1185
+ "read-only": "default"
1186
+ };
1187
+ return codexToClaudeMap[mode] ?? mode;
1188
+ }
1189
+
1190
+ function parseCompact(message) {
1191
+ const trimmed = message.trim();
1192
+ if (trimmed === "/compact") {
1193
+ return {
1194
+ isCompact: true,
1195
+ originalMessage: trimmed
1196
+ };
1197
+ }
1198
+ if (trimmed.startsWith("/compact ")) {
1199
+ return {
1200
+ isCompact: true,
1201
+ originalMessage: trimmed
1202
+ };
1203
+ }
1204
+ return {
1205
+ isCompact: false,
1206
+ originalMessage: message
1207
+ };
1208
+ }
1209
+ function parseClear(message) {
1210
+ const trimmed = message.trim();
1211
+ return {
1212
+ isClear: trimmed === "/clear"
1213
+ };
1214
+ }
1215
+ function parseSpecialCommand(message) {
1216
+ const compactResult = parseCompact(message);
1217
+ if (compactResult.isCompact) {
1218
+ return {
1219
+ type: "compact",
1220
+ originalMessage: compactResult.originalMessage
1221
+ };
1222
+ }
1223
+ const clearResult = parseClear(message);
1224
+ if (clearResult.isClear) {
1225
+ return {
1226
+ type: "clear"
1227
+ };
1228
+ }
1229
+ return {
1230
+ type: null
1231
+ };
1232
+ }
1233
+
1234
+ class PushableAsyncIterable {
1235
+ queue = [];
1236
+ waiters = [];
1237
+ isDone = false;
1238
+ error = null;
1239
+ started = false;
1240
+ constructor() {
1241
+ }
1242
+ /**
1243
+ * Push a value to the iterable
1244
+ */
1245
+ push(value) {
1246
+ if (this.isDone) {
1247
+ throw new Error("Cannot push to completed iterable");
1248
+ }
1249
+ if (this.error) {
1250
+ throw this.error;
1251
+ }
1252
+ const waiter = this.waiters.shift();
1253
+ if (waiter) {
1254
+ waiter.resolve({ done: false, value });
1255
+ } else {
1256
+ this.queue.push(value);
1257
+ }
1258
+ }
1259
+ /**
1260
+ * Mark the iterable as complete
1261
+ */
1262
+ end() {
1263
+ if (this.isDone) {
1264
+ return;
1265
+ }
1266
+ this.isDone = true;
1267
+ this.cleanup();
1268
+ }
1269
+ /**
1270
+ * Set an error on the iterable
1271
+ */
1272
+ setError(err) {
1273
+ if (this.isDone) {
1274
+ return;
1275
+ }
1276
+ this.error = err;
1277
+ this.isDone = true;
1278
+ this.cleanup();
1279
+ }
1280
+ /**
1281
+ * Cleanup waiting consumers
1282
+ */
1283
+ cleanup() {
1284
+ while (this.waiters.length > 0) {
1285
+ const waiter = this.waiters.shift();
1286
+ if (this.error) {
1287
+ waiter.reject(this.error);
1288
+ } else {
1289
+ waiter.resolve({ done: true, value: void 0 });
1290
+ }
1291
+ }
1292
+ }
1293
+ /**
1294
+ * AsyncIterableIterator implementation
1295
+ */
1296
+ async next() {
1297
+ if (this.queue.length > 0) {
1298
+ return { done: false, value: this.queue.shift() };
1299
+ }
1300
+ if (this.isDone) {
1301
+ if (this.error) {
1302
+ throw this.error;
1303
+ }
1304
+ return { done: true, value: void 0 };
1305
+ }
1306
+ return new Promise((resolve, reject) => {
1307
+ this.waiters.push({ resolve, reject });
1308
+ });
1309
+ }
1310
+ /**
1311
+ * AsyncIterableIterator return implementation
1312
+ */
1313
+ async return(_value) {
1314
+ this.end();
1315
+ return { done: true, value: void 0 };
1316
+ }
1317
+ /**
1318
+ * AsyncIterableIterator throw implementation
1319
+ */
1320
+ async throw(e) {
1321
+ this.setError(e instanceof Error ? e : new Error(String(e)));
1322
+ throw this.error;
1323
+ }
1324
+ /**
1325
+ * Make this iterable
1326
+ */
1327
+ [Symbol.asyncIterator]() {
1328
+ if (this.started) {
1329
+ throw new Error("PushableAsyncIterable can only be iterated once");
1330
+ }
1331
+ this.started = true;
1332
+ return this;
1333
+ }
1334
+ /**
1335
+ * Check if the iterable is done
1336
+ */
1337
+ get done() {
1338
+ return this.isDone;
1339
+ }
1340
+ /**
1341
+ * Check if the iterable has an error
1342
+ */
1343
+ get hasError() {
1344
+ return this.error !== null;
1345
+ }
1346
+ /**
1347
+ * Get the current queue size
1348
+ */
1349
+ get queueSize() {
1350
+ return this.queue.length;
1351
+ }
1352
+ /**
1353
+ * Get the number of waiting consumers
1354
+ */
1355
+ get waiterCount() {
1356
+ return this.waiters.length;
1357
+ }
1358
+ }
1359
+
1360
+ async function awaitFileExist(file, timeout = 1e4) {
1361
+ const startTime = Date.now();
1362
+ while (Date.now() - startTime < timeout) {
1363
+ try {
1364
+ await fs.access(file);
1365
+ return true;
1366
+ } catch (e) {
1367
+ await api.delay(1e3);
1368
+ }
1369
+ }
1370
+ return false;
1371
+ }
1372
+
1373
+ function getClaudeSettingsPath() {
1374
+ const claudeConfigDir = process.env.CLAUDE_CONFIG_DIR || path.join(os.homedir(), ".claude");
1375
+ return path.join(claudeConfigDir, "settings.json");
1376
+ }
1377
+ function readClaudeSettings() {
1378
+ try {
1379
+ const settingsPath = getClaudeSettingsPath();
1380
+ if (!fs$1.existsSync(settingsPath)) {
1381
+ api.logger.debug(`[ClaudeSettings] No Claude settings file found at ${settingsPath}`);
1382
+ return null;
1383
+ }
1384
+ const settingsContent = fs$1.readFileSync(settingsPath, "utf-8");
1385
+ const settings = JSON.parse(settingsContent);
1386
+ api.logger.debug(`[ClaudeSettings] Successfully read Claude settings from ${settingsPath}`);
1387
+ api.logger.debug(`[ClaudeSettings] includeCoAuthoredBy: ${settings.includeCoAuthoredBy}`);
1388
+ return settings;
1389
+ } catch (error) {
1390
+ api.logger.debug(`[ClaudeSettings] Error reading Claude settings: ${error}`);
1391
+ return null;
1392
+ }
1393
+ }
1394
+ function shouldIncludeCoAuthoredBy() {
1395
+ const settings = readClaudeSettings();
1396
+ if (!settings || settings.includeCoAuthoredBy === void 0) {
1397
+ return true;
1398
+ }
1399
+ return settings.includeCoAuthoredBy;
1400
+ }
1401
+
1402
+ const BASE_SYSTEM_PROMPT = (() => index.trimIdent(`
1403
+ Help human.
1404
+ `))();
1405
+ const CO_AUTHORED_CREDITS = (() => index.trimIdent(`
1406
+ When making commit messages, instead of just giving co-credit to Claude, also give credit to Happy like so:
1407
+
1408
+ <main commit message>
1409
+
1410
+ Generated with [Claude Code](https://claude.ai/code)
1411
+ via [Happy](https://happy.engineering)
1412
+
1413
+ Co-Authored-By: Claude <noreply@anthropic.com>
1414
+ Co-Authored-By: Happy <yesreply@happy.engineering>
1415
+ `))();
1416
+ const systemPrompt = (() => {
1417
+ const includeCoAuthored = shouldIncludeCoAuthoredBy();
1418
+ if (includeCoAuthored) {
1419
+ return BASE_SYSTEM_PROMPT + "\n\n" + CO_AUTHORED_CREDITS;
1420
+ } else {
1421
+ return BASE_SYSTEM_PROMPT;
1422
+ }
1423
+ })();
1424
+
1425
+ async function claudeRemote(opts) {
1426
+ let startFrom = opts.sessionId;
1427
+ if (opts.sessionId && !index.claudeCheckSession(opts.sessionId, opts.path)) {
1428
+ startFrom = null;
1429
+ }
1430
+ if (!startFrom && opts.claudeArgs) {
1431
+ for (let i = 0; i < opts.claudeArgs.length; i++) {
1432
+ if (opts.claudeArgs[i] === "--resume") {
1433
+ if (i + 1 < opts.claudeArgs.length) {
1434
+ const nextArg = opts.claudeArgs[i + 1];
1435
+ if (!nextArg.startsWith("-") && nextArg.includes("-")) {
1436
+ startFrom = nextArg;
1437
+ api.logger.debug(`[claudeRemote] Found --resume with session ID: ${startFrom}`);
1438
+ break;
1439
+ } else {
1440
+ api.logger.debug("[claudeRemote] Found --resume without session ID - not supported in remote mode");
1441
+ break;
1442
+ }
1443
+ } else {
1444
+ api.logger.debug("[claudeRemote] Found --resume without session ID - not supported in remote mode");
1445
+ break;
1446
+ }
1447
+ }
1448
+ }
1449
+ }
1450
+ if (opts.claudeEnvVars) {
1451
+ Object.entries(opts.claudeEnvVars).forEach(([key, value]) => {
1452
+ process.env[key] = value;
1453
+ });
1454
+ }
1455
+ const initial = await opts.nextMessage();
1456
+ if (!initial) {
1457
+ return;
1458
+ }
1459
+ const specialCommand = parseSpecialCommand(initial.message);
1460
+ if (specialCommand.type === "clear") {
1461
+ if (opts.onCompletionEvent) {
1462
+ opts.onCompletionEvent("Context was reset");
1463
+ }
1464
+ if (opts.onSessionReset) {
1465
+ opts.onSessionReset();
1466
+ }
1467
+ return;
1468
+ }
1469
+ let isCompactCommand = false;
1470
+ if (specialCommand.type === "compact") {
1471
+ api.logger.debug("[claudeRemote] /compact command detected - will process as normal but with compaction behavior");
1472
+ isCompactCommand = true;
1473
+ if (opts.onCompletionEvent) {
1474
+ opts.onCompletionEvent("Compaction started");
1475
+ }
1476
+ }
1477
+ let mode = initial.mode;
1478
+ const sdkOptions = {
1479
+ cwd: opts.path,
1480
+ resume: startFrom ?? void 0,
1481
+ mcpServers: opts.mcpServers,
1482
+ permissionMode: mapToClaudeMode(initial.mode.permissionMode),
1483
+ model: initial.mode.model,
1484
+ fallbackModel: initial.mode.fallbackModel,
1485
+ customSystemPrompt: initial.mode.customSystemPrompt ? initial.mode.customSystemPrompt + "\n\n" + systemPrompt : void 0,
1486
+ appendSystemPrompt: initial.mode.appendSystemPrompt ? initial.mode.appendSystemPrompt + "\n\n" + systemPrompt : systemPrompt,
1487
+ allowedTools: initial.mode.allowedTools ? initial.mode.allowedTools.concat(opts.allowedTools) : opts.allowedTools,
1488
+ disallowedTools: initial.mode.disallowedTools,
1489
+ canCallTool: (toolName, input, options) => opts.canCallTool(toolName, input, mode, options),
1490
+ executable: opts.jsRuntime ?? "node",
1491
+ abort: opts.signal,
1492
+ pathToClaudeCodeExecutable: (() => {
1493
+ return path.resolve(path.join(index.projectPath(), "scripts", "claude_remote_launcher.cjs"));
1494
+ })(),
1495
+ settingsPath: opts.hookSettingsPath
1496
+ };
1497
+ let thinking = false;
1498
+ const updateThinking = (newThinking) => {
1499
+ if (thinking !== newThinking) {
1500
+ thinking = newThinking;
1501
+ api.logger.debug(`[claudeRemote] Thinking state changed to: ${thinking}`);
1502
+ if (opts.onThinkingChange) {
1503
+ opts.onThinkingChange(thinking);
1504
+ }
1505
+ }
1506
+ };
1507
+ let messages = new PushableAsyncIterable();
1508
+ messages.push({
1509
+ type: "user",
1510
+ message: {
1511
+ role: "user",
1512
+ content: initial.message
1513
+ }
1514
+ });
1515
+ const response = query({
1516
+ prompt: messages,
1517
+ options: sdkOptions
1518
+ });
1519
+ updateThinking(true);
1520
+ try {
1521
+ api.logger.debug(`[claudeRemote] Starting to iterate over response`);
1522
+ for await (const message of response) {
1523
+ api.logger.debugLargeJson(`[claudeRemote] Message ${message.type}`, message);
1524
+ opts.onMessage(message);
1525
+ if (message.type === "system" && message.subtype === "init") {
1526
+ updateThinking(true);
1527
+ const systemInit = message;
1528
+ if (systemInit.session_id) {
1529
+ api.logger.debug(`[claudeRemote] Waiting for session file to be written to disk: ${systemInit.session_id}`);
1530
+ const projectDir = index.getProjectPath(opts.path);
1531
+ const found = await awaitFileExist(path.join(projectDir, `${systemInit.session_id}.jsonl`));
1532
+ api.logger.debug(`[claudeRemote] Session file found: ${systemInit.session_id} ${found}`);
1533
+ opts.onSessionFound(systemInit.session_id);
1534
+ }
1535
+ }
1536
+ if (message.type === "result") {
1537
+ updateThinking(false);
1538
+ api.logger.debug("[claudeRemote] Result received, exiting claudeRemote");
1539
+ if (isCompactCommand) {
1540
+ api.logger.debug("[claudeRemote] Compaction completed");
1541
+ if (opts.onCompletionEvent) {
1542
+ opts.onCompletionEvent("Compaction completed");
1543
+ }
1544
+ isCompactCommand = false;
1545
+ }
1546
+ opts.onReady();
1547
+ const next = await opts.nextMessage();
1548
+ if (!next) {
1549
+ messages.end();
1550
+ return;
1551
+ }
1552
+ mode = next.mode;
1553
+ messages.push({ type: "user", message: { role: "user", content: next.message } });
1554
+ }
1555
+ if (message.type === "user") {
1556
+ const msg = message;
1557
+ if (msg.message.role === "user" && Array.isArray(msg.message.content)) {
1558
+ for (let c of msg.message.content) {
1559
+ if (c.type === "tool_result" && c.tool_use_id && opts.isAborted(c.tool_use_id)) {
1560
+ api.logger.debug("[claudeRemote] Tool aborted, exiting claudeRemote");
1561
+ return;
1562
+ }
1563
+ }
1564
+ }
1565
+ }
1566
+ }
1567
+ } catch (e) {
1568
+ if (e instanceof AbortError) {
1569
+ api.logger.debug(`[claudeRemote] Aborted`);
1570
+ } else {
1571
+ throw e;
1572
+ }
1573
+ } finally {
1574
+ updateThinking(false);
1575
+ }
1576
+ }
1577
+
1578
+ 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.`;
1579
+ const PLAN_FAKE_RESTART = `PlEaZe Continue with plan.`;
1580
+
1581
+ const STANDARD_TOOLS = {
1582
+ // File operations
1583
+ "Read": "Read File",
1584
+ "Write": "Write File",
1585
+ "Edit": "Edit File",
1586
+ "MultiEdit": "Edit File",
1587
+ "NotebookEdit": "Edit Notebook",
1588
+ // Search and navigation
1589
+ "Glob": "Find Files",
1590
+ "Grep": "Search in Files",
1591
+ "LS": "List Directory",
1592
+ // Command execution
1593
+ "Bash": "Run Command",
1594
+ "BashOutput": "Check Command Output",
1595
+ "KillBash": "Stop Command",
1596
+ // Task management
1597
+ "TodoWrite": "Update Tasks",
1598
+ "TodoRead": "Read Tasks",
1599
+ "Task": "Launch Agent",
1600
+ // Web tools
1601
+ "WebFetch": "Fetch Web Page",
1602
+ "WebSearch": "Search Web",
1603
+ // Special cases
1604
+ "exit_plan_mode": "Execute Plan",
1605
+ "ExitPlanMode": "Execute Plan"
1606
+ };
1607
+ function toTitleCase(str) {
1608
+ return str.replace(/([a-z])([A-Z])/g, "$1 $2").replace(/_/g, " ").replace(/\b\w/g, (char) => char.toUpperCase());
1609
+ }
1610
+ function getToolName(toolName) {
1611
+ if (STANDARD_TOOLS[toolName]) {
1612
+ return STANDARD_TOOLS[toolName];
1613
+ }
1614
+ if (toolName.startsWith("mcp__")) {
1615
+ const parts = toolName.split("__");
1616
+ if (parts.length >= 3) {
1617
+ const server = toTitleCase(parts[1]);
1618
+ const action = toTitleCase(parts.slice(2).join("_"));
1619
+ return `${server}: ${action}`;
1620
+ }
1621
+ }
1622
+ return toTitleCase(toolName);
1623
+ }
1624
+
1625
+ function getToolDescriptor(toolName) {
1626
+ if (toolName === "exit_plan_mode" || toolName === "ExitPlanMode") {
1627
+ return { edit: false, exitPlan: true };
1628
+ }
1629
+ if (toolName === "Edit" || toolName === "MultiEdit" || toolName === "Write" || toolName === "NotebookEdit") {
1630
+ return { edit: true, exitPlan: false };
1631
+ }
1632
+ return { edit: false, exitPlan: false };
1633
+ }
1634
+
1635
+ class PermissionHandler {
1636
+ toolCalls = [];
1637
+ responses = /* @__PURE__ */ new Map();
1638
+ pendingRequests = /* @__PURE__ */ new Map();
1639
+ session;
1640
+ allowedTools = /* @__PURE__ */ new Set();
1641
+ allowedBashLiterals = /* @__PURE__ */ new Set();
1642
+ allowedBashPrefixes = /* @__PURE__ */ new Set();
1643
+ permissionMode = "default";
1644
+ onPermissionRequestCallback;
1645
+ constructor(session) {
1646
+ this.session = session;
1647
+ this.setupClientHandler();
1648
+ }
1649
+ /**
1650
+ * Set callback to trigger when permission request is made
1651
+ */
1652
+ setOnPermissionRequest(callback) {
1653
+ this.onPermissionRequestCallback = callback;
1654
+ }
1655
+ handleModeChange(mode) {
1656
+ this.permissionMode = mode;
1657
+ }
1658
+ /**
1659
+ * Handler response
1660
+ */
1661
+ handlePermissionResponse(response, pending) {
1662
+ if (response.allowTools && response.allowTools.length > 0) {
1663
+ response.allowTools.forEach((tool) => {
1664
+ if (tool.startsWith("Bash(") || tool === "Bash") {
1665
+ this.parseBashPermission(tool);
1666
+ } else {
1667
+ this.allowedTools.add(tool);
1668
+ }
1669
+ });
1670
+ }
1671
+ if (response.mode) {
1672
+ this.permissionMode = response.mode;
1673
+ }
1674
+ if (pending.toolName === "exit_plan_mode" || pending.toolName === "ExitPlanMode") {
1675
+ api.logger.debug("Plan mode result received", response);
1676
+ if (response.approved) {
1677
+ api.logger.debug("Plan approved - injecting PLAN_FAKE_RESTART");
1678
+ if (response.mode && ["default", "acceptEdits", "bypassPermissions"].includes(response.mode)) {
1679
+ this.session.queue.unshift(PLAN_FAKE_RESTART, { permissionMode: response.mode });
1680
+ } else {
1681
+ this.session.queue.unshift(PLAN_FAKE_RESTART, { permissionMode: "default" });
1682
+ }
1683
+ pending.resolve({ behavior: "deny", message: PLAN_FAKE_REJECT });
1684
+ } else {
1685
+ pending.resolve({ behavior: "deny", message: response.reason || "Plan rejected" });
1686
+ }
1687
+ } else {
1688
+ const result = response.approved ? { behavior: "allow", updatedInput: pending.input || {} } : { behavior: "deny", message: response.reason || `The user doesn't want to proceed with this tool use. The tool use was rejected (eg. if it was a file edit, the new_string was NOT written to the file). STOP what you are doing and wait for the user to tell you how to proceed.` };
1689
+ pending.resolve(result);
1690
+ }
1691
+ }
1692
+ /**
1693
+ * Creates the canCallTool callback for the SDK
1694
+ */
1695
+ handleToolCall = async (toolName, input, mode, options) => {
1696
+ if (toolName === "Bash") {
1697
+ const inputObj = input;
1698
+ if (inputObj?.command) {
1699
+ if (this.allowedBashLiterals.has(inputObj.command)) {
1700
+ return { behavior: "allow", updatedInput: input };
1701
+ }
1702
+ for (const prefix of this.allowedBashPrefixes) {
1703
+ if (inputObj.command.startsWith(prefix)) {
1704
+ return { behavior: "allow", updatedInput: input };
1705
+ }
1706
+ }
1707
+ }
1708
+ } else if (this.allowedTools.has(toolName)) {
1709
+ return { behavior: "allow", updatedInput: input };
1710
+ }
1711
+ const descriptor = getToolDescriptor(toolName);
1712
+ if (this.permissionMode === "bypassPermissions") {
1713
+ return { behavior: "allow", updatedInput: input };
1714
+ }
1715
+ if (this.permissionMode === "acceptEdits" && descriptor.edit) {
1716
+ return { behavior: "allow", updatedInput: input };
1717
+ }
1718
+ let toolCallId = this.resolveToolCallId(toolName, input);
1719
+ if (!toolCallId) {
1720
+ await api.delay(1e3);
1721
+ toolCallId = this.resolveToolCallId(toolName, input);
1722
+ if (!toolCallId) {
1723
+ throw new Error(`Could not resolve tool call ID for ${toolName}`);
1724
+ }
1725
+ }
1726
+ return this.handlePermissionRequest(toolCallId, toolName, input, options.signal);
1727
+ };
1728
+ /**
1729
+ * Handles individual permission requests
1730
+ */
1731
+ async handlePermissionRequest(id, toolName, input, signal) {
1732
+ return new Promise((resolve, reject) => {
1733
+ const abortHandler = () => {
1734
+ this.pendingRequests.delete(id);
1735
+ reject(new Error("Permission request aborted"));
1736
+ };
1737
+ signal.addEventListener("abort", abortHandler, { once: true });
1738
+ this.pendingRequests.set(id, {
1739
+ resolve: (result) => {
1740
+ signal.removeEventListener("abort", abortHandler);
1741
+ resolve(result);
1742
+ },
1743
+ reject: (error) => {
1744
+ signal.removeEventListener("abort", abortHandler);
1745
+ reject(error);
1746
+ },
1747
+ toolName,
1748
+ input
1749
+ });
1750
+ if (this.onPermissionRequestCallback) {
1751
+ this.onPermissionRequestCallback(id);
1752
+ }
1753
+ this.session.api.push().sendToAllDevices(
1754
+ "Permission Request",
1755
+ `Claude wants to ${getToolName(toolName)}`,
1756
+ {
1757
+ sessionId: this.session.client.sessionId,
1758
+ requestId: id,
1759
+ tool: toolName,
1760
+ type: "permission_request"
1761
+ }
1762
+ );
1763
+ this.session.client.updateAgentState((currentState) => ({
1764
+ ...currentState,
1765
+ requests: {
1766
+ ...currentState.requests,
1767
+ [id]: {
1768
+ tool: toolName,
1769
+ arguments: input,
1770
+ createdAt: Date.now()
1771
+ }
1772
+ }
1773
+ }));
1774
+ api.logger.debug(`Permission request sent for tool call ${id}: ${toolName}`);
1775
+ });
1776
+ }
1777
+ /**
1778
+ * Parses Bash permission strings into literal and prefix sets
1779
+ */
1780
+ parseBashPermission(permission) {
1781
+ if (permission === "Bash") {
1782
+ return;
1783
+ }
1784
+ const bashPattern = /^Bash\((.+?)\)$/;
1785
+ const match = permission.match(bashPattern);
1786
+ if (!match) {
1787
+ return;
1788
+ }
1789
+ const command = match[1];
1790
+ if (command.endsWith(":*")) {
1791
+ const prefix = command.slice(0, -2);
1792
+ this.allowedBashPrefixes.add(prefix);
1793
+ } else {
1794
+ this.allowedBashLiterals.add(command);
1795
+ }
1796
+ }
1797
+ /**
1798
+ * Resolves tool call ID based on tool name and input
1799
+ */
1800
+ resolveToolCallId(name, args) {
1801
+ for (let i = this.toolCalls.length - 1; i >= 0; i--) {
1802
+ const call = this.toolCalls[i];
1803
+ if (call.name === name && node_util.isDeepStrictEqual(call.input, args)) {
1804
+ if (call.used) {
1805
+ return null;
1806
+ }
1807
+ call.used = true;
1808
+ return call.id;
1809
+ }
1810
+ }
1811
+ return null;
1812
+ }
1813
+ /**
1814
+ * Handles messages to track tool calls
1815
+ */
1816
+ onMessage(message) {
1817
+ if (message.type === "assistant") {
1818
+ const assistantMsg = message;
1819
+ if (assistantMsg.message && assistantMsg.message.content) {
1820
+ for (const block of assistantMsg.message.content) {
1821
+ if (block.type === "tool_use") {
1822
+ this.toolCalls.push({
1823
+ id: block.id,
1824
+ name: block.name,
1825
+ input: block.input,
1826
+ used: false
1827
+ });
1828
+ }
1829
+ }
1830
+ }
1831
+ }
1832
+ if (message.type === "user") {
1833
+ const userMsg = message;
1834
+ if (userMsg.message && userMsg.message.content && Array.isArray(userMsg.message.content)) {
1835
+ for (const block of userMsg.message.content) {
1836
+ if (block.type === "tool_result" && block.tool_use_id) {
1837
+ const toolCall = this.toolCalls.find((tc) => tc.id === block.tool_use_id);
1838
+ if (toolCall && !toolCall.used) {
1839
+ toolCall.used = true;
1840
+ }
1841
+ }
1842
+ }
1843
+ }
1844
+ }
1845
+ }
1846
+ /**
1847
+ * Checks if a tool call is rejected
1848
+ */
1849
+ isAborted(toolCallId) {
1850
+ if (this.responses.get(toolCallId)?.approved === false) {
1851
+ return true;
1852
+ }
1853
+ const toolCall = this.toolCalls.find((tc) => tc.id === toolCallId);
1854
+ if (toolCall && (toolCall.name === "exit_plan_mode" || toolCall.name === "ExitPlanMode")) {
1855
+ return true;
1856
+ }
1857
+ return false;
1858
+ }
1859
+ /**
1860
+ * Resets all state for new sessions
1861
+ */
1862
+ reset() {
1863
+ this.toolCalls = [];
1864
+ this.responses.clear();
1865
+ this.allowedTools.clear();
1866
+ this.allowedBashLiterals.clear();
1867
+ this.allowedBashPrefixes.clear();
1868
+ for (const [, pending] of this.pendingRequests.entries()) {
1869
+ pending.reject(new Error("Session reset"));
1870
+ }
1871
+ this.pendingRequests.clear();
1872
+ this.session.client.updateAgentState((currentState) => {
1873
+ const pendingRequests = currentState.requests || {};
1874
+ const completedRequests = { ...currentState.completedRequests };
1875
+ for (const [id, request] of Object.entries(pendingRequests)) {
1876
+ completedRequests[id] = {
1877
+ ...request,
1878
+ completedAt: Date.now(),
1879
+ status: "canceled",
1880
+ reason: "Session switched to local mode"
1881
+ };
1882
+ }
1883
+ return {
1884
+ ...currentState,
1885
+ requests: {},
1886
+ // Clear all pending requests
1887
+ completedRequests
1888
+ };
1889
+ });
1890
+ }
1891
+ /**
1892
+ * Sets up the client handler for permission responses
1893
+ */
1894
+ setupClientHandler() {
1895
+ this.session.client.rpcHandlerManager.registerHandler("permission", async (message) => {
1896
+ api.logger.debug(`Permission response: ${JSON.stringify(message)}`);
1897
+ const id = message.id;
1898
+ const pending = this.pendingRequests.get(id);
1899
+ if (!pending) {
1900
+ api.logger.debug("Permission request not found or already resolved");
1901
+ return;
1902
+ }
1903
+ this.responses.set(id, { ...message, receivedAt: Date.now() });
1904
+ this.pendingRequests.delete(id);
1905
+ this.handlePermissionResponse(message, pending);
1906
+ this.session.client.updateAgentState((currentState) => {
1907
+ const request = currentState.requests?.[id];
1908
+ if (!request) return currentState;
1909
+ let r = { ...currentState.requests };
1910
+ delete r[id];
1911
+ return {
1912
+ ...currentState,
1913
+ requests: r,
1914
+ completedRequests: {
1915
+ ...currentState.completedRequests,
1916
+ [id]: {
1917
+ ...request,
1918
+ completedAt: Date.now(),
1919
+ status: message.approved ? "approved" : "denied",
1920
+ reason: message.reason,
1921
+ mode: message.mode,
1922
+ allowTools: message.allowTools
1923
+ }
1924
+ }
1925
+ };
1926
+ });
1927
+ });
1928
+ }
1929
+ /**
1930
+ * Gets the responses map (for compatibility with existing code)
1931
+ */
1932
+ getResponses() {
1933
+ return this.responses;
1934
+ }
1935
+ }
1936
+
1937
+ function formatClaudeMessageForInk(message, messageBuffer, onAssistantResult) {
1938
+ api.logger.debugLargeJson("[CLAUDE INK] Message from remote mode:", message);
1939
+ switch (message.type) {
1940
+ case "system": {
1941
+ const sysMsg = message;
1942
+ if (sysMsg.subtype === "init") {
1943
+ messageBuffer.addMessage("\u2500".repeat(40), "status");
1944
+ messageBuffer.addMessage(`\u{1F680} Session initialized: ${sysMsg.session_id}`, "system");
1945
+ messageBuffer.addMessage(` Model: ${sysMsg.model}`, "status");
1946
+ messageBuffer.addMessage(` CWD: ${sysMsg.cwd}`, "status");
1947
+ if (sysMsg.tools && sysMsg.tools.length > 0) {
1948
+ messageBuffer.addMessage(` Tools: ${sysMsg.tools.join(", ")}`, "status");
1949
+ }
1950
+ messageBuffer.addMessage("\u2500".repeat(40), "status");
1951
+ }
1952
+ break;
1953
+ }
1954
+ case "user": {
1955
+ const userMsg = message;
1956
+ if (userMsg.message && typeof userMsg.message === "object" && "content" in userMsg.message) {
1957
+ const content = userMsg.message.content;
1958
+ if (typeof content === "string") {
1959
+ messageBuffer.addMessage(`\u{1F464} User: ${content}`, "user");
1960
+ } else if (Array.isArray(content)) {
1961
+ for (const block of content) {
1962
+ if (block.type === "text") {
1963
+ messageBuffer.addMessage(`\u{1F464} User: ${block.text}`, "user");
1964
+ } else if (block.type === "tool_result") {
1965
+ messageBuffer.addMessage(`\u2705 Tool Result (ID: ${block.tool_use_id})`, "result");
1966
+ if (block.content) {
1967
+ const outputStr = typeof block.content === "string" ? block.content : JSON.stringify(block.content, null, 2);
1968
+ const maxLength = 200;
1969
+ if (outputStr.length > maxLength) {
1970
+ messageBuffer.addMessage(outputStr.substring(0, maxLength) + "... (truncated)", "result");
1971
+ } else {
1972
+ messageBuffer.addMessage(outputStr, "result");
1973
+ }
1974
+ }
1975
+ }
1976
+ }
1977
+ } else {
1978
+ messageBuffer.addMessage(`\u{1F464} User: ${JSON.stringify(content, null, 2)}`, "user");
1979
+ }
1980
+ }
1981
+ break;
1982
+ }
1983
+ case "assistant": {
1984
+ const assistantMsg = message;
1985
+ if (assistantMsg.message && assistantMsg.message.content) {
1986
+ messageBuffer.addMessage("\u{1F916} Assistant:", "assistant");
1987
+ for (const block of assistantMsg.message.content) {
1988
+ if (block.type === "text") {
1989
+ messageBuffer.addMessage(block.text || "", "assistant");
1990
+ } else if (block.type === "tool_use") {
1991
+ messageBuffer.addMessage(`\u{1F527} Tool: ${block.name}`, "tool");
1992
+ if (block.input) {
1993
+ const inputStr = JSON.stringify(block.input, null, 2);
1994
+ const maxLength = 500;
1995
+ if (inputStr.length > maxLength) {
1996
+ messageBuffer.addMessage(`Input: ${inputStr.substring(0, maxLength)}... (truncated)`, "tool");
1997
+ } else {
1998
+ messageBuffer.addMessage(`Input: ${inputStr}`, "tool");
1999
+ }
2000
+ }
2001
+ }
2002
+ }
2003
+ }
2004
+ break;
2005
+ }
2006
+ case "result": {
2007
+ const resultMsg = message;
2008
+ if (resultMsg.subtype === "success") {
2009
+ if ("result" in resultMsg && resultMsg.result) {
2010
+ messageBuffer.addMessage("\u2728 Summary:", "result");
2011
+ messageBuffer.addMessage(resultMsg.result || "", "result");
2012
+ }
2013
+ if (resultMsg.usage) {
2014
+ messageBuffer.addMessage("\u{1F4CA} Session Stats:", "status");
2015
+ messageBuffer.addMessage(` \u2022 Turns: ${resultMsg.num_turns}`, "status");
2016
+ messageBuffer.addMessage(` \u2022 Input tokens: ${resultMsg.usage.input_tokens}`, "status");
2017
+ messageBuffer.addMessage(` \u2022 Output tokens: ${resultMsg.usage.output_tokens}`, "status");
2018
+ if (resultMsg.usage.cache_read_input_tokens) {
2019
+ messageBuffer.addMessage(` \u2022 Cache read tokens: ${resultMsg.usage.cache_read_input_tokens}`, "status");
2020
+ }
2021
+ if (resultMsg.usage.cache_creation_input_tokens) {
2022
+ messageBuffer.addMessage(` \u2022 Cache creation tokens: ${resultMsg.usage.cache_creation_input_tokens}`, "status");
2023
+ }
2024
+ messageBuffer.addMessage(` \u2022 Cost: $${resultMsg.total_cost_usd.toFixed(4)}`, "status");
2025
+ messageBuffer.addMessage(` \u2022 Duration: ${resultMsg.duration_ms}ms`, "status");
2026
+ }
2027
+ } else if (resultMsg.subtype === "error_max_turns") {
2028
+ messageBuffer.addMessage("\u274C Error: Maximum turns reached", "result");
2029
+ messageBuffer.addMessage(`Completed ${resultMsg.num_turns} turns`, "status");
2030
+ } else if (resultMsg.subtype === "error_during_execution") {
2031
+ messageBuffer.addMessage("\u274C Error during execution", "result");
2032
+ messageBuffer.addMessage(`Completed ${resultMsg.num_turns} turns before error`, "status");
2033
+ api.logger.debugLargeJson("[RESULT] Error during execution", resultMsg);
2034
+ }
2035
+ break;
2036
+ }
2037
+ default: {
2038
+ if (process.env.DEBUG) {
2039
+ messageBuffer.addMessage(`[Unknown message type: ${message.type}]`, "status");
2040
+ }
2041
+ }
2042
+ }
2043
+ }
2044
+
2045
+ function getGitBranch(cwd) {
2046
+ try {
2047
+ const branch = node_child_process.execSync("git rev-parse --abbrev-ref HEAD", {
2048
+ cwd,
2049
+ encoding: "utf8",
2050
+ stdio: ["ignore", "pipe", "ignore"]
2051
+ }).trim();
2052
+ return branch || void 0;
2053
+ } catch {
2054
+ return void 0;
2055
+ }
2056
+ }
2057
+ class SDKToLogConverter {
2058
+ lastUuid = null;
2059
+ context;
2060
+ responses;
2061
+ sidechainLastUUID = /* @__PURE__ */ new Map();
2062
+ constructor(context, responses) {
2063
+ this.context = {
2064
+ ...context,
2065
+ gitBranch: context.gitBranch ?? getGitBranch(context.cwd),
2066
+ version: context.version ?? process.env.npm_package_version ?? "0.0.0",
2067
+ parentUuid: null
2068
+ };
2069
+ this.responses = responses;
2070
+ }
2071
+ /**
2072
+ * Update session ID (for when session changes during resume)
2073
+ */
2074
+ updateSessionId(sessionId) {
2075
+ this.context.sessionId = sessionId;
2076
+ }
2077
+ /**
2078
+ * Reset parent chain (useful when starting new conversation)
2079
+ */
2080
+ resetParentChain() {
2081
+ this.lastUuid = null;
2082
+ this.context.parentUuid = null;
2083
+ }
2084
+ /**
2085
+ * Convert SDK message to log format
2086
+ */
2087
+ convert(sdkMessage) {
2088
+ const uuid = node_crypto.randomUUID();
2089
+ const timestamp = (/* @__PURE__ */ new Date()).toISOString();
2090
+ let parentUuid = this.lastUuid;
2091
+ let isSidechain = false;
2092
+ if (sdkMessage.parent_tool_use_id) {
2093
+ isSidechain = true;
2094
+ parentUuid = this.sidechainLastUUID.get(sdkMessage.parent_tool_use_id) ?? null;
2095
+ this.sidechainLastUUID.set(sdkMessage.parent_tool_use_id, uuid);
2096
+ }
2097
+ const baseFields = {
2098
+ parentUuid,
2099
+ isSidechain,
2100
+ userType: "external",
2101
+ cwd: this.context.cwd,
2102
+ sessionId: this.context.sessionId,
2103
+ version: this.context.version,
2104
+ gitBranch: this.context.gitBranch,
2105
+ uuid,
2106
+ timestamp
2107
+ };
2108
+ let logMessage = null;
2109
+ switch (sdkMessage.type) {
2110
+ case "user": {
2111
+ const userMsg = sdkMessage;
2112
+ logMessage = {
2113
+ ...baseFields,
2114
+ type: "user",
2115
+ message: userMsg.message
2116
+ };
2117
+ if (Array.isArray(userMsg.message.content)) {
2118
+ for (const content of userMsg.message.content) {
2119
+ if (content.type === "tool_result" && content.tool_use_id && this.responses?.has(content.tool_use_id)) {
2120
+ const response = this.responses.get(content.tool_use_id);
2121
+ if (response?.mode) {
2122
+ logMessage.mode = response.mode;
2123
+ }
2124
+ }
2125
+ }
2126
+ } else if (typeof userMsg.message.content === "string") ;
2127
+ break;
2128
+ }
2129
+ case "assistant": {
2130
+ const assistantMsg = sdkMessage;
2131
+ logMessage = {
2132
+ ...baseFields,
2133
+ type: "assistant",
2134
+ message: assistantMsg.message,
2135
+ // Assistant messages often have additional fields
2136
+ requestId: assistantMsg.requestId
2137
+ };
2138
+ break;
2139
+ }
2140
+ case "system": {
2141
+ const systemMsg = sdkMessage;
2142
+ if (systemMsg.subtype === "init" && systemMsg.session_id) {
2143
+ this.updateSessionId(systemMsg.session_id);
2144
+ }
2145
+ logMessage = {
2146
+ ...baseFields,
2147
+ type: "system",
2148
+ subtype: systemMsg.subtype,
2149
+ model: systemMsg.model,
2150
+ tools: systemMsg.tools,
2151
+ // Include all other fields
2152
+ ...systemMsg
2153
+ };
2154
+ break;
2155
+ }
2156
+ case "result": {
2157
+ break;
2158
+ }
2159
+ // Handle tool use results (often comes as user messages)
2160
+ case "tool_result": {
2161
+ const toolMsg = sdkMessage;
2162
+ const baseLogMessage = {
2163
+ ...baseFields,
2164
+ type: "user",
2165
+ message: {
2166
+ role: "user",
2167
+ content: [{
2168
+ type: "tool_result",
2169
+ tool_use_id: toolMsg.tool_use_id,
2170
+ content: toolMsg.content
2171
+ }]
2172
+ },
2173
+ toolUseResult: toolMsg.content
2174
+ };
2175
+ if (toolMsg.tool_use_id && this.responses?.has(toolMsg.tool_use_id)) {
2176
+ const response = this.responses.get(toolMsg.tool_use_id);
2177
+ if (response?.mode) {
2178
+ baseLogMessage.mode = response.mode;
2179
+ }
2180
+ }
2181
+ logMessage = baseLogMessage;
2182
+ break;
2183
+ }
2184
+ default:
2185
+ logMessage = {
2186
+ ...baseFields,
2187
+ ...sdkMessage,
2188
+ type: sdkMessage.type
2189
+ // Override type last to ensure it's set
2190
+ };
2191
+ }
2192
+ if (logMessage && logMessage.type !== "summary") {
2193
+ this.lastUuid = uuid;
2194
+ }
2195
+ return logMessage;
2196
+ }
2197
+ /**
2198
+ * Convert multiple SDK messages to log format
2199
+ */
2200
+ convertMany(sdkMessages) {
2201
+ return sdkMessages.map((msg) => this.convert(msg)).filter((msg) => msg !== null);
2202
+ }
2203
+ /**
2204
+ * Convert a simple string content to a sidechain user message
2205
+ * Used for Task tool sub-agent prompts
2206
+ */
2207
+ convertSidechainUserMessage(toolUseId, content) {
2208
+ const uuid = node_crypto.randomUUID();
2209
+ const timestamp = (/* @__PURE__ */ new Date()).toISOString();
2210
+ this.sidechainLastUUID.set(toolUseId, uuid);
2211
+ return {
2212
+ parentUuid: null,
2213
+ isSidechain: true,
2214
+ userType: "external",
2215
+ cwd: this.context.cwd,
2216
+ sessionId: this.context.sessionId,
2217
+ version: this.context.version,
2218
+ gitBranch: this.context.gitBranch,
2219
+ type: "user",
2220
+ message: {
2221
+ role: "user",
2222
+ content
2223
+ },
2224
+ uuid,
2225
+ timestamp
2226
+ };
2227
+ }
2228
+ /**
2229
+ * Generate an interrupted tool result message
2230
+ * Used when a tool call is interrupted by the user
2231
+ * @param toolUseId - The ID of the tool that was interrupted
2232
+ * @param parentToolUseId - Optional parent tool ID if this is a sidechain tool
2233
+ */
2234
+ generateInterruptedToolResult(toolUseId, parentToolUseId) {
2235
+ const uuid = node_crypto.randomUUID();
2236
+ const timestamp = (/* @__PURE__ */ new Date()).toISOString();
2237
+ const errorMessage = "[Request interrupted by user for tool use]";
2238
+ let isSidechain = false;
2239
+ let parentUuid = this.lastUuid;
2240
+ if (parentToolUseId) {
2241
+ isSidechain = true;
2242
+ parentUuid = this.sidechainLastUUID.get(parentToolUseId) ?? null;
2243
+ this.sidechainLastUUID.set(parentToolUseId, uuid);
2244
+ }
2245
+ const logMessage = {
2246
+ type: "user",
2247
+ isSidechain,
2248
+ uuid,
2249
+ message: {
2250
+ role: "user",
2251
+ content: [
2252
+ {
2253
+ type: "tool_result",
2254
+ content: errorMessage,
2255
+ is_error: true,
2256
+ tool_use_id: toolUseId
2257
+ }
2258
+ ]
2259
+ },
2260
+ parentUuid,
2261
+ userType: "external",
2262
+ cwd: this.context.cwd,
2263
+ sessionId: this.context.sessionId,
2264
+ version: this.context.version,
2265
+ gitBranch: this.context.gitBranch,
2266
+ timestamp,
2267
+ toolUseResult: `Error: ${errorMessage}`
2268
+ };
2269
+ this.lastUuid = uuid;
2270
+ return logMessage;
2271
+ }
2272
+ }
2273
+
2274
+ class OutgoingMessageQueue {
2275
+ constructor(sendFunction) {
2276
+ this.sendFunction = sendFunction;
2277
+ }
2278
+ queue = [];
2279
+ nextId = 1;
2280
+ lock = new api.AsyncLock();
2281
+ processTimer;
2282
+ delayTimers = /* @__PURE__ */ new Map();
2283
+ /**
2284
+ * Add message to queue
2285
+ */
2286
+ enqueue(logMessage, options) {
2287
+ this.lock.inLock(async () => {
2288
+ const item = {
2289
+ id: this.nextId++,
2290
+ logMessage,
2291
+ delayed: !!options?.delay,
2292
+ delayMs: options?.delay || 0,
2293
+ toolCallIds: options?.toolCallIds,
2294
+ released: !options?.delay,
2295
+ // Not delayed = already released
2296
+ sent: false
2297
+ };
2298
+ this.queue.push(item);
2299
+ if (item.delayed) {
2300
+ const timer = setTimeout(() => {
2301
+ this.releaseItem(item.id);
2302
+ }, item.delayMs);
2303
+ this.delayTimers.set(item.id, timer);
2304
+ }
2305
+ });
2306
+ this.scheduleProcessing();
2307
+ }
2308
+ /**
2309
+ * Release specific item by ID
2310
+ */
2311
+ async releaseItem(itemId) {
2312
+ await this.lock.inLock(async () => {
2313
+ const item = this.queue.find((i) => i.id === itemId);
2314
+ if (item && !item.released) {
2315
+ item.released = true;
2316
+ const timer = this.delayTimers.get(itemId);
2317
+ if (timer) {
2318
+ clearTimeout(timer);
2319
+ this.delayTimers.delete(itemId);
2320
+ }
2321
+ }
2322
+ });
2323
+ this.scheduleProcessing();
2324
+ }
2325
+ /**
2326
+ * Release all messages with specific tool call ID
2327
+ */
2328
+ async releaseToolCall(toolCallId) {
2329
+ await this.lock.inLock(async () => {
2330
+ for (const item of this.queue) {
2331
+ if (item.toolCallIds?.includes(toolCallId) && !item.released) {
2332
+ item.released = true;
2333
+ const timer = this.delayTimers.get(item.id);
2334
+ if (timer) {
2335
+ clearTimeout(timer);
2336
+ this.delayTimers.delete(item.id);
2337
+ }
2338
+ }
2339
+ }
2340
+ });
2341
+ this.scheduleProcessing();
2342
+ }
2343
+ /**
2344
+ * Process queue - send messages in ID order that are released
2345
+ * (Internal implementation without lock)
2346
+ */
2347
+ processQueueInternal() {
2348
+ this.queue.sort((a, b) => a.id - b.id);
2349
+ while (this.queue.length > 0) {
2350
+ const item = this.queue[0];
2351
+ if (!item.released) {
2352
+ break;
2353
+ }
2354
+ if (!item.sent) {
2355
+ if (item.logMessage.type !== "system") {
2356
+ this.sendFunction(item.logMessage);
2357
+ }
2358
+ item.sent = true;
2359
+ }
2360
+ this.queue.shift();
2361
+ }
2362
+ }
2363
+ /**
2364
+ * Process queue - send messages in ID order that are released
2365
+ */
2366
+ async processQueue() {
2367
+ await this.lock.inLock(async () => {
2368
+ this.processQueueInternal();
2369
+ });
2370
+ }
2371
+ /**
2372
+ * Flush all messages immediately (for cleanup)
2373
+ */
2374
+ async flush() {
2375
+ await this.lock.inLock(async () => {
2376
+ for (const timer of this.delayTimers.values()) {
2377
+ clearTimeout(timer);
2378
+ }
2379
+ this.delayTimers.clear();
2380
+ for (const item of this.queue) {
2381
+ item.released = true;
2382
+ }
2383
+ this.processQueueInternal();
2384
+ });
2385
+ }
2386
+ /**
2387
+ * Schedule processing on next tick
2388
+ */
2389
+ scheduleProcessing() {
2390
+ if (this.processTimer) {
2391
+ clearTimeout(this.processTimer);
2392
+ }
2393
+ this.processTimer = setTimeout(() => {
2394
+ this.processQueue();
2395
+ }, 0);
2396
+ }
2397
+ /**
2398
+ * Cleanup timers and resources
2399
+ */
2400
+ destroy() {
2401
+ if (this.processTimer) {
2402
+ clearTimeout(this.processTimer);
2403
+ }
2404
+ for (const timer of this.delayTimers.values()) {
2405
+ clearTimeout(timer);
2406
+ }
2407
+ this.delayTimers.clear();
2408
+ }
2409
+ }
2410
+
2411
+ async function claudeRemoteLauncher(session) {
2412
+ api.logger.debug("[claudeRemoteLauncher] Starting remote launcher");
2413
+ const hasTTY = process.stdout.isTTY && process.stdin.isTTY;
2414
+ api.logger.debug(`[claudeRemoteLauncher] TTY available: ${hasTTY}`);
2415
+ let messageBuffer = new registerKillSessionHandler.MessageBuffer();
2416
+ let inkInstance = null;
2417
+ if (hasTTY) {
2418
+ console.clear();
2419
+ inkInstance = ink.render(React.createElement(RemoteModeDisplay, {
2420
+ messageBuffer,
2421
+ logPath: process.env.DEBUG ? session.logPath : void 0,
2422
+ onExit: async () => {
2423
+ api.logger.debug("[remote]: Exiting client via Ctrl-C");
2424
+ if (!exitReason) {
2425
+ exitReason = "exit";
2426
+ }
2427
+ await abort();
2428
+ },
2429
+ onSwitchToLocal: () => {
2430
+ api.logger.debug("[remote]: Switching to local mode via double space");
2431
+ doSwitch();
2432
+ }
2433
+ }), {
2434
+ exitOnCtrlC: false,
2435
+ patchConsole: false
2436
+ });
2437
+ }
2438
+ if (hasTTY) {
2439
+ process.stdin.resume();
2440
+ if (process.stdin.isTTY) {
2441
+ process.stdin.setRawMode(true);
2442
+ }
2443
+ process.stdin.setEncoding("utf8");
2444
+ }
2445
+ let exitReason = null;
2446
+ let abortController = null;
2447
+ let abortFuture = null;
2448
+ async function abort() {
2449
+ if (abortController && !abortController.signal.aborted) {
2450
+ abortController.abort();
2451
+ }
2452
+ await abortFuture?.promise;
2453
+ }
2454
+ async function doAbort() {
2455
+ api.logger.debug("[remote]: doAbort");
2456
+ await abort();
2457
+ }
2458
+ async function doSwitch() {
2459
+ api.logger.debug("[remote]: doSwitch");
2460
+ if (!exitReason) {
2461
+ exitReason = "switch";
2462
+ }
2463
+ await abort();
2464
+ }
2465
+ session.client.rpcHandlerManager.registerHandler("abort", doAbort);
2466
+ session.client.rpcHandlerManager.registerHandler("switch", doSwitch);
2467
+ const permissionHandler = new PermissionHandler(session);
2468
+ const messageQueue = new OutgoingMessageQueue(
2469
+ (logMessage) => session.client.sendClaudeSessionMessage(logMessage)
2470
+ );
2471
+ permissionHandler.setOnPermissionRequest((toolCallId) => {
2472
+ messageQueue.releaseToolCall(toolCallId);
2473
+ });
2474
+ const sdkToLogConverter = new SDKToLogConverter({
2475
+ sessionId: session.sessionId || "unknown",
2476
+ cwd: session.path,
2477
+ version: process.env.npm_package_version
2478
+ }, permissionHandler.getResponses());
2479
+ let planModeToolCalls = /* @__PURE__ */ new Set();
2480
+ let ongoingToolCalls = /* @__PURE__ */ new Map();
2481
+ function onMessage(message) {
2482
+ formatClaudeMessageForInk(message, messageBuffer);
2483
+ permissionHandler.onMessage(message);
2484
+ if (message.type === "assistant") {
2485
+ let umessage = message;
2486
+ if (umessage.message.content && Array.isArray(umessage.message.content)) {
2487
+ for (let c of umessage.message.content) {
2488
+ if (c.type === "tool_use" && (c.name === "exit_plan_mode" || c.name === "ExitPlanMode")) {
2489
+ api.logger.debug("[remote]: detected plan mode tool call " + c.id);
2490
+ planModeToolCalls.add(c.id);
2491
+ }
2492
+ }
2493
+ }
2494
+ }
2495
+ if (message.type === "assistant") {
2496
+ let umessage = message;
2497
+ if (umessage.message.content && Array.isArray(umessage.message.content)) {
2498
+ for (let c of umessage.message.content) {
2499
+ if (c.type === "tool_use") {
2500
+ api.logger.debug("[remote]: detected tool use " + c.id + " parent: " + umessage.parent_tool_use_id);
2501
+ ongoingToolCalls.set(c.id, { parentToolCallId: umessage.parent_tool_use_id ?? null });
2502
+ }
2503
+ }
2504
+ }
2505
+ }
2506
+ if (message.type === "user") {
2507
+ let umessage = message;
2508
+ if (umessage.message.content && Array.isArray(umessage.message.content)) {
2509
+ for (let c of umessage.message.content) {
2510
+ if (c.type === "tool_result" && c.tool_use_id) {
2511
+ ongoingToolCalls.delete(c.tool_use_id);
2512
+ messageQueue.releaseToolCall(c.tool_use_id);
2513
+ }
2514
+ }
2515
+ }
2516
+ }
2517
+ let msg = message;
2518
+ if (message.type === "user") {
2519
+ let umessage = message;
2520
+ if (umessage.message.content && Array.isArray(umessage.message.content)) {
2521
+ msg = {
2522
+ ...umessage,
2523
+ message: {
2524
+ ...umessage.message,
2525
+ content: umessage.message.content.map((c) => {
2526
+ if (c.type === "tool_result" && c.tool_use_id && planModeToolCalls.has(c.tool_use_id)) {
2527
+ if (c.content === PLAN_FAKE_REJECT) {
2528
+ api.logger.debug("[remote]: hack plan mode exit");
2529
+ api.logger.debugLargeJson("[remote]: hack plan mode exit", c);
2530
+ return {
2531
+ ...c,
2532
+ is_error: false,
2533
+ content: "Plan approved",
2534
+ mode: c.mode
2535
+ };
2536
+ } else {
2537
+ return c;
2538
+ }
2539
+ }
2540
+ return c;
2541
+ })
2542
+ }
2543
+ };
2544
+ }
2545
+ }
2546
+ const logMessage = sdkToLogConverter.convert(msg);
2547
+ if (logMessage) {
2548
+ if (logMessage.type === "user" && logMessage.message?.content) {
2549
+ const content = Array.isArray(logMessage.message.content) ? logMessage.message.content : [];
2550
+ for (let i = 0; i < content.length; i++) {
2551
+ const c = content[i];
2552
+ if (c.type === "tool_result" && c.tool_use_id) {
2553
+ const responses = permissionHandler.getResponses();
2554
+ const response = responses.get(c.tool_use_id);
2555
+ if (response) {
2556
+ const permissions = {
2557
+ date: response.receivedAt || Date.now(),
2558
+ result: response.approved ? "approved" : "denied"
2559
+ };
2560
+ if (response.mode) {
2561
+ permissions.mode = response.mode;
2562
+ }
2563
+ if (response.allowTools && response.allowTools.length > 0) {
2564
+ permissions.allowedTools = response.allowTools;
2565
+ }
2566
+ content[i] = {
2567
+ ...c,
2568
+ permissions
2569
+ };
2570
+ }
2571
+ }
2572
+ }
2573
+ }
2574
+ if (logMessage.type === "assistant" && message.type === "assistant") {
2575
+ const assistantMsg = message;
2576
+ const toolCallIds = [];
2577
+ if (assistantMsg.message.content && Array.isArray(assistantMsg.message.content)) {
2578
+ for (const block of assistantMsg.message.content) {
2579
+ if (block.type === "tool_use" && block.id) {
2580
+ toolCallIds.push(block.id);
2581
+ }
2582
+ }
2583
+ }
2584
+ if (toolCallIds.length > 0) {
2585
+ const isSidechain = assistantMsg.parent_tool_use_id !== void 0;
2586
+ if (!isSidechain) {
2587
+ messageQueue.enqueue(logMessage, {
2588
+ delay: 250,
2589
+ toolCallIds
2590
+ });
2591
+ return;
2592
+ }
2593
+ }
2594
+ }
2595
+ messageQueue.enqueue(logMessage);
2596
+ }
2597
+ if (message.type === "assistant") {
2598
+ let umessage = message;
2599
+ if (umessage.message.content && Array.isArray(umessage.message.content)) {
2600
+ for (let c of umessage.message.content) {
2601
+ if (c.type === "tool_use" && c.name === "Task" && c.input && typeof c.input.prompt === "string") {
2602
+ const logMessage2 = sdkToLogConverter.convertSidechainUserMessage(c.id, c.input.prompt);
2603
+ if (logMessage2) {
2604
+ messageQueue.enqueue(logMessage2);
2605
+ }
2606
+ }
2607
+ }
2608
+ }
2609
+ }
2610
+ }
2611
+ try {
2612
+ let pending = null;
2613
+ let previousSessionId = null;
2614
+ while (!exitReason) {
2615
+ api.logger.debug("[remote]: launch");
2616
+ messageBuffer.addMessage("\u2550".repeat(40), "status");
2617
+ const isNewSession = session.sessionId !== previousSessionId;
2618
+ if (isNewSession) {
2619
+ messageBuffer.addMessage("Starting new Claude session...", "status");
2620
+ permissionHandler.reset();
2621
+ sdkToLogConverter.resetParentChain();
2622
+ api.logger.debug(`[remote]: New session detected (previous: ${previousSessionId}, current: ${session.sessionId})`);
2623
+ } else {
2624
+ messageBuffer.addMessage("Continuing Claude session...", "status");
2625
+ api.logger.debug(`[remote]: Continuing existing session: ${session.sessionId}`);
2626
+ }
2627
+ previousSessionId = session.sessionId;
2628
+ const controller = new AbortController();
2629
+ abortController = controller;
2630
+ abortFuture = new future.Future();
2631
+ let modeHash = null;
2632
+ let mode = null;
2633
+ try {
2634
+ const remoteResult = await claudeRemote({
2635
+ sessionId: session.sessionId,
2636
+ path: session.path,
2637
+ allowedTools: session.allowedTools ?? [],
2638
+ mcpServers: session.mcpServers,
2639
+ hookSettingsPath: session.hookSettingsPath,
2640
+ jsRuntime: session.jsRuntime,
2641
+ canCallTool: permissionHandler.handleToolCall,
2642
+ isAborted: (toolCallId) => {
2643
+ return permissionHandler.isAborted(toolCallId);
2644
+ },
2645
+ nextMessage: async () => {
2646
+ if (pending) {
2647
+ let p = pending;
2648
+ pending = null;
2649
+ permissionHandler.handleModeChange(p.mode.permissionMode);
2650
+ return p;
2651
+ }
2652
+ let msg = await session.queue.waitForMessagesAndGetAsString(controller.signal);
2653
+ if (msg) {
2654
+ if (modeHash && msg.hash !== modeHash || msg.isolate) {
2655
+ api.logger.debug("[remote]: mode has changed, pending message");
2656
+ pending = msg;
2657
+ return null;
2658
+ }
2659
+ modeHash = msg.hash;
2660
+ mode = msg.mode;
2661
+ permissionHandler.handleModeChange(mode.permissionMode);
2662
+ return {
2663
+ message: msg.message,
2664
+ mode: msg.mode
2665
+ };
2666
+ }
2667
+ return null;
2668
+ },
2669
+ onSessionFound: (sessionId) => {
2670
+ sdkToLogConverter.updateSessionId(sessionId);
2671
+ session.onSessionFound(sessionId);
2672
+ },
2673
+ onThinkingChange: session.onThinkingChange,
2674
+ claudeEnvVars: session.claudeEnvVars,
2675
+ claudeArgs: session.claudeArgs,
2676
+ onMessage,
2677
+ onCompletionEvent: (message) => {
2678
+ api.logger.debug(`[remote]: Completion event: ${message}`);
2679
+ session.client.sendSessionEvent({ type: "message", message });
2680
+ },
2681
+ onSessionReset: () => {
2682
+ api.logger.debug("[remote]: Session reset");
2683
+ session.clearSessionId();
2684
+ },
2685
+ onReady: () => {
2686
+ if (!pending && session.queue.size() === 0) {
2687
+ session.client.sendSessionEvent({ type: "ready" });
2688
+ session.api.push().sendToAllDevices(
2689
+ "It's ready!",
2690
+ `Claude is waiting for your command`,
2691
+ { sessionId: session.client.sessionId }
2692
+ );
2693
+ }
2694
+ },
2695
+ signal: abortController.signal
2696
+ });
2697
+ session.consumeOneTimeFlags();
2698
+ if (!exitReason && abortController.signal.aborted) {
2699
+ session.client.sendSessionEvent({ type: "message", message: "Aborted by user" });
2700
+ }
2701
+ } catch (e) {
2702
+ api.logger.debug("[remote]: launch error", e);
2703
+ if (!exitReason) {
2704
+ session.client.sendSessionEvent({ type: "message", message: "Process exited unexpectedly" });
2705
+ continue;
2706
+ }
2707
+ } finally {
2708
+ api.logger.debug("[remote]: launch finally");
2709
+ for (let [toolCallId, { parentToolCallId }] of ongoingToolCalls) {
2710
+ const converted = sdkToLogConverter.generateInterruptedToolResult(toolCallId, parentToolCallId);
2711
+ if (converted) {
2712
+ api.logger.debug("[remote]: terminating tool call " + toolCallId + " parent: " + parentToolCallId);
2713
+ session.client.sendClaudeSessionMessage(converted);
2714
+ }
2715
+ }
2716
+ ongoingToolCalls.clear();
2717
+ api.logger.debug("[remote]: flushing message queue");
2718
+ await messageQueue.flush();
2719
+ messageQueue.destroy();
2720
+ api.logger.debug("[remote]: message queue flushed");
2721
+ abortController = null;
2722
+ abortFuture?.resolve(void 0);
2723
+ abortFuture = null;
2724
+ api.logger.debug("[remote]: launch done");
2725
+ permissionHandler.reset();
2726
+ modeHash = null;
2727
+ mode = null;
2728
+ }
2729
+ }
2730
+ } finally {
2731
+ permissionHandler.reset();
2732
+ process.stdin.off("data", abort);
2733
+ if (process.stdin.isTTY) {
2734
+ process.stdin.setRawMode(false);
2735
+ }
2736
+ if (inkInstance) {
2737
+ inkInstance.unmount();
2738
+ }
2739
+ messageBuffer.clear();
2740
+ if (abortFuture) {
2741
+ abortFuture.resolve(void 0);
2742
+ }
2743
+ }
2744
+ return exitReason || "exit";
2745
+ }
2746
+
2747
+ async function loop(opts) {
2748
+ const logPath = api.logger.logFilePath;
2749
+ let session = new Session({
2750
+ api: opts.api,
2751
+ client: opts.session,
2752
+ path: opts.path,
2753
+ sessionId: null,
2754
+ claudeEnvVars: opts.claudeEnvVars,
2755
+ claudeArgs: opts.claudeArgs,
2756
+ mcpServers: opts.mcpServers,
2757
+ logPath,
2758
+ messageQueue: opts.messageQueue,
2759
+ allowedTools: opts.allowedTools,
2760
+ onModeChange: opts.onModeChange,
2761
+ hookSettingsPath: opts.hookSettingsPath,
2762
+ jsRuntime: opts.jsRuntime
2763
+ });
2764
+ opts.onSessionReady?.(session);
2765
+ let mode = opts.startingMode ?? "local";
2766
+ while (true) {
2767
+ api.logger.debug(`[loop] Iteration with mode: ${mode}`);
2768
+ switch (mode) {
2769
+ case "local": {
2770
+ const result = await claudeLocalLauncher(session);
2771
+ switch (result.type) {
2772
+ case "switch":
2773
+ mode = "remote";
2774
+ opts.onModeChange?.(mode);
2775
+ break;
2776
+ case "exit":
2777
+ return result.code;
2778
+ }
2779
+ break;
2780
+ }
2781
+ case "remote": {
2782
+ const reason = await claudeRemoteLauncher(session);
2783
+ switch (reason) {
2784
+ case "exit":
2785
+ return 0;
2786
+ case "switch":
2787
+ mode = "local";
2788
+ opts.onModeChange?.(mode);
2789
+ break;
2790
+ }
2791
+ break;
2792
+ }
2793
+ }
2794
+ }
2795
+ }
2796
+
2797
+ async function extractSDKMetadata() {
2798
+ const abortController = new AbortController();
2799
+ try {
2800
+ api.logger.debug("[metadataExtractor] Starting SDK metadata extraction");
2801
+ const sdkQuery = query({
2802
+ prompt: "hello",
2803
+ options: {
2804
+ allowedTools: ["Bash(echo)"],
2805
+ maxTurns: 1,
2806
+ abort: abortController.signal
2807
+ }
2808
+ });
2809
+ for await (const message of sdkQuery) {
2810
+ if (message.type === "system" && message.subtype === "init") {
2811
+ const systemMessage = message;
2812
+ const metadata = {
2813
+ tools: systemMessage.tools,
2814
+ slashCommands: systemMessage.slash_commands
2815
+ };
2816
+ api.logger.debug("[metadataExtractor] Captured SDK metadata:", metadata);
2817
+ abortController.abort();
2818
+ return metadata;
2819
+ }
2820
+ }
2821
+ api.logger.debug("[metadataExtractor] No init message received from SDK");
2822
+ return {};
2823
+ } catch (error) {
2824
+ if (error instanceof Error && error.name === "AbortError") {
2825
+ api.logger.debug("[metadataExtractor] SDK query aborted after capturing metadata");
2826
+ return {};
2827
+ }
2828
+ api.logger.debug("[metadataExtractor] Error extracting SDK metadata:", error);
2829
+ return {};
2830
+ }
2831
+ }
2832
+ function extractSDKMetadataAsync(onComplete) {
2833
+ extractSDKMetadata().then((metadata) => {
2834
+ if (metadata.tools || metadata.slashCommands) {
2835
+ onComplete(metadata);
2836
+ }
2837
+ }).catch((error) => {
2838
+ api.logger.debug("[metadataExtractor] Async extraction failed:", error);
2839
+ });
2840
+ }
2841
+
2842
+ async function startHookServer(options) {
2843
+ const { onSessionHook } = options;
2844
+ return new Promise((resolve, reject) => {
2845
+ const server = node_http.createServer(async (req, res) => {
2846
+ if (req.method === "POST" && req.url === "/hook/session-start") {
2847
+ const timeout = setTimeout(() => {
2848
+ if (!res.headersSent) {
2849
+ api.logger.debug("[hookServer] Request timeout");
2850
+ res.writeHead(408).end("timeout");
2851
+ }
2852
+ }, 5e3);
2853
+ try {
2854
+ const chunks = [];
2855
+ for await (const chunk of req) {
2856
+ chunks.push(chunk);
2857
+ }
2858
+ clearTimeout(timeout);
2859
+ const body = Buffer.concat(chunks).toString("utf-8");
2860
+ api.logger.debug("[hookServer] Received session hook:", body);
2861
+ let data = {};
2862
+ try {
2863
+ data = JSON.parse(body);
2864
+ } catch (parseError) {
2865
+ api.logger.debug("[hookServer] Failed to parse hook data as JSON:", parseError);
2866
+ }
2867
+ const sessionId = data.session_id || data.sessionId;
2868
+ if (sessionId) {
2869
+ api.logger.debug(`[hookServer] Session hook received session ID: ${sessionId}`);
2870
+ onSessionHook(sessionId, data);
2871
+ } else {
2872
+ api.logger.debug("[hookServer] Session hook received but no session_id found in data");
2873
+ }
2874
+ res.writeHead(200, { "Content-Type": "text/plain" }).end("ok");
2875
+ } catch (error) {
2876
+ clearTimeout(timeout);
2877
+ api.logger.debug("[hookServer] Error handling session hook:", error);
2878
+ if (!res.headersSent) {
2879
+ res.writeHead(500).end("error");
2880
+ }
2881
+ }
2882
+ return;
2883
+ }
2884
+ res.writeHead(404).end("not found");
2885
+ });
2886
+ server.listen(0, "127.0.0.1", () => {
2887
+ const address = server.address();
2888
+ if (!address || typeof address === "string") {
2889
+ reject(new Error("Failed to get server address"));
2890
+ return;
2891
+ }
2892
+ const port = address.port;
2893
+ api.logger.debug(`[hookServer] Started on port ${port}`);
2894
+ resolve({
2895
+ port,
2896
+ stop: () => {
2897
+ server.close();
2898
+ api.logger.debug("[hookServer] Stopped");
2899
+ }
2900
+ });
2901
+ });
2902
+ server.on("error", (err) => {
2903
+ api.logger.debug("[hookServer] Server error:", err);
2904
+ reject(err);
2905
+ });
2906
+ });
2907
+ }
2908
+
2909
+ function generateHookSettingsFile(port) {
2910
+ const hooksDir = path.join(api.configuration.happyCloudHomeDir, "tmp", "hooks");
2911
+ fs$1.mkdirSync(hooksDir, { recursive: true });
2912
+ const filename = `session-hook-${process.pid}.json`;
2913
+ const filepath = path.join(hooksDir, filename);
2914
+ const forwarderScript = path.resolve(index.projectPath(), "scripts", "session_hook_forwarder.cjs");
2915
+ const hookCommand = `node "${forwarderScript}" ${port}`;
2916
+ const settings = {
2917
+ hooks: {
2918
+ SessionStart: [
2919
+ {
2920
+ matcher: "*",
2921
+ hooks: [
2922
+ {
2923
+ type: "command",
2924
+ command: hookCommand
2925
+ }
2926
+ ]
2927
+ }
2928
+ ]
2929
+ }
2930
+ };
2931
+ fs$1.writeFileSync(filepath, JSON.stringify(settings, null, 2));
2932
+ api.logger.debug(`[generateHookSettings] Created hook settings file: ${filepath}`);
2933
+ return filepath;
2934
+ }
2935
+ function cleanupHookSettingsFile(filepath) {
2936
+ try {
2937
+ if (fs$1.existsSync(filepath)) {
2938
+ fs$1.unlinkSync(filepath);
2939
+ api.logger.debug(`[generateHookSettings] Cleaned up hook settings file: ${filepath}`);
2940
+ }
2941
+ } catch (error) {
2942
+ api.logger.debug(`[generateHookSettings] Failed to cleanup hook settings file: ${error}`);
2943
+ }
2944
+ }
2945
+
2946
+ async function runClaude(credentials, options = {}) {
2947
+ api.logger.debug(`[CLAUDE] ===== CLAUDE MODE STARTING =====`);
2948
+ api.logger.debug(`[CLAUDE] This is the Claude agent, NOT Gemini`);
2949
+ const workingDirectory = process.cwd();
2950
+ const sessionTag = node_crypto.randomUUID();
2951
+ api.logger.debugLargeJson("[START] Happy process started", index.getEnvironmentInfo());
2952
+ api.logger.debug(`[START] Options: startedBy=${options.startedBy}, startingMode=${options.startingMode}`);
2953
+ if (options.startedBy === "daemon" && options.startingMode === "local") {
2954
+ throw new Error("Daemon-spawned sessions cannot use local/interactive mode. Use --happy-starting-mode remote or spawn sessions directly from terminal.");
2955
+ }
2956
+ api.connectionState.setBackend("Claude");
2957
+ const api$1 = await api.ApiClient.create(credentials);
2958
+ let state = {};
2959
+ const settings = await persistence.readSettings();
2960
+ let machineId = settings?.machineId;
2961
+ if (!machineId) {
2962
+ console.error(`[START] No machine ID found in settings, which is unexpected since authAndSetupMachineIfNeeded should have created it. Please report this issue on https://github.com/slopus/happy-cli/issues`);
2963
+ process.exit(1);
2964
+ }
2965
+ api.logger.debug(`Using machineId: ${machineId}`);
2966
+ await api$1.getOrCreateMachine({
2967
+ machineId,
2968
+ metadata: index.initialMachineMetadata
2969
+ });
2970
+ let metadata = {
2971
+ path: workingDirectory,
2972
+ host: os.hostname(),
2973
+ version: api.packageJson.version,
2974
+ os: os.platform(),
2975
+ machineId,
2976
+ homeDir: os.homedir(),
2977
+ happyHomeDir: api.configuration.happyCloudHomeDir,
2978
+ happyLibDir: index.projectPath(),
2979
+ happyToolsDir: path.resolve(index.projectPath(), "tools", "unpacked"),
2980
+ startedFromDaemon: options.startedBy === "daemon",
2981
+ hostPid: process.pid,
2982
+ startedBy: options.startedBy || "terminal",
2983
+ // Initialize lifecycle state
2984
+ lifecycleState: "running",
2985
+ lifecycleStateSince: Date.now(),
2986
+ flavor: "claude"
2987
+ };
2988
+ const response = await api$1.getOrCreateSession({ tag: sessionTag, metadata, state });
2989
+ if (!response) {
2990
+ let offlineSessionId = null;
2991
+ const reconnection = api.startOfflineReconnection({
2992
+ serverUrl: api.configuration.serverUrl,
2993
+ onReconnected: async () => {
2994
+ const resp = await api$1.getOrCreateSession({ tag: node_crypto.randomUUID(), metadata, state });
2995
+ if (!resp) throw new Error("Server unavailable");
2996
+ const session2 = api$1.sessionSyncClient(resp);
2997
+ const scanner = await createSessionScanner({
2998
+ sessionId: null,
2999
+ workingDirectory,
3000
+ onMessage: (msg) => session2.sendClaudeSessionMessage(msg)
3001
+ });
3002
+ if (offlineSessionId) scanner.onNewSession(offlineSessionId);
3003
+ return { session: session2, scanner };
3004
+ },
3005
+ onNotify: console.log,
3006
+ onCleanup: () => {
3007
+ }
3008
+ });
3009
+ try {
3010
+ await index.claudeLocal({
3011
+ path: workingDirectory,
3012
+ sessionId: null,
3013
+ onSessionFound: (id) => {
3014
+ offlineSessionId = id;
3015
+ },
3016
+ onThinkingChange: () => {
3017
+ },
3018
+ abort: new AbortController().signal,
3019
+ claudeEnvVars: options.claudeEnvVars,
3020
+ claudeArgs: options.claudeArgs,
3021
+ allowedTools: []
3022
+ });
3023
+ } finally {
3024
+ reconnection.cancel();
3025
+ index.stopCaffeinate();
3026
+ }
3027
+ process.exit(0);
3028
+ }
3029
+ api.logger.debug(`Session created: ${response.id}`);
3030
+ try {
3031
+ api.logger.debug(`[START] Reporting session ${response.id} to daemon`);
3032
+ const result = await index.notifyDaemonSessionStarted(response.id, metadata);
3033
+ if (result.error) {
3034
+ api.logger.debug(`[START] Failed to report to daemon (may not be running):`, result.error);
3035
+ } else {
3036
+ api.logger.debug(`[START] Reported session ${response.id} to daemon`);
3037
+ }
3038
+ } catch (error) {
3039
+ api.logger.debug("[START] Failed to report to daemon (may not be running):", error);
3040
+ }
3041
+ extractSDKMetadataAsync(async (sdkMetadata) => {
3042
+ api.logger.debug("[start] SDK metadata extracted, updating session:", sdkMetadata);
3043
+ try {
3044
+ api$1.sessionSyncClient(response).updateMetadata((currentMetadata) => ({
3045
+ ...currentMetadata,
3046
+ tools: sdkMetadata.tools,
3047
+ slashCommands: sdkMetadata.slashCommands
3048
+ }));
3049
+ api.logger.debug("[start] Session metadata updated with SDK capabilities");
3050
+ } catch (error) {
3051
+ api.logger.debug("[start] Failed to update session metadata:", error);
3052
+ }
3053
+ });
3054
+ const session = api$1.sessionSyncClient(response);
3055
+ let currentSession = null;
3056
+ const hookServer = await startHookServer({
3057
+ onSessionHook: (sessionId, data) => {
3058
+ api.logger.debug(`[START] Session hook received: ${sessionId}`, data);
3059
+ if (currentSession) {
3060
+ const previousSessionId = currentSession.sessionId;
3061
+ if (previousSessionId !== sessionId) {
3062
+ api.logger.debug(`[START] Claude session ID changed: ${previousSessionId} -> ${sessionId}`);
3063
+ currentSession.onSessionFound(sessionId);
3064
+ }
3065
+ }
3066
+ }
3067
+ });
3068
+ api.logger.debug(`[START] Hook server started on port ${hookServer.port}`);
3069
+ const hookSettingsPath = generateHookSettingsFile(hookServer.port);
3070
+ api.logger.debug(`[START] Generated hook settings file: ${hookSettingsPath}`);
3071
+ const logPath = api.logger.logFilePath;
3072
+ api.logger.infoDeveloper(`Session: ${response.id}`);
3073
+ api.logger.infoDeveloper(`Logs: ${logPath}`);
3074
+ session.updateAgentState((currentState) => ({
3075
+ ...currentState,
3076
+ controlledByUser: options.startingMode !== "remote"
3077
+ }));
3078
+ const caffeinateStarted = index.startCaffeinate();
3079
+ if (caffeinateStarted) {
3080
+ api.logger.infoDeveloper("Sleep prevention enabled (macOS)");
3081
+ }
3082
+ const messageQueue = new registerKillSessionHandler.MessageQueue2((mode) => registerKillSessionHandler.hashObject({
3083
+ isPlan: mode.permissionMode === "plan",
3084
+ model: mode.model,
3085
+ fallbackModel: mode.fallbackModel,
3086
+ customSystemPrompt: mode.customSystemPrompt,
3087
+ appendSystemPrompt: mode.appendSystemPrompt,
3088
+ allowedTools: mode.allowedTools,
3089
+ disallowedTools: mode.disallowedTools
3090
+ }));
3091
+ let currentPermissionMode = options.permissionMode;
3092
+ let currentModel = options.model;
3093
+ let currentFallbackModel = void 0;
3094
+ let currentCustomSystemPrompt = void 0;
3095
+ let currentAppendSystemPrompt = void 0;
3096
+ let currentAllowedTools = void 0;
3097
+ let currentDisallowedTools = void 0;
3098
+ session.onUserMessage((message) => {
3099
+ let messagePermissionMode = currentPermissionMode;
3100
+ if (message.meta?.permissionMode) {
3101
+ messagePermissionMode = message.meta.permissionMode;
3102
+ currentPermissionMode = messagePermissionMode;
3103
+ api.logger.debug(`[loop] Permission mode updated from user message to: ${currentPermissionMode}`);
3104
+ } else {
3105
+ api.logger.debug(`[loop] User message received with no permission mode override, using current: ${currentPermissionMode}`);
3106
+ }
3107
+ let messageModel = currentModel;
3108
+ if (message.meta?.hasOwnProperty("model")) {
3109
+ messageModel = message.meta.model || void 0;
3110
+ currentModel = messageModel;
3111
+ api.logger.debug(`[loop] Model updated from user message: ${messageModel || "reset to default"}`);
3112
+ } else {
3113
+ api.logger.debug(`[loop] User message received with no model override, using current: ${currentModel || "default"}`);
3114
+ }
3115
+ let messageCustomSystemPrompt = currentCustomSystemPrompt;
3116
+ if (message.meta?.hasOwnProperty("customSystemPrompt")) {
3117
+ messageCustomSystemPrompt = message.meta.customSystemPrompt || void 0;
3118
+ currentCustomSystemPrompt = messageCustomSystemPrompt;
3119
+ api.logger.debug(`[loop] Custom system prompt updated from user message: ${messageCustomSystemPrompt ? "set" : "reset to none"}`);
3120
+ } else {
3121
+ api.logger.debug(`[loop] User message received with no custom system prompt override, using current: ${currentCustomSystemPrompt ? "set" : "none"}`);
3122
+ }
3123
+ let messageFallbackModel = currentFallbackModel;
3124
+ if (message.meta?.hasOwnProperty("fallbackModel")) {
3125
+ messageFallbackModel = message.meta.fallbackModel || void 0;
3126
+ currentFallbackModel = messageFallbackModel;
3127
+ api.logger.debug(`[loop] Fallback model updated from user message: ${messageFallbackModel || "reset to none"}`);
3128
+ } else {
3129
+ api.logger.debug(`[loop] User message received with no fallback model override, using current: ${currentFallbackModel || "none"}`);
3130
+ }
3131
+ let messageAppendSystemPrompt = currentAppendSystemPrompt;
3132
+ if (message.meta?.hasOwnProperty("appendSystemPrompt")) {
3133
+ messageAppendSystemPrompt = message.meta.appendSystemPrompt || void 0;
3134
+ currentAppendSystemPrompt = messageAppendSystemPrompt;
3135
+ api.logger.debug(`[loop] Append system prompt updated from user message: ${messageAppendSystemPrompt ? "set" : "reset to none"}`);
3136
+ } else {
3137
+ api.logger.debug(`[loop] User message received with no append system prompt override, using current: ${currentAppendSystemPrompt ? "set" : "none"}`);
3138
+ }
3139
+ let messageAllowedTools = currentAllowedTools;
3140
+ if (message.meta?.hasOwnProperty("allowedTools")) {
3141
+ messageAllowedTools = message.meta.allowedTools || void 0;
3142
+ currentAllowedTools = messageAllowedTools;
3143
+ api.logger.debug(`[loop] Allowed tools updated from user message: ${messageAllowedTools ? messageAllowedTools.join(", ") : "reset to none"}`);
3144
+ } else {
3145
+ api.logger.debug(`[loop] User message received with no allowed tools override, using current: ${currentAllowedTools ? currentAllowedTools.join(", ") : "none"}`);
3146
+ }
3147
+ let messageDisallowedTools = currentDisallowedTools;
3148
+ if (message.meta?.hasOwnProperty("disallowedTools")) {
3149
+ messageDisallowedTools = message.meta.disallowedTools || void 0;
3150
+ currentDisallowedTools = messageDisallowedTools;
3151
+ api.logger.debug(`[loop] Disallowed tools updated from user message: ${messageDisallowedTools ? messageDisallowedTools.join(", ") : "reset to none"}`);
3152
+ } else {
3153
+ api.logger.debug(`[loop] User message received with no disallowed tools override, using current: ${currentDisallowedTools ? currentDisallowedTools.join(", ") : "none"}`);
3154
+ }
3155
+ const specialCommand = parseSpecialCommand(message.content.text);
3156
+ if (specialCommand.type === "compact") {
3157
+ api.logger.debug("[start] Detected /compact command");
3158
+ const enhancedMode2 = {
3159
+ permissionMode: messagePermissionMode || "default",
3160
+ model: messageModel,
3161
+ fallbackModel: messageFallbackModel,
3162
+ customSystemPrompt: messageCustomSystemPrompt,
3163
+ appendSystemPrompt: messageAppendSystemPrompt,
3164
+ allowedTools: messageAllowedTools,
3165
+ disallowedTools: messageDisallowedTools
3166
+ };
3167
+ messageQueue.pushIsolateAndClear(specialCommand.originalMessage || message.content.text, enhancedMode2);
3168
+ api.logger.debugLargeJson("[start] /compact command pushed to queue:", message);
3169
+ return;
3170
+ }
3171
+ if (specialCommand.type === "clear") {
3172
+ api.logger.debug("[start] Detected /clear command");
3173
+ const enhancedMode2 = {
3174
+ permissionMode: messagePermissionMode || "default",
3175
+ model: messageModel,
3176
+ fallbackModel: messageFallbackModel,
3177
+ customSystemPrompt: messageCustomSystemPrompt,
3178
+ appendSystemPrompt: messageAppendSystemPrompt,
3179
+ allowedTools: messageAllowedTools,
3180
+ disallowedTools: messageDisallowedTools
3181
+ };
3182
+ messageQueue.pushIsolateAndClear(specialCommand.originalMessage || message.content.text, enhancedMode2);
3183
+ api.logger.debugLargeJson("[start] /compact command pushed to queue:", message);
3184
+ return;
3185
+ }
3186
+ const enhancedMode = {
3187
+ permissionMode: messagePermissionMode || "default",
3188
+ model: messageModel,
3189
+ fallbackModel: messageFallbackModel,
3190
+ customSystemPrompt: messageCustomSystemPrompt,
3191
+ appendSystemPrompt: messageAppendSystemPrompt,
3192
+ allowedTools: messageAllowedTools,
3193
+ disallowedTools: messageDisallowedTools
3194
+ };
3195
+ messageQueue.push(message.content.text, enhancedMode);
3196
+ api.logger.debugLargeJson("User message pushed to queue:", message);
3197
+ });
3198
+ const cleanup = async () => {
3199
+ api.logger.debug("[START] Received termination signal, cleaning up...");
3200
+ try {
3201
+ if (session) {
3202
+ session.updateMetadata((currentMetadata) => ({
3203
+ ...currentMetadata,
3204
+ lifecycleState: "archived",
3205
+ lifecycleStateSince: Date.now(),
3206
+ archivedBy: "cli",
3207
+ archiveReason: "User terminated"
3208
+ }));
3209
+ currentSession?.cleanup();
3210
+ session.sendSessionDeath();
3211
+ await session.flush();
3212
+ await session.close();
3213
+ }
3214
+ index.stopCaffeinate();
3215
+ hookServer.stop();
3216
+ cleanupHookSettingsFile(hookSettingsPath);
3217
+ api.logger.debug("[START] Cleanup complete, exiting");
3218
+ process.exit(0);
3219
+ } catch (error) {
3220
+ api.logger.debug("[START] Error during cleanup:", error);
3221
+ process.exit(1);
3222
+ }
3223
+ };
3224
+ process.on("SIGTERM", cleanup);
3225
+ process.on("SIGINT", cleanup);
3226
+ process.on("uncaughtException", (error) => {
3227
+ api.logger.debug("[START] Uncaught exception:", error);
3228
+ cleanup();
3229
+ });
3230
+ process.on("unhandledRejection", (reason) => {
3231
+ api.logger.debug("[START] Unhandled rejection:", reason);
3232
+ cleanup();
3233
+ });
3234
+ registerKillSessionHandler.registerKillSessionHandler(session.rpcHandlerManager, cleanup);
3235
+ const exitCode = await loop({
3236
+ path: workingDirectory,
3237
+ model: options.model,
3238
+ permissionMode: options.permissionMode,
3239
+ startingMode: options.startingMode,
3240
+ messageQueue,
3241
+ api: api$1,
3242
+ allowedTools: [],
3243
+ mcpServers: {},
3244
+ onModeChange: (newMode) => {
3245
+ session.sendSessionEvent({ type: "switch", mode: newMode });
3246
+ session.updateAgentState((currentState) => ({
3247
+ ...currentState,
3248
+ controlledByUser: newMode === "local"
3249
+ }));
3250
+ },
3251
+ onSessionReady: (sessionInstance) => {
3252
+ currentSession = sessionInstance;
3253
+ },
3254
+ session,
3255
+ claudeEnvVars: options.claudeEnvVars,
3256
+ claudeArgs: options.claudeArgs,
3257
+ hookSettingsPath,
3258
+ jsRuntime: options.jsRuntime
3259
+ });
3260
+ currentSession?.cleanup();
3261
+ session.sendSessionDeath();
3262
+ api.logger.debug("Waiting for socket to flush...");
3263
+ await session.flush();
3264
+ api.logger.debug("Closing session...");
3265
+ await session.close();
3266
+ index.stopCaffeinate();
3267
+ api.logger.debug("Stopped sleep prevention");
3268
+ hookServer.stop();
3269
+ cleanupHookSettingsFile(hookSettingsPath);
3270
+ api.logger.debug("Stopped Hook server and cleaned up settings file");
3271
+ process.exit(exitCode);
3272
+ }
3273
+
3274
+ exports.runClaude = runClaude;