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.
- package/bin/happy-cloud.mjs +1 -1
- package/dist/ConversationHistory-V3VLmjJf.cjs +868 -0
- package/dist/ConversationHistory-_ciJNIgH.mjs +856 -0
- package/dist/{api-BcWf5v4i.mjs → api-D1meoL-9.mjs} +2 -2
- package/dist/{api-Ye-rPX6s.cjs → api-DH5-IqeM.cjs} +2 -2
- package/dist/{command-nOI80Mnm.mjs → command-CMvWClny.mjs} +3 -3
- package/dist/{command-BK93nizl.cjs → command-Ch8Dgidj.cjs} +3 -3
- package/dist/createKeepAliveController-C5cQlDRr.mjs +51 -0
- package/dist/createKeepAliveController-DO8H6d5E.cjs +54 -0
- package/dist/{index-J7QKJ8lc.cjs → index-CryJfCh5.cjs} +54 -23
- package/dist/{index-DnsqY6I_.mjs → index-Cxrx9m5D.mjs} +53 -21
- package/dist/index.cjs +3 -3
- package/dist/index.mjs +3 -3
- package/dist/lib.cjs +1 -1
- package/dist/lib.mjs +1 -1
- package/dist/{persistence-BMa6cyw9.mjs → persistence-9Iu0wGNM.mjs} +1 -1
- package/dist/{persistence-xK5CKhbn.cjs → persistence-Bl3FYvwd.cjs} +1 -1
- package/dist/{registerKillSessionHandler-Dxwg4L4J.mjs → registerKillSessionHandler-BElGmD1E.mjs} +5 -541
- package/dist/{registerKillSessionHandler-CH6yN0eG.cjs → registerKillSessionHandler-BjkY-oUn.cjs} +4 -549
- package/dist/{runClaude-BMHlBny_.cjs → runClaude-CDZxAF3l.cjs} +129 -630
- package/dist/{runClaude-cQ-UT0Ke.mjs → runClaude-D7dF4RDM.mjs} +126 -627
- package/dist/{runCodex-roOSOWWL.cjs → runCodex-Cik8VzFs.cjs} +224 -17
- package/dist/{runCodex-AnJUPIhX.mjs → runCodex-DnGz1XES.mjs} +213 -6
- package/dist/{runGemini-BGo_0mzA.mjs → runGemini-B8tXMHeL.mjs} +5 -5
- package/dist/{runGemini-Cg6Zbqlz.cjs → runGemini-BM2BQ4I7.cjs} +13 -13
- package/package.json +9 -9
- package/scripts/build.mjs +66 -66
- package/scripts/devtools/README.md +9 -9
- package/scripts/e2e/fake-codex-acp-agent.mjs +139 -139
- package/scripts/e2e/local-server-session-roundtrip.mjs +1063 -1063
- package/scripts/release-smoke.mjs +202 -202
- package/dist/BaseReasoningProcessor-5ACv9gKu.mjs +0 -320
- package/dist/BaseReasoningProcessor-COrRWyn0.cjs +0 -323
- package/dist/ProviderSelectionHandler-BljOLMQn.mjs +0 -261
- 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-
|
|
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
|
|
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-
|
|
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,
|
|
26
|
+
import { c as createKeepAliveController, r as runModeLoop } from './createKeepAliveController-C5cQlDRr.mjs';
|
|
27
27
|
import { R as RawJSONLinesSchema } from './types-CiliQpqS.mjs';
|
|
28
|
-
import {
|
|
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 =
|
|
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
|
}
|