happy-imou-cloud 2.0.13 → 2.0.14

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-CB7luAdI.mjs +320 -0
  2. package/dist/BaseReasoningProcessor-Cwhs4PwS.cjs +323 -0
  3. package/dist/ProviderSelectionHandler-CmDe9yDh.cjs +265 -0
  4. package/dist/ProviderSelectionHandler-DnnCwXqU.mjs +261 -0
  5. package/dist/{api-D1meoL-9.mjs → api-Bad69WzY.mjs} +2 -2
  6. package/dist/{api-DH5-IqeM.cjs → api-CaqY_2x_.cjs} +2 -2
  7. package/dist/{command-CMvWClny.mjs → command-D9ZaQZQN.mjs} +4 -3
  8. package/dist/{command-Ch8Dgidj.cjs → command-DkVbAV9p.cjs} +4 -3
  9. package/dist/{index-Cxrx9m5D.mjs → index-naa51zV7.mjs} +1102 -50
  10. package/dist/{index-CryJfCh5.cjs → index-oxTpdQx2.cjs} +1106 -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-4BmGePWc.mjs} +1 -1
  16. package/dist/{persistence-Bl3FYvwd.cjs → persistence-Byr0hWCR.cjs} +1 -1
  17. package/dist/{registerKillSessionHandler-BjkY-oUn.cjs → registerKillSessionHandler-CodtiIoq.cjs} +549 -4
  18. package/dist/{registerKillSessionHandler-BElGmD1E.mjs → registerKillSessionHandler-HO4ql8g-.mjs} +541 -5
  19. package/dist/{runClaude-CDZxAF3l.cjs → runClaude-C3UNcGqk.cjs} +583 -747
  20. package/dist/{runClaude-D7dF4RDM.mjs → runClaude-Ev-A-kLN.mjs} +575 -738
  21. package/dist/{runCodex-DnGz1XES.mjs → runCodex-BwFOTxMW.mjs} +9 -215
  22. package/dist/{runCodex-Cik8VzFs.cjs → runCodex-CNM6wz69.cjs} +20 -226
  23. package/dist/{runGemini-B8tXMHeL.mjs → runGemini-CXctVflO.mjs} +8 -7
  24. package/dist/{runGemini-BM2BQ4I7.cjs → runGemini-DqfqSBsP.cjs} +16 -15
  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-Bad69WzY.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-naa51zV7.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-4BmGePWc.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-DnnCwXqU.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-HO4ql8g-.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,577 @@ 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
951
  }
1089
- [Symbol.asyncIterator]() {
1090
- return this.sdkMessages;
1091
- }
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
+ async function claudeAcpRemoteLauncher(session) {
990
+ const hasTTY = process.stdout.isTTY && process.stdin.isTTY;
991
+ const messageBuffer = new MessageBuffer({ enabled: hasTTY });
992
+ let inkInstance = null;
993
+ let shouldExit = false;
994
+ let abortController = new AbortController();
995
+ let runtimeHandle = null;
996
+ let unsubscribeRuntimeMessages = null;
997
+ let currentModeHash = null;
998
+ let pending = null;
999
+ let accumulatedResponse = "";
1000
+ let isResponseInProgress = false;
1001
+ let taskStartedSent = false;
1002
+ let currentAssistantMessageId = null;
1003
+ let currentThinkingMessageId = null;
1004
+ let shouldInjectHistoryOnNextSession = false;
1005
+ let readyAlreadySent = false;
1006
+ const permissionHandler = new ClaudeAcpPermissionHandler(session.client);
1007
+ const selectionHandler = new ProviderSelectionHandler(session.client, "Claude");
1008
+ const conversationHistory = new ConversationHistory$1({
1009
+ maxMessages: 20,
1010
+ maxCharacters: 5e4
1011
+ });
1012
+ const rotateAbortController = () => {
1013
+ const activeController = abortController;
1014
+ abortController = new AbortController();
1015
+ return activeController;
1016
+ };
1017
+ const sendReady = () => {
1018
+ session.client.sendSessionEvent({ type: "ready" });
1019
+ try {
1020
+ session.api.push().sendToAllDevices(
1021
+ "It's ready!",
1022
+ "Claude is waiting for your command",
1023
+ { sessionId: session.client.sessionId }
1024
+ );
1025
+ } catch (pushError) {
1026
+ logger.debug("[ClaudeACP] Failed to send ready push", pushError);
1137
1027
  }
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");
1028
+ };
1029
+ const emitStatusMessage = (message) => {
1030
+ messageBuffer.addMessage(message, "status");
1031
+ session.client.sendSessionEvent({ type: "message", message });
1032
+ };
1033
+ const emitUserVisibleErrorMessage = (message) => {
1034
+ emitStatusMessage(message);
1035
+ session.client.sendAgentMessage("claude", {
1036
+ type: "message",
1037
+ message
1038
+ });
1039
+ };
1040
+ const resetTurnState = () => {
1041
+ accumulatedResponse = "";
1042
+ isResponseInProgress = false;
1043
+ taskStartedSent = false;
1044
+ currentAssistantMessageId = null;
1045
+ currentThinkingMessageId = null;
1046
+ session.onThinkingChange(false);
1047
+ };
1048
+ const emitFinalAssistantMessage = () => {
1049
+ const finalMessage = accumulatedResponse.trim();
1050
+ if (!finalMessage) {
1051
+ accumulatedResponse = "";
1052
+ isResponseInProgress = false;
1053
+ return;
1145
1054
  }
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");
1055
+ conversationHistory.addAssistantMessage(finalMessage);
1056
+ session.client.sendAgentMessage("claude", {
1057
+ type: "message",
1058
+ message: finalMessage
1169
1059
  });
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");
1060
+ accumulatedResponse = "";
1061
+ isResponseInProgress = false;
1062
+ };
1063
+ const disposeRuntimeHandle = async () => {
1064
+ if (!runtimeHandle) {
1178
1065
  return;
1179
1066
  }
1180
- const controller = new AbortController();
1181
- this.cancelControllers.set(request.request_id, controller);
1067
+ const activeHandle = runtimeHandle;
1068
+ runtimeHandle = null;
1069
+ unsubscribeRuntimeMessages?.();
1070
+ unsubscribeRuntimeMessages = null;
1182
1071
  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");
1072
+ await activeHandle.dispose();
1193
1073
  } 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);
1074
+ logger.debug("[ClaudeACP] Error disposing runtime handle:", error);
1205
1075
  }
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);
1076
+ };
1077
+ const queueHistoryInjectionForRestart = (reason) => {
1078
+ messageBuffer.addMessage("\u2550".repeat(40), "status");
1079
+ if (conversationHistory.hasHistory()) {
1080
+ shouldInjectHistoryOnNextSession = true;
1081
+ const message = `${reason} Preserving ${conversationHistory.size()} earlier messages of context.`;
1082
+ emitStatusMessage(message);
1083
+ logger.debug(`[ClaudeACP] Will inject conversation history after restart: ${conversationHistory.getSummary()}`);
1084
+ return;
1216
1085
  }
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
1086
+ emitStatusMessage(reason);
1087
+ };
1088
+ const setupRuntimeMessageHandler = (activeRuntimeHandle) => {
1089
+ const forwardAgentMessage = (agentMessage) => {
1090
+ forwardAgentMessageToProviderSession(agentMessage, {
1091
+ provider: "claude",
1092
+ send: (body) => session.client.sendAgentMessage("claude", body)
1229
1093
  });
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());
1094
+ };
1095
+ unsubscribeRuntimeMessages?.();
1096
+ unsubscribeRuntimeMessages = activeRuntimeHandle.onMessage((msg) => {
1097
+ switch (msg.type) {
1098
+ case "model-output": {
1099
+ const text = msg.textDelta ?? msg.fullText ?? "";
1100
+ if (!text) {
1101
+ return;
1102
+ }
1103
+ if (!isResponseInProgress) {
1104
+ if (currentThinkingMessageId) {
1105
+ messageBuffer.removeMessage(currentThinkingMessageId);
1106
+ currentThinkingMessageId = null;
1107
+ }
1108
+ currentAssistantMessageId = messageBuffer.addMessage(text, "assistant");
1109
+ isResponseInProgress = true;
1110
+ } else if (currentAssistantMessageId) {
1111
+ const updated = messageBuffer.updateMessage(currentAssistantMessageId, text);
1112
+ if (!updated) {
1113
+ currentAssistantMessageId = messageBuffer.addMessage(text, "assistant");
1114
+ }
1115
+ }
1116
+ accumulatedResponse += text;
1117
+ return;
1118
+ }
1119
+ case "status": {
1120
+ if (msg.status === "running") {
1121
+ session.onThinkingChange(true);
1122
+ if (!taskStartedSent) {
1123
+ session.client.sendAgentMessage("claude", {
1124
+ type: "task_started",
1125
+ id: randomUUID()
1126
+ });
1127
+ taskStartedSent = true;
1128
+ }
1129
+ if (!isResponseInProgress && !currentThinkingMessageId) {
1130
+ currentThinkingMessageId = messageBuffer.addMessage("Thinking...", "system");
1131
+ }
1132
+ return;
1133
+ }
1134
+ if (msg.status === "idle" || msg.status === "stopped") {
1135
+ session.onThinkingChange(false);
1136
+ return;
1137
+ }
1138
+ if (msg.status === "error") {
1139
+ messageBuffer.addMessage(`Error: ${normalizeClaudeBackendError(msg.detail)}`, "status");
1140
+ }
1141
+ return;
1142
+ }
1143
+ case "tool-call": {
1144
+ const toolArgs = truncateDisplayMessage(msg.args, 100);
1145
+ messageBuffer.addMessage(
1146
+ `Executing: ${msg.toolName}${toolArgs ? ` ${toolArgs}` : ""}`,
1147
+ "tool"
1148
+ );
1149
+ forwardAgentMessage(msg);
1150
+ return;
1151
+ }
1152
+ case "tool-result": {
1153
+ const resultText = truncateDisplayMessage(msg.result, 200);
1154
+ messageBuffer.addMessage(resultText ? `Result: ${resultText}` : "Tool completed", "result");
1155
+ forwardAgentMessage(msg);
1156
+ return;
1157
+ }
1158
+ case "fs-edit": {
1159
+ messageBuffer.addMessage(`File edit: ${msg.description}`, "tool");
1160
+ forwardAgentMessage(msg);
1161
+ return;
1162
+ }
1163
+ case "terminal-output": {
1164
+ const output = formatDisplayMessage(msg.data);
1165
+ messageBuffer.addMessage(output, "result");
1166
+ forwardAgentMessage({
1167
+ ...msg,
1168
+ data: output
1169
+ });
1170
+ return;
1171
+ }
1172
+ case "permission-request": {
1173
+ forwardAgentMessage(msg);
1174
+ return;
1175
+ }
1176
+ case "token-count": {
1177
+ forwardAgentMessage(msg);
1178
+ return;
1179
+ }
1180
+ case "exec-approval-request":
1181
+ case "patch-apply-begin":
1182
+ case "patch-apply-end": {
1183
+ forwardAgentMessage(msg);
1184
+ return;
1185
+ }
1186
+ case "event": {
1187
+ if (msg.name === "thinking") {
1188
+ const payload = msg.payload;
1189
+ const thinkingText = typeof payload?.text === "string" ? payload.text : "";
1190
+ if (thinkingText) {
1191
+ session.client.sendAgentMessage("claude", {
1192
+ type: "thinking",
1193
+ text: thinkingText
1194
+ });
1195
+ if (!isResponseInProgress) {
1196
+ const preview = `[Thinking] ${thinkingText.substring(0, 100)}...`;
1197
+ if (currentThinkingMessageId) {
1198
+ messageBuffer.updateMessage(currentThinkingMessageId, preview, { mode: "replace" });
1199
+ } else {
1200
+ currentThinkingMessageId = messageBuffer.addMessage(preview, "system");
1201
+ }
1202
+ }
1203
+ }
1204
+ }
1205
+ return;
1206
+ }
1207
+ default:
1208
+ return;
1209
+ }
1329
1210
  });
1330
- }
1331
- const cleanup = () => {
1332
- if (!child.killed) {
1333
- child.kill("SIGTERM");
1334
- }
1335
1211
  };
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
- }
1212
+ const createRuntimeHandle = async (mode) => {
1213
+ const { session: nextRuntimeHandle, factoryResult } = await launchRuntimeHandleWithFactoryResult({
1214
+ provider: "claude",
1215
+ cwd: session.path,
1216
+ createBackendResult: (opts) => createClaudeBackend({
1217
+ ...opts,
1218
+ baseArgs: session.claudeArgs,
1219
+ mcpServers: session.mcpServers,
1220
+ permissionHandler,
1221
+ permissionMode: mode.permissionMode,
1222
+ model: mode.model,
1223
+ fallbackModel: mode.fallbackModel,
1224
+ customSystemPrompt: mode.customSystemPrompt ? `${mode.customSystemPrompt}
1225
+
1226
+ ${systemPrompt}` : void 0,
1227
+ appendSystemPrompt: mode.appendSystemPrompt ? `${mode.appendSystemPrompt}
1228
+
1229
+ ${systemPrompt}` : systemPrompt,
1230
+ allowedTools: mode.allowedTools ? mode.allowedTools.concat(session.allowedTools ?? []) : session.allowedTools,
1231
+ disallowedTools: mode.disallowedTools,
1232
+ settingsPath: session.hookSettingsPath
1233
+ })
1348
1234
  });
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;
1235
+ logger.debug("[ClaudeACP] Started Claude ACP backend", {
1236
+ command: factoryResult.command,
1237
+ args: factoryResult.args
1238
+ });
1239
+ runtimeHandle = nextRuntimeHandle;
1240
+ setupRuntimeMessageHandler(nextRuntimeHandle);
1241
+ session.consumeOneTimeFlags();
1242
+ return nextRuntimeHandle;
1243
+ };
1244
+ const abortActiveTurn = async () => {
1245
+ const activeController = rotateAbortController();
1246
+ activeController.abort();
1247
+ session.onThinkingChange(false);
1248
+ if (runtimeHandle) {
1249
+ await runtimeHandle.cancel().catch((error) => {
1250
+ logger.debug("[ClaudeACP] Error cancelling runtime handle:", error);
1251
+ });
1363
1252
  }
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
1253
  };
1386
- }
1387
- function parseClear(message) {
1388
- const trimmed = message.trim();
1389
- return {
1390
- isClear: trimmed === "/clear"
1254
+ const completeSyntheticTurn = () => {
1255
+ session.client.sendAgentMessage("claude", {
1256
+ type: "task_complete",
1257
+ id: randomUUID()
1258
+ });
1391
1259
  };
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
1260
+ const handleClearCommand = async () => {
1261
+ logger.debug("[ClaudeACP] /clear command received - resetting runtime and local history");
1262
+ conversationHistory.clear();
1263
+ shouldInjectHistoryOnNextSession = false;
1264
+ await disposeRuntimeHandle();
1265
+ session.clearSessionId();
1266
+ currentModeHash = null;
1267
+ permissionHandler.reset();
1268
+ selectionHandler.reset();
1269
+ resetTurnState();
1270
+ emitStatusMessage("Context was reset");
1271
+ completeSyntheticTurn();
1409
1272
  };
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();
1273
+ const handleSwitchToLocal = async () => {
1274
+ const message = "Daemon-spawned Claude ACP sessions stay in remote mode.";
1275
+ logger.debug("[ClaudeACP] Ignoring switch request because daemon sessions are remote-only");
1276
+ emitStatusMessage(message);
1277
+ };
1278
+ const handleAbort = async () => {
1279
+ logger.debug("[ClaudeACP] Abort requested - stopping current task");
1280
+ await abortActiveTurn();
1281
+ };
1282
+ if (hasTTY) {
1283
+ console.clear();
1284
+ inkInstance = render(React.createElement(RemoteModeDisplay, {
1285
+ messageBuffer,
1286
+ logPath: process.env.DEBUG ? session.logPath : void 0,
1287
+ onExit: async () => {
1288
+ shouldExit = true;
1289
+ await handleAbort();
1290
+ },
1291
+ onSwitchToLocal: () => {
1292
+ void handleSwitchToLocal();
1293
+ }
1294
+ }), {
1295
+ exitOnCtrlC: false,
1296
+ patchConsole: false
1297
+ });
1446
1298
  }
1447
- /**
1448
- * Set an error on the iterable
1449
- */
1450
- setError(err) {
1451
- if (this.isDone) {
1452
- return;
1299
+ if (hasTTY) {
1300
+ process.stdin.resume();
1301
+ if (process.stdin.isTTY) {
1302
+ process.stdin.setRawMode(true);
1453
1303
  }
1454
- this.error = err;
1455
- this.isDone = true;
1456
- this.cleanup();
1304
+ process.stdin.setEncoding("utf8");
1457
1305
  }
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 });
1306
+ session.client.rpcHandlerManager.registerHandler("abort", handleAbort);
1307
+ session.client.rpcHandlerManager.registerHandler("switch", handleSwitchToLocal);
1308
+ try {
1309
+ while (!shouldExit) {
1310
+ let message = pending;
1311
+ pending = null;
1312
+ if (!message) {
1313
+ const waitSignal = abortController.signal;
1314
+ const batch = await session.queue.waitForMessagesAndGetAsString(waitSignal);
1315
+ if (!batch) {
1316
+ if (waitSignal.aborted && !shouldExit) {
1317
+ continue;
1318
+ }
1319
+ break;
1320
+ }
1321
+ message = batch;
1322
+ }
1323
+ if (!message) {
1324
+ break;
1325
+ }
1326
+ if (runtimeHandle && currentModeHash && message.hash !== currentModeHash) {
1327
+ queueHistoryInjectionForRestart("Starting new Claude ACP session (execution settings changed)...");
1328
+ await disposeRuntimeHandle();
1329
+ session.clearSessionId();
1330
+ currentModeHash = null;
1331
+ pending = message;
1332
+ permissionHandler.reset();
1333
+ selectionHandler.reset();
1334
+ resetTurnState();
1335
+ continue;
1336
+ }
1337
+ currentModeHash = message.hash;
1338
+ readyAlreadySent = false;
1339
+ const specialCommand = parseSpecialCommand(message.message);
1340
+ if (specialCommand.type === "clear") {
1341
+ await handleClearCommand();
1342
+ if (!shouldExit && !pending && session.queue.size() === 0 && !readyAlreadySent) {
1343
+ sendReady();
1344
+ readyAlreadySent = true;
1345
+ }
1346
+ continue;
1347
+ }
1348
+ permissionHandler.setPermissionMode(message.mode.permissionMode);
1349
+ messageBuffer.addMessage(message.message, "user");
1350
+ let shouldClearHistoryAfterTurn = false;
1351
+ try {
1352
+ const activeRuntimeHandle = runtimeHandle ?? await createRuntimeHandle(message.mode);
1353
+ let promptToSend = message.message;
1354
+ if (shouldInjectHistoryOnNextSession && conversationHistory.hasHistory()) {
1355
+ const historyContext = conversationHistory.getContextForNewSession(
1356
+ "Continue from the prior Claude ACP session using the conversation below as context."
1357
+ );
1358
+ promptToSend = historyContext + promptToSend;
1359
+ logger.debug(`[ClaudeACP] Injected conversation history context (${historyContext.length} chars)`);
1360
+ }
1361
+ if (specialCommand.type === "compact") {
1362
+ emitStatusMessage("Compaction started");
1363
+ }
1364
+ conversationHistory.addUserMessage(message.message);
1365
+ await activeRuntimeHandle.sendPrompt(promptToSend);
1366
+ await waitForResponseCompleteWithAbort(activeRuntimeHandle.backend, abortController.signal);
1367
+ shouldInjectHistoryOnNextSession = false;
1368
+ shouldClearHistoryAfterTurn = specialCommand.type === "compact";
1369
+ } catch (error) {
1370
+ logger.warn("[ClaudeACP] Error in Claude ACP session:", error);
1371
+ const isAbortError = error instanceof Error && error.name === "AbortError";
1372
+ const isExpectedInterruption = isAbortError || abortController.signal.aborted || shouldExit;
1373
+ if (isExpectedInterruption) {
1374
+ session.client.sendAgentMessage("claude", {
1375
+ type: "turn_aborted",
1376
+ id: randomUUID()
1377
+ });
1378
+ emitStatusMessage("Aborted by user");
1379
+ } else {
1380
+ const errorMessage = normalizeClaudeBackendError(error);
1381
+ emitUserVisibleErrorMessage(errorMessage);
1382
+ if (conversationHistory.hasHistory()) {
1383
+ shouldInjectHistoryOnNextSession = true;
1384
+ }
1385
+ await disposeRuntimeHandle();
1386
+ session.clearSessionId();
1387
+ currentModeHash = null;
1388
+ }
1389
+ } finally {
1390
+ emitFinalAssistantMessage();
1391
+ if (shouldClearHistoryAfterTurn) {
1392
+ conversationHistory.clear();
1393
+ emitStatusMessage("Compaction completed");
1394
+ }
1395
+ if (!shouldExit) {
1396
+ session.client.sendAgentMessage("claude", {
1397
+ type: "task_complete",
1398
+ id: randomUUID()
1399
+ });
1400
+ }
1401
+ permissionHandler.reset();
1402
+ selectionHandler.reset();
1403
+ resetTurnState();
1404
+ if (!shouldExit && !pending && session.queue.size() === 0 && !readyAlreadySent) {
1405
+ sendReady();
1406
+ readyAlreadySent = true;
1407
+ }
1468
1408
  }
1469
1409
  }
1470
- }
1471
- /**
1472
- * AsyncIterableIterator implementation
1473
- */
1474
- async next() {
1475
- if (this.queue.length > 0) {
1476
- return { done: false, value: this.queue.shift() };
1410
+ } finally {
1411
+ await disposeRuntimeHandle();
1412
+ permissionHandler.reset();
1413
+ selectionHandler.reset();
1414
+ if (process.stdin.isTTY) {
1415
+ try {
1416
+ process.stdin.setRawMode(false);
1417
+ } catch {
1418
+ }
1477
1419
  }
1478
- if (this.isDone) {
1479
- if (this.error) {
1480
- throw this.error;
1420
+ if (hasTTY) {
1421
+ try {
1422
+ process.stdin.pause();
1423
+ } catch {
1481
1424
  }
1482
- return { done: true, value: void 0 };
1483
1425
  }
1484
- return new Promise((resolve, reject) => {
1485
- this.waiters.push({ resolve, reject });
1426
+ session.client.rpcHandlerManager.registerHandler("abort", async () => {
1486
1427
  });
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");
1428
+ session.client.rpcHandlerManager.registerHandler("switch", async () => {
1429
+ });
1430
+ if (inkInstance) {
1431
+ inkInstance.unmount();
1508
1432
  }
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;
1433
+ messageBuffer.clear();
1535
1434
  }
1435
+ logger.debug("[ClaudeACP] Remote launcher returning: exit");
1436
+ return "exit";
1536
1437
  }
1537
1438
 
1538
1439
  async function awaitFileExist(file, timeout = 1e4) {
@@ -1548,58 +1449,6 @@ async function awaitFileExist(file, timeout = 1e4) {
1548
1449
  return false;
1549
1450
  }
1550
1451
 
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
1452
  async function claudeRemote(opts) {
1604
1453
  let startFrom = opts.sessionId;
1605
1454
  if (opts.sessionId && !claudeCheckSession(opts.sessionId, opts.path, opts.transcriptPath)) {
@@ -1803,16 +1652,6 @@ function getToolName(toolName) {
1803
1652
  return toTitleCase(toolName);
1804
1653
  }
1805
1654
 
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
1655
  class PermissionHandler {
1817
1656
  toolCalls = [];
1818
1657
  responses = /* @__PURE__ */ new Map();
@@ -2982,9 +2821,7 @@ async function loop(opts) {
2982
2821
  return { type: "exit", value: result.code };
2983
2822
  },
2984
2823
  remote: async () => {
2985
- const reason = await claudeRemoteLauncher(session, {
2986
- allowSwitchToLocal: opts.startedBy !== "daemon"
2987
- });
2824
+ const reason = opts.startedBy === "daemon" ? await claudeAcpRemoteLauncher(session) : await claudeRemoteLauncher(session);
2988
2825
  if (reason === "switch") {
2989
2826
  return { type: "switch", mode: "local" };
2990
2827
  }