happy-imou-cloud 2.0.11 → 2.0.13

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 (35) hide show
  1. package/bin/happy-cloud.mjs +1 -1
  2. package/dist/ConversationHistory-V3VLmjJf.cjs +868 -0
  3. package/dist/ConversationHistory-_ciJNIgH.mjs +856 -0
  4. package/dist/{api-BcWf5v4i.mjs → api-D1meoL-9.mjs} +2 -2
  5. package/dist/{api-Ye-rPX6s.cjs → api-DH5-IqeM.cjs} +2 -2
  6. package/dist/{command-nOI80Mnm.mjs → command-CMvWClny.mjs} +3 -3
  7. package/dist/{command-BK93nizl.cjs → command-Ch8Dgidj.cjs} +3 -3
  8. package/dist/createKeepAliveController-C5cQlDRr.mjs +51 -0
  9. package/dist/createKeepAliveController-DO8H6d5E.cjs +54 -0
  10. package/dist/{index-J7QKJ8lc.cjs → index-CryJfCh5.cjs} +54 -23
  11. package/dist/{index-DnsqY6I_.mjs → index-Cxrx9m5D.mjs} +53 -21
  12. package/dist/index.cjs +3 -3
  13. package/dist/index.mjs +3 -3
  14. package/dist/lib.cjs +1 -1
  15. package/dist/lib.mjs +1 -1
  16. package/dist/{persistence-BMa6cyw9.mjs → persistence-9Iu0wGNM.mjs} +1 -1
  17. package/dist/{persistence-xK5CKhbn.cjs → persistence-Bl3FYvwd.cjs} +1 -1
  18. package/dist/{registerKillSessionHandler-Dxwg4L4J.mjs → registerKillSessionHandler-BElGmD1E.mjs} +5 -541
  19. package/dist/{registerKillSessionHandler-CH6yN0eG.cjs → registerKillSessionHandler-BjkY-oUn.cjs} +4 -549
  20. package/dist/{runClaude-BMHlBny_.cjs → runClaude-CDZxAF3l.cjs} +129 -630
  21. package/dist/{runClaude-cQ-UT0Ke.mjs → runClaude-D7dF4RDM.mjs} +126 -627
  22. package/dist/{runCodex-roOSOWWL.cjs → runCodex-Cik8VzFs.cjs} +224 -17
  23. package/dist/{runCodex-AnJUPIhX.mjs → runCodex-DnGz1XES.mjs} +213 -6
  24. package/dist/{runGemini-BGo_0mzA.mjs → runGemini-B8tXMHeL.mjs} +5 -5
  25. package/dist/{runGemini-Cg6Zbqlz.cjs → runGemini-BM2BQ4I7.cjs} +13 -13
  26. package/package.json +9 -9
  27. package/scripts/build.mjs +66 -66
  28. package/scripts/devtools/README.md +9 -9
  29. package/scripts/e2e/fake-codex-acp-agent.mjs +139 -139
  30. package/scripts/e2e/local-server-session-roundtrip.mjs +1063 -1063
  31. package/scripts/release-smoke.mjs +202 -202
  32. package/dist/BaseReasoningProcessor-5ACv9gKu.mjs +0 -320
  33. package/dist/BaseReasoningProcessor-COrRWyn0.cjs +0 -323
  34. package/dist/ProviderSelectionHandler-BljOLMQn.mjs +0 -261
  35. package/dist/ProviderSelectionHandler-DBGobhGZ.cjs +0 -265
@@ -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-BcWf5v4i.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-D1meoL-9.mjs';
3
3
  import 'cross-spawn';
4
4
  import '@agentclientprotocol/sdk';
5
- import { j as getProjectPath, k as claudeLocal, E as ExitCodeError, l as trimIdent, m as createClaudeBackend, f as formatDisplayMessage, t as truncateDisplayMessage, n as isBun, o as claudeCheckSession, e as projectPath, q as mapToClaudeMode, b as stopCaffeinate, p as publishSessionRegistration, u as getEnvironmentInfo, w as startCaffeinate } from './index-DnsqY6I_.mjs';
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';
6
6
  import 'ps-list';
7
7
  import 'fs';
8
8
  import 'path';
@@ -11,7 +11,7 @@ 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-BMa6cyw9.mjs';
14
+ import './persistence-9Iu0wGNM.mjs';
15
15
  import { readFile } from 'node:fs/promises';
16
16
  import { stat, watch, access } from 'fs/promises';
17
17
  import 'crypto';
@@ -23,9 +23,9 @@ import 'tweetnacl';
23
23
  import 'open';
24
24
  import React, { useState, useRef, useEffect, useCallback } from 'react';
25
25
  import { useStdout, useInput, Box, Text, render } from 'ink';
26
- import { c as createKeepAliveController, P as ProviderSelectionHandler, r as runModeLoop } from './ProviderSelectionHandler-BljOLMQn.mjs';
26
+ import { c as createKeepAliveController, r as runModeLoop } from './createKeepAliveController-C5cQlDRr.mjs';
27
27
  import { R as RawJSONLinesSchema } from './types-CiliQpqS.mjs';
28
- 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-Dxwg4L4J.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
29
  import { createInterface } from 'node:readline';
30
30
  import { fileURLToPath } from 'node:url';
31
31
  import 'socket.io-client';
@@ -715,50 +715,6 @@ async function claudeLocalLauncher(session) {
715
715
  return exitReason || { type: "exit", code: 0 };
716
716
  }
717
717
 
718
- function parseCompact(message) {
719
- const trimmed = message.trim();
720
- if (trimmed === "/compact") {
721
- return {
722
- isCompact: true,
723
- originalMessage: trimmed
724
- };
725
- }
726
- if (trimmed.startsWith("/compact ")) {
727
- return {
728
- isCompact: true,
729
- originalMessage: trimmed
730
- };
731
- }
732
- return {
733
- isCompact: false,
734
- originalMessage: message
735
- };
736
- }
737
- function parseClear(message) {
738
- const trimmed = message.trim();
739
- return {
740
- isClear: trimmed === "/clear"
741
- };
742
- }
743
- function parseSpecialCommand(message) {
744
- const compactResult = parseCompact(message);
745
- if (compactResult.isCompact) {
746
- return {
747
- type: "compact",
748
- originalMessage: compactResult.originalMessage
749
- };
750
- }
751
- const clearResult = parseClear(message);
752
- if (clearResult.isClear) {
753
- return {
754
- type: "clear"
755
- };
756
- }
757
- return {
758
- type: null
759
- };
760
- }
761
-
762
718
  const RemoteModeDisplay = ({ messageBuffer, logPath, onExit, onSwitchToLocal }) => {
763
719
  const [messages, setMessages] = useState([]);
764
720
  const [confirmationMode, setConfirmationMode] = useState(null);
@@ -884,582 +840,6 @@ const RemoteModeDisplay = ({ messageBuffer, logPath, onExit, onSwitchToLocal })
884
840
  ));
885
841
  };
886
842
 
887
- function getClaudeSettingsPath() {
888
- const claudeConfigDir = process.env.CLAUDE_CONFIG_DIR || join(homedir(), ".claude");
889
- return join(claudeConfigDir, "settings.json");
890
- }
891
- function readClaudeSettings() {
892
- try {
893
- const settingsPath = getClaudeSettingsPath();
894
- if (!existsSync(settingsPath)) {
895
- logger.debug(`[ClaudeSettings] No Claude settings file found at ${settingsPath}`);
896
- return null;
897
- }
898
- const settingsContent = readFileSync(settingsPath, "utf-8");
899
- const settings = JSON.parse(settingsContent);
900
- logger.debug(`[ClaudeSettings] Successfully read Claude settings from ${settingsPath}`);
901
- logger.debug(`[ClaudeSettings] includeCoAuthoredBy: ${settings.includeCoAuthoredBy}`);
902
- return settings;
903
- } catch (error) {
904
- logger.debug(`[ClaudeSettings] Error reading Claude settings: ${error}`);
905
- return null;
906
- }
907
- }
908
- function shouldIncludeCoAuthoredBy() {
909
- const settings = readClaudeSettings();
910
- if (!settings || settings.includeCoAuthoredBy === void 0) {
911
- return true;
912
- }
913
- return settings.includeCoAuthoredBy;
914
- }
915
-
916
- const BASE_SYSTEM_PROMPT = (() => trimIdent(`
917
- Help human.
918
- `))();
919
- const CO_AUTHORED_CREDITS = (() => trimIdent(`
920
- When making commit messages, instead of just giving co-credit to Claude, also give credit to Happy like so:
921
-
922
- <main commit message>
923
-
924
- Generated with [Claude Code](https://claude.ai/code)
925
- via [Happy](https://happy.engineering)
926
-
927
- Co-Authored-By: Claude <noreply@anthropic.com>
928
- Co-Authored-By: Happy <yesreply@happy.engineering>
929
- `))();
930
- const systemPrompt = (() => {
931
- const includeCoAuthored = shouldIncludeCoAuthoredBy();
932
- if (includeCoAuthored) {
933
- return BASE_SYSTEM_PROMPT + "\n\n" + CO_AUTHORED_CREDITS;
934
- } else {
935
- return BASE_SYSTEM_PROMPT;
936
- }
937
- })();
938
-
939
- function getToolDescriptor(toolName) {
940
- if (toolName === "exit_plan_mode" || toolName === "ExitPlanMode") {
941
- return { edit: false, exitPlan: true };
942
- }
943
- if (toolName === "Edit" || toolName === "MultiEdit" || toolName === "Write" || toolName === "NotebookEdit") {
944
- return { edit: true, exitPlan: false };
945
- }
946
- return { edit: false, exitPlan: false };
947
- }
948
-
949
- class ClaudeAcpPermissionHandler extends BasePermissionHandler {
950
- currentPermissionMode = "default";
951
- constructor(session) {
952
- super(session);
953
- }
954
- getLogPrefix() {
955
- return "[ClaudeACP]";
956
- }
957
- setPermissionMode(mode) {
958
- this.currentPermissionMode = mode;
959
- logger.debug(`${this.getLogPrefix()} Permission mode set to: ${mode}`);
960
- }
961
- shouldAutoApprove(toolName) {
962
- const descriptor = getToolDescriptor(toolName);
963
- switch (this.currentPermissionMode) {
964
- case "bypassPermissions":
965
- case "yolo":
966
- return true;
967
- case "acceptEdits":
968
- return descriptor.edit;
969
- default:
970
- return false;
971
- }
972
- }
973
- async handleToolCall(toolCallId, toolName, input) {
974
- if (this.shouldAutoApprove(toolName)) {
975
- const decision = this.currentPermissionMode === "bypassPermissions" || this.currentPermissionMode === "yolo" ? "approved_for_session" : "approved";
976
- this.session.updateAgentState((currentState) => ({
977
- ...currentState,
978
- completedRequests: {
979
- ...currentState.completedRequests,
980
- [toolCallId]: {
981
- tool: toolName,
982
- arguments: input,
983
- createdAt: Date.now(),
984
- completedAt: Date.now(),
985
- status: "approved",
986
- decision
987
- }
988
- }
989
- }));
990
- logger.debug(`${this.getLogPrefix()} Auto-approved tool ${toolName} (${toolCallId}) in ${this.currentPermissionMode} mode`);
991
- return { decision };
992
- }
993
- return this.registerPendingRequest(
994
- toolCallId,
995
- toolName,
996
- input,
997
- ` in ${this.currentPermissionMode} mode`
998
- );
999
- }
1000
- }
1001
-
1002
- function normalizeClaudeBackendError(error) {
1003
- const record = typeof error === "object" && error !== null ? error : null;
1004
- const text = formatDisplayMessage(error).trim();
1005
- const stderrText = record ? formatDisplayMessage(record.stderr).trim() : "";
1006
- const detailText = record ? formatDisplayMessage(record.detail).trim() : "";
1007
- const searchable = [text, stderrText, detailText].filter(Boolean).join("\n").trim();
1008
- return searchable || "Claude ACP backend exited unexpectedly";
1009
- }
1010
- async function claudeAcpRemoteLauncher(session) {
1011
- const hasTTY = process.stdout.isTTY && process.stdin.isTTY;
1012
- const messageBuffer = new MessageBuffer({ enabled: hasTTY });
1013
- let inkInstance = null;
1014
- let shouldExit = false;
1015
- let abortController = new AbortController();
1016
- let runtimeHandle = null;
1017
- let unsubscribeRuntimeMessages = null;
1018
- let currentModeHash = null;
1019
- let pending = null;
1020
- let accumulatedResponse = "";
1021
- let isResponseInProgress = false;
1022
- let taskStartedSent = false;
1023
- let currentAssistantMessageId = null;
1024
- let currentThinkingMessageId = null;
1025
- let shouldInjectHistoryOnNextSession = false;
1026
- let readyAlreadySent = false;
1027
- const permissionHandler = new ClaudeAcpPermissionHandler(session.client);
1028
- const selectionHandler = new ProviderSelectionHandler(session.client, "Claude");
1029
- const conversationHistory = new ConversationHistory$1({
1030
- maxMessages: 20,
1031
- maxCharacters: 5e4
1032
- });
1033
- const rotateAbortController = () => {
1034
- const activeController = abortController;
1035
- abortController = new AbortController();
1036
- return activeController;
1037
- };
1038
- const sendReady = () => {
1039
- session.client.sendSessionEvent({ type: "ready" });
1040
- try {
1041
- session.api.push().sendToAllDevices(
1042
- "It's ready!",
1043
- "Claude is waiting for your command",
1044
- { sessionId: session.client.sessionId }
1045
- );
1046
- } catch (pushError) {
1047
- logger.debug("[ClaudeACP] Failed to send ready push", pushError);
1048
- }
1049
- };
1050
- const emitStatusMessage = (message) => {
1051
- messageBuffer.addMessage(message, "status");
1052
- session.client.sendSessionEvent({ type: "message", message });
1053
- };
1054
- const emitUserVisibleErrorMessage = (message) => {
1055
- emitStatusMessage(message);
1056
- session.client.sendAgentMessage("claude", {
1057
- type: "message",
1058
- message
1059
- });
1060
- };
1061
- const resetTurnState = () => {
1062
- accumulatedResponse = "";
1063
- isResponseInProgress = false;
1064
- taskStartedSent = false;
1065
- currentAssistantMessageId = null;
1066
- currentThinkingMessageId = null;
1067
- session.onThinkingChange(false);
1068
- };
1069
- const emitFinalAssistantMessage = () => {
1070
- const finalMessage = accumulatedResponse.trim();
1071
- if (!finalMessage) {
1072
- accumulatedResponse = "";
1073
- isResponseInProgress = false;
1074
- return;
1075
- }
1076
- conversationHistory.addAssistantMessage(finalMessage);
1077
- session.client.sendAgentMessage("claude", {
1078
- type: "message",
1079
- message: finalMessage
1080
- });
1081
- accumulatedResponse = "";
1082
- isResponseInProgress = false;
1083
- };
1084
- const disposeRuntimeHandle = async () => {
1085
- if (!runtimeHandle) {
1086
- return;
1087
- }
1088
- const activeHandle = runtimeHandle;
1089
- runtimeHandle = null;
1090
- unsubscribeRuntimeMessages?.();
1091
- unsubscribeRuntimeMessages = null;
1092
- try {
1093
- await activeHandle.dispose();
1094
- } catch (error) {
1095
- logger.debug("[ClaudeACP] Error disposing runtime handle:", error);
1096
- }
1097
- };
1098
- const queueHistoryInjectionForRestart = (reason) => {
1099
- messageBuffer.addMessage("\u2550".repeat(40), "status");
1100
- if (conversationHistory.hasHistory()) {
1101
- shouldInjectHistoryOnNextSession = true;
1102
- const message = `${reason} Preserving ${conversationHistory.size()} earlier messages of context.`;
1103
- emitStatusMessage(message);
1104
- logger.debug(`[ClaudeACP] Will inject conversation history after restart: ${conversationHistory.getSummary()}`);
1105
- return;
1106
- }
1107
- emitStatusMessage(reason);
1108
- };
1109
- const setupRuntimeMessageHandler = (activeRuntimeHandle) => {
1110
- const forwardAgentMessage = (agentMessage) => {
1111
- forwardAgentMessageToProviderSession(agentMessage, {
1112
- provider: "claude",
1113
- send: (body) => session.client.sendAgentMessage("claude", body)
1114
- });
1115
- };
1116
- unsubscribeRuntimeMessages?.();
1117
- unsubscribeRuntimeMessages = activeRuntimeHandle.onMessage((msg) => {
1118
- switch (msg.type) {
1119
- case "model-output": {
1120
- const text = msg.textDelta ?? msg.fullText ?? "";
1121
- if (!text) {
1122
- return;
1123
- }
1124
- if (!isResponseInProgress) {
1125
- if (currentThinkingMessageId) {
1126
- messageBuffer.removeMessage(currentThinkingMessageId);
1127
- currentThinkingMessageId = null;
1128
- }
1129
- currentAssistantMessageId = messageBuffer.addMessage(text, "assistant");
1130
- isResponseInProgress = true;
1131
- } else if (currentAssistantMessageId) {
1132
- const updated = messageBuffer.updateMessage(currentAssistantMessageId, text);
1133
- if (!updated) {
1134
- currentAssistantMessageId = messageBuffer.addMessage(text, "assistant");
1135
- }
1136
- }
1137
- accumulatedResponse += text;
1138
- return;
1139
- }
1140
- case "status": {
1141
- if (msg.status === "running") {
1142
- session.onThinkingChange(true);
1143
- if (!taskStartedSent) {
1144
- session.client.sendAgentMessage("claude", {
1145
- type: "task_started",
1146
- id: randomUUID()
1147
- });
1148
- taskStartedSent = true;
1149
- }
1150
- if (!isResponseInProgress && !currentThinkingMessageId) {
1151
- currentThinkingMessageId = messageBuffer.addMessage("Thinking...", "system");
1152
- }
1153
- return;
1154
- }
1155
- if (msg.status === "idle" || msg.status === "stopped") {
1156
- session.onThinkingChange(false);
1157
- return;
1158
- }
1159
- if (msg.status === "error") {
1160
- messageBuffer.addMessage(`Error: ${normalizeClaudeBackendError(msg.detail)}`, "status");
1161
- }
1162
- return;
1163
- }
1164
- case "tool-call": {
1165
- const toolArgs = truncateDisplayMessage(msg.args, 100);
1166
- messageBuffer.addMessage(
1167
- `Executing: ${msg.toolName}${toolArgs ? ` ${toolArgs}` : ""}`,
1168
- "tool"
1169
- );
1170
- forwardAgentMessage(msg);
1171
- return;
1172
- }
1173
- case "tool-result": {
1174
- const resultText = truncateDisplayMessage(msg.result, 200);
1175
- messageBuffer.addMessage(resultText ? `Result: ${resultText}` : "Tool completed", "result");
1176
- forwardAgentMessage(msg);
1177
- return;
1178
- }
1179
- case "fs-edit": {
1180
- messageBuffer.addMessage(`File edit: ${msg.description}`, "tool");
1181
- forwardAgentMessage(msg);
1182
- return;
1183
- }
1184
- case "terminal-output": {
1185
- const output = formatDisplayMessage(msg.data);
1186
- messageBuffer.addMessage(output, "result");
1187
- forwardAgentMessage({
1188
- ...msg,
1189
- data: output
1190
- });
1191
- return;
1192
- }
1193
- case "permission-request": {
1194
- forwardAgentMessage(msg);
1195
- return;
1196
- }
1197
- case "token-count": {
1198
- forwardAgentMessage(msg);
1199
- return;
1200
- }
1201
- case "exec-approval-request":
1202
- case "patch-apply-begin":
1203
- case "patch-apply-end": {
1204
- forwardAgentMessage(msg);
1205
- return;
1206
- }
1207
- case "event": {
1208
- if (msg.name === "thinking") {
1209
- const payload = msg.payload;
1210
- const thinkingText = typeof payload?.text === "string" ? payload.text : "";
1211
- if (thinkingText) {
1212
- session.client.sendAgentMessage("claude", {
1213
- type: "thinking",
1214
- text: thinkingText
1215
- });
1216
- if (!isResponseInProgress) {
1217
- const preview = `[Thinking] ${thinkingText.substring(0, 100)}...`;
1218
- if (currentThinkingMessageId) {
1219
- messageBuffer.updateMessage(currentThinkingMessageId, preview, { mode: "replace" });
1220
- } else {
1221
- currentThinkingMessageId = messageBuffer.addMessage(preview, "system");
1222
- }
1223
- }
1224
- }
1225
- }
1226
- return;
1227
- }
1228
- default:
1229
- return;
1230
- }
1231
- });
1232
- };
1233
- const createRuntimeHandle = async (mode) => {
1234
- const { session: nextRuntimeHandle, factoryResult } = await launchRuntimeHandleWithFactoryResult({
1235
- provider: "claude",
1236
- cwd: session.path,
1237
- createBackendResult: (opts) => createClaudeBackend({
1238
- ...opts,
1239
- baseArgs: session.claudeArgs,
1240
- mcpServers: session.mcpServers,
1241
- permissionHandler,
1242
- selectionHandler: {
1243
- handleSelection: (request) => selectionHandler.requestSelection(request)
1244
- },
1245
- permissionMode: mode.permissionMode,
1246
- model: mode.model,
1247
- fallbackModel: mode.fallbackModel,
1248
- customSystemPrompt: mode.customSystemPrompt ? `${mode.customSystemPrompt}
1249
-
1250
- ${systemPrompt}` : void 0,
1251
- appendSystemPrompt: mode.appendSystemPrompt ? `${mode.appendSystemPrompt}
1252
-
1253
- ${systemPrompt}` : systemPrompt,
1254
- allowedTools: mode.allowedTools ? mode.allowedTools.concat(session.allowedTools ?? []) : session.allowedTools,
1255
- disallowedTools: mode.disallowedTools,
1256
- settingsPath: session.hookSettingsPath
1257
- })
1258
- });
1259
- logger.debug("[ClaudeACP] Started Claude ACP backend", {
1260
- command: factoryResult.command,
1261
- args: factoryResult.args
1262
- });
1263
- runtimeHandle = nextRuntimeHandle;
1264
- setupRuntimeMessageHandler(nextRuntimeHandle);
1265
- session.consumeOneTimeFlags();
1266
- return nextRuntimeHandle;
1267
- };
1268
- const abortActiveTurn = async () => {
1269
- const activeController = rotateAbortController();
1270
- activeController.abort();
1271
- session.onThinkingChange(false);
1272
- if (runtimeHandle) {
1273
- await runtimeHandle.cancel().catch((error) => {
1274
- logger.debug("[ClaudeACP] Error cancelling runtime handle:", error);
1275
- });
1276
- }
1277
- };
1278
- const completeSyntheticTurn = () => {
1279
- session.client.sendAgentMessage("claude", {
1280
- type: "task_complete",
1281
- id: randomUUID()
1282
- });
1283
- };
1284
- const handleClearCommand = async () => {
1285
- logger.debug("[ClaudeACP] /clear command received - resetting runtime and local history");
1286
- conversationHistory.clear();
1287
- shouldInjectHistoryOnNextSession = false;
1288
- await disposeRuntimeHandle();
1289
- session.clearSessionId();
1290
- currentModeHash = null;
1291
- permissionHandler.reset();
1292
- selectionHandler.reset();
1293
- resetTurnState();
1294
- emitStatusMessage("Context was reset");
1295
- completeSyntheticTurn();
1296
- };
1297
- const handleSwitchToLocal = async () => {
1298
- const message = "Daemon-spawned Claude ACP sessions stay in remote mode.";
1299
- logger.debug("[ClaudeACP] Ignoring switch request because daemon sessions are remote-only");
1300
- emitStatusMessage(message);
1301
- };
1302
- const handleAbort = async () => {
1303
- logger.debug("[ClaudeACP] Abort requested - stopping current task");
1304
- await abortActiveTurn();
1305
- };
1306
- if (hasTTY) {
1307
- console.clear();
1308
- inkInstance = render(React.createElement(RemoteModeDisplay, {
1309
- messageBuffer,
1310
- logPath: process.env.DEBUG ? session.logPath : void 0,
1311
- onExit: async () => {
1312
- shouldExit = true;
1313
- await handleAbort();
1314
- },
1315
- onSwitchToLocal: () => {
1316
- void handleSwitchToLocal();
1317
- }
1318
- }), {
1319
- exitOnCtrlC: false,
1320
- patchConsole: false
1321
- });
1322
- }
1323
- if (hasTTY) {
1324
- process.stdin.resume();
1325
- if (process.stdin.isTTY) {
1326
- process.stdin.setRawMode(true);
1327
- }
1328
- process.stdin.setEncoding("utf8");
1329
- }
1330
- session.client.rpcHandlerManager.registerHandler("abort", handleAbort);
1331
- session.client.rpcHandlerManager.registerHandler("switch", handleSwitchToLocal);
1332
- try {
1333
- while (!shouldExit) {
1334
- let message = pending;
1335
- pending = null;
1336
- if (!message) {
1337
- const waitSignal = abortController.signal;
1338
- const batch = await session.queue.waitForMessagesAndGetAsString(waitSignal);
1339
- if (!batch) {
1340
- if (waitSignal.aborted && !shouldExit) {
1341
- continue;
1342
- }
1343
- break;
1344
- }
1345
- message = batch;
1346
- }
1347
- if (!message) {
1348
- break;
1349
- }
1350
- if (runtimeHandle && currentModeHash && message.hash !== currentModeHash) {
1351
- queueHistoryInjectionForRestart("Starting new Claude ACP session (execution settings changed)...");
1352
- await disposeRuntimeHandle();
1353
- session.clearSessionId();
1354
- currentModeHash = null;
1355
- pending = message;
1356
- permissionHandler.reset();
1357
- selectionHandler.reset();
1358
- resetTurnState();
1359
- continue;
1360
- }
1361
- currentModeHash = message.hash;
1362
- readyAlreadySent = false;
1363
- const specialCommand = parseSpecialCommand(message.message);
1364
- if (specialCommand.type === "clear") {
1365
- await handleClearCommand();
1366
- if (!shouldExit && !pending && session.queue.size() === 0 && !readyAlreadySent) {
1367
- sendReady();
1368
- readyAlreadySent = true;
1369
- }
1370
- continue;
1371
- }
1372
- permissionHandler.setPermissionMode(message.mode.permissionMode);
1373
- messageBuffer.addMessage(message.message, "user");
1374
- let shouldClearHistoryAfterTurn = false;
1375
- try {
1376
- const activeRuntimeHandle = runtimeHandle ?? await createRuntimeHandle(message.mode);
1377
- let promptToSend = message.message;
1378
- if (shouldInjectHistoryOnNextSession && conversationHistory.hasHistory()) {
1379
- const historyContext = conversationHistory.getContextForNewSession(
1380
- "Continue from the prior Claude ACP session using the conversation below as context."
1381
- );
1382
- promptToSend = historyContext + promptToSend;
1383
- logger.debug(`[ClaudeACP] Injected conversation history context (${historyContext.length} chars)`);
1384
- }
1385
- if (specialCommand.type === "compact") {
1386
- emitStatusMessage("Compaction started");
1387
- }
1388
- conversationHistory.addUserMessage(message.message);
1389
- await activeRuntimeHandle.sendPrompt(promptToSend);
1390
- await waitForResponseCompleteWithAbort(activeRuntimeHandle.backend, abortController.signal);
1391
- shouldInjectHistoryOnNextSession = false;
1392
- shouldClearHistoryAfterTurn = specialCommand.type === "compact";
1393
- } catch (error) {
1394
- logger.warn("[ClaudeACP] Error in Claude ACP session:", error);
1395
- const isAbortError = error instanceof Error && error.name === "AbortError";
1396
- const isExpectedInterruption = isAbortError || abortController.signal.aborted || shouldExit;
1397
- if (isExpectedInterruption) {
1398
- session.client.sendAgentMessage("claude", {
1399
- type: "turn_aborted",
1400
- id: randomUUID()
1401
- });
1402
- emitStatusMessage("Aborted by user");
1403
- } else {
1404
- const errorMessage = normalizeClaudeBackendError(error);
1405
- emitUserVisibleErrorMessage(errorMessage);
1406
- if (conversationHistory.hasHistory()) {
1407
- shouldInjectHistoryOnNextSession = true;
1408
- }
1409
- await disposeRuntimeHandle();
1410
- session.clearSessionId();
1411
- currentModeHash = null;
1412
- }
1413
- } finally {
1414
- emitFinalAssistantMessage();
1415
- if (shouldClearHistoryAfterTurn) {
1416
- conversationHistory.clear();
1417
- emitStatusMessage("Compaction completed");
1418
- }
1419
- if (!shouldExit) {
1420
- session.client.sendAgentMessage("claude", {
1421
- type: "task_complete",
1422
- id: randomUUID()
1423
- });
1424
- }
1425
- permissionHandler.reset();
1426
- selectionHandler.reset();
1427
- resetTurnState();
1428
- if (!shouldExit && !pending && session.queue.size() === 0 && !readyAlreadySent) {
1429
- sendReady();
1430
- readyAlreadySent = true;
1431
- }
1432
- }
1433
- }
1434
- } finally {
1435
- await disposeRuntimeHandle();
1436
- permissionHandler.reset();
1437
- selectionHandler.reset();
1438
- if (process.stdin.isTTY) {
1439
- try {
1440
- process.stdin.setRawMode(false);
1441
- } catch {
1442
- }
1443
- }
1444
- if (hasTTY) {
1445
- try {
1446
- process.stdin.pause();
1447
- } catch {
1448
- }
1449
- }
1450
- session.client.rpcHandlerManager.registerHandler("abort", async () => {
1451
- });
1452
- session.client.rpcHandlerManager.registerHandler("switch", async () => {
1453
- });
1454
- if (inkInstance) {
1455
- inkInstance.unmount();
1456
- }
1457
- messageBuffer.clear();
1458
- }
1459
- logger.debug("[ClaudeACP] Remote launcher returning: exit");
1460
- return "exit";
1461
- }
1462
-
1463
843
  class Stream {
1464
844
  constructor(returned) {
1465
845
  this.returned = returned;
@@ -1985,6 +1365,50 @@ function query(config) {
1985
1365
  return query2;
1986
1366
  }
1987
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
+ };
1386
+ }
1387
+ function parseClear(message) {
1388
+ const trimmed = message.trim();
1389
+ return {
1390
+ isClear: trimmed === "/clear"
1391
+ };
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
1409
+ };
1410
+ }
1411
+
1988
1412
  class PushableAsyncIterable {
1989
1413
  queue = [];
1990
1414
  waiters = [];
@@ -2124,6 +1548,58 @@ async function awaitFileExist(file, timeout = 1e4) {
2124
1548
  return false;
2125
1549
  }
2126
1550
 
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
+
2127
1603
  async function claudeRemote(opts) {
2128
1604
  let startFrom = opts.sessionId;
2129
1605
  if (opts.sessionId && !claudeCheckSession(opts.sessionId, opts.path, opts.transcriptPath)) {
@@ -2327,6 +1803,16 @@ function getToolName(toolName) {
2327
1803
  return toTitleCase(toolName);
2328
1804
  }
2329
1805
 
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
+
2330
1816
  class PermissionHandler {
2331
1817
  toolCalls = [];
2332
1818
  responses = /* @__PURE__ */ new Map();
@@ -3113,8 +2599,9 @@ class OutgoingMessageQueue {
3113
2599
  }
3114
2600
  }
3115
2601
 
3116
- async function claudeRemoteLauncher(session) {
2602
+ async function claudeRemoteLauncher(session, options = {}) {
3117
2603
  logger.debug("[claudeRemoteLauncher] Starting remote launcher");
2604
+ const allowSwitchToLocal = options.allowSwitchToLocal ?? true;
3118
2605
  const hasTTY = process.stdout.isTTY && process.stdin.isTTY;
3119
2606
  logger.debug(`[claudeRemoteLauncher] TTY available: ${hasTTY}`);
3120
2607
  let messageBuffer = new MessageBuffer();
@@ -3132,6 +2619,11 @@ async function claudeRemoteLauncher(session) {
3132
2619
  await abort();
3133
2620
  },
3134
2621
  onSwitchToLocal: () => {
2622
+ if (!allowSwitchToLocal) {
2623
+ logger.debug("[remote]: Ignoring switch-to-local request for remote-only session");
2624
+ session.client.sendSessionEvent({ type: "message", message: "Daemon-spawned Claude sessions stay in remote mode." });
2625
+ return;
2626
+ }
3135
2627
  logger.debug("[remote]: Switching to local mode via double space");
3136
2628
  doSwitch();
3137
2629
  }
@@ -3161,6 +2653,11 @@ async function claudeRemoteLauncher(session) {
3161
2653
  await abort();
3162
2654
  }
3163
2655
  async function doSwitch() {
2656
+ if (!allowSwitchToLocal) {
2657
+ logger.debug("[remote]: Ignoring RPC switch-to-local request for remote-only session");
2658
+ session.client.sendSessionEvent({ type: "message", message: "Daemon-spawned Claude sessions stay in remote mode." });
2659
+ return;
2660
+ }
3164
2661
  logger.debug("[remote]: doSwitch");
3165
2662
  if (!exitReason) {
3166
2663
  exitReason = "switch";
@@ -3485,7 +2982,9 @@ async function loop(opts) {
3485
2982
  return { type: "exit", value: result.code };
3486
2983
  },
3487
2984
  remote: async () => {
3488
- const reason = opts.startedBy === "daemon" ? await claudeAcpRemoteLauncher(session) : await claudeRemoteLauncher(session);
2985
+ const reason = await claudeRemoteLauncher(session, {
2986
+ allowSwitchToLocal: opts.startedBy !== "daemon"
2987
+ });
3489
2988
  if (reason === "switch") {
3490
2989
  return { type: "switch", mode: "local" };
3491
2990
  }