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