happy-imou-cloud 2.0.13 → 2.0.16

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 (30) hide show
  1. package/dist/BaseReasoningProcessor-BqMAZlIw.cjs +323 -0
  2. package/dist/BaseReasoningProcessor-C04_LHjN.mjs +320 -0
  3. package/dist/ProviderSelectionHandler-CO9NkAt6.cjs +265 -0
  4. package/dist/ProviderSelectionHandler-DDWyn9Lo.mjs +261 -0
  5. package/dist/{api-DH5-IqeM.cjs → api-Db1SQcP_.cjs} +2 -2
  6. package/dist/{api-D1meoL-9.mjs → api-sRF6xXi-.mjs} +2 -2
  7. package/dist/{command-CMvWClny.mjs → command-WcgGTRnG.mjs} +4 -3
  8. package/dist/{command-Ch8Dgidj.cjs → command-eRjSBm2C.cjs} +4 -3
  9. package/dist/{index-CryJfCh5.cjs → index-B6ID1zDR.cjs} +1106 -50
  10. package/dist/{index-Cxrx9m5D.mjs → index-DpWeKfvS.mjs} +1102 -50
  11. package/dist/index.cjs +5 -4
  12. package/dist/index.mjs +5 -4
  13. package/dist/lib.cjs +1 -1
  14. package/dist/lib.mjs +1 -1
  15. package/dist/{persistence-9Iu0wGNM.mjs → persistence-B89V4xY5.mjs} +1 -1
  16. package/dist/{persistence-Bl3FYvwd.cjs → persistence-Btu2VPXI.cjs} +1 -1
  17. package/dist/{registerKillSessionHandler-BElGmD1E.mjs → registerKillSessionHandler-CwmYlUfS.mjs} +541 -5
  18. package/dist/{registerKillSessionHandler-BjkY-oUn.cjs → registerKillSessionHandler-eZ2TsHqx.cjs} +549 -4
  19. package/dist/{runClaude-CDZxAF3l.cjs → runClaude-C9-ylbQh.cjs} +599 -747
  20. package/dist/{runClaude-D7dF4RDM.mjs → runClaude-kRPXCaBj.mjs} +591 -738
  21. package/dist/{runCodex-DnGz1XES.mjs → runCodex-B1xN0wAU.mjs} +9 -215
  22. package/dist/{runCodex-Cik8VzFs.cjs → runCodex-CRNBxY5f.cjs} +20 -226
  23. package/dist/{runGemini-BM2BQ4I7.cjs → runGemini-BZ5hqJyl.cjs} +16 -15
  24. package/dist/{runGemini-B8tXMHeL.mjs → runGemini-Xn2VwS88.mjs} +8 -7
  25. package/package.json +1 -1
  26. package/scripts/release-smoke.mjs +6 -5
  27. package/dist/ConversationHistory-V3VLmjJf.cjs +0 -868
  28. package/dist/ConversationHistory-_ciJNIgH.mjs +0 -856
  29. package/dist/createKeepAliveController-C5cQlDRr.mjs +0 -51
  30. package/dist/createKeepAliveController-DO8H6d5E.cjs +0 -54
@@ -1,8 +1,8 @@
1
1
  import { randomUUID } from 'node:crypto';
2
- import { l as logger, d as backoff, f as delay, g as AsyncLock, c as configuration, s as startOfflineReconnection, b as connectionState, A as ApiClient, i as isAuthenticationRequiredError } from './api-D1meoL-9.mjs';
2
+ import { l as logger, d as backoff, f as delay, g as AsyncLock, c as configuration, s as startOfflineReconnection, b as connectionState, A as ApiClient, i as isAuthenticationRequiredError } from './api-sRF6xXi-.mjs';
3
3
  import 'cross-spawn';
4
4
  import '@agentclientprotocol/sdk';
5
- import { j as getProjectPath, k as claudeLocal, E as ExitCodeError, l as isBun, m as trimIdent, n as claudeCheckSession, p as projectPath, o as mapToClaudeMode, b as stopCaffeinate, e as publishSessionRegistration, q as getEnvironmentInfo, u as startCaffeinate } from './index-Cxrx9m5D.mjs';
5
+ import { j as getProjectPath, F as Future, k as claudeLocal, E as ExitCodeError, l as trimIdent, m as createClaudeBackend, f as formatDisplayMessage, t as truncateDisplayMessage, n as claudeCheckSession, e as projectPath, o as mapToClaudeMode, P as PushableAsyncIterable, q as query, A as AbortError, b as stopCaffeinate, p as publishSessionRegistration, u as getEnvironmentInfo, w as startCaffeinate } from './index-DpWeKfvS.mjs';
6
6
  import 'ps-list';
7
7
  import 'fs';
8
8
  import 'path';
@@ -11,11 +11,12 @@ import 'child_process';
11
11
  import { existsSync, readFileSync, mkdirSync, writeFileSync, unlinkSync } from 'node:fs';
12
12
  import { dirname, basename, join, resolve } from 'node:path';
13
13
  import { homedir } from 'node:os';
14
- import './persistence-9Iu0wGNM.mjs';
14
+ import { execSync } from 'node:child_process';
15
+ import 'node:readline';
16
+ import './persistence-B89V4xY5.mjs';
15
17
  import { readFile } from 'node:fs/promises';
16
18
  import { stat, watch, access } from 'fs/promises';
17
19
  import 'crypto';
18
- import { execSync, spawn } from 'node:child_process';
19
20
  import 'chalk';
20
21
  import 'node:events';
21
22
  import 'axios';
@@ -23,11 +24,9 @@ import 'tweetnacl';
23
24
  import 'open';
24
25
  import React, { useState, useRef, useEffect, useCallback } from 'react';
25
26
  import { useStdout, useInput, Box, Text, render } from 'ink';
26
- import { c as createKeepAliveController, r as runModeLoop } from './createKeepAliveController-C5cQlDRr.mjs';
27
+ import { c as createKeepAliveController, P as ProviderSelectionHandler, r as runModeLoop } from './ProviderSelectionHandler-DDWyn9Lo.mjs';
27
28
  import { R as RawJSONLinesSchema } from './types-CiliQpqS.mjs';
28
- import { b as MessageBuffer, s as syncControlledByUserState, e as ensureManagedProviderMachine, M as MissingMachineIdError, d as createSessionMetadata, a as MessageQueue2, h as hashObject, r as registerKillSessionHandler, c as closeProviderSession } from './registerKillSessionHandler-BElGmD1E.mjs';
29
- import { createInterface } from 'node:readline';
30
- import { fileURLToPath } from 'node:url';
29
+ import { B as BasePermissionHandler, d as MessageBuffer, C as ConversationHistory$1, w as waitForResponseCompleteWithAbort, l as launchRuntimeHandleWithFactoryResult, j as forwardAgentMessageToProviderSession, s as syncControlledByUserState, e as ensureManagedProviderMachine, M as MissingMachineIdError, c as createSessionMetadata, b as MessageQueue2, h as hashObject, r as registerKillSessionHandler, f as closeProviderSession } from './registerKillSessionHandler-CwmYlUfS.mjs';
31
30
  import 'socket.io-client';
32
31
  import 'expo-server-sdk';
33
32
  import { isDeepStrictEqual } from 'node:util';
@@ -41,6 +40,7 @@ import 'fastify';
41
40
  import 'fastify-type-provider-zod';
42
41
  import 'http';
43
42
  import 'util';
43
+ import 'node:url';
44
44
 
45
45
  class Session {
46
46
  path;
@@ -201,27 +201,6 @@ class Session {
201
201
  };
202
202
  }
203
203
 
204
- class Future {
205
- _resolve;
206
- _reject;
207
- _promise;
208
- constructor() {
209
- this._promise = new Promise((resolve, reject) => {
210
- this._resolve = resolve;
211
- this._reject = reject;
212
- });
213
- }
214
- resolve(value) {
215
- this._resolve(value);
216
- }
217
- reject(reason) {
218
- this._reject(reason);
219
- }
220
- get promise() {
221
- return this._promise;
222
- }
223
- }
224
-
225
204
  class InvalidateSync {
226
205
  _invalidated = false;
227
206
  _invalidatedDouble = false;
@@ -715,6 +694,50 @@ async function claudeLocalLauncher(session) {
715
694
  return exitReason || { type: "exit", code: 0 };
716
695
  }
717
696
 
697
+ function parseCompact(message) {
698
+ const trimmed = message.trim();
699
+ if (trimmed === "/compact") {
700
+ return {
701
+ isCompact: true,
702
+ originalMessage: trimmed
703
+ };
704
+ }
705
+ if (trimmed.startsWith("/compact ")) {
706
+ return {
707
+ isCompact: true,
708
+ originalMessage: trimmed
709
+ };
710
+ }
711
+ return {
712
+ isCompact: false,
713
+ originalMessage: message
714
+ };
715
+ }
716
+ function parseClear(message) {
717
+ const trimmed = message.trim();
718
+ return {
719
+ isClear: trimmed === "/clear"
720
+ };
721
+ }
722
+ function parseSpecialCommand(message) {
723
+ const compactResult = parseCompact(message);
724
+ if (compactResult.isCompact) {
725
+ return {
726
+ type: "compact",
727
+ originalMessage: compactResult.originalMessage
728
+ };
729
+ }
730
+ const clearResult = parseClear(message);
731
+ if (clearResult.isClear) {
732
+ return {
733
+ type: "clear"
734
+ };
735
+ }
736
+ return {
737
+ type: null
738
+ };
739
+ }
740
+
718
741
  const RemoteModeDisplay = ({ messageBuffer, logPath, onExit, onSwitchToLocal }) => {
719
742
  const [messages, setMessages] = useState([]);
720
743
  const [confirmationMode, setConfirmationMode] = useState(null);
@@ -840,699 +863,593 @@ const RemoteModeDisplay = ({ messageBuffer, logPath, onExit, onSwitchToLocal })
840
863
  ));
841
864
  };
842
865
 
843
- class Stream {
844
- constructor(returned) {
845
- this.returned = returned;
846
- }
847
- queue = [];
848
- readResolve;
849
- readReject;
850
- isDone = false;
851
- hasError;
852
- started = false;
853
- /**
854
- * Implements async iterable protocol
855
- */
856
- [Symbol.asyncIterator]() {
857
- if (this.started) {
858
- throw new Error("Stream can only be iterated once");
859
- }
860
- this.started = true;
861
- return this;
862
- }
863
- /**
864
- * Gets the next value from the stream
865
- */
866
- async next() {
867
- if (this.queue.length > 0) {
868
- return Promise.resolve({
869
- done: false,
870
- value: this.queue.shift()
871
- });
872
- }
873
- if (this.isDone) {
874
- return Promise.resolve({ done: true, value: void 0 });
875
- }
876
- if (this.hasError) {
877
- return Promise.reject(this.hasError);
878
- }
879
- return new Promise((resolve, reject) => {
880
- this.readResolve = resolve;
881
- this.readReject = reject;
882
- });
883
- }
884
- /**
885
- * Adds a value to the stream
886
- */
887
- enqueue(value) {
888
- if (this.readResolve) {
889
- const resolve = this.readResolve;
890
- this.readResolve = void 0;
891
- this.readReject = void 0;
892
- resolve({ done: false, value });
893
- } else {
894
- this.queue.push(value);
895
- }
896
- }
897
- /**
898
- * Marks the stream as complete
899
- */
900
- done() {
901
- this.isDone = true;
902
- if (this.readResolve) {
903
- const resolve = this.readResolve;
904
- this.readResolve = void 0;
905
- this.readReject = void 0;
906
- resolve({ done: true, value: void 0 });
907
- }
908
- }
909
- /**
910
- * Propagates an error through the stream
911
- */
912
- error(error) {
913
- this.hasError = error;
914
- if (this.readReject) {
915
- const reject = this.readReject;
916
- this.readResolve = void 0;
917
- this.readReject = void 0;
918
- reject(error);
919
- }
920
- }
921
- /**
922
- * Implements async iterator cleanup
923
- */
924
- async return() {
925
- this.isDone = true;
926
- if (this.returned) {
927
- this.returned();
928
- }
929
- return Promise.resolve({ done: true, value: void 0 });
930
- }
931
- }
932
-
933
- class AbortError extends Error {
934
- constructor(message) {
935
- super(message);
936
- this.name = "AbortError";
937
- }
866
+ function getClaudeSettingsPath() {
867
+ const claudeConfigDir = process.env.CLAUDE_CONFIG_DIR || join(homedir(), ".claude");
868
+ return join(claudeConfigDir, "settings.json");
938
869
  }
939
-
940
- const __filename$1 = fileURLToPath(import.meta.url);
941
- const __dirname$1 = join(__filename$1, "..");
942
- function getGlobalClaudeVersion() {
870
+ function readClaudeSettings() {
943
871
  try {
944
- const cleanEnv = getCleanEnv();
945
- const output = execSync("claude --version", {
946
- encoding: "utf8",
947
- stdio: ["pipe", "pipe", "pipe"],
948
- cwd: homedir(),
949
- env: cleanEnv
950
- }).trim();
951
- const match = output.match(/(\d+\.\d+\.\d+)/);
952
- logger.debug(`[Claude SDK] Global claude --version output: ${output}`);
953
- return match ? match[1] : null;
954
- } catch {
872
+ const settingsPath = getClaudeSettingsPath();
873
+ if (!existsSync(settingsPath)) {
874
+ logger.debug(`[ClaudeSettings] No Claude settings file found at ${settingsPath}`);
875
+ return null;
876
+ }
877
+ const settingsContent = readFileSync(settingsPath, "utf-8");
878
+ const settings = JSON.parse(settingsContent);
879
+ logger.debug(`[ClaudeSettings] Successfully read Claude settings from ${settingsPath}`);
880
+ logger.debug(`[ClaudeSettings] includeCoAuthoredBy: ${settings.includeCoAuthoredBy}`);
881
+ return settings;
882
+ } catch (error) {
883
+ logger.debug(`[ClaudeSettings] Error reading Claude settings: ${error}`);
955
884
  return null;
956
885
  }
957
886
  }
958
- function getCleanEnv() {
959
- const env = { ...process.env };
960
- const cwd = process.cwd();
961
- const pathSep = process.platform === "win32" ? ";" : ":";
962
- const pathKey = process.platform === "win32" ? "Path" : "PATH";
963
- const actualPathKey = Object.keys(env).find((k) => k.toLowerCase() === "path") || pathKey;
964
- if (env[actualPathKey]) {
965
- const cleanPath = env[actualPathKey].split(pathSep).filter((p) => {
966
- const normalizedP = p.replace(/\\/g, "/").toLowerCase();
967
- const normalizedCwd = cwd.replace(/\\/g, "/").toLowerCase();
968
- return !normalizedP.startsWith(normalizedCwd);
969
- }).join(pathSep);
970
- env[actualPathKey] = cleanPath;
971
- logger.debug(`[Claude SDK] Cleaned PATH, removed local paths from: ${cwd}`);
972
- }
973
- if (isBun()) {
974
- Object.keys(env).forEach((key) => {
975
- if (key.startsWith("BUN_")) {
976
- delete env[key];
977
- }
978
- });
979
- logger.debug("[Claude SDK] Removed Bun-specific environment variables for Node.js compatibility");
887
+ function shouldIncludeCoAuthoredBy() {
888
+ const settings = readClaudeSettings();
889
+ if (!settings || settings.includeCoAuthoredBy === void 0) {
890
+ return true;
980
891
  }
981
- return env;
892
+ return settings.includeCoAuthoredBy;
982
893
  }
983
- function findGlobalClaudePath() {
984
- const homeDir = homedir();
985
- const cleanEnv = getCleanEnv();
986
- try {
987
- execSync("claude --version", {
988
- encoding: "utf8",
989
- stdio: ["pipe", "pipe", "pipe"],
990
- cwd: homeDir,
991
- env: cleanEnv
992
- });
993
- logger.debug("[Claude SDK] Global claude command available (checked with clean PATH)");
994
- return "claude";
995
- } catch {
996
- }
997
- if (process.platform !== "win32") {
998
- try {
999
- const result = execSync("which claude", {
1000
- encoding: "utf8",
1001
- stdio: ["pipe", "pipe", "pipe"],
1002
- cwd: homeDir,
1003
- env: cleanEnv
1004
- }).trim();
1005
- if (result && existsSync(result)) {
1006
- logger.debug(`[Claude SDK] Found global claude path via which: ${result}`);
1007
- return result;
1008
- }
1009
- } catch {
1010
- }
894
+
895
+ const BASE_SYSTEM_PROMPT = (() => trimIdent(`
896
+ Help human.
897
+ `))();
898
+ const CO_AUTHORED_CREDITS = (() => trimIdent(`
899
+ When making commit messages, instead of just giving co-credit to Claude, also give credit to Happy like so:
900
+
901
+ <main commit message>
902
+
903
+ Generated with [Claude Code](https://claude.ai/code)
904
+ via [Happy](https://happy.engineering)
905
+
906
+ Co-Authored-By: Claude <noreply@anthropic.com>
907
+ Co-Authored-By: Happy <yesreply@happy.engineering>
908
+ `))();
909
+ const systemPrompt = (() => {
910
+ const includeCoAuthored = shouldIncludeCoAuthoredBy();
911
+ if (includeCoAuthored) {
912
+ return BASE_SYSTEM_PROMPT + "\n\n" + CO_AUTHORED_CREDITS;
913
+ } else {
914
+ return BASE_SYSTEM_PROMPT;
1011
915
  }
1012
- return null;
1013
- }
1014
- function getDefaultClaudeCodePath() {
1015
- const nodeModulesPath = join(__dirname$1, "..", "..", "..", "node_modules", "@anthropic-ai", "claude-code", "cli.js");
1016
- if (process.env.HAPPY_CLAUDE_PATH) {
1017
- logger.debug(`[Claude SDK] Using HAPPY_CLAUDE_PATH: ${process.env.HAPPY_CLAUDE_PATH}`);
1018
- return process.env.HAPPY_CLAUDE_PATH;
1019
- }
1020
- if (process.env.HAPPY_USE_BUNDLED_CLAUDE === "1") {
1021
- logger.debug(`[Claude SDK] Forced bundled version: ${nodeModulesPath}`);
1022
- return nodeModulesPath;
1023
- }
1024
- const globalPath = findGlobalClaudePath();
1025
- if (!globalPath) {
1026
- logger.debug(`[Claude SDK] No global claude found, using bundled: ${nodeModulesPath}`);
1027
- return nodeModulesPath;
1028
- }
1029
- const globalVersion = getGlobalClaudeVersion();
1030
- logger.debug(`[Claude SDK] Global version: ${globalVersion || "unknown"}`);
1031
- if (!globalVersion) {
1032
- logger.debug(`[Claude SDK] Cannot compare versions, using global: ${globalPath}`);
1033
- return globalPath;
1034
- }
1035
- return globalPath;
1036
- }
1037
- function logDebug(message) {
1038
- if (process.env.DEBUG) {
1039
- logger.debug(message);
1040
- console.log(message);
916
+ })();
917
+
918
+ function getToolDescriptor(toolName) {
919
+ if (toolName === "exit_plan_mode" || toolName === "ExitPlanMode") {
920
+ return { edit: false, exitPlan: true };
1041
921
  }
1042
- }
1043
- async function streamToStdin(stream, stdin, abort) {
1044
- for await (const message of stream) {
1045
- if (abort?.aborted) break;
1046
- stdin.write(JSON.stringify(message) + "\n");
922
+ if (toolName === "Edit" || toolName === "MultiEdit" || toolName === "Write" || toolName === "NotebookEdit") {
923
+ return { edit: true, exitPlan: false };
1047
924
  }
1048
- stdin.end();
925
+ return { edit: false, exitPlan: false };
1049
926
  }
1050
927
 
1051
- class Query {
1052
- constructor(childStdin, childStdout, processExitPromise, canCallTool) {
1053
- this.childStdin = childStdin;
1054
- this.childStdout = childStdout;
1055
- this.processExitPromise = processExitPromise;
1056
- this.canCallTool = canCallTool;
1057
- this.readMessages();
1058
- this.sdkMessages = this.readSdkMessages();
1059
- }
1060
- pendingControlResponses = /* @__PURE__ */ new Map();
1061
- cancelControllers = /* @__PURE__ */ new Map();
1062
- sdkMessages;
1063
- inputStream = new Stream();
1064
- canCallTool;
1065
- /**
1066
- * Set an error on the stream
1067
- */
1068
- setError(error) {
1069
- this.inputStream.error(error);
928
+ class ClaudeAcpPermissionHandler extends BasePermissionHandler {
929
+ currentPermissionMode = "default";
930
+ constructor(session) {
931
+ super(session);
1070
932
  }
1071
- /**
1072
- * AsyncIterableIterator implementation
1073
- */
1074
- next(...args) {
1075
- return this.sdkMessages.next(...args);
933
+ getLogPrefix() {
934
+ return "[ClaudeACP]";
1076
935
  }
1077
- return(value) {
1078
- if (this.sdkMessages.return) {
1079
- return this.sdkMessages.return(value);
1080
- }
1081
- return Promise.resolve({ done: true, value: void 0 });
936
+ setPermissionMode(mode) {
937
+ this.currentPermissionMode = mode;
938
+ logger.debug(`${this.getLogPrefix()} Permission mode set to: ${mode}`);
1082
939
  }
1083
- throw(e) {
1084
- if (this.sdkMessages.throw) {
1085
- return this.sdkMessages.throw(e);
940
+ shouldAutoApprove(toolName) {
941
+ const descriptor = getToolDescriptor(toolName);
942
+ switch (this.currentPermissionMode) {
943
+ case "bypassPermissions":
944
+ case "yolo":
945
+ return true;
946
+ case "acceptEdits":
947
+ return descriptor.edit;
948
+ default:
949
+ return false;
1086
950
  }
1087
- return Promise.reject(e);
1088
- }
1089
- [Symbol.asyncIterator]() {
1090
- return this.sdkMessages;
1091
951
  }
1092
- /**
1093
- * Read messages from Claude process stdout
1094
- */
1095
- async readMessages() {
1096
- const rl = createInterface({ input: this.childStdout });
1097
- try {
1098
- for await (const line of rl) {
1099
- if (line.trim()) {
1100
- try {
1101
- const message = JSON.parse(line);
1102
- if (message.type === "control_response") {
1103
- const controlResponse = message;
1104
- const handler = this.pendingControlResponses.get(controlResponse.response.request_id);
1105
- if (handler) {
1106
- handler(controlResponse.response);
1107
- }
1108
- continue;
1109
- } else if (message.type === "control_request") {
1110
- await this.handleControlRequest(message);
1111
- continue;
1112
- } else if (message.type === "control_cancel_request") {
1113
- this.handleControlCancelRequest(message);
1114
- continue;
1115
- }
1116
- this.inputStream.enqueue(message);
1117
- } catch (e) {
1118
- logger.debug(line);
952
+ async handleToolCall(toolCallId, toolName, input) {
953
+ if (this.shouldAutoApprove(toolName)) {
954
+ const decision = this.currentPermissionMode === "bypassPermissions" || this.currentPermissionMode === "yolo" ? "approved_for_session" : "approved";
955
+ this.session.updateAgentState((currentState) => ({
956
+ ...currentState,
957
+ completedRequests: {
958
+ ...currentState.completedRequests,
959
+ [toolCallId]: {
960
+ tool: toolName,
961
+ arguments: input,
962
+ createdAt: Date.now(),
963
+ completedAt: Date.now(),
964
+ status: "approved",
965
+ decision
1119
966
  }
1120
967
  }
1121
- }
1122
- await this.processExitPromise;
1123
- } catch (error) {
1124
- this.inputStream.error(error);
1125
- } finally {
1126
- this.inputStream.done();
1127
- this.cleanupControllers();
1128
- rl.close();
968
+ }));
969
+ logger.debug(`${this.getLogPrefix()} Auto-approved tool ${toolName} (${toolCallId}) in ${this.currentPermissionMode} mode`);
970
+ return { decision };
1129
971
  }
972
+ return this.registerPendingRequest(
973
+ toolCallId,
974
+ toolName,
975
+ input,
976
+ ` in ${this.currentPermissionMode} mode`
977
+ );
1130
978
  }
1131
- /**
1132
- * Async generator for SDK messages
1133
- */
1134
- async *readSdkMessages() {
1135
- for await (const message of this.inputStream) {
1136
- yield message;
979
+ }
980
+
981
+ function normalizeClaudeBackendError(error) {
982
+ const record = typeof error === "object" && error !== null ? error : null;
983
+ const text = formatDisplayMessage(error).trim();
984
+ const stderrText = record ? formatDisplayMessage(record.stderr).trim() : "";
985
+ const detailText = record ? formatDisplayMessage(record.detail).trim() : "";
986
+ const searchable = [text, stderrText, detailText].filter(Boolean).join("\n").trim();
987
+ return searchable || "Claude ACP backend exited unexpectedly";
988
+ }
989
+ const CLAUDE_ACP_LEGACY_FINAL_MESSAGE_SHADOW = "claude-acp-final-message-shadow";
990
+ async function claudeAcpRemoteLauncher(session) {
991
+ const hasTTY = process.stdout.isTTY && process.stdin.isTTY;
992
+ const messageBuffer = new MessageBuffer({ enabled: hasTTY });
993
+ let inkInstance = null;
994
+ let shouldExit = false;
995
+ let abortController = new AbortController();
996
+ let runtimeHandle = null;
997
+ let unsubscribeRuntimeMessages = null;
998
+ let currentModeHash = null;
999
+ let pending = null;
1000
+ let accumulatedResponse = "";
1001
+ let isResponseInProgress = false;
1002
+ let taskStartedSent = false;
1003
+ let currentAssistantMessageId = null;
1004
+ let currentThinkingMessageId = null;
1005
+ let shouldInjectHistoryOnNextSession = false;
1006
+ let readyAlreadySent = false;
1007
+ const permissionHandler = new ClaudeAcpPermissionHandler(session.client);
1008
+ const selectionHandler = new ProviderSelectionHandler(session.client, "Claude");
1009
+ const conversationHistory = new ConversationHistory$1({
1010
+ maxMessages: 20,
1011
+ maxCharacters: 5e4
1012
+ });
1013
+ const rotateAbortController = () => {
1014
+ const activeController = abortController;
1015
+ abortController = new AbortController();
1016
+ return activeController;
1017
+ };
1018
+ const sendReady = () => {
1019
+ session.client.sendSessionEvent({ type: "ready" });
1020
+ try {
1021
+ session.api.push().sendToAllDevices(
1022
+ "It's ready!",
1023
+ "Claude is waiting for your command",
1024
+ { sessionId: session.client.sessionId }
1025
+ );
1026
+ } catch (pushError) {
1027
+ logger.debug("[ClaudeACP] Failed to send ready push", pushError);
1137
1028
  }
1138
- }
1139
- /**
1140
- * Send interrupt request to Claude
1141
- */
1142
- async interrupt() {
1143
- if (!this.childStdin) {
1144
- throw new Error("Interrupt requires --input-format stream-json");
1029
+ };
1030
+ const emitStatusMessage = (message) => {
1031
+ messageBuffer.addMessage(message, "status");
1032
+ session.client.sendSessionEvent({ type: "message", message });
1033
+ };
1034
+ const emitUserVisibleErrorMessage = (message) => {
1035
+ emitStatusMessage(message);
1036
+ session.client.sendAgentMessage("claude", {
1037
+ type: "message",
1038
+ message
1039
+ });
1040
+ };
1041
+ const resetTurnState = () => {
1042
+ accumulatedResponse = "";
1043
+ isResponseInProgress = false;
1044
+ taskStartedSent = false;
1045
+ currentAssistantMessageId = null;
1046
+ currentThinkingMessageId = null;
1047
+ session.onThinkingChange(false);
1048
+ };
1049
+ const emitFinalAssistantMessage = () => {
1050
+ const finalMessage = accumulatedResponse.trim();
1051
+ if (!finalMessage) {
1052
+ accumulatedResponse = "";
1053
+ isResponseInProgress = false;
1054
+ return;
1145
1055
  }
1146
- await this.request({
1147
- subtype: "interrupt"
1148
- }, this.childStdin);
1149
- }
1150
- /**
1151
- * Send control request to Claude process
1152
- */
1153
- request(request, childStdin) {
1154
- const requestId = Math.random().toString(36).substring(2, 15);
1155
- const sdkRequest = {
1156
- request_id: requestId,
1157
- type: "control_request",
1158
- request
1159
- };
1160
- return new Promise((resolve, reject) => {
1161
- this.pendingControlResponses.set(requestId, (response) => {
1162
- if (response.subtype === "success") {
1163
- resolve(response);
1164
- } else {
1165
- reject(new Error(response.error));
1166
- }
1167
- });
1168
- childStdin.write(JSON.stringify(sdkRequest) + "\n");
1056
+ conversationHistory.addAssistantMessage(finalMessage);
1057
+ session.client.sendAgentMessage("claude", {
1058
+ type: "message",
1059
+ message: finalMessage
1169
1060
  });
1170
- }
1171
- /**
1172
- * Handle incoming control requests for tool permissions
1173
- * Replicates the exact logic from the SDK's handleControlRequest method
1174
- */
1175
- async handleControlRequest(request) {
1176
- if (!this.childStdin) {
1177
- logDebug("Cannot handle control request - no stdin available");
1061
+ session.client.sendClaudeSessionMessage({
1062
+ type: "assistant",
1063
+ uuid: randomUUID(),
1064
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
1065
+ sessionId: session.sessionId ?? void 0,
1066
+ happyCompatibilityShadow: CLAUDE_ACP_LEGACY_FINAL_MESSAGE_SHADOW,
1067
+ message: {
1068
+ role: "assistant",
1069
+ content: [{
1070
+ type: "text",
1071
+ text: finalMessage,
1072
+ happyCompatibilityShadow: CLAUDE_ACP_LEGACY_FINAL_MESSAGE_SHADOW
1073
+ }]
1074
+ }
1075
+ });
1076
+ accumulatedResponse = "";
1077
+ isResponseInProgress = false;
1078
+ };
1079
+ const disposeRuntimeHandle = async () => {
1080
+ if (!runtimeHandle) {
1178
1081
  return;
1179
1082
  }
1180
- const controller = new AbortController();
1181
- this.cancelControllers.set(request.request_id, controller);
1083
+ const activeHandle = runtimeHandle;
1084
+ runtimeHandle = null;
1085
+ unsubscribeRuntimeMessages?.();
1086
+ unsubscribeRuntimeMessages = null;
1182
1087
  try {
1183
- const response = await this.processControlRequest(request, controller.signal);
1184
- const controlResponse = {
1185
- type: "control_response",
1186
- response: {
1187
- subtype: "success",
1188
- request_id: request.request_id,
1189
- response
1190
- }
1191
- };
1192
- this.childStdin.write(JSON.stringify(controlResponse) + "\n");
1088
+ await activeHandle.dispose();
1193
1089
  } catch (error) {
1194
- const controlErrorResponse = {
1195
- type: "control_response",
1196
- response: {
1197
- subtype: "error",
1198
- request_id: request.request_id,
1199
- error: error instanceof Error ? error.message : String(error)
1200
- }
1201
- };
1202
- this.childStdin.write(JSON.stringify(controlErrorResponse) + "\n");
1203
- } finally {
1204
- this.cancelControllers.delete(request.request_id);
1090
+ logger.debug("[ClaudeACP] Error disposing runtime handle:", error);
1205
1091
  }
1206
- }
1207
- /**
1208
- * Handle control cancel requests
1209
- * Replicates the exact logic from the SDK's handleControlCancelRequest method
1210
- */
1211
- handleControlCancelRequest(request) {
1212
- const controller = this.cancelControllers.get(request.request_id);
1213
- if (controller) {
1214
- controller.abort();
1215
- this.cancelControllers.delete(request.request_id);
1092
+ };
1093
+ const queueHistoryInjectionForRestart = (reason) => {
1094
+ messageBuffer.addMessage("\u2550".repeat(40), "status");
1095
+ if (conversationHistory.hasHistory()) {
1096
+ shouldInjectHistoryOnNextSession = true;
1097
+ const message = `${reason} Preserving ${conversationHistory.size()} earlier messages of context.`;
1098
+ emitStatusMessage(message);
1099
+ logger.debug(`[ClaudeACP] Will inject conversation history after restart: ${conversationHistory.getSummary()}`);
1100
+ return;
1216
1101
  }
1217
- }
1218
- /**
1219
- * Process control requests based on subtype
1220
- * Replicates the exact logic from the SDK's processControlRequest method
1221
- */
1222
- async processControlRequest(request, signal) {
1223
- if (request.request.subtype === "can_use_tool") {
1224
- if (!this.canCallTool) {
1225
- throw new Error("canCallTool callback is not provided.");
1226
- }
1227
- return this.canCallTool(request.request.tool_name, request.request.input, {
1228
- signal
1102
+ emitStatusMessage(reason);
1103
+ };
1104
+ const setupRuntimeMessageHandler = (activeRuntimeHandle) => {
1105
+ const forwardAgentMessage = (agentMessage) => {
1106
+ forwardAgentMessageToProviderSession(agentMessage, {
1107
+ provider: "claude",
1108
+ send: (body) => session.client.sendAgentMessage("claude", body)
1229
1109
  });
1230
- }
1231
- throw new Error("Unsupported control request subtype: " + request.request.subtype);
1232
- }
1233
- /**
1234
- * Cleanup method to abort all pending control requests
1235
- */
1236
- cleanupControllers() {
1237
- for (const [requestId, controller] of this.cancelControllers.entries()) {
1238
- controller.abort();
1239
- this.cancelControllers.delete(requestId);
1240
- }
1241
- }
1242
- }
1243
- function query(config) {
1244
- const {
1245
- prompt,
1246
- options: {
1247
- allowedTools = [],
1248
- appendSystemPrompt,
1249
- customSystemPrompt,
1250
- cwd,
1251
- disallowedTools = [],
1252
- executable = "node",
1253
- executableArgs = [],
1254
- maxTurns,
1255
- mcpServers,
1256
- pathToClaudeCodeExecutable = getDefaultClaudeCodePath(),
1257
- permissionMode = "default",
1258
- continue: continueConversation,
1259
- resume,
1260
- model,
1261
- fallbackModel,
1262
- strictMcpConfig,
1263
- canCallTool,
1264
- settingsPath
1265
- } = {}
1266
- } = config;
1267
- if (!process.env.CLAUDE_CODE_ENTRYPOINT) {
1268
- process.env.CLAUDE_CODE_ENTRYPOINT = "sdk-ts";
1269
- }
1270
- const args = ["--output-format", "stream-json", "--verbose"];
1271
- if (customSystemPrompt) args.push("--system-prompt", customSystemPrompt);
1272
- if (appendSystemPrompt) args.push("--append-system-prompt", appendSystemPrompt);
1273
- if (maxTurns) args.push("--max-turns", maxTurns.toString());
1274
- if (model) args.push("--model", model);
1275
- if (canCallTool) {
1276
- if (typeof prompt === "string") {
1277
- throw new Error("canCallTool callback requires --input-format stream-json. Please set prompt as an AsyncIterable.");
1278
- }
1279
- args.push("--permission-prompt-tool", "stdio");
1280
- }
1281
- if (continueConversation) args.push("--continue");
1282
- if (resume) args.push("--resume", resume);
1283
- if (allowedTools.length > 0) args.push("--allowedTools", allowedTools.join(","));
1284
- if (disallowedTools.length > 0) args.push("--disallowedTools", disallowedTools.join(","));
1285
- if (mcpServers && Object.keys(mcpServers).length > 0) {
1286
- args.push("--mcp-config", JSON.stringify({ mcpServers }));
1287
- }
1288
- if (strictMcpConfig) args.push("--strict-mcp-config");
1289
- if (permissionMode) args.push("--permission-mode", permissionMode);
1290
- if (settingsPath) args.push("--settings", settingsPath);
1291
- if (fallbackModel) {
1292
- if (model && fallbackModel === model) {
1293
- throw new Error("Fallback model cannot be the same as the main model. Please specify a different model for fallbackModel option.");
1294
- }
1295
- args.push("--fallback-model", fallbackModel);
1296
- }
1297
- if (typeof prompt === "string") {
1298
- args.push("--print", prompt.trim());
1299
- } else {
1300
- args.push("--input-format", "stream-json");
1301
- }
1302
- const isJsFile = pathToClaudeCodeExecutable.endsWith(".js") || pathToClaudeCodeExecutable.endsWith(".cjs");
1303
- const isCommandOnly = pathToClaudeCodeExecutable === "claude";
1304
- if (!isCommandOnly && !existsSync(pathToClaudeCodeExecutable)) {
1305
- throw new ReferenceError(`Claude Code executable not found at ${pathToClaudeCodeExecutable}. Is options.pathToClaudeCodeExecutable set?`);
1306
- }
1307
- const spawnCommand = isJsFile ? executable : pathToClaudeCodeExecutable;
1308
- const spawnArgs = isJsFile ? [...executableArgs, pathToClaudeCodeExecutable, ...args] : args;
1309
- const spawnEnv = isCommandOnly ? getCleanEnv() : process.env;
1310
- logDebug(`Spawning Claude Code process: ${spawnCommand} ${spawnArgs.join(" ")} (using ${isCommandOnly ? "clean" : "normal"} env)`);
1311
- const child = spawn(spawnCommand, spawnArgs, {
1312
- cwd,
1313
- stdio: ["pipe", "pipe", "pipe"],
1314
- signal: config.options?.abort,
1315
- env: spawnEnv,
1316
- // Use shell on Windows for global binaries and command-only mode
1317
- shell: !isJsFile && process.platform === "win32"
1318
- });
1319
- let childStdin = null;
1320
- if (typeof prompt === "string") {
1321
- child.stdin.end();
1322
- } else {
1323
- streamToStdin(prompt, child.stdin, config.options?.abort);
1324
- childStdin = child.stdin;
1325
- }
1326
- if (process.env.DEBUG) {
1327
- child.stderr.on("data", (data) => {
1328
- console.error("Claude Code stderr:", data.toString());
1110
+ };
1111
+ unsubscribeRuntimeMessages?.();
1112
+ unsubscribeRuntimeMessages = activeRuntimeHandle.onMessage((msg) => {
1113
+ switch (msg.type) {
1114
+ case "model-output": {
1115
+ const text = msg.textDelta ?? msg.fullText ?? "";
1116
+ if (!text) {
1117
+ return;
1118
+ }
1119
+ if (!isResponseInProgress) {
1120
+ if (currentThinkingMessageId) {
1121
+ messageBuffer.removeMessage(currentThinkingMessageId);
1122
+ currentThinkingMessageId = null;
1123
+ }
1124
+ currentAssistantMessageId = messageBuffer.addMessage(text, "assistant");
1125
+ isResponseInProgress = true;
1126
+ } else if (currentAssistantMessageId) {
1127
+ const updated = messageBuffer.updateMessage(currentAssistantMessageId, text);
1128
+ if (!updated) {
1129
+ currentAssistantMessageId = messageBuffer.addMessage(text, "assistant");
1130
+ }
1131
+ }
1132
+ accumulatedResponse += text;
1133
+ return;
1134
+ }
1135
+ case "status": {
1136
+ if (msg.status === "running") {
1137
+ session.onThinkingChange(true);
1138
+ if (!taskStartedSent) {
1139
+ session.client.sendAgentMessage("claude", {
1140
+ type: "task_started",
1141
+ id: randomUUID()
1142
+ });
1143
+ taskStartedSent = true;
1144
+ }
1145
+ if (!isResponseInProgress && !currentThinkingMessageId) {
1146
+ currentThinkingMessageId = messageBuffer.addMessage("Thinking...", "system");
1147
+ }
1148
+ return;
1149
+ }
1150
+ if (msg.status === "idle" || msg.status === "stopped") {
1151
+ session.onThinkingChange(false);
1152
+ return;
1153
+ }
1154
+ if (msg.status === "error") {
1155
+ messageBuffer.addMessage(`Error: ${normalizeClaudeBackendError(msg.detail)}`, "status");
1156
+ }
1157
+ return;
1158
+ }
1159
+ case "tool-call": {
1160
+ const toolArgs = truncateDisplayMessage(msg.args, 100);
1161
+ messageBuffer.addMessage(
1162
+ `Executing: ${msg.toolName}${toolArgs ? ` ${toolArgs}` : ""}`,
1163
+ "tool"
1164
+ );
1165
+ forwardAgentMessage(msg);
1166
+ return;
1167
+ }
1168
+ case "tool-result": {
1169
+ const resultText = truncateDisplayMessage(msg.result, 200);
1170
+ messageBuffer.addMessage(resultText ? `Result: ${resultText}` : "Tool completed", "result");
1171
+ forwardAgentMessage(msg);
1172
+ return;
1173
+ }
1174
+ case "fs-edit": {
1175
+ messageBuffer.addMessage(`File edit: ${msg.description}`, "tool");
1176
+ forwardAgentMessage(msg);
1177
+ return;
1178
+ }
1179
+ case "terminal-output": {
1180
+ const output = formatDisplayMessage(msg.data);
1181
+ messageBuffer.addMessage(output, "result");
1182
+ forwardAgentMessage({
1183
+ ...msg,
1184
+ data: output
1185
+ });
1186
+ return;
1187
+ }
1188
+ case "permission-request": {
1189
+ forwardAgentMessage(msg);
1190
+ return;
1191
+ }
1192
+ case "token-count": {
1193
+ forwardAgentMessage(msg);
1194
+ return;
1195
+ }
1196
+ case "exec-approval-request":
1197
+ case "patch-apply-begin":
1198
+ case "patch-apply-end": {
1199
+ forwardAgentMessage(msg);
1200
+ return;
1201
+ }
1202
+ case "event": {
1203
+ if (msg.name === "thinking") {
1204
+ const payload = msg.payload;
1205
+ const thinkingText = typeof payload?.text === "string" ? payload.text : "";
1206
+ if (thinkingText) {
1207
+ session.client.sendAgentMessage("claude", {
1208
+ type: "thinking",
1209
+ text: thinkingText
1210
+ });
1211
+ if (!isResponseInProgress) {
1212
+ const preview = `[Thinking] ${thinkingText.substring(0, 100)}...`;
1213
+ if (currentThinkingMessageId) {
1214
+ messageBuffer.updateMessage(currentThinkingMessageId, preview, { mode: "replace" });
1215
+ } else {
1216
+ currentThinkingMessageId = messageBuffer.addMessage(preview, "system");
1217
+ }
1218
+ }
1219
+ }
1220
+ }
1221
+ return;
1222
+ }
1223
+ default:
1224
+ return;
1225
+ }
1329
1226
  });
1330
- }
1331
- const cleanup = () => {
1332
- if (!child.killed) {
1333
- child.kill("SIGTERM");
1334
- }
1335
1227
  };
1336
- config.options?.abort?.addEventListener("abort", cleanup);
1337
- process.on("exit", cleanup);
1338
- const processExitPromise = new Promise((resolve) => {
1339
- child.on("close", (code) => {
1340
- if (config.options?.abort?.aborted) {
1341
- query2.setError(new AbortError("Claude Code process aborted by user"));
1342
- }
1343
- if (code !== 0) {
1344
- query2.setError(new Error(`Claude Code process exited with code ${code}`));
1345
- } else {
1346
- resolve();
1347
- }
1228
+ const createRuntimeHandle = async (mode) => {
1229
+ const { session: nextRuntimeHandle, factoryResult } = await launchRuntimeHandleWithFactoryResult({
1230
+ provider: "claude",
1231
+ cwd: session.path,
1232
+ createBackendResult: (opts) => createClaudeBackend({
1233
+ ...opts,
1234
+ baseArgs: session.claudeArgs,
1235
+ mcpServers: session.mcpServers,
1236
+ permissionHandler,
1237
+ permissionMode: mode.permissionMode,
1238
+ model: mode.model,
1239
+ fallbackModel: mode.fallbackModel,
1240
+ customSystemPrompt: mode.customSystemPrompt ? `${mode.customSystemPrompt}
1241
+
1242
+ ${systemPrompt}` : void 0,
1243
+ appendSystemPrompt: mode.appendSystemPrompt ? `${mode.appendSystemPrompt}
1244
+
1245
+ ${systemPrompt}` : systemPrompt,
1246
+ allowedTools: mode.allowedTools ? mode.allowedTools.concat(session.allowedTools ?? []) : session.allowedTools,
1247
+ disallowedTools: mode.disallowedTools,
1248
+ settingsPath: session.hookSettingsPath
1249
+ })
1348
1250
  });
1349
- });
1350
- const query2 = new Query(childStdin, child.stdout, processExitPromise, canCallTool);
1351
- child.on("error", (error) => {
1352
- if (config.options?.abort?.aborted) {
1353
- query2.setError(new AbortError("Claude Code process aborted by user"));
1354
- } else {
1355
- query2.setError(new Error(`Failed to spawn Claude Code process: ${error.message}`));
1356
- }
1357
- });
1358
- processExitPromise.finally(() => {
1359
- cleanup();
1360
- config.options?.abort?.removeEventListener("abort", cleanup);
1361
- if (process.env.CLAUDE_SDK_MCP_SERVERS) {
1362
- delete process.env.CLAUDE_SDK_MCP_SERVERS;
1251
+ logger.debug("[ClaudeACP] Started Claude ACP backend", {
1252
+ command: factoryResult.command,
1253
+ args: factoryResult.args
1254
+ });
1255
+ runtimeHandle = nextRuntimeHandle;
1256
+ setupRuntimeMessageHandler(nextRuntimeHandle);
1257
+ session.consumeOneTimeFlags();
1258
+ return nextRuntimeHandle;
1259
+ };
1260
+ const abortActiveTurn = async () => {
1261
+ const activeController = rotateAbortController();
1262
+ activeController.abort();
1263
+ session.onThinkingChange(false);
1264
+ if (runtimeHandle) {
1265
+ await runtimeHandle.cancel().catch((error) => {
1266
+ logger.debug("[ClaudeACP] Error cancelling runtime handle:", error);
1267
+ });
1363
1268
  }
1364
- });
1365
- return query2;
1366
- }
1367
-
1368
- function parseCompact(message) {
1369
- const trimmed = message.trim();
1370
- if (trimmed === "/compact") {
1371
- return {
1372
- isCompact: true,
1373
- originalMessage: trimmed
1374
- };
1375
- }
1376
- if (trimmed.startsWith("/compact ")) {
1377
- return {
1378
- isCompact: true,
1379
- originalMessage: trimmed
1380
- };
1381
- }
1382
- return {
1383
- isCompact: false,
1384
- originalMessage: message
1385
1269
  };
1386
- }
1387
- function parseClear(message) {
1388
- const trimmed = message.trim();
1389
- return {
1390
- isClear: trimmed === "/clear"
1270
+ const completeSyntheticTurn = () => {
1271
+ session.client.sendAgentMessage("claude", {
1272
+ type: "task_complete",
1273
+ id: randomUUID()
1274
+ });
1391
1275
  };
1392
- }
1393
- function parseSpecialCommand(message) {
1394
- const compactResult = parseCompact(message);
1395
- if (compactResult.isCompact) {
1396
- return {
1397
- type: "compact",
1398
- originalMessage: compactResult.originalMessage
1399
- };
1400
- }
1401
- const clearResult = parseClear(message);
1402
- if (clearResult.isClear) {
1403
- return {
1404
- type: "clear"
1405
- };
1406
- }
1407
- return {
1408
- type: null
1276
+ const handleClearCommand = async () => {
1277
+ logger.debug("[ClaudeACP] /clear command received - resetting runtime and local history");
1278
+ conversationHistory.clear();
1279
+ shouldInjectHistoryOnNextSession = false;
1280
+ await disposeRuntimeHandle();
1281
+ session.clearSessionId();
1282
+ currentModeHash = null;
1283
+ permissionHandler.reset();
1284
+ selectionHandler.reset();
1285
+ resetTurnState();
1286
+ emitStatusMessage("Context was reset");
1287
+ completeSyntheticTurn();
1409
1288
  };
1410
- }
1411
-
1412
- class PushableAsyncIterable {
1413
- queue = [];
1414
- waiters = [];
1415
- isDone = false;
1416
- error = null;
1417
- started = false;
1418
- constructor() {
1419
- }
1420
- /**
1421
- * Push a value to the iterable
1422
- */
1423
- push(value) {
1424
- if (this.isDone) {
1425
- throw new Error("Cannot push to completed iterable");
1426
- }
1427
- if (this.error) {
1428
- throw this.error;
1429
- }
1430
- const waiter = this.waiters.shift();
1431
- if (waiter) {
1432
- waiter.resolve({ done: false, value });
1433
- } else {
1434
- this.queue.push(value);
1435
- }
1436
- }
1437
- /**
1438
- * Mark the iterable as complete
1439
- */
1440
- end() {
1441
- if (this.isDone) {
1442
- return;
1443
- }
1444
- this.isDone = true;
1445
- this.cleanup();
1289
+ const handleSwitchToLocal = async () => {
1290
+ const message = "Daemon-spawned Claude ACP sessions stay in remote mode.";
1291
+ logger.debug("[ClaudeACP] Ignoring switch request because daemon sessions are remote-only");
1292
+ emitStatusMessage(message);
1293
+ };
1294
+ const handleAbort = async () => {
1295
+ logger.debug("[ClaudeACP] Abort requested - stopping current task");
1296
+ await abortActiveTurn();
1297
+ };
1298
+ if (hasTTY) {
1299
+ console.clear();
1300
+ inkInstance = render(React.createElement(RemoteModeDisplay, {
1301
+ messageBuffer,
1302
+ logPath: process.env.DEBUG ? session.logPath : void 0,
1303
+ onExit: async () => {
1304
+ shouldExit = true;
1305
+ await handleAbort();
1306
+ },
1307
+ onSwitchToLocal: () => {
1308
+ void handleSwitchToLocal();
1309
+ }
1310
+ }), {
1311
+ exitOnCtrlC: false,
1312
+ patchConsole: false
1313
+ });
1446
1314
  }
1447
- /**
1448
- * Set an error on the iterable
1449
- */
1450
- setError(err) {
1451
- if (this.isDone) {
1452
- return;
1315
+ if (hasTTY) {
1316
+ process.stdin.resume();
1317
+ if (process.stdin.isTTY) {
1318
+ process.stdin.setRawMode(true);
1453
1319
  }
1454
- this.error = err;
1455
- this.isDone = true;
1456
- this.cleanup();
1320
+ process.stdin.setEncoding("utf8");
1457
1321
  }
1458
- /**
1459
- * Cleanup waiting consumers
1460
- */
1461
- cleanup() {
1462
- while (this.waiters.length > 0) {
1463
- const waiter = this.waiters.shift();
1464
- if (this.error) {
1465
- waiter.reject(this.error);
1466
- } else {
1467
- waiter.resolve({ done: true, value: void 0 });
1322
+ session.client.rpcHandlerManager.registerHandler("abort", handleAbort);
1323
+ session.client.rpcHandlerManager.registerHandler("switch", handleSwitchToLocal);
1324
+ try {
1325
+ while (!shouldExit) {
1326
+ let message = pending;
1327
+ pending = null;
1328
+ if (!message) {
1329
+ const waitSignal = abortController.signal;
1330
+ const batch = await session.queue.waitForMessagesAndGetAsString(waitSignal);
1331
+ if (!batch) {
1332
+ if (waitSignal.aborted && !shouldExit) {
1333
+ continue;
1334
+ }
1335
+ break;
1336
+ }
1337
+ message = batch;
1338
+ }
1339
+ if (!message) {
1340
+ break;
1341
+ }
1342
+ if (runtimeHandle && currentModeHash && message.hash !== currentModeHash) {
1343
+ queueHistoryInjectionForRestart("Starting new Claude ACP session (execution settings changed)...");
1344
+ await disposeRuntimeHandle();
1345
+ session.clearSessionId();
1346
+ currentModeHash = null;
1347
+ pending = message;
1348
+ permissionHandler.reset();
1349
+ selectionHandler.reset();
1350
+ resetTurnState();
1351
+ continue;
1352
+ }
1353
+ currentModeHash = message.hash;
1354
+ readyAlreadySent = false;
1355
+ const specialCommand = parseSpecialCommand(message.message);
1356
+ if (specialCommand.type === "clear") {
1357
+ await handleClearCommand();
1358
+ if (!shouldExit && !pending && session.queue.size() === 0 && !readyAlreadySent) {
1359
+ sendReady();
1360
+ readyAlreadySent = true;
1361
+ }
1362
+ continue;
1363
+ }
1364
+ permissionHandler.setPermissionMode(message.mode.permissionMode);
1365
+ messageBuffer.addMessage(message.message, "user");
1366
+ let shouldClearHistoryAfterTurn = false;
1367
+ try {
1368
+ const activeRuntimeHandle = runtimeHandle ?? await createRuntimeHandle(message.mode);
1369
+ let promptToSend = message.message;
1370
+ if (shouldInjectHistoryOnNextSession && conversationHistory.hasHistory()) {
1371
+ const historyContext = conversationHistory.getContextForNewSession(
1372
+ "Continue from the prior Claude ACP session using the conversation below as context."
1373
+ );
1374
+ promptToSend = historyContext + promptToSend;
1375
+ logger.debug(`[ClaudeACP] Injected conversation history context (${historyContext.length} chars)`);
1376
+ }
1377
+ if (specialCommand.type === "compact") {
1378
+ emitStatusMessage("Compaction started");
1379
+ }
1380
+ conversationHistory.addUserMessage(message.message);
1381
+ await activeRuntimeHandle.sendPrompt(promptToSend);
1382
+ await waitForResponseCompleteWithAbort(activeRuntimeHandle.backend, abortController.signal);
1383
+ shouldInjectHistoryOnNextSession = false;
1384
+ shouldClearHistoryAfterTurn = specialCommand.type === "compact";
1385
+ } catch (error) {
1386
+ logger.warn("[ClaudeACP] Error in Claude ACP session:", error);
1387
+ const isAbortError = error instanceof Error && error.name === "AbortError";
1388
+ const isExpectedInterruption = isAbortError || abortController.signal.aborted || shouldExit;
1389
+ if (isExpectedInterruption) {
1390
+ session.client.sendAgentMessage("claude", {
1391
+ type: "turn_aborted",
1392
+ id: randomUUID()
1393
+ });
1394
+ emitStatusMessage("Aborted by user");
1395
+ } else {
1396
+ const errorMessage = normalizeClaudeBackendError(error);
1397
+ emitUserVisibleErrorMessage(errorMessage);
1398
+ if (conversationHistory.hasHistory()) {
1399
+ shouldInjectHistoryOnNextSession = true;
1400
+ }
1401
+ await disposeRuntimeHandle();
1402
+ session.clearSessionId();
1403
+ currentModeHash = null;
1404
+ }
1405
+ } finally {
1406
+ emitFinalAssistantMessage();
1407
+ if (shouldClearHistoryAfterTurn) {
1408
+ conversationHistory.clear();
1409
+ emitStatusMessage("Compaction completed");
1410
+ }
1411
+ if (!shouldExit) {
1412
+ session.client.sendAgentMessage("claude", {
1413
+ type: "task_complete",
1414
+ id: randomUUID()
1415
+ });
1416
+ }
1417
+ permissionHandler.reset();
1418
+ selectionHandler.reset();
1419
+ resetTurnState();
1420
+ if (!shouldExit && !pending && session.queue.size() === 0 && !readyAlreadySent) {
1421
+ sendReady();
1422
+ readyAlreadySent = true;
1423
+ }
1468
1424
  }
1469
1425
  }
1470
- }
1471
- /**
1472
- * AsyncIterableIterator implementation
1473
- */
1474
- async next() {
1475
- if (this.queue.length > 0) {
1476
- return { done: false, value: this.queue.shift() };
1426
+ } finally {
1427
+ await disposeRuntimeHandle();
1428
+ permissionHandler.reset();
1429
+ selectionHandler.reset();
1430
+ if (process.stdin.isTTY) {
1431
+ try {
1432
+ process.stdin.setRawMode(false);
1433
+ } catch {
1434
+ }
1477
1435
  }
1478
- if (this.isDone) {
1479
- if (this.error) {
1480
- throw this.error;
1436
+ if (hasTTY) {
1437
+ try {
1438
+ process.stdin.pause();
1439
+ } catch {
1481
1440
  }
1482
- return { done: true, value: void 0 };
1483
1441
  }
1484
- return new Promise((resolve, reject) => {
1485
- this.waiters.push({ resolve, reject });
1442
+ session.client.rpcHandlerManager.registerHandler("abort", async () => {
1486
1443
  });
1487
- }
1488
- /**
1489
- * AsyncIterableIterator return implementation
1490
- */
1491
- async return(_value) {
1492
- this.end();
1493
- return { done: true, value: void 0 };
1494
- }
1495
- /**
1496
- * AsyncIterableIterator throw implementation
1497
- */
1498
- async throw(e) {
1499
- this.setError(e instanceof Error ? e : new Error(String(e)));
1500
- throw this.error;
1501
- }
1502
- /**
1503
- * Make this iterable
1504
- */
1505
- [Symbol.asyncIterator]() {
1506
- if (this.started) {
1507
- throw new Error("PushableAsyncIterable can only be iterated once");
1444
+ session.client.rpcHandlerManager.registerHandler("switch", async () => {
1445
+ });
1446
+ if (inkInstance) {
1447
+ inkInstance.unmount();
1508
1448
  }
1509
- this.started = true;
1510
- return this;
1511
- }
1512
- /**
1513
- * Check if the iterable is done
1514
- */
1515
- get done() {
1516
- return this.isDone;
1517
- }
1518
- /**
1519
- * Check if the iterable has an error
1520
- */
1521
- get hasError() {
1522
- return this.error !== null;
1523
- }
1524
- /**
1525
- * Get the current queue size
1526
- */
1527
- get queueSize() {
1528
- return this.queue.length;
1529
- }
1530
- /**
1531
- * Get the number of waiting consumers
1532
- */
1533
- get waiterCount() {
1534
- return this.waiters.length;
1449
+ messageBuffer.clear();
1535
1450
  }
1451
+ logger.debug("[ClaudeACP] Remote launcher returning: exit");
1452
+ return "exit";
1536
1453
  }
1537
1454
 
1538
1455
  async function awaitFileExist(file, timeout = 1e4) {
@@ -1548,58 +1465,6 @@ async function awaitFileExist(file, timeout = 1e4) {
1548
1465
  return false;
1549
1466
  }
1550
1467
 
1551
- function getClaudeSettingsPath() {
1552
- const claudeConfigDir = process.env.CLAUDE_CONFIG_DIR || join(homedir(), ".claude");
1553
- return join(claudeConfigDir, "settings.json");
1554
- }
1555
- function readClaudeSettings() {
1556
- try {
1557
- const settingsPath = getClaudeSettingsPath();
1558
- if (!existsSync(settingsPath)) {
1559
- logger.debug(`[ClaudeSettings] No Claude settings file found at ${settingsPath}`);
1560
- return null;
1561
- }
1562
- const settingsContent = readFileSync(settingsPath, "utf-8");
1563
- const settings = JSON.parse(settingsContent);
1564
- logger.debug(`[ClaudeSettings] Successfully read Claude settings from ${settingsPath}`);
1565
- logger.debug(`[ClaudeSettings] includeCoAuthoredBy: ${settings.includeCoAuthoredBy}`);
1566
- return settings;
1567
- } catch (error) {
1568
- logger.debug(`[ClaudeSettings] Error reading Claude settings: ${error}`);
1569
- return null;
1570
- }
1571
- }
1572
- function shouldIncludeCoAuthoredBy() {
1573
- const settings = readClaudeSettings();
1574
- if (!settings || settings.includeCoAuthoredBy === void 0) {
1575
- return true;
1576
- }
1577
- return settings.includeCoAuthoredBy;
1578
- }
1579
-
1580
- const BASE_SYSTEM_PROMPT = (() => trimIdent(`
1581
- Help human.
1582
- `))();
1583
- const CO_AUTHORED_CREDITS = (() => trimIdent(`
1584
- When making commit messages, instead of just giving co-credit to Claude, also give credit to Happy like so:
1585
-
1586
- <main commit message>
1587
-
1588
- Generated with [Claude Code](https://claude.ai/code)
1589
- via [Happy](https://happy.engineering)
1590
-
1591
- Co-Authored-By: Claude <noreply@anthropic.com>
1592
- Co-Authored-By: Happy <yesreply@happy.engineering>
1593
- `))();
1594
- const systemPrompt = (() => {
1595
- const includeCoAuthored = shouldIncludeCoAuthoredBy();
1596
- if (includeCoAuthored) {
1597
- return BASE_SYSTEM_PROMPT + "\n\n" + CO_AUTHORED_CREDITS;
1598
- } else {
1599
- return BASE_SYSTEM_PROMPT;
1600
- }
1601
- })();
1602
-
1603
1468
  async function claudeRemote(opts) {
1604
1469
  let startFrom = opts.sessionId;
1605
1470
  if (opts.sessionId && !claudeCheckSession(opts.sessionId, opts.path, opts.transcriptPath)) {
@@ -1803,16 +1668,6 @@ function getToolName(toolName) {
1803
1668
  return toTitleCase(toolName);
1804
1669
  }
1805
1670
 
1806
- function getToolDescriptor(toolName) {
1807
- if (toolName === "exit_plan_mode" || toolName === "ExitPlanMode") {
1808
- return { edit: false, exitPlan: true };
1809
- }
1810
- if (toolName === "Edit" || toolName === "MultiEdit" || toolName === "Write" || toolName === "NotebookEdit") {
1811
- return { edit: true, exitPlan: false };
1812
- }
1813
- return { edit: false, exitPlan: false };
1814
- }
1815
-
1816
1671
  class PermissionHandler {
1817
1672
  toolCalls = [];
1818
1673
  responses = /* @__PURE__ */ new Map();
@@ -2982,9 +2837,7 @@ async function loop(opts) {
2982
2837
  return { type: "exit", value: result.code };
2983
2838
  },
2984
2839
  remote: async () => {
2985
- const reason = await claudeRemoteLauncher(session, {
2986
- allowSwitchToLocal: opts.startedBy !== "daemon"
2987
- });
2840
+ const reason = opts.startedBy === "daemon" ? await claudeAcpRemoteLauncher(session) : await claudeRemoteLauncher(session);
2988
2841
  if (reason === "switch") {
2989
2842
  return { type: "switch", mode: "local" };
2990
2843
  }