happy-imou-cloud 2.0.10 → 2.0.11
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/dist/BaseReasoningProcessor-5ACv9gKu.mjs +320 -0
- package/dist/BaseReasoningProcessor-COrRWyn0.cjs +323 -0
- package/dist/ProviderSelectionHandler-BljOLMQn.mjs +261 -0
- package/dist/ProviderSelectionHandler-DBGobhGZ.cjs +265 -0
- package/dist/{api-BjxmW-0W.mjs → api-BcWf5v4i.mjs} +174 -46
- package/dist/{api-DUE5TJBE.cjs → api-Ye-rPX6s.cjs} +174 -46
- package/dist/{command-Df7u5eAT.cjs → command-BK93nizl.cjs} +6 -6
- package/dist/{command-ComOeFLY.mjs → command-nOI80Mnm.mjs} +6 -6
- package/dist/{index-CzvgPwr1.mjs → index-DnsqY6I_.mjs} +189 -60
- package/dist/{index-Cuvfa15L.cjs → index-J7QKJ8lc.cjs} +194 -61
- package/dist/index.cjs +8 -8
- package/dist/index.mjs +8 -8
- package/dist/lib.cjs +2 -3
- package/dist/lib.d.cts +2 -0
- package/dist/lib.d.mts +2 -0
- package/dist/lib.mjs +2 -3
- package/dist/{persistence-BxP6Jw1f.mjs → persistence-BMa6cyw9.mjs} +2 -3
- package/dist/{persistence-D7JtnrYA.cjs → persistence-xK5CKhbn.cjs} +2 -3
- package/dist/registerKillSessionHandler-CH6yN0eG.cjs +1198 -0
- package/dist/registerKillSessionHandler-Dxwg4L4J.mjs +1179 -0
- package/dist/{runClaude-C1W_Nw0C.cjs → runClaude-BMHlBny_.cjs} +884 -398
- package/dist/{runClaude-B_fTMxc4.mjs → runClaude-cQ-UT0Ke.mjs} +882 -396
- package/dist/{runCodex-CUgOiIEz.mjs → runCodex-AnJUPIhX.mjs} +155 -578
- package/dist/{runCodex-D2VCWVEK.cjs → runCodex-roOSOWWL.cjs} +158 -583
- package/dist/{runGemini-2_FEtJYa.mjs → runGemini-BGo_0mzA.mjs} +142 -387
- package/dist/{runGemini-CiGnjflq.cjs → runGemini-Cg6Zbqlz.cjs} +145 -390
- package/package.json +9 -8
- package/scripts/e2e/fake-codex-acp-agent.mjs +139 -0
- package/scripts/e2e/local-server-session-roundtrip.mjs +1063 -0
- package/scripts/release-smoke.mjs +23 -6
- package/dist/names-CicSgRNg.mjs +0 -625
- package/dist/names-yJNZoTv_.cjs +0 -636
- package/dist/registerKillSessionHandler-ARQrPvnT.mjs +0 -454
- package/dist/registerKillSessionHandler-DCMFiXyA.cjs +0 -459
|
@@ -1,47 +1,48 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
var os = require('node:os');
|
|
4
3
|
var node_crypto = require('node:crypto');
|
|
5
|
-
var api = require('./api-
|
|
6
|
-
|
|
7
|
-
|
|
4
|
+
var api = require('./api-Ye-rPX6s.cjs');
|
|
5
|
+
require('cross-spawn');
|
|
6
|
+
require('@agentclientprotocol/sdk');
|
|
7
|
+
var index = require('./index-J7QKJ8lc.cjs');
|
|
8
|
+
require('ps-list');
|
|
9
|
+
require('fs');
|
|
10
|
+
require('path');
|
|
11
|
+
require('os');
|
|
12
|
+
require('child_process');
|
|
13
|
+
var fs$1 = require('node:fs');
|
|
8
14
|
var path = require('node:path');
|
|
15
|
+
var os = require('node:os');
|
|
16
|
+
require('./persistence-xK5CKhbn.cjs');
|
|
9
17
|
var promises = require('node:fs/promises');
|
|
10
18
|
var fs = require('fs/promises');
|
|
11
|
-
|
|
12
|
-
var registerKillSessionHandler = require('./registerKillSessionHandler-DCMFiXyA.cjs');
|
|
13
|
-
var React = require('react');
|
|
19
|
+
require('crypto');
|
|
14
20
|
var node_child_process = require('node:child_process');
|
|
21
|
+
require('chalk');
|
|
22
|
+
require('node:events');
|
|
23
|
+
require('axios');
|
|
24
|
+
require('tweetnacl');
|
|
25
|
+
require('open');
|
|
26
|
+
var React = require('react');
|
|
27
|
+
var ink = require('ink');
|
|
28
|
+
var ProviderSelectionHandler = require('./ProviderSelectionHandler-DBGobhGZ.cjs');
|
|
29
|
+
var types = require('./types-DVk3crez.cjs');
|
|
30
|
+
var registerKillSessionHandler = require('./registerKillSessionHandler-CH6yN0eG.cjs');
|
|
15
31
|
var node_readline = require('node:readline');
|
|
16
|
-
var fs$1 = require('node:fs');
|
|
17
32
|
var node_url = require('node:url');
|
|
18
|
-
require('axios');
|
|
19
|
-
require('node:events');
|
|
20
33
|
require('socket.io-client');
|
|
21
|
-
require('tweetnacl');
|
|
22
34
|
require('expo-server-sdk');
|
|
23
|
-
require('chalk');
|
|
24
35
|
var node_util = require('node:util');
|
|
25
|
-
var persistence = require('./persistence-D7JtnrYA.cjs');
|
|
26
36
|
var node_http = require('node:http');
|
|
27
|
-
require('fs');
|
|
28
37
|
require('zod');
|
|
29
|
-
require('child_process');
|
|
30
|
-
require('util');
|
|
31
|
-
require('crypto');
|
|
32
|
-
require('path');
|
|
33
|
-
require('os');
|
|
34
38
|
require('tmp');
|
|
35
39
|
require('qrcode-terminal');
|
|
36
40
|
require('node:module');
|
|
37
|
-
require('open');
|
|
38
41
|
require('url');
|
|
39
|
-
require('ps-list');
|
|
40
|
-
require('cross-spawn');
|
|
41
42
|
require('fastify');
|
|
42
43
|
require('fastify-type-provider-zod');
|
|
43
44
|
require('http');
|
|
44
|
-
require('
|
|
45
|
+
require('util');
|
|
45
46
|
|
|
46
47
|
var _documentCurrentScript = typeof document !== 'undefined' ? document.currentScript : null;
|
|
47
48
|
class Session {
|
|
@@ -66,8 +67,7 @@ class Session {
|
|
|
66
67
|
thinking = false;
|
|
67
68
|
/** Callbacks to be notified when session ID is found/changed */
|
|
68
69
|
sessionFoundCallbacks = [];
|
|
69
|
-
|
|
70
|
-
keepAliveInterval;
|
|
70
|
+
keepAliveController;
|
|
71
71
|
constructor(opts) {
|
|
72
72
|
this.path = opts.path;
|
|
73
73
|
this.api = opts.api;
|
|
@@ -82,26 +82,29 @@ class Session {
|
|
|
82
82
|
this._onModeChange = opts.onModeChange;
|
|
83
83
|
this.hookSettingsPath = opts.hookSettingsPath;
|
|
84
84
|
this.jsRuntime = opts.jsRuntime ?? "node";
|
|
85
|
-
this.
|
|
86
|
-
|
|
87
|
-
this.
|
|
88
|
-
|
|
85
|
+
this.keepAliveController = ProviderSelectionHandler.createKeepAliveController({
|
|
86
|
+
initialMode: this.mode,
|
|
87
|
+
initialThinking: this.thinking,
|
|
88
|
+
send: (thinking, mode) => {
|
|
89
|
+
this.client.keepAlive(thinking, mode);
|
|
90
|
+
}
|
|
91
|
+
});
|
|
89
92
|
}
|
|
90
93
|
/**
|
|
91
94
|
* Cleanup resources (call when session is no longer needed)
|
|
92
95
|
*/
|
|
93
96
|
cleanup = () => {
|
|
94
|
-
|
|
97
|
+
this.keepAliveController.dispose();
|
|
95
98
|
this.sessionFoundCallbacks = [];
|
|
96
99
|
api.logger.debug("[Session] Cleaned up resources");
|
|
97
100
|
};
|
|
98
101
|
onThinkingChange = (thinking) => {
|
|
99
102
|
this.thinking = thinking;
|
|
100
|
-
this.
|
|
103
|
+
this.keepAliveController.setThinking(thinking);
|
|
101
104
|
};
|
|
102
105
|
onModeChange = (mode) => {
|
|
103
106
|
this.mode = mode;
|
|
104
|
-
this.
|
|
107
|
+
this.keepAliveController.setMode(mode);
|
|
105
108
|
this._onModeChange(mode);
|
|
106
109
|
};
|
|
107
110
|
/**
|
|
@@ -715,6 +718,50 @@ async function claudeLocalLauncher(session) {
|
|
|
715
718
|
return exitReason || { type: "exit", code: 0 };
|
|
716
719
|
}
|
|
717
720
|
|
|
721
|
+
function parseCompact(message) {
|
|
722
|
+
const trimmed = message.trim();
|
|
723
|
+
if (trimmed === "/compact") {
|
|
724
|
+
return {
|
|
725
|
+
isCompact: true,
|
|
726
|
+
originalMessage: trimmed
|
|
727
|
+
};
|
|
728
|
+
}
|
|
729
|
+
if (trimmed.startsWith("/compact ")) {
|
|
730
|
+
return {
|
|
731
|
+
isCompact: true,
|
|
732
|
+
originalMessage: trimmed
|
|
733
|
+
};
|
|
734
|
+
}
|
|
735
|
+
return {
|
|
736
|
+
isCompact: false,
|
|
737
|
+
originalMessage: message
|
|
738
|
+
};
|
|
739
|
+
}
|
|
740
|
+
function parseClear(message) {
|
|
741
|
+
const trimmed = message.trim();
|
|
742
|
+
return {
|
|
743
|
+
isClear: trimmed === "/clear"
|
|
744
|
+
};
|
|
745
|
+
}
|
|
746
|
+
function parseSpecialCommand(message) {
|
|
747
|
+
const compactResult = parseCompact(message);
|
|
748
|
+
if (compactResult.isCompact) {
|
|
749
|
+
return {
|
|
750
|
+
type: "compact",
|
|
751
|
+
originalMessage: compactResult.originalMessage
|
|
752
|
+
};
|
|
753
|
+
}
|
|
754
|
+
const clearResult = parseClear(message);
|
|
755
|
+
if (clearResult.isClear) {
|
|
756
|
+
return {
|
|
757
|
+
type: "clear"
|
|
758
|
+
};
|
|
759
|
+
}
|
|
760
|
+
return {
|
|
761
|
+
type: null
|
|
762
|
+
};
|
|
763
|
+
}
|
|
764
|
+
|
|
718
765
|
const RemoteModeDisplay = ({ messageBuffer, logPath, onExit, onSwitchToLocal }) => {
|
|
719
766
|
const [messages, setMessages] = React.useState([]);
|
|
720
767
|
const [confirmationMode, setConfirmationMode] = React.useState(null);
|
|
@@ -806,39 +853,615 @@ const RemoteModeDisplay = ({ messageBuffer, logPath, onExit, onSwitchToLocal })
|
|
|
806
853
|
for (let i = 0; i < line.length; i += maxLineLength) {
|
|
807
854
|
chunks.push(line.slice(i, i + maxLineLength));
|
|
808
855
|
}
|
|
809
|
-
return chunks.join("\n");
|
|
810
|
-
}).join("\n");
|
|
811
|
-
};
|
|
812
|
-
return /* @__PURE__ */ React.createElement(ink.Box, { flexDirection: "column", width: terminalWidth, height: terminalHeight }, /* @__PURE__ */ React.createElement(
|
|
813
|
-
ink.Box,
|
|
814
|
-
{
|
|
815
|
-
flexDirection: "column",
|
|
816
|
-
width: terminalWidth,
|
|
817
|
-
height: terminalHeight - 4,
|
|
818
|
-
borderStyle: "round",
|
|
819
|
-
borderColor: "gray",
|
|
820
|
-
paddingX: 1,
|
|
821
|
-
overflow: "hidden"
|
|
822
|
-
},
|
|
823
|
-
/* @__PURE__ */ React.createElement(ink.Box, { flexDirection: "column", marginBottom: 1 }, /* @__PURE__ */ React.createElement(ink.Text, { color: "gray", bold: true }, "\u{1F4E1} Remote Mode - Claude Messages"), /* @__PURE__ */ React.createElement(ink.Text, { color: "gray", dimColor: true }, "\u2500".repeat(Math.min(terminalWidth - 4, 60)))),
|
|
824
|
-
/* @__PURE__ */ React.createElement(ink.Box, { flexDirection: "column", height: terminalHeight - 10, overflow: "hidden" }, messages.length === 0 ? /* @__PURE__ */ React.createElement(ink.Text, { color: "gray", dimColor: true }, "Waiting for messages...") : (
|
|
825
|
-
// Show only the last messages that fit in the available space
|
|
826
|
-
messages.slice(-Math.max(1, terminalHeight - 10)).map((msg) => /* @__PURE__ */ React.createElement(ink.Box, { key: msg.id, flexDirection: "column", marginBottom: 1 }, /* @__PURE__ */ React.createElement(ink.Text, { color: getMessageColor(msg.type), dimColor: true }, formatMessage(msg))))
|
|
827
|
-
))
|
|
828
|
-
), /* @__PURE__ */ React.createElement(
|
|
829
|
-
ink.Box,
|
|
830
|
-
{
|
|
831
|
-
width: terminalWidth,
|
|
832
|
-
borderStyle: "round",
|
|
833
|
-
borderColor: actionInProgress ? "gray" : confirmationMode === "exit" ? "red" : confirmationMode === "switch" ? "yellow" : "green",
|
|
834
|
-
paddingX: 2,
|
|
835
|
-
justifyContent: "center",
|
|
836
|
-
alignItems: "center",
|
|
837
|
-
flexDirection: "column"
|
|
838
|
-
},
|
|
839
|
-
/* @__PURE__ */ React.createElement(ink.Box, { flexDirection: "column", alignItems: "center" }, actionInProgress === "exiting" ? /* @__PURE__ */ React.createElement(ink.Text, { color: "gray", bold: true }, "Exiting...") : actionInProgress === "switching" ? /* @__PURE__ */ React.createElement(ink.Text, { color: "gray", bold: true }, "Switching to local mode...") : confirmationMode === "exit" ? /* @__PURE__ */ React.createElement(ink.Text, { color: "red", bold: true }, "\u26A0\uFE0F Press Ctrl-C again to exit completely") : confirmationMode === "switch" ? /* @__PURE__ */ React.createElement(ink.Text, { color: "yellow", bold: true }, "\u23F8\uFE0F Press space again to switch to local mode") : /* @__PURE__ */ React.createElement(React.Fragment, null, /* @__PURE__ */ React.createElement(ink.Text, { color: "green", bold: true }, "\u{1F4F1} Press space to switch to local mode \u2022 Ctrl-C to exit")), process.env.DEBUG && logPath && /* @__PURE__ */ React.createElement(ink.Text, { color: "gray", dimColor: true }, "Debug logs: ", logPath))
|
|
840
|
-
));
|
|
841
|
-
};
|
|
856
|
+
return chunks.join("\n");
|
|
857
|
+
}).join("\n");
|
|
858
|
+
};
|
|
859
|
+
return /* @__PURE__ */ React.createElement(ink.Box, { flexDirection: "column", width: terminalWidth, height: terminalHeight }, /* @__PURE__ */ React.createElement(
|
|
860
|
+
ink.Box,
|
|
861
|
+
{
|
|
862
|
+
flexDirection: "column",
|
|
863
|
+
width: terminalWidth,
|
|
864
|
+
height: terminalHeight - 4,
|
|
865
|
+
borderStyle: "round",
|
|
866
|
+
borderColor: "gray",
|
|
867
|
+
paddingX: 1,
|
|
868
|
+
overflow: "hidden"
|
|
869
|
+
},
|
|
870
|
+
/* @__PURE__ */ React.createElement(ink.Box, { flexDirection: "column", marginBottom: 1 }, /* @__PURE__ */ React.createElement(ink.Text, { color: "gray", bold: true }, "\u{1F4E1} Remote Mode - Claude Messages"), /* @__PURE__ */ React.createElement(ink.Text, { color: "gray", dimColor: true }, "\u2500".repeat(Math.min(terminalWidth - 4, 60)))),
|
|
871
|
+
/* @__PURE__ */ React.createElement(ink.Box, { flexDirection: "column", height: terminalHeight - 10, overflow: "hidden" }, messages.length === 0 ? /* @__PURE__ */ React.createElement(ink.Text, { color: "gray", dimColor: true }, "Waiting for messages...") : (
|
|
872
|
+
// Show only the last messages that fit in the available space
|
|
873
|
+
messages.slice(-Math.max(1, terminalHeight - 10)).map((msg) => /* @__PURE__ */ React.createElement(ink.Box, { key: msg.id, flexDirection: "column", marginBottom: 1 }, /* @__PURE__ */ React.createElement(ink.Text, { color: getMessageColor(msg.type), dimColor: true }, formatMessage(msg))))
|
|
874
|
+
))
|
|
875
|
+
), /* @__PURE__ */ React.createElement(
|
|
876
|
+
ink.Box,
|
|
877
|
+
{
|
|
878
|
+
width: terminalWidth,
|
|
879
|
+
borderStyle: "round",
|
|
880
|
+
borderColor: actionInProgress ? "gray" : confirmationMode === "exit" ? "red" : confirmationMode === "switch" ? "yellow" : "green",
|
|
881
|
+
paddingX: 2,
|
|
882
|
+
justifyContent: "center",
|
|
883
|
+
alignItems: "center",
|
|
884
|
+
flexDirection: "column"
|
|
885
|
+
},
|
|
886
|
+
/* @__PURE__ */ React.createElement(ink.Box, { flexDirection: "column", alignItems: "center" }, actionInProgress === "exiting" ? /* @__PURE__ */ React.createElement(ink.Text, { color: "gray", bold: true }, "Exiting...") : actionInProgress === "switching" ? /* @__PURE__ */ React.createElement(ink.Text, { color: "gray", bold: true }, "Switching to local mode...") : confirmationMode === "exit" ? /* @__PURE__ */ React.createElement(ink.Text, { color: "red", bold: true }, "\u26A0\uFE0F Press Ctrl-C again to exit completely") : confirmationMode === "switch" ? /* @__PURE__ */ React.createElement(ink.Text, { color: "yellow", bold: true }, "\u23F8\uFE0F Press space again to switch to local mode") : /* @__PURE__ */ React.createElement(React.Fragment, null, /* @__PURE__ */ React.createElement(ink.Text, { color: "green", bold: true }, "\u{1F4F1} Press space to switch to local mode \u2022 Ctrl-C to exit")), process.env.DEBUG && logPath && /* @__PURE__ */ React.createElement(ink.Text, { color: "gray", dimColor: true }, "Debug logs: ", logPath))
|
|
887
|
+
));
|
|
888
|
+
};
|
|
889
|
+
|
|
890
|
+
function getClaudeSettingsPath() {
|
|
891
|
+
const claudeConfigDir = process.env.CLAUDE_CONFIG_DIR || path.join(os.homedir(), ".claude");
|
|
892
|
+
return path.join(claudeConfigDir, "settings.json");
|
|
893
|
+
}
|
|
894
|
+
function readClaudeSettings() {
|
|
895
|
+
try {
|
|
896
|
+
const settingsPath = getClaudeSettingsPath();
|
|
897
|
+
if (!fs$1.existsSync(settingsPath)) {
|
|
898
|
+
api.logger.debug(`[ClaudeSettings] No Claude settings file found at ${settingsPath}`);
|
|
899
|
+
return null;
|
|
900
|
+
}
|
|
901
|
+
const settingsContent = fs$1.readFileSync(settingsPath, "utf-8");
|
|
902
|
+
const settings = JSON.parse(settingsContent);
|
|
903
|
+
api.logger.debug(`[ClaudeSettings] Successfully read Claude settings from ${settingsPath}`);
|
|
904
|
+
api.logger.debug(`[ClaudeSettings] includeCoAuthoredBy: ${settings.includeCoAuthoredBy}`);
|
|
905
|
+
return settings;
|
|
906
|
+
} catch (error) {
|
|
907
|
+
api.logger.debug(`[ClaudeSettings] Error reading Claude settings: ${error}`);
|
|
908
|
+
return null;
|
|
909
|
+
}
|
|
910
|
+
}
|
|
911
|
+
function shouldIncludeCoAuthoredBy() {
|
|
912
|
+
const settings = readClaudeSettings();
|
|
913
|
+
if (!settings || settings.includeCoAuthoredBy === void 0) {
|
|
914
|
+
return true;
|
|
915
|
+
}
|
|
916
|
+
return settings.includeCoAuthoredBy;
|
|
917
|
+
}
|
|
918
|
+
|
|
919
|
+
const BASE_SYSTEM_PROMPT = (() => index.trimIdent(`
|
|
920
|
+
Help human.
|
|
921
|
+
`))();
|
|
922
|
+
const CO_AUTHORED_CREDITS = (() => index.trimIdent(`
|
|
923
|
+
When making commit messages, instead of just giving co-credit to Claude, also give credit to Happy like so:
|
|
924
|
+
|
|
925
|
+
<main commit message>
|
|
926
|
+
|
|
927
|
+
Generated with [Claude Code](https://claude.ai/code)
|
|
928
|
+
via [Happy](https://happy.engineering)
|
|
929
|
+
|
|
930
|
+
Co-Authored-By: Claude <noreply@anthropic.com>
|
|
931
|
+
Co-Authored-By: Happy <yesreply@happy.engineering>
|
|
932
|
+
`))();
|
|
933
|
+
const systemPrompt = (() => {
|
|
934
|
+
const includeCoAuthored = shouldIncludeCoAuthoredBy();
|
|
935
|
+
if (includeCoAuthored) {
|
|
936
|
+
return BASE_SYSTEM_PROMPT + "\n\n" + CO_AUTHORED_CREDITS;
|
|
937
|
+
} else {
|
|
938
|
+
return BASE_SYSTEM_PROMPT;
|
|
939
|
+
}
|
|
940
|
+
})();
|
|
941
|
+
|
|
942
|
+
function getToolDescriptor(toolName) {
|
|
943
|
+
if (toolName === "exit_plan_mode" || toolName === "ExitPlanMode") {
|
|
944
|
+
return { edit: false, exitPlan: true };
|
|
945
|
+
}
|
|
946
|
+
if (toolName === "Edit" || toolName === "MultiEdit" || toolName === "Write" || toolName === "NotebookEdit") {
|
|
947
|
+
return { edit: true, exitPlan: false };
|
|
948
|
+
}
|
|
949
|
+
return { edit: false, exitPlan: false };
|
|
950
|
+
}
|
|
951
|
+
|
|
952
|
+
class ClaudeAcpPermissionHandler extends registerKillSessionHandler.BasePermissionHandler {
|
|
953
|
+
currentPermissionMode = "default";
|
|
954
|
+
constructor(session) {
|
|
955
|
+
super(session);
|
|
956
|
+
}
|
|
957
|
+
getLogPrefix() {
|
|
958
|
+
return "[ClaudeACP]";
|
|
959
|
+
}
|
|
960
|
+
setPermissionMode(mode) {
|
|
961
|
+
this.currentPermissionMode = mode;
|
|
962
|
+
api.logger.debug(`${this.getLogPrefix()} Permission mode set to: ${mode}`);
|
|
963
|
+
}
|
|
964
|
+
shouldAutoApprove(toolName) {
|
|
965
|
+
const descriptor = getToolDescriptor(toolName);
|
|
966
|
+
switch (this.currentPermissionMode) {
|
|
967
|
+
case "bypassPermissions":
|
|
968
|
+
case "yolo":
|
|
969
|
+
return true;
|
|
970
|
+
case "acceptEdits":
|
|
971
|
+
return descriptor.edit;
|
|
972
|
+
default:
|
|
973
|
+
return false;
|
|
974
|
+
}
|
|
975
|
+
}
|
|
976
|
+
async handleToolCall(toolCallId, toolName, input) {
|
|
977
|
+
if (this.shouldAutoApprove(toolName)) {
|
|
978
|
+
const decision = this.currentPermissionMode === "bypassPermissions" || this.currentPermissionMode === "yolo" ? "approved_for_session" : "approved";
|
|
979
|
+
this.session.updateAgentState((currentState) => ({
|
|
980
|
+
...currentState,
|
|
981
|
+
completedRequests: {
|
|
982
|
+
...currentState.completedRequests,
|
|
983
|
+
[toolCallId]: {
|
|
984
|
+
tool: toolName,
|
|
985
|
+
arguments: input,
|
|
986
|
+
createdAt: Date.now(),
|
|
987
|
+
completedAt: Date.now(),
|
|
988
|
+
status: "approved",
|
|
989
|
+
decision
|
|
990
|
+
}
|
|
991
|
+
}
|
|
992
|
+
}));
|
|
993
|
+
api.logger.debug(`${this.getLogPrefix()} Auto-approved tool ${toolName} (${toolCallId}) in ${this.currentPermissionMode} mode`);
|
|
994
|
+
return { decision };
|
|
995
|
+
}
|
|
996
|
+
return this.registerPendingRequest(
|
|
997
|
+
toolCallId,
|
|
998
|
+
toolName,
|
|
999
|
+
input,
|
|
1000
|
+
` in ${this.currentPermissionMode} mode`
|
|
1001
|
+
);
|
|
1002
|
+
}
|
|
1003
|
+
}
|
|
1004
|
+
|
|
1005
|
+
function normalizeClaudeBackendError(error) {
|
|
1006
|
+
const record = typeof error === "object" && error !== null ? error : null;
|
|
1007
|
+
const text = index.formatDisplayMessage(error).trim();
|
|
1008
|
+
const stderrText = record ? index.formatDisplayMessage(record.stderr).trim() : "";
|
|
1009
|
+
const detailText = record ? index.formatDisplayMessage(record.detail).trim() : "";
|
|
1010
|
+
const searchable = [text, stderrText, detailText].filter(Boolean).join("\n").trim();
|
|
1011
|
+
return searchable || "Claude ACP backend exited unexpectedly";
|
|
1012
|
+
}
|
|
1013
|
+
async function claudeAcpRemoteLauncher(session) {
|
|
1014
|
+
const hasTTY = process.stdout.isTTY && process.stdin.isTTY;
|
|
1015
|
+
const messageBuffer = new registerKillSessionHandler.MessageBuffer({ enabled: hasTTY });
|
|
1016
|
+
let inkInstance = null;
|
|
1017
|
+
let shouldExit = false;
|
|
1018
|
+
let abortController = new AbortController();
|
|
1019
|
+
let runtimeHandle = null;
|
|
1020
|
+
let unsubscribeRuntimeMessages = null;
|
|
1021
|
+
let currentModeHash = null;
|
|
1022
|
+
let pending = null;
|
|
1023
|
+
let accumulatedResponse = "";
|
|
1024
|
+
let isResponseInProgress = false;
|
|
1025
|
+
let taskStartedSent = false;
|
|
1026
|
+
let currentAssistantMessageId = null;
|
|
1027
|
+
let currentThinkingMessageId = null;
|
|
1028
|
+
let shouldInjectHistoryOnNextSession = false;
|
|
1029
|
+
let readyAlreadySent = false;
|
|
1030
|
+
const permissionHandler = new ClaudeAcpPermissionHandler(session.client);
|
|
1031
|
+
const selectionHandler = new ProviderSelectionHandler.ProviderSelectionHandler(session.client, "Claude");
|
|
1032
|
+
const conversationHistory = new registerKillSessionHandler.ConversationHistory({
|
|
1033
|
+
maxMessages: 20,
|
|
1034
|
+
maxCharacters: 5e4
|
|
1035
|
+
});
|
|
1036
|
+
const rotateAbortController = () => {
|
|
1037
|
+
const activeController = abortController;
|
|
1038
|
+
abortController = new AbortController();
|
|
1039
|
+
return activeController;
|
|
1040
|
+
};
|
|
1041
|
+
const sendReady = () => {
|
|
1042
|
+
session.client.sendSessionEvent({ type: "ready" });
|
|
1043
|
+
try {
|
|
1044
|
+
session.api.push().sendToAllDevices(
|
|
1045
|
+
"It's ready!",
|
|
1046
|
+
"Claude is waiting for your command",
|
|
1047
|
+
{ sessionId: session.client.sessionId }
|
|
1048
|
+
);
|
|
1049
|
+
} catch (pushError) {
|
|
1050
|
+
api.logger.debug("[ClaudeACP] Failed to send ready push", pushError);
|
|
1051
|
+
}
|
|
1052
|
+
};
|
|
1053
|
+
const emitStatusMessage = (message) => {
|
|
1054
|
+
messageBuffer.addMessage(message, "status");
|
|
1055
|
+
session.client.sendSessionEvent({ type: "message", message });
|
|
1056
|
+
};
|
|
1057
|
+
const emitUserVisibleErrorMessage = (message) => {
|
|
1058
|
+
emitStatusMessage(message);
|
|
1059
|
+
session.client.sendAgentMessage("claude", {
|
|
1060
|
+
type: "message",
|
|
1061
|
+
message
|
|
1062
|
+
});
|
|
1063
|
+
};
|
|
1064
|
+
const resetTurnState = () => {
|
|
1065
|
+
accumulatedResponse = "";
|
|
1066
|
+
isResponseInProgress = false;
|
|
1067
|
+
taskStartedSent = false;
|
|
1068
|
+
currentAssistantMessageId = null;
|
|
1069
|
+
currentThinkingMessageId = null;
|
|
1070
|
+
session.onThinkingChange(false);
|
|
1071
|
+
};
|
|
1072
|
+
const emitFinalAssistantMessage = () => {
|
|
1073
|
+
const finalMessage = accumulatedResponse.trim();
|
|
1074
|
+
if (!finalMessage) {
|
|
1075
|
+
accumulatedResponse = "";
|
|
1076
|
+
isResponseInProgress = false;
|
|
1077
|
+
return;
|
|
1078
|
+
}
|
|
1079
|
+
conversationHistory.addAssistantMessage(finalMessage);
|
|
1080
|
+
session.client.sendAgentMessage("claude", {
|
|
1081
|
+
type: "message",
|
|
1082
|
+
message: finalMessage
|
|
1083
|
+
});
|
|
1084
|
+
accumulatedResponse = "";
|
|
1085
|
+
isResponseInProgress = false;
|
|
1086
|
+
};
|
|
1087
|
+
const disposeRuntimeHandle = async () => {
|
|
1088
|
+
if (!runtimeHandle) {
|
|
1089
|
+
return;
|
|
1090
|
+
}
|
|
1091
|
+
const activeHandle = runtimeHandle;
|
|
1092
|
+
runtimeHandle = null;
|
|
1093
|
+
unsubscribeRuntimeMessages?.();
|
|
1094
|
+
unsubscribeRuntimeMessages = null;
|
|
1095
|
+
try {
|
|
1096
|
+
await activeHandle.dispose();
|
|
1097
|
+
} catch (error) {
|
|
1098
|
+
api.logger.debug("[ClaudeACP] Error disposing runtime handle:", error);
|
|
1099
|
+
}
|
|
1100
|
+
};
|
|
1101
|
+
const queueHistoryInjectionForRestart = (reason) => {
|
|
1102
|
+
messageBuffer.addMessage("\u2550".repeat(40), "status");
|
|
1103
|
+
if (conversationHistory.hasHistory()) {
|
|
1104
|
+
shouldInjectHistoryOnNextSession = true;
|
|
1105
|
+
const message = `${reason} Preserving ${conversationHistory.size()} earlier messages of context.`;
|
|
1106
|
+
emitStatusMessage(message);
|
|
1107
|
+
api.logger.debug(`[ClaudeACP] Will inject conversation history after restart: ${conversationHistory.getSummary()}`);
|
|
1108
|
+
return;
|
|
1109
|
+
}
|
|
1110
|
+
emitStatusMessage(reason);
|
|
1111
|
+
};
|
|
1112
|
+
const setupRuntimeMessageHandler = (activeRuntimeHandle) => {
|
|
1113
|
+
const forwardAgentMessage = (agentMessage) => {
|
|
1114
|
+
registerKillSessionHandler.forwardAgentMessageToProviderSession(agentMessage, {
|
|
1115
|
+
provider: "claude",
|
|
1116
|
+
send: (body) => session.client.sendAgentMessage("claude", body)
|
|
1117
|
+
});
|
|
1118
|
+
};
|
|
1119
|
+
unsubscribeRuntimeMessages?.();
|
|
1120
|
+
unsubscribeRuntimeMessages = activeRuntimeHandle.onMessage((msg) => {
|
|
1121
|
+
switch (msg.type) {
|
|
1122
|
+
case "model-output": {
|
|
1123
|
+
const text = msg.textDelta ?? msg.fullText ?? "";
|
|
1124
|
+
if (!text) {
|
|
1125
|
+
return;
|
|
1126
|
+
}
|
|
1127
|
+
if (!isResponseInProgress) {
|
|
1128
|
+
if (currentThinkingMessageId) {
|
|
1129
|
+
messageBuffer.removeMessage(currentThinkingMessageId);
|
|
1130
|
+
currentThinkingMessageId = null;
|
|
1131
|
+
}
|
|
1132
|
+
currentAssistantMessageId = messageBuffer.addMessage(text, "assistant");
|
|
1133
|
+
isResponseInProgress = true;
|
|
1134
|
+
} else if (currentAssistantMessageId) {
|
|
1135
|
+
const updated = messageBuffer.updateMessage(currentAssistantMessageId, text);
|
|
1136
|
+
if (!updated) {
|
|
1137
|
+
currentAssistantMessageId = messageBuffer.addMessage(text, "assistant");
|
|
1138
|
+
}
|
|
1139
|
+
}
|
|
1140
|
+
accumulatedResponse += text;
|
|
1141
|
+
return;
|
|
1142
|
+
}
|
|
1143
|
+
case "status": {
|
|
1144
|
+
if (msg.status === "running") {
|
|
1145
|
+
session.onThinkingChange(true);
|
|
1146
|
+
if (!taskStartedSent) {
|
|
1147
|
+
session.client.sendAgentMessage("claude", {
|
|
1148
|
+
type: "task_started",
|
|
1149
|
+
id: node_crypto.randomUUID()
|
|
1150
|
+
});
|
|
1151
|
+
taskStartedSent = true;
|
|
1152
|
+
}
|
|
1153
|
+
if (!isResponseInProgress && !currentThinkingMessageId) {
|
|
1154
|
+
currentThinkingMessageId = messageBuffer.addMessage("Thinking...", "system");
|
|
1155
|
+
}
|
|
1156
|
+
return;
|
|
1157
|
+
}
|
|
1158
|
+
if (msg.status === "idle" || msg.status === "stopped") {
|
|
1159
|
+
session.onThinkingChange(false);
|
|
1160
|
+
return;
|
|
1161
|
+
}
|
|
1162
|
+
if (msg.status === "error") {
|
|
1163
|
+
messageBuffer.addMessage(`Error: ${normalizeClaudeBackendError(msg.detail)}`, "status");
|
|
1164
|
+
}
|
|
1165
|
+
return;
|
|
1166
|
+
}
|
|
1167
|
+
case "tool-call": {
|
|
1168
|
+
const toolArgs = index.truncateDisplayMessage(msg.args, 100);
|
|
1169
|
+
messageBuffer.addMessage(
|
|
1170
|
+
`Executing: ${msg.toolName}${toolArgs ? ` ${toolArgs}` : ""}`,
|
|
1171
|
+
"tool"
|
|
1172
|
+
);
|
|
1173
|
+
forwardAgentMessage(msg);
|
|
1174
|
+
return;
|
|
1175
|
+
}
|
|
1176
|
+
case "tool-result": {
|
|
1177
|
+
const resultText = index.truncateDisplayMessage(msg.result, 200);
|
|
1178
|
+
messageBuffer.addMessage(resultText ? `Result: ${resultText}` : "Tool completed", "result");
|
|
1179
|
+
forwardAgentMessage(msg);
|
|
1180
|
+
return;
|
|
1181
|
+
}
|
|
1182
|
+
case "fs-edit": {
|
|
1183
|
+
messageBuffer.addMessage(`File edit: ${msg.description}`, "tool");
|
|
1184
|
+
forwardAgentMessage(msg);
|
|
1185
|
+
return;
|
|
1186
|
+
}
|
|
1187
|
+
case "terminal-output": {
|
|
1188
|
+
const output = index.formatDisplayMessage(msg.data);
|
|
1189
|
+
messageBuffer.addMessage(output, "result");
|
|
1190
|
+
forwardAgentMessage({
|
|
1191
|
+
...msg,
|
|
1192
|
+
data: output
|
|
1193
|
+
});
|
|
1194
|
+
return;
|
|
1195
|
+
}
|
|
1196
|
+
case "permission-request": {
|
|
1197
|
+
forwardAgentMessage(msg);
|
|
1198
|
+
return;
|
|
1199
|
+
}
|
|
1200
|
+
case "token-count": {
|
|
1201
|
+
forwardAgentMessage(msg);
|
|
1202
|
+
return;
|
|
1203
|
+
}
|
|
1204
|
+
case "exec-approval-request":
|
|
1205
|
+
case "patch-apply-begin":
|
|
1206
|
+
case "patch-apply-end": {
|
|
1207
|
+
forwardAgentMessage(msg);
|
|
1208
|
+
return;
|
|
1209
|
+
}
|
|
1210
|
+
case "event": {
|
|
1211
|
+
if (msg.name === "thinking") {
|
|
1212
|
+
const payload = msg.payload;
|
|
1213
|
+
const thinkingText = typeof payload?.text === "string" ? payload.text : "";
|
|
1214
|
+
if (thinkingText) {
|
|
1215
|
+
session.client.sendAgentMessage("claude", {
|
|
1216
|
+
type: "thinking",
|
|
1217
|
+
text: thinkingText
|
|
1218
|
+
});
|
|
1219
|
+
if (!isResponseInProgress) {
|
|
1220
|
+
const preview = `[Thinking] ${thinkingText.substring(0, 100)}...`;
|
|
1221
|
+
if (currentThinkingMessageId) {
|
|
1222
|
+
messageBuffer.updateMessage(currentThinkingMessageId, preview, { mode: "replace" });
|
|
1223
|
+
} else {
|
|
1224
|
+
currentThinkingMessageId = messageBuffer.addMessage(preview, "system");
|
|
1225
|
+
}
|
|
1226
|
+
}
|
|
1227
|
+
}
|
|
1228
|
+
}
|
|
1229
|
+
return;
|
|
1230
|
+
}
|
|
1231
|
+
default:
|
|
1232
|
+
return;
|
|
1233
|
+
}
|
|
1234
|
+
});
|
|
1235
|
+
};
|
|
1236
|
+
const createRuntimeHandle = async (mode) => {
|
|
1237
|
+
const { session: nextRuntimeHandle, factoryResult } = await registerKillSessionHandler.launchRuntimeHandleWithFactoryResult({
|
|
1238
|
+
provider: "claude",
|
|
1239
|
+
cwd: session.path,
|
|
1240
|
+
createBackendResult: (opts) => index.createClaudeBackend({
|
|
1241
|
+
...opts,
|
|
1242
|
+
baseArgs: session.claudeArgs,
|
|
1243
|
+
mcpServers: session.mcpServers,
|
|
1244
|
+
permissionHandler,
|
|
1245
|
+
selectionHandler: {
|
|
1246
|
+
handleSelection: (request) => selectionHandler.requestSelection(request)
|
|
1247
|
+
},
|
|
1248
|
+
permissionMode: mode.permissionMode,
|
|
1249
|
+
model: mode.model,
|
|
1250
|
+
fallbackModel: mode.fallbackModel,
|
|
1251
|
+
customSystemPrompt: mode.customSystemPrompt ? `${mode.customSystemPrompt}
|
|
1252
|
+
|
|
1253
|
+
${systemPrompt}` : void 0,
|
|
1254
|
+
appendSystemPrompt: mode.appendSystemPrompt ? `${mode.appendSystemPrompt}
|
|
1255
|
+
|
|
1256
|
+
${systemPrompt}` : systemPrompt,
|
|
1257
|
+
allowedTools: mode.allowedTools ? mode.allowedTools.concat(session.allowedTools ?? []) : session.allowedTools,
|
|
1258
|
+
disallowedTools: mode.disallowedTools,
|
|
1259
|
+
settingsPath: session.hookSettingsPath
|
|
1260
|
+
})
|
|
1261
|
+
});
|
|
1262
|
+
api.logger.debug("[ClaudeACP] Started Claude ACP backend", {
|
|
1263
|
+
command: factoryResult.command,
|
|
1264
|
+
args: factoryResult.args
|
|
1265
|
+
});
|
|
1266
|
+
runtimeHandle = nextRuntimeHandle;
|
|
1267
|
+
setupRuntimeMessageHandler(nextRuntimeHandle);
|
|
1268
|
+
session.consumeOneTimeFlags();
|
|
1269
|
+
return nextRuntimeHandle;
|
|
1270
|
+
};
|
|
1271
|
+
const abortActiveTurn = async () => {
|
|
1272
|
+
const activeController = rotateAbortController();
|
|
1273
|
+
activeController.abort();
|
|
1274
|
+
session.onThinkingChange(false);
|
|
1275
|
+
if (runtimeHandle) {
|
|
1276
|
+
await runtimeHandle.cancel().catch((error) => {
|
|
1277
|
+
api.logger.debug("[ClaudeACP] Error cancelling runtime handle:", error);
|
|
1278
|
+
});
|
|
1279
|
+
}
|
|
1280
|
+
};
|
|
1281
|
+
const completeSyntheticTurn = () => {
|
|
1282
|
+
session.client.sendAgentMessage("claude", {
|
|
1283
|
+
type: "task_complete",
|
|
1284
|
+
id: node_crypto.randomUUID()
|
|
1285
|
+
});
|
|
1286
|
+
};
|
|
1287
|
+
const handleClearCommand = async () => {
|
|
1288
|
+
api.logger.debug("[ClaudeACP] /clear command received - resetting runtime and local history");
|
|
1289
|
+
conversationHistory.clear();
|
|
1290
|
+
shouldInjectHistoryOnNextSession = false;
|
|
1291
|
+
await disposeRuntimeHandle();
|
|
1292
|
+
session.clearSessionId();
|
|
1293
|
+
currentModeHash = null;
|
|
1294
|
+
permissionHandler.reset();
|
|
1295
|
+
selectionHandler.reset();
|
|
1296
|
+
resetTurnState();
|
|
1297
|
+
emitStatusMessage("Context was reset");
|
|
1298
|
+
completeSyntheticTurn();
|
|
1299
|
+
};
|
|
1300
|
+
const handleSwitchToLocal = async () => {
|
|
1301
|
+
const message = "Daemon-spawned Claude ACP sessions stay in remote mode.";
|
|
1302
|
+
api.logger.debug("[ClaudeACP] Ignoring switch request because daemon sessions are remote-only");
|
|
1303
|
+
emitStatusMessage(message);
|
|
1304
|
+
};
|
|
1305
|
+
const handleAbort = async () => {
|
|
1306
|
+
api.logger.debug("[ClaudeACP] Abort requested - stopping current task");
|
|
1307
|
+
await abortActiveTurn();
|
|
1308
|
+
};
|
|
1309
|
+
if (hasTTY) {
|
|
1310
|
+
console.clear();
|
|
1311
|
+
inkInstance = ink.render(React.createElement(RemoteModeDisplay, {
|
|
1312
|
+
messageBuffer,
|
|
1313
|
+
logPath: process.env.DEBUG ? session.logPath : void 0,
|
|
1314
|
+
onExit: async () => {
|
|
1315
|
+
shouldExit = true;
|
|
1316
|
+
await handleAbort();
|
|
1317
|
+
},
|
|
1318
|
+
onSwitchToLocal: () => {
|
|
1319
|
+
void handleSwitchToLocal();
|
|
1320
|
+
}
|
|
1321
|
+
}), {
|
|
1322
|
+
exitOnCtrlC: false,
|
|
1323
|
+
patchConsole: false
|
|
1324
|
+
});
|
|
1325
|
+
}
|
|
1326
|
+
if (hasTTY) {
|
|
1327
|
+
process.stdin.resume();
|
|
1328
|
+
if (process.stdin.isTTY) {
|
|
1329
|
+
process.stdin.setRawMode(true);
|
|
1330
|
+
}
|
|
1331
|
+
process.stdin.setEncoding("utf8");
|
|
1332
|
+
}
|
|
1333
|
+
session.client.rpcHandlerManager.registerHandler("abort", handleAbort);
|
|
1334
|
+
session.client.rpcHandlerManager.registerHandler("switch", handleSwitchToLocal);
|
|
1335
|
+
try {
|
|
1336
|
+
while (!shouldExit) {
|
|
1337
|
+
let message = pending;
|
|
1338
|
+
pending = null;
|
|
1339
|
+
if (!message) {
|
|
1340
|
+
const waitSignal = abortController.signal;
|
|
1341
|
+
const batch = await session.queue.waitForMessagesAndGetAsString(waitSignal);
|
|
1342
|
+
if (!batch) {
|
|
1343
|
+
if (waitSignal.aborted && !shouldExit) {
|
|
1344
|
+
continue;
|
|
1345
|
+
}
|
|
1346
|
+
break;
|
|
1347
|
+
}
|
|
1348
|
+
message = batch;
|
|
1349
|
+
}
|
|
1350
|
+
if (!message) {
|
|
1351
|
+
break;
|
|
1352
|
+
}
|
|
1353
|
+
if (runtimeHandle && currentModeHash && message.hash !== currentModeHash) {
|
|
1354
|
+
queueHistoryInjectionForRestart("Starting new Claude ACP session (execution settings changed)...");
|
|
1355
|
+
await disposeRuntimeHandle();
|
|
1356
|
+
session.clearSessionId();
|
|
1357
|
+
currentModeHash = null;
|
|
1358
|
+
pending = message;
|
|
1359
|
+
permissionHandler.reset();
|
|
1360
|
+
selectionHandler.reset();
|
|
1361
|
+
resetTurnState();
|
|
1362
|
+
continue;
|
|
1363
|
+
}
|
|
1364
|
+
currentModeHash = message.hash;
|
|
1365
|
+
readyAlreadySent = false;
|
|
1366
|
+
const specialCommand = parseSpecialCommand(message.message);
|
|
1367
|
+
if (specialCommand.type === "clear") {
|
|
1368
|
+
await handleClearCommand();
|
|
1369
|
+
if (!shouldExit && !pending && session.queue.size() === 0 && !readyAlreadySent) {
|
|
1370
|
+
sendReady();
|
|
1371
|
+
readyAlreadySent = true;
|
|
1372
|
+
}
|
|
1373
|
+
continue;
|
|
1374
|
+
}
|
|
1375
|
+
permissionHandler.setPermissionMode(message.mode.permissionMode);
|
|
1376
|
+
messageBuffer.addMessage(message.message, "user");
|
|
1377
|
+
let shouldClearHistoryAfterTurn = false;
|
|
1378
|
+
try {
|
|
1379
|
+
const activeRuntimeHandle = runtimeHandle ?? await createRuntimeHandle(message.mode);
|
|
1380
|
+
let promptToSend = message.message;
|
|
1381
|
+
if (shouldInjectHistoryOnNextSession && conversationHistory.hasHistory()) {
|
|
1382
|
+
const historyContext = conversationHistory.getContextForNewSession(
|
|
1383
|
+
"Continue from the prior Claude ACP session using the conversation below as context."
|
|
1384
|
+
);
|
|
1385
|
+
promptToSend = historyContext + promptToSend;
|
|
1386
|
+
api.logger.debug(`[ClaudeACP] Injected conversation history context (${historyContext.length} chars)`);
|
|
1387
|
+
}
|
|
1388
|
+
if (specialCommand.type === "compact") {
|
|
1389
|
+
emitStatusMessage("Compaction started");
|
|
1390
|
+
}
|
|
1391
|
+
conversationHistory.addUserMessage(message.message);
|
|
1392
|
+
await activeRuntimeHandle.sendPrompt(promptToSend);
|
|
1393
|
+
await registerKillSessionHandler.waitForResponseCompleteWithAbort(activeRuntimeHandle.backend, abortController.signal);
|
|
1394
|
+
shouldInjectHistoryOnNextSession = false;
|
|
1395
|
+
shouldClearHistoryAfterTurn = specialCommand.type === "compact";
|
|
1396
|
+
} catch (error) {
|
|
1397
|
+
api.logger.warn("[ClaudeACP] Error in Claude ACP session:", error);
|
|
1398
|
+
const isAbortError = error instanceof Error && error.name === "AbortError";
|
|
1399
|
+
const isExpectedInterruption = isAbortError || abortController.signal.aborted || shouldExit;
|
|
1400
|
+
if (isExpectedInterruption) {
|
|
1401
|
+
session.client.sendAgentMessage("claude", {
|
|
1402
|
+
type: "turn_aborted",
|
|
1403
|
+
id: node_crypto.randomUUID()
|
|
1404
|
+
});
|
|
1405
|
+
emitStatusMessage("Aborted by user");
|
|
1406
|
+
} else {
|
|
1407
|
+
const errorMessage = normalizeClaudeBackendError(error);
|
|
1408
|
+
emitUserVisibleErrorMessage(errorMessage);
|
|
1409
|
+
if (conversationHistory.hasHistory()) {
|
|
1410
|
+
shouldInjectHistoryOnNextSession = true;
|
|
1411
|
+
}
|
|
1412
|
+
await disposeRuntimeHandle();
|
|
1413
|
+
session.clearSessionId();
|
|
1414
|
+
currentModeHash = null;
|
|
1415
|
+
}
|
|
1416
|
+
} finally {
|
|
1417
|
+
emitFinalAssistantMessage();
|
|
1418
|
+
if (shouldClearHistoryAfterTurn) {
|
|
1419
|
+
conversationHistory.clear();
|
|
1420
|
+
emitStatusMessage("Compaction completed");
|
|
1421
|
+
}
|
|
1422
|
+
if (!shouldExit) {
|
|
1423
|
+
session.client.sendAgentMessage("claude", {
|
|
1424
|
+
type: "task_complete",
|
|
1425
|
+
id: node_crypto.randomUUID()
|
|
1426
|
+
});
|
|
1427
|
+
}
|
|
1428
|
+
permissionHandler.reset();
|
|
1429
|
+
selectionHandler.reset();
|
|
1430
|
+
resetTurnState();
|
|
1431
|
+
if (!shouldExit && !pending && session.queue.size() === 0 && !readyAlreadySent) {
|
|
1432
|
+
sendReady();
|
|
1433
|
+
readyAlreadySent = true;
|
|
1434
|
+
}
|
|
1435
|
+
}
|
|
1436
|
+
}
|
|
1437
|
+
} finally {
|
|
1438
|
+
await disposeRuntimeHandle();
|
|
1439
|
+
permissionHandler.reset();
|
|
1440
|
+
selectionHandler.reset();
|
|
1441
|
+
if (process.stdin.isTTY) {
|
|
1442
|
+
try {
|
|
1443
|
+
process.stdin.setRawMode(false);
|
|
1444
|
+
} catch {
|
|
1445
|
+
}
|
|
1446
|
+
}
|
|
1447
|
+
if (hasTTY) {
|
|
1448
|
+
try {
|
|
1449
|
+
process.stdin.pause();
|
|
1450
|
+
} catch {
|
|
1451
|
+
}
|
|
1452
|
+
}
|
|
1453
|
+
session.client.rpcHandlerManager.registerHandler("abort", async () => {
|
|
1454
|
+
});
|
|
1455
|
+
session.client.rpcHandlerManager.registerHandler("switch", async () => {
|
|
1456
|
+
});
|
|
1457
|
+
if (inkInstance) {
|
|
1458
|
+
inkInstance.unmount();
|
|
1459
|
+
}
|
|
1460
|
+
messageBuffer.clear();
|
|
1461
|
+
}
|
|
1462
|
+
api.logger.debug("[ClaudeACP] Remote launcher returning: exit");
|
|
1463
|
+
return "exit";
|
|
1464
|
+
}
|
|
842
1465
|
|
|
843
1466
|
class Stream {
|
|
844
1467
|
constructor(returned) {
|
|
@@ -937,7 +1560,7 @@ class AbortError extends Error {
|
|
|
937
1560
|
}
|
|
938
1561
|
}
|
|
939
1562
|
|
|
940
|
-
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-
|
|
1563
|
+
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-BMHlBny_.cjs', document.baseURI).href)));
|
|
941
1564
|
const __dirname$1 = path.join(__filename$1, "..");
|
|
942
1565
|
function getGlobalClaudeVersion() {
|
|
943
1566
|
try {
|
|
@@ -1365,59 +1988,6 @@ function query(config) {
|
|
|
1365
1988
|
return query2;
|
|
1366
1989
|
}
|
|
1367
1990
|
|
|
1368
|
-
function mapToClaudeMode(mode) {
|
|
1369
|
-
const codexToClaudeMap = {
|
|
1370
|
-
"yolo": "bypassPermissions",
|
|
1371
|
-
"safe-yolo": "default",
|
|
1372
|
-
"read-only": "default"
|
|
1373
|
-
};
|
|
1374
|
-
return codexToClaudeMap[mode] ?? mode;
|
|
1375
|
-
}
|
|
1376
|
-
|
|
1377
|
-
function parseCompact(message) {
|
|
1378
|
-
const trimmed = message.trim();
|
|
1379
|
-
if (trimmed === "/compact") {
|
|
1380
|
-
return {
|
|
1381
|
-
isCompact: true,
|
|
1382
|
-
originalMessage: trimmed
|
|
1383
|
-
};
|
|
1384
|
-
}
|
|
1385
|
-
if (trimmed.startsWith("/compact ")) {
|
|
1386
|
-
return {
|
|
1387
|
-
isCompact: true,
|
|
1388
|
-
originalMessage: trimmed
|
|
1389
|
-
};
|
|
1390
|
-
}
|
|
1391
|
-
return {
|
|
1392
|
-
isCompact: false,
|
|
1393
|
-
originalMessage: message
|
|
1394
|
-
};
|
|
1395
|
-
}
|
|
1396
|
-
function parseClear(message) {
|
|
1397
|
-
const trimmed = message.trim();
|
|
1398
|
-
return {
|
|
1399
|
-
isClear: trimmed === "/clear"
|
|
1400
|
-
};
|
|
1401
|
-
}
|
|
1402
|
-
function parseSpecialCommand(message) {
|
|
1403
|
-
const compactResult = parseCompact(message);
|
|
1404
|
-
if (compactResult.isCompact) {
|
|
1405
|
-
return {
|
|
1406
|
-
type: "compact",
|
|
1407
|
-
originalMessage: compactResult.originalMessage
|
|
1408
|
-
};
|
|
1409
|
-
}
|
|
1410
|
-
const clearResult = parseClear(message);
|
|
1411
|
-
if (clearResult.isClear) {
|
|
1412
|
-
return {
|
|
1413
|
-
type: "clear"
|
|
1414
|
-
};
|
|
1415
|
-
}
|
|
1416
|
-
return {
|
|
1417
|
-
type: null
|
|
1418
|
-
};
|
|
1419
|
-
}
|
|
1420
|
-
|
|
1421
1991
|
class PushableAsyncIterable {
|
|
1422
1992
|
queue = [];
|
|
1423
1993
|
waiters = [];
|
|
@@ -1557,58 +2127,6 @@ async function awaitFileExist(file, timeout = 1e4) {
|
|
|
1557
2127
|
return false;
|
|
1558
2128
|
}
|
|
1559
2129
|
|
|
1560
|
-
function getClaudeSettingsPath() {
|
|
1561
|
-
const claudeConfigDir = process.env.CLAUDE_CONFIG_DIR || path.join(os.homedir(), ".claude");
|
|
1562
|
-
return path.join(claudeConfigDir, "settings.json");
|
|
1563
|
-
}
|
|
1564
|
-
function readClaudeSettings() {
|
|
1565
|
-
try {
|
|
1566
|
-
const settingsPath = getClaudeSettingsPath();
|
|
1567
|
-
if (!fs$1.existsSync(settingsPath)) {
|
|
1568
|
-
api.logger.debug(`[ClaudeSettings] No Claude settings file found at ${settingsPath}`);
|
|
1569
|
-
return null;
|
|
1570
|
-
}
|
|
1571
|
-
const settingsContent = fs$1.readFileSync(settingsPath, "utf-8");
|
|
1572
|
-
const settings = JSON.parse(settingsContent);
|
|
1573
|
-
api.logger.debug(`[ClaudeSettings] Successfully read Claude settings from ${settingsPath}`);
|
|
1574
|
-
api.logger.debug(`[ClaudeSettings] includeCoAuthoredBy: ${settings.includeCoAuthoredBy}`);
|
|
1575
|
-
return settings;
|
|
1576
|
-
} catch (error) {
|
|
1577
|
-
api.logger.debug(`[ClaudeSettings] Error reading Claude settings: ${error}`);
|
|
1578
|
-
return null;
|
|
1579
|
-
}
|
|
1580
|
-
}
|
|
1581
|
-
function shouldIncludeCoAuthoredBy() {
|
|
1582
|
-
const settings = readClaudeSettings();
|
|
1583
|
-
if (!settings || settings.includeCoAuthoredBy === void 0) {
|
|
1584
|
-
return true;
|
|
1585
|
-
}
|
|
1586
|
-
return settings.includeCoAuthoredBy;
|
|
1587
|
-
}
|
|
1588
|
-
|
|
1589
|
-
const BASE_SYSTEM_PROMPT = (() => index.trimIdent(`
|
|
1590
|
-
Help human.
|
|
1591
|
-
`))();
|
|
1592
|
-
const CO_AUTHORED_CREDITS = (() => index.trimIdent(`
|
|
1593
|
-
When making commit messages, instead of just giving co-credit to Claude, also give credit to Happy like so:
|
|
1594
|
-
|
|
1595
|
-
<main commit message>
|
|
1596
|
-
|
|
1597
|
-
Generated with [Claude Code](https://claude.ai/code)
|
|
1598
|
-
via [Happy](https://happy.engineering)
|
|
1599
|
-
|
|
1600
|
-
Co-Authored-By: Claude <noreply@anthropic.com>
|
|
1601
|
-
Co-Authored-By: Happy <yesreply@happy.engineering>
|
|
1602
|
-
`))();
|
|
1603
|
-
const systemPrompt = (() => {
|
|
1604
|
-
const includeCoAuthored = shouldIncludeCoAuthoredBy();
|
|
1605
|
-
if (includeCoAuthored) {
|
|
1606
|
-
return BASE_SYSTEM_PROMPT + "\n\n" + CO_AUTHORED_CREDITS;
|
|
1607
|
-
} else {
|
|
1608
|
-
return BASE_SYSTEM_PROMPT;
|
|
1609
|
-
}
|
|
1610
|
-
})();
|
|
1611
|
-
|
|
1612
2130
|
async function claudeRemote(opts) {
|
|
1613
2131
|
let startFrom = opts.sessionId;
|
|
1614
2132
|
if (opts.sessionId && !index.claudeCheckSession(opts.sessionId, opts.path, opts.transcriptPath)) {
|
|
@@ -1666,7 +2184,7 @@ async function claudeRemote(opts) {
|
|
|
1666
2184
|
cwd: opts.path,
|
|
1667
2185
|
resume: startFrom ?? void 0,
|
|
1668
2186
|
mcpServers: opts.mcpServers,
|
|
1669
|
-
permissionMode: mapToClaudeMode(initial.mode.permissionMode),
|
|
2187
|
+
permissionMode: index.mapToClaudeMode(initial.mode.permissionMode),
|
|
1670
2188
|
model: initial.mode.model,
|
|
1671
2189
|
fallbackModel: initial.mode.fallbackModel,
|
|
1672
2190
|
customSystemPrompt: initial.mode.customSystemPrompt ? initial.mode.customSystemPrompt + "\n\n" + systemPrompt : void 0,
|
|
@@ -1812,16 +2330,6 @@ function getToolName(toolName) {
|
|
|
1812
2330
|
return toTitleCase(toolName);
|
|
1813
2331
|
}
|
|
1814
2332
|
|
|
1815
|
-
function getToolDescriptor(toolName) {
|
|
1816
|
-
if (toolName === "exit_plan_mode" || toolName === "ExitPlanMode") {
|
|
1817
|
-
return { edit: false, exitPlan: true };
|
|
1818
|
-
}
|
|
1819
|
-
if (toolName === "Edit" || toolName === "MultiEdit" || toolName === "Write" || toolName === "NotebookEdit") {
|
|
1820
|
-
return { edit: true, exitPlan: false };
|
|
1821
|
-
}
|
|
1822
|
-
return { edit: false, exitPlan: false };
|
|
1823
|
-
}
|
|
1824
|
-
|
|
1825
2333
|
class PermissionHandler {
|
|
1826
2334
|
toolCalls = [];
|
|
1827
2335
|
responses = /* @__PURE__ */ new Map();
|
|
@@ -2963,36 +3471,31 @@ async function loop(opts) {
|
|
|
2963
3471
|
jsRuntime: opts.jsRuntime
|
|
2964
3472
|
});
|
|
2965
3473
|
opts.onSessionReady?.(session);
|
|
2966
|
-
|
|
2967
|
-
|
|
2968
|
-
|
|
2969
|
-
|
|
2970
|
-
|
|
3474
|
+
return await ProviderSelectionHandler.runModeLoop({
|
|
3475
|
+
startingMode: opts.startingMode ?? "local",
|
|
3476
|
+
onIteration: (mode) => {
|
|
3477
|
+
api.logger.debug(`[loop] Iteration with mode: ${mode}`);
|
|
3478
|
+
},
|
|
3479
|
+
onModeChange: async (mode) => {
|
|
3480
|
+
await opts.onModeChange(mode);
|
|
3481
|
+
},
|
|
3482
|
+
launchers: {
|
|
3483
|
+
local: async () => {
|
|
2971
3484
|
const result = await claudeLocalLauncher(session);
|
|
2972
|
-
|
|
2973
|
-
|
|
2974
|
-
mode = "remote";
|
|
2975
|
-
opts.onModeChange?.(mode);
|
|
2976
|
-
break;
|
|
2977
|
-
case "exit":
|
|
2978
|
-
return result.code;
|
|
3485
|
+
if (result.type === "switch") {
|
|
3486
|
+
return { type: "switch", mode: "remote" };
|
|
2979
3487
|
}
|
|
2980
|
-
|
|
2981
|
-
}
|
|
2982
|
-
|
|
2983
|
-
const reason = await claudeRemoteLauncher(session);
|
|
2984
|
-
|
|
2985
|
-
|
|
2986
|
-
return 0;
|
|
2987
|
-
case "switch":
|
|
2988
|
-
mode = "local";
|
|
2989
|
-
opts.onModeChange?.(mode);
|
|
2990
|
-
break;
|
|
3488
|
+
return { type: "exit", value: result.code };
|
|
3489
|
+
},
|
|
3490
|
+
remote: async () => {
|
|
3491
|
+
const reason = opts.startedBy === "daemon" ? await claudeAcpRemoteLauncher(session) : await claudeRemoteLauncher(session);
|
|
3492
|
+
if (reason === "switch") {
|
|
3493
|
+
return { type: "switch", mode: "local" };
|
|
2991
3494
|
}
|
|
2992
|
-
|
|
3495
|
+
return { type: "exit", value: 0 };
|
|
2993
3496
|
}
|
|
2994
3497
|
}
|
|
2995
|
-
}
|
|
3498
|
+
});
|
|
2996
3499
|
}
|
|
2997
3500
|
|
|
2998
3501
|
async function extractSDKMetadata() {
|
|
@@ -3151,6 +3654,143 @@ function cleanupHookSettingsFile(filepath) {
|
|
|
3151
3654
|
}
|
|
3152
3655
|
}
|
|
3153
3656
|
|
|
3657
|
+
async function runOfflineClaudeLocalFallback(opts) {
|
|
3658
|
+
let offlineSessionId = null;
|
|
3659
|
+
const reconnection = api.startOfflineReconnection({
|
|
3660
|
+
serverUrl: api.configuration.serverUrl,
|
|
3661
|
+
onReconnected: async () => {
|
|
3662
|
+
const response = await opts.api.getOrCreateSession({
|
|
3663
|
+
tag: node_crypto.randomUUID(),
|
|
3664
|
+
metadata: opts.metadata,
|
|
3665
|
+
state: opts.state
|
|
3666
|
+
});
|
|
3667
|
+
if (!response) {
|
|
3668
|
+
throw new Error("Server unavailable");
|
|
3669
|
+
}
|
|
3670
|
+
await index.publishSessionRegistration(response.id, opts.metadata);
|
|
3671
|
+
const session = opts.api.sessionSyncClient(response);
|
|
3672
|
+
await registerKillSessionHandler.syncControlledByUserState(session, true);
|
|
3673
|
+
const scanner = await createSessionScanner({
|
|
3674
|
+
sessionId: null,
|
|
3675
|
+
workingDirectory: opts.workingDirectory,
|
|
3676
|
+
onMessage: (message) => session.sendClaudeSessionMessage(message)
|
|
3677
|
+
});
|
|
3678
|
+
if (offlineSessionId) {
|
|
3679
|
+
scanner.onNewSession(offlineSessionId);
|
|
3680
|
+
}
|
|
3681
|
+
return { session, scanner };
|
|
3682
|
+
},
|
|
3683
|
+
onNotify: console.log,
|
|
3684
|
+
onCleanup: () => {
|
|
3685
|
+
api.logger.debug("[claude] Offline reconnection cleanup triggered");
|
|
3686
|
+
}
|
|
3687
|
+
});
|
|
3688
|
+
try {
|
|
3689
|
+
await index.claudeLocal({
|
|
3690
|
+
path: opts.workingDirectory,
|
|
3691
|
+
sessionId: null,
|
|
3692
|
+
onSessionFound: (sessionId) => {
|
|
3693
|
+
offlineSessionId = sessionId;
|
|
3694
|
+
},
|
|
3695
|
+
onThinkingChange: () => {
|
|
3696
|
+
},
|
|
3697
|
+
abort: new AbortController().signal,
|
|
3698
|
+
claudeEnvVars: opts.claudeEnvVars,
|
|
3699
|
+
claudeArgs: opts.claudeArgs,
|
|
3700
|
+
allowedTools: []
|
|
3701
|
+
});
|
|
3702
|
+
} finally {
|
|
3703
|
+
reconnection.cancel();
|
|
3704
|
+
index.stopCaffeinate();
|
|
3705
|
+
}
|
|
3706
|
+
}
|
|
3707
|
+
|
|
3708
|
+
function hasMetaOverride(message, key) {
|
|
3709
|
+
return Object.prototype.hasOwnProperty.call(message.meta ?? {}, key);
|
|
3710
|
+
}
|
|
3711
|
+
function resolveNullableStringOverride(message, key, currentValue) {
|
|
3712
|
+
if (!hasMetaOverride(message, key)) {
|
|
3713
|
+
return currentValue;
|
|
3714
|
+
}
|
|
3715
|
+
return message.meta?.[key] || void 0;
|
|
3716
|
+
}
|
|
3717
|
+
function resolveNullableArrayOverride(message, key, currentValue) {
|
|
3718
|
+
if (!hasMetaOverride(message, key)) {
|
|
3719
|
+
return currentValue;
|
|
3720
|
+
}
|
|
3721
|
+
return message.meta?.[key] || void 0;
|
|
3722
|
+
}
|
|
3723
|
+
function resolveClaudeQueuedMessage(message, currentState) {
|
|
3724
|
+
const permissionMode = message.meta?.permissionMode ?? currentState.permissionMode;
|
|
3725
|
+
const model = resolveNullableStringOverride(message, "model", currentState.model);
|
|
3726
|
+
const fallbackModel = resolveNullableStringOverride(message, "fallbackModel", currentState.fallbackModel);
|
|
3727
|
+
const customSystemPrompt = resolveNullableStringOverride(
|
|
3728
|
+
message,
|
|
3729
|
+
"customSystemPrompt",
|
|
3730
|
+
currentState.customSystemPrompt
|
|
3731
|
+
);
|
|
3732
|
+
const appendSystemPrompt = resolveNullableStringOverride(
|
|
3733
|
+
message,
|
|
3734
|
+
"appendSystemPrompt",
|
|
3735
|
+
currentState.appendSystemPrompt
|
|
3736
|
+
);
|
|
3737
|
+
const allowedTools = resolveNullableArrayOverride(message, "allowedTools", currentState.allowedTools);
|
|
3738
|
+
const disallowedTools = resolveNullableArrayOverride(message, "disallowedTools", currentState.disallowedTools);
|
|
3739
|
+
const mode = {
|
|
3740
|
+
permissionMode: permissionMode || "default",
|
|
3741
|
+
model,
|
|
3742
|
+
fallbackModel,
|
|
3743
|
+
customSystemPrompt,
|
|
3744
|
+
appendSystemPrompt,
|
|
3745
|
+
allowedTools,
|
|
3746
|
+
disallowedTools
|
|
3747
|
+
};
|
|
3748
|
+
const specialCommand = parseSpecialCommand(message.content.text);
|
|
3749
|
+
const nextState = {
|
|
3750
|
+
permissionMode,
|
|
3751
|
+
model,
|
|
3752
|
+
fallbackModel,
|
|
3753
|
+
customSystemPrompt,
|
|
3754
|
+
appendSystemPrompt,
|
|
3755
|
+
allowedTools,
|
|
3756
|
+
disallowedTools
|
|
3757
|
+
};
|
|
3758
|
+
if (specialCommand.type === "compact" || specialCommand.type === "clear") {
|
|
3759
|
+
return {
|
|
3760
|
+
nextState,
|
|
3761
|
+
queuedMessage: {
|
|
3762
|
+
text: specialCommand.originalMessage || message.content.text,
|
|
3763
|
+
mode,
|
|
3764
|
+
isolate: true,
|
|
3765
|
+
specialCommand: specialCommand.type
|
|
3766
|
+
}
|
|
3767
|
+
};
|
|
3768
|
+
}
|
|
3769
|
+
return {
|
|
3770
|
+
nextState,
|
|
3771
|
+
queuedMessage: {
|
|
3772
|
+
text: message.content.text,
|
|
3773
|
+
mode,
|
|
3774
|
+
isolate: false,
|
|
3775
|
+
specialCommand: null
|
|
3776
|
+
}
|
|
3777
|
+
};
|
|
3778
|
+
}
|
|
3779
|
+
function bindClaudeUserMessageQueue(opts) {
|
|
3780
|
+
let currentState = { ...opts.initialState };
|
|
3781
|
+
opts.session.onUserMessage((message) => {
|
|
3782
|
+
const { nextState, queuedMessage } = resolveClaudeQueuedMessage(message, currentState);
|
|
3783
|
+
currentState = nextState;
|
|
3784
|
+
if (queuedMessage.isolate) {
|
|
3785
|
+
api.logger.debug(`[claude] Queuing isolated special command: ${queuedMessage.specialCommand}`);
|
|
3786
|
+
opts.messageQueue.pushIsolateAndClear(queuedMessage.text, queuedMessage.mode);
|
|
3787
|
+
} else {
|
|
3788
|
+
opts.messageQueue.push(queuedMessage.text, queuedMessage.mode);
|
|
3789
|
+
}
|
|
3790
|
+
api.logger.debugLargeJson("[claude] User message queued:", message);
|
|
3791
|
+
});
|
|
3792
|
+
}
|
|
3793
|
+
|
|
3154
3794
|
async function runClaude(credentials, options = {}) {
|
|
3155
3795
|
api.logger.debug(`[CLAUDE] ===== CLAUDE MODE STARTING =====`);
|
|
3156
3796
|
api.logger.debug(`[CLAUDE] This is the Claude agent, NOT Gemini`);
|
|
@@ -3163,36 +3803,25 @@ async function runClaude(credentials, options = {}) {
|
|
|
3163
3803
|
}
|
|
3164
3804
|
api.connectionState.setBackend("Claude");
|
|
3165
3805
|
const api$1 = await api.ApiClient.create(credentials);
|
|
3166
|
-
let
|
|
3167
|
-
|
|
3168
|
-
|
|
3169
|
-
|
|
3170
|
-
|
|
3171
|
-
|
|
3806
|
+
let machineId;
|
|
3807
|
+
try {
|
|
3808
|
+
machineId = await registerKillSessionHandler.ensureManagedProviderMachine({
|
|
3809
|
+
api: api$1,
|
|
3810
|
+
missingMachineIdMessage: "[START] No machine ID found in settings, which is unexpected since authAndSetupMachineIfNeeded should have created it. Please report this issue on https://github.com/slopus/happy-cli/issues"
|
|
3811
|
+
});
|
|
3812
|
+
} catch (error) {
|
|
3813
|
+
if (error instanceof registerKillSessionHandler.MissingMachineIdError) {
|
|
3814
|
+
console.error(error.message);
|
|
3815
|
+
process.exit(1);
|
|
3816
|
+
}
|
|
3817
|
+
throw error;
|
|
3172
3818
|
}
|
|
3173
3819
|
api.logger.debug(`Using machineId: ${machineId}`);
|
|
3174
|
-
|
|
3820
|
+
const { state, metadata } = registerKillSessionHandler.createSessionMetadata({
|
|
3821
|
+
flavor: "claude",
|
|
3175
3822
|
machineId,
|
|
3176
|
-
|
|
3823
|
+
startedBy: options.startedBy
|
|
3177
3824
|
});
|
|
3178
|
-
let metadata = {
|
|
3179
|
-
path: workingDirectory,
|
|
3180
|
-
host: os.hostname(),
|
|
3181
|
-
version: api.packageJson.version,
|
|
3182
|
-
os: os.platform(),
|
|
3183
|
-
machineId,
|
|
3184
|
-
homeDir: os.homedir(),
|
|
3185
|
-
happyHomeDir: api.configuration.happyCloudHomeDir,
|
|
3186
|
-
happyLibDir: index.projectPath(),
|
|
3187
|
-
happyToolsDir: path.resolve(index.projectPath(), "tools", "unpacked"),
|
|
3188
|
-
startedFromDaemon: options.startedBy === "daemon",
|
|
3189
|
-
hostPid: process.pid,
|
|
3190
|
-
startedBy: options.startedBy || "terminal",
|
|
3191
|
-
// Initialize lifecycle state
|
|
3192
|
-
lifecycleState: "running",
|
|
3193
|
-
lifecycleStateSince: Date.now(),
|
|
3194
|
-
flavor: "claude"
|
|
3195
|
-
};
|
|
3196
3825
|
let response = null;
|
|
3197
3826
|
try {
|
|
3198
3827
|
response = await api$1.getOrCreateSession({ tag: sessionTag, metadata, state });
|
|
@@ -3203,44 +3832,14 @@ async function runClaude(credentials, options = {}) {
|
|
|
3203
3832
|
api.logger.debug("[START] Falling back to local/offline mode after authentication failure");
|
|
3204
3833
|
}
|
|
3205
3834
|
if (!response) {
|
|
3206
|
-
|
|
3207
|
-
|
|
3208
|
-
|
|
3209
|
-
|
|
3210
|
-
|
|
3211
|
-
|
|
3212
|
-
|
|
3213
|
-
const session2 = api$1.sessionSyncClient(resp);
|
|
3214
|
-
const scanner = await createSessionScanner({
|
|
3215
|
-
sessionId: null,
|
|
3216
|
-
workingDirectory,
|
|
3217
|
-
onMessage: (msg) => session2.sendClaudeSessionMessage(msg)
|
|
3218
|
-
});
|
|
3219
|
-
if (offlineSessionId) scanner.onNewSession(offlineSessionId);
|
|
3220
|
-
return { session: session2, scanner };
|
|
3221
|
-
},
|
|
3222
|
-
onNotify: console.log,
|
|
3223
|
-
onCleanup: () => {
|
|
3224
|
-
}
|
|
3835
|
+
await runOfflineClaudeLocalFallback({
|
|
3836
|
+
api: api$1,
|
|
3837
|
+
metadata,
|
|
3838
|
+
state,
|
|
3839
|
+
workingDirectory,
|
|
3840
|
+
claudeEnvVars: options.claudeEnvVars,
|
|
3841
|
+
claudeArgs: options.claudeArgs
|
|
3225
3842
|
});
|
|
3226
|
-
try {
|
|
3227
|
-
await index.claudeLocal({
|
|
3228
|
-
path: workingDirectory,
|
|
3229
|
-
sessionId: null,
|
|
3230
|
-
onSessionFound: (id) => {
|
|
3231
|
-
offlineSessionId = id;
|
|
3232
|
-
},
|
|
3233
|
-
onThinkingChange: () => {
|
|
3234
|
-
},
|
|
3235
|
-
abort: new AbortController().signal,
|
|
3236
|
-
claudeEnvVars: options.claudeEnvVars,
|
|
3237
|
-
claudeArgs: options.claudeArgs,
|
|
3238
|
-
allowedTools: []
|
|
3239
|
-
});
|
|
3240
|
-
} finally {
|
|
3241
|
-
reconnection.cancel();
|
|
3242
|
-
index.stopCaffeinate();
|
|
3243
|
-
}
|
|
3244
3843
|
process.exit(0);
|
|
3245
3844
|
}
|
|
3246
3845
|
api.logger.debug(`Session created: ${response.id}`);
|
|
@@ -3274,10 +3873,7 @@ async function runClaude(credentials, options = {}) {
|
|
|
3274
3873
|
const logPath = api.logger.logFilePath;
|
|
3275
3874
|
api.logger.infoDeveloper(`Session: ${response.id}`);
|
|
3276
3875
|
api.logger.infoDeveloper(`Logs: ${logPath}`);
|
|
3277
|
-
|
|
3278
|
-
...currentState,
|
|
3279
|
-
controlledByUser: options.startingMode !== "remote"
|
|
3280
|
-
}));
|
|
3876
|
+
await registerKillSessionHandler.syncControlledByUserState(session, options.startingMode !== "remote");
|
|
3281
3877
|
const caffeinateStarted = index.startCaffeinate();
|
|
3282
3878
|
if (caffeinateStarted) {
|
|
3283
3879
|
api.logger.infoDeveloper("Sleep prevention enabled (macOS)");
|
|
@@ -3291,128 +3887,22 @@ async function runClaude(credentials, options = {}) {
|
|
|
3291
3887
|
allowedTools: mode.allowedTools,
|
|
3292
3888
|
disallowedTools: mode.disallowedTools
|
|
3293
3889
|
}));
|
|
3294
|
-
|
|
3295
|
-
|
|
3296
|
-
|
|
3297
|
-
|
|
3298
|
-
|
|
3299
|
-
|
|
3300
|
-
let currentDisallowedTools = void 0;
|
|
3301
|
-
session.onUserMessage((message) => {
|
|
3302
|
-
let messagePermissionMode = currentPermissionMode;
|
|
3303
|
-
if (message.meta?.permissionMode) {
|
|
3304
|
-
messagePermissionMode = message.meta.permissionMode;
|
|
3305
|
-
currentPermissionMode = messagePermissionMode;
|
|
3306
|
-
api.logger.debug(`[loop] Permission mode updated from user message to: ${currentPermissionMode}`);
|
|
3307
|
-
} else {
|
|
3308
|
-
api.logger.debug(`[loop] User message received with no permission mode override, using current: ${currentPermissionMode}`);
|
|
3309
|
-
}
|
|
3310
|
-
let messageModel = currentModel;
|
|
3311
|
-
if (message.meta?.hasOwnProperty("model")) {
|
|
3312
|
-
messageModel = message.meta.model || void 0;
|
|
3313
|
-
currentModel = messageModel;
|
|
3314
|
-
api.logger.debug(`[loop] Model updated from user message: ${messageModel || "reset to default"}`);
|
|
3315
|
-
} else {
|
|
3316
|
-
api.logger.debug(`[loop] User message received with no model override, using current: ${currentModel || "default"}`);
|
|
3317
|
-
}
|
|
3318
|
-
let messageCustomSystemPrompt = currentCustomSystemPrompt;
|
|
3319
|
-
if (message.meta?.hasOwnProperty("customSystemPrompt")) {
|
|
3320
|
-
messageCustomSystemPrompt = message.meta.customSystemPrompt || void 0;
|
|
3321
|
-
currentCustomSystemPrompt = messageCustomSystemPrompt;
|
|
3322
|
-
api.logger.debug(`[loop] Custom system prompt updated from user message: ${messageCustomSystemPrompt ? "set" : "reset to none"}`);
|
|
3323
|
-
} else {
|
|
3324
|
-
api.logger.debug(`[loop] User message received with no custom system prompt override, using current: ${currentCustomSystemPrompt ? "set" : "none"}`);
|
|
3325
|
-
}
|
|
3326
|
-
let messageFallbackModel = currentFallbackModel;
|
|
3327
|
-
if (message.meta?.hasOwnProperty("fallbackModel")) {
|
|
3328
|
-
messageFallbackModel = message.meta.fallbackModel || void 0;
|
|
3329
|
-
currentFallbackModel = messageFallbackModel;
|
|
3330
|
-
api.logger.debug(`[loop] Fallback model updated from user message: ${messageFallbackModel || "reset to none"}`);
|
|
3331
|
-
} else {
|
|
3332
|
-
api.logger.debug(`[loop] User message received with no fallback model override, using current: ${currentFallbackModel || "none"}`);
|
|
3333
|
-
}
|
|
3334
|
-
let messageAppendSystemPrompt = currentAppendSystemPrompt;
|
|
3335
|
-
if (message.meta?.hasOwnProperty("appendSystemPrompt")) {
|
|
3336
|
-
messageAppendSystemPrompt = message.meta.appendSystemPrompt || void 0;
|
|
3337
|
-
currentAppendSystemPrompt = messageAppendSystemPrompt;
|
|
3338
|
-
api.logger.debug(`[loop] Append system prompt updated from user message: ${messageAppendSystemPrompt ? "set" : "reset to none"}`);
|
|
3339
|
-
} else {
|
|
3340
|
-
api.logger.debug(`[loop] User message received with no append system prompt override, using current: ${currentAppendSystemPrompt ? "set" : "none"}`);
|
|
3341
|
-
}
|
|
3342
|
-
let messageAllowedTools = currentAllowedTools;
|
|
3343
|
-
if (message.meta?.hasOwnProperty("allowedTools")) {
|
|
3344
|
-
messageAllowedTools = message.meta.allowedTools || void 0;
|
|
3345
|
-
currentAllowedTools = messageAllowedTools;
|
|
3346
|
-
api.logger.debug(`[loop] Allowed tools updated from user message: ${messageAllowedTools ? messageAllowedTools.join(", ") : "reset to none"}`);
|
|
3347
|
-
} else {
|
|
3348
|
-
api.logger.debug(`[loop] User message received with no allowed tools override, using current: ${currentAllowedTools ? currentAllowedTools.join(", ") : "none"}`);
|
|
3349
|
-
}
|
|
3350
|
-
let messageDisallowedTools = currentDisallowedTools;
|
|
3351
|
-
if (message.meta?.hasOwnProperty("disallowedTools")) {
|
|
3352
|
-
messageDisallowedTools = message.meta.disallowedTools || void 0;
|
|
3353
|
-
currentDisallowedTools = messageDisallowedTools;
|
|
3354
|
-
api.logger.debug(`[loop] Disallowed tools updated from user message: ${messageDisallowedTools ? messageDisallowedTools.join(", ") : "reset to none"}`);
|
|
3355
|
-
} else {
|
|
3356
|
-
api.logger.debug(`[loop] User message received with no disallowed tools override, using current: ${currentDisallowedTools ? currentDisallowedTools.join(", ") : "none"}`);
|
|
3357
|
-
}
|
|
3358
|
-
const specialCommand = parseSpecialCommand(message.content.text);
|
|
3359
|
-
if (specialCommand.type === "compact") {
|
|
3360
|
-
api.logger.debug("[start] Detected /compact command");
|
|
3361
|
-
const enhancedMode2 = {
|
|
3362
|
-
permissionMode: messagePermissionMode || "default",
|
|
3363
|
-
model: messageModel,
|
|
3364
|
-
fallbackModel: messageFallbackModel,
|
|
3365
|
-
customSystemPrompt: messageCustomSystemPrompt,
|
|
3366
|
-
appendSystemPrompt: messageAppendSystemPrompt,
|
|
3367
|
-
allowedTools: messageAllowedTools,
|
|
3368
|
-
disallowedTools: messageDisallowedTools
|
|
3369
|
-
};
|
|
3370
|
-
messageQueue.pushIsolateAndClear(specialCommand.originalMessage || message.content.text, enhancedMode2);
|
|
3371
|
-
api.logger.debugLargeJson("[start] /compact command pushed to queue:", message);
|
|
3372
|
-
return;
|
|
3373
|
-
}
|
|
3374
|
-
if (specialCommand.type === "clear") {
|
|
3375
|
-
api.logger.debug("[start] Detected /clear command");
|
|
3376
|
-
const enhancedMode2 = {
|
|
3377
|
-
permissionMode: messagePermissionMode || "default",
|
|
3378
|
-
model: messageModel,
|
|
3379
|
-
fallbackModel: messageFallbackModel,
|
|
3380
|
-
customSystemPrompt: messageCustomSystemPrompt,
|
|
3381
|
-
appendSystemPrompt: messageAppendSystemPrompt,
|
|
3382
|
-
allowedTools: messageAllowedTools,
|
|
3383
|
-
disallowedTools: messageDisallowedTools
|
|
3384
|
-
};
|
|
3385
|
-
messageQueue.pushIsolateAndClear(specialCommand.originalMessage || message.content.text, enhancedMode2);
|
|
3386
|
-
api.logger.debugLargeJson("[start] /compact command pushed to queue:", message);
|
|
3387
|
-
return;
|
|
3890
|
+
bindClaudeUserMessageQueue({
|
|
3891
|
+
session,
|
|
3892
|
+
messageQueue,
|
|
3893
|
+
initialState: {
|
|
3894
|
+
permissionMode: options.permissionMode,
|
|
3895
|
+
model: options.model
|
|
3388
3896
|
}
|
|
3389
|
-
const enhancedMode = {
|
|
3390
|
-
permissionMode: messagePermissionMode || "default",
|
|
3391
|
-
model: messageModel,
|
|
3392
|
-
fallbackModel: messageFallbackModel,
|
|
3393
|
-
customSystemPrompt: messageCustomSystemPrompt,
|
|
3394
|
-
appendSystemPrompt: messageAppendSystemPrompt,
|
|
3395
|
-
allowedTools: messageAllowedTools,
|
|
3396
|
-
disallowedTools: messageDisallowedTools
|
|
3397
|
-
};
|
|
3398
|
-
messageQueue.push(message.content.text, enhancedMode);
|
|
3399
|
-
api.logger.debugLargeJson("User message pushed to queue:", message);
|
|
3400
3897
|
});
|
|
3401
3898
|
const cleanup = async () => {
|
|
3402
3899
|
api.logger.debug("[START] Received termination signal, cleaning up...");
|
|
3403
3900
|
try {
|
|
3404
3901
|
if (session) {
|
|
3405
|
-
session.updateMetadata((currentMetadata) => ({
|
|
3406
|
-
...currentMetadata,
|
|
3407
|
-
lifecycleState: "archived",
|
|
3408
|
-
lifecycleStateSince: Date.now(),
|
|
3409
|
-
archivedBy: "cli",
|
|
3410
|
-
archiveReason: "User terminated"
|
|
3411
|
-
}));
|
|
3412
3902
|
currentSession?.cleanup();
|
|
3413
|
-
|
|
3414
|
-
|
|
3415
|
-
|
|
3903
|
+
await registerKillSessionHandler.closeProviderSession(session, {
|
|
3904
|
+
archiveReason: "User terminated"
|
|
3905
|
+
});
|
|
3416
3906
|
}
|
|
3417
3907
|
index.stopCaffeinate();
|
|
3418
3908
|
hookServer.stop();
|
|
@@ -3444,12 +3934,9 @@ async function runClaude(credentials, options = {}) {
|
|
|
3444
3934
|
api: api$1,
|
|
3445
3935
|
allowedTools: [],
|
|
3446
3936
|
mcpServers: {},
|
|
3447
|
-
onModeChange: (newMode) => {
|
|
3937
|
+
onModeChange: async (newMode) => {
|
|
3448
3938
|
session.sendSessionEvent({ type: "switch", mode: newMode });
|
|
3449
|
-
|
|
3450
|
-
...currentState,
|
|
3451
|
-
controlledByUser: newMode === "local"
|
|
3452
|
-
}));
|
|
3939
|
+
await registerKillSessionHandler.syncControlledByUserState(session, newMode === "local");
|
|
3453
3940
|
},
|
|
3454
3941
|
onSessionReady: (sessionInstance) => {
|
|
3455
3942
|
currentSession = sessionInstance;
|
|
@@ -3458,14 +3945,13 @@ async function runClaude(credentials, options = {}) {
|
|
|
3458
3945
|
claudeEnvVars: options.claudeEnvVars,
|
|
3459
3946
|
claudeArgs: options.claudeArgs,
|
|
3460
3947
|
hookSettingsPath,
|
|
3461
|
-
jsRuntime: options.jsRuntime
|
|
3948
|
+
jsRuntime: options.jsRuntime,
|
|
3949
|
+
startedBy: options.startedBy
|
|
3462
3950
|
});
|
|
3463
3951
|
currentSession?.cleanup();
|
|
3464
|
-
session.sendSessionDeath();
|
|
3465
3952
|
api.logger.debug("Waiting for socket to flush...");
|
|
3466
|
-
await session.flush();
|
|
3467
3953
|
api.logger.debug("Closing session...");
|
|
3468
|
-
await
|
|
3954
|
+
await registerKillSessionHandler.closeProviderSession(session);
|
|
3469
3955
|
index.stopCaffeinate();
|
|
3470
3956
|
api.logger.debug("Stopped sleep prevention");
|
|
3471
3957
|
hookServer.stop();
|