happy-imou-cloud 2.0.9 → 2.0.10
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/{api-CnvyGas2.mjs → api-BjxmW-0W.mjs} +2 -2
- package/dist/{api-CUTdFiFP.cjs → api-DUE5TJBE.cjs} +43 -43
- package/dist/{command-BGA3qCKR.mjs → command-ComOeFLY.mjs} +3 -3
- package/dist/{command-DLAJZsKX.cjs → command-Df7u5eAT.cjs} +3 -3
- package/dist/{index-D4OdFq68.cjs → index-Cuvfa15L.cjs} +791 -197
- package/dist/{index-BpZL4RcT.mjs → index-CzvgPwr1.mjs} +711 -117
- package/dist/index.cjs +4 -4
- package/dist/index.mjs +4 -4
- package/dist/lib.cjs +1 -1
- package/dist/lib.mjs +1 -1
- package/dist/{names-C9iJODqA.mjs → names-CicSgRNg.mjs} +2 -2
- package/dist/{names-YEhZwVT0.cjs → names-yJNZoTv_.cjs} +4 -4
- package/dist/{persistence-BPV3AmJL.mjs → persistence-BxP6Jw1f.mjs} +1 -1
- package/dist/{persistence-CxvL0cwp.cjs → persistence-D7JtnrYA.cjs} +29 -29
- package/dist/{registerKillSessionHandler-C2O8b5wH.mjs → registerKillSessionHandler-ARQrPvnT.mjs} +2 -2
- package/dist/{registerKillSessionHandler-rqd7duc9.cjs → registerKillSessionHandler-DCMFiXyA.cjs} +2 -2
- package/dist/{runClaude-8inO7C5p.mjs → runClaude-B_fTMxc4.mjs} +6 -15
- package/dist/{runClaude-KwIVwFp1.cjs → runClaude-C1W_Nw0C.cjs} +32 -41
- package/dist/{runCodex-BQ-fN5E6.mjs → runCodex-CUgOiIEz.mjs} +12 -13
- package/dist/{runCodex-Ba8COxZe.cjs → runCodex-D2VCWVEK.cjs} +12 -13
- package/dist/{runGemini-BE0FizuV.mjs → runGemini-2_FEtJYa.mjs} +7 -16
- package/dist/{runGemini-DtdLLX9o.cjs → runGemini-CiGnjflq.cjs} +7 -16
- package/package.json +1 -1
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
3
|
var chalk = require('chalk');
|
|
4
|
-
var api = require('./api-
|
|
5
|
-
var persistence = require('./persistence-
|
|
4
|
+
var api = require('./api-DUE5TJBE.cjs');
|
|
5
|
+
var persistence = require('./persistence-D7JtnrYA.cjs');
|
|
6
6
|
var z = require('zod');
|
|
7
|
-
var fs$
|
|
7
|
+
var fs$2 = require('fs/promises');
|
|
8
8
|
var os$1 = require('os');
|
|
9
9
|
var tmp = require('tmp');
|
|
10
10
|
var node_crypto = require('node:crypto');
|
|
@@ -14,23 +14,23 @@ var qrcode = require('qrcode-terminal');
|
|
|
14
14
|
var promises = require('node:fs/promises');
|
|
15
15
|
var node_module = require('node:module');
|
|
16
16
|
var os = require('node:os');
|
|
17
|
-
var
|
|
17
|
+
var path = require('node:path');
|
|
18
18
|
var open = require('open');
|
|
19
19
|
var React = require('react');
|
|
20
20
|
var ink = require('ink');
|
|
21
21
|
var child_process = require('child_process');
|
|
22
|
-
var path = require('path');
|
|
22
|
+
var path$1 = require('path');
|
|
23
23
|
var url = require('url');
|
|
24
|
-
var fs = require('fs');
|
|
24
|
+
var fs$1 = require('fs');
|
|
25
25
|
var node_child_process = require('node:child_process');
|
|
26
26
|
var psList = require('ps-list');
|
|
27
27
|
var spawn = require('cross-spawn');
|
|
28
|
-
var
|
|
28
|
+
var fs = require('node:fs');
|
|
29
29
|
var fastify = require('fastify');
|
|
30
30
|
var fastifyTypeProviderZod = require('fastify-type-provider-zod');
|
|
31
|
+
var crypto = require('crypto');
|
|
31
32
|
var node_readline = require('node:readline');
|
|
32
33
|
var http = require('http');
|
|
33
|
-
var crypto = require('crypto');
|
|
34
34
|
var util = require('util');
|
|
35
35
|
var sdk = require('@agentclientprotocol/sdk');
|
|
36
36
|
|
|
@@ -70,7 +70,7 @@ async function openBrowser(url) {
|
|
|
70
70
|
}
|
|
71
71
|
}
|
|
72
72
|
|
|
73
|
-
const require$1 = node_module.createRequire((typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('index-
|
|
73
|
+
const require$1 = node_module.createRequire((typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('index-Cuvfa15L.cjs', document.baseURI).href)));
|
|
74
74
|
const QRCode = require$1("qrcode-terminal/vendor/QRCode");
|
|
75
75
|
const QRErrorCorrectLevel = require$1("qrcode-terminal/vendor/QRCode/QRErrorCorrectLevel");
|
|
76
76
|
const pendingTempFiles = /* @__PURE__ */ new Set();
|
|
@@ -164,7 +164,7 @@ async function openWindowsQrCode(value) {
|
|
|
164
164
|
"</body>",
|
|
165
165
|
"</html>"
|
|
166
166
|
].join("");
|
|
167
|
-
const filePath =
|
|
167
|
+
const filePath = path.join(os.tmpdir(), `happy-auth-qrcode-${node_crypto.randomUUID()}.html`);
|
|
168
168
|
await promises.writeFile(filePath, html, "utf8");
|
|
169
169
|
const opened = await openBrowser(filePath);
|
|
170
170
|
if (opened) {
|
|
@@ -693,18 +693,28 @@ function setupCleanupHandlers() {
|
|
|
693
693
|
});
|
|
694
694
|
}
|
|
695
695
|
|
|
696
|
-
const __dirname$1 = path.dirname(url.fileURLToPath((typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('index-
|
|
696
|
+
const __dirname$1 = path$1.dirname(url.fileURLToPath((typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('index-Cuvfa15L.cjs', document.baseURI).href))));
|
|
697
697
|
function projectPath() {
|
|
698
|
-
const path
|
|
699
|
-
return path
|
|
698
|
+
const path = path$1.resolve(__dirname$1, "..");
|
|
699
|
+
return path;
|
|
700
700
|
}
|
|
701
701
|
|
|
702
|
+
function classifyHappyProcessLookup(proc) {
|
|
703
|
+
const match = classifyHappyProcess(proc);
|
|
704
|
+
if (match) {
|
|
705
|
+
return match;
|
|
706
|
+
}
|
|
707
|
+
if (process.platform === "win32" && isWindowsCliHostProcess(proc.name) && proc.cmd.trim().length === 0) {
|
|
708
|
+
return "indeterminate";
|
|
709
|
+
}
|
|
710
|
+
return null;
|
|
711
|
+
}
|
|
702
712
|
function getDaemonPid() {
|
|
703
713
|
try {
|
|
704
|
-
if (!
|
|
714
|
+
if (!fs.existsSync(api.configuration.daemonStateFile)) {
|
|
705
715
|
return null;
|
|
706
716
|
}
|
|
707
|
-
const state = JSON.parse(
|
|
717
|
+
const state = JSON.parse(fs.readFileSync(api.configuration.daemonStateFile, "utf-8"));
|
|
708
718
|
return typeof state.pid === "number" ? state.pid : null;
|
|
709
719
|
} catch {
|
|
710
720
|
return null;
|
|
@@ -779,7 +789,7 @@ function findWindowsDaemonProcess(processes) {
|
|
|
779
789
|
return {
|
|
780
790
|
pid: daemonPid,
|
|
781
791
|
command: `${daemonProcess.name || "unknown"} (PID ${daemonPid})`,
|
|
782
|
-
type: "daemon"
|
|
792
|
+
type: "daemon-indeterminate"
|
|
783
793
|
};
|
|
784
794
|
}
|
|
785
795
|
function findWindowsHappyProcesses(processes) {
|
|
@@ -796,6 +806,40 @@ function findWindowsHappyProcesses(processes) {
|
|
|
796
806
|
}
|
|
797
807
|
return Array.from(matches.values());
|
|
798
808
|
}
|
|
809
|
+
function toProcessSnapshot(proc) {
|
|
810
|
+
return {
|
|
811
|
+
pid: proc.pid,
|
|
812
|
+
name: proc.name || "",
|
|
813
|
+
cmd: proc.cmd || ""
|
|
814
|
+
};
|
|
815
|
+
}
|
|
816
|
+
async function findHappyProcessByPid(pid) {
|
|
817
|
+
try {
|
|
818
|
+
if (process.platform === "win32") {
|
|
819
|
+
const windowsProcesses = getWindowsProcessSnapshots();
|
|
820
|
+
if (windowsProcesses.length > 0) {
|
|
821
|
+
const proc3 = windowsProcesses.find((candidate) => candidate.pid === pid);
|
|
822
|
+
if (!proc3) {
|
|
823
|
+
return null;
|
|
824
|
+
}
|
|
825
|
+
return classifyHappyProcessLookup(proc3);
|
|
826
|
+
}
|
|
827
|
+
const fallbackProcesses = (await psList()).map(toProcessSnapshot);
|
|
828
|
+
const proc2 = fallbackProcesses.find((candidate) => candidate.pid === pid);
|
|
829
|
+
if (!proc2) {
|
|
830
|
+
return null;
|
|
831
|
+
}
|
|
832
|
+
return classifyHappyProcessLookup(proc2);
|
|
833
|
+
}
|
|
834
|
+
const proc = (await psList()).map(toProcessSnapshot).find((candidate) => candidate.pid === pid);
|
|
835
|
+
if (!proc) {
|
|
836
|
+
return null;
|
|
837
|
+
}
|
|
838
|
+
return classifyHappyProcess(proc);
|
|
839
|
+
} catch (error) {
|
|
840
|
+
return "indeterminate";
|
|
841
|
+
}
|
|
842
|
+
}
|
|
799
843
|
async function findAllHappyProcesses() {
|
|
800
844
|
try {
|
|
801
845
|
if (process.platform === "win32") {
|
|
@@ -803,15 +847,8 @@ async function findAllHappyProcesses() {
|
|
|
803
847
|
if (windowsProcesses.length > 0) {
|
|
804
848
|
return findWindowsHappyProcesses(windowsProcesses);
|
|
805
849
|
}
|
|
806
|
-
const fallbackProcesses = await psList();
|
|
807
|
-
|
|
808
|
-
fallbackProcesses.map((proc) => ({
|
|
809
|
-
pid: proc.pid,
|
|
810
|
-
name: proc.name || "",
|
|
811
|
-
cmd: proc.cmd || ""
|
|
812
|
-
}))
|
|
813
|
-
);
|
|
814
|
-
return fallbackDaemon ? [fallbackDaemon] : [];
|
|
850
|
+
const fallbackProcesses = (await psList()).map(toProcessSnapshot);
|
|
851
|
+
return findWindowsHappyProcesses(fallbackProcesses);
|
|
815
852
|
}
|
|
816
853
|
const processes = await psList();
|
|
817
854
|
const allProcesses = [];
|
|
@@ -868,6 +905,46 @@ async function killRunawayHappyProcesses() {
|
|
|
868
905
|
return { killed, errors };
|
|
869
906
|
}
|
|
870
907
|
|
|
908
|
+
const CONFIRMED_DAEMON_PROCESS_TYPES = /* @__PURE__ */ new Set([
|
|
909
|
+
"daemon",
|
|
910
|
+
"dev-daemon"
|
|
911
|
+
]);
|
|
912
|
+
function currentProcessLooksLikeDaemon() {
|
|
913
|
+
return process.argv.join(" ").includes("daemon start-sync");
|
|
914
|
+
}
|
|
915
|
+
async function inspectDaemonStateProcess(state) {
|
|
916
|
+
if (!state) {
|
|
917
|
+
return "stale";
|
|
918
|
+
}
|
|
919
|
+
try {
|
|
920
|
+
process.kill(state.pid, 0);
|
|
921
|
+
} catch {
|
|
922
|
+
api.logger.debug("[DAEMON RUN] Daemon PID not running, cleaning up state");
|
|
923
|
+
await cleanupDaemonState();
|
|
924
|
+
return "stale";
|
|
925
|
+
}
|
|
926
|
+
const processInfo = await findHappyProcessByPid(state.pid);
|
|
927
|
+
if (processInfo === "indeterminate") {
|
|
928
|
+
api.logger.debug(`[DAEMON RUN] Daemon PID ${state.pid} is alive but process identity is indeterminate`);
|
|
929
|
+
return "indeterminate";
|
|
930
|
+
}
|
|
931
|
+
if (!processInfo) {
|
|
932
|
+
api.logger.debug(`[DAEMON RUN] PID ${state.pid} is alive but is not a Happy daemon process, cleaning up state`);
|
|
933
|
+
await cleanupDaemonState();
|
|
934
|
+
return "stale";
|
|
935
|
+
}
|
|
936
|
+
if (processInfo.type === "current" && currentProcessLooksLikeDaemon()) {
|
|
937
|
+
return "confirmed";
|
|
938
|
+
}
|
|
939
|
+
if (!CONFIRMED_DAEMON_PROCESS_TYPES.has(processInfo.type)) {
|
|
940
|
+
api.logger.debug(
|
|
941
|
+
`[DAEMON RUN] PID ${state.pid} resolved to Happy process type ${processInfo.type}, not a daemon; cleaning up state`
|
|
942
|
+
);
|
|
943
|
+
await cleanupDaemonState();
|
|
944
|
+
return "stale";
|
|
945
|
+
}
|
|
946
|
+
return "confirmed";
|
|
947
|
+
}
|
|
871
948
|
async function daemonPost(path, body) {
|
|
872
949
|
const state = await persistence.readDaemonState();
|
|
873
950
|
if (!state?.httpPort) {
|
|
@@ -877,9 +954,8 @@ async function daemonPost(path, body) {
|
|
|
877
954
|
error: errorMessage
|
|
878
955
|
};
|
|
879
956
|
}
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
} catch (error) {
|
|
957
|
+
const processStatus = await inspectDaemonStateProcess(state);
|
|
958
|
+
if (processStatus === "stale") {
|
|
883
959
|
const errorMessage = "Daemon is not running, file is stale";
|
|
884
960
|
api.logger.debug(`[CONTROL CLIENT] ${errorMessage}`);
|
|
885
961
|
return {
|
|
@@ -978,18 +1054,22 @@ async function stopDaemonHttp() {
|
|
|
978
1054
|
await daemonPost("/stop");
|
|
979
1055
|
}
|
|
980
1056
|
async function checkIfDaemonRunningAndCleanupStaleState() {
|
|
1057
|
+
const runtimeStatus = await getDaemonRuntimeStatus();
|
|
1058
|
+
return runtimeStatus !== "not-running";
|
|
1059
|
+
}
|
|
1060
|
+
async function getDaemonRuntimeStatus() {
|
|
981
1061
|
const state = await persistence.readDaemonState();
|
|
982
1062
|
if (!state) {
|
|
983
|
-
return
|
|
1063
|
+
return "not-running";
|
|
984
1064
|
}
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
return
|
|
988
|
-
}
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
return false;
|
|
1065
|
+
const processStatus = await inspectDaemonStateProcess(state);
|
|
1066
|
+
if (processStatus === "confirmed") {
|
|
1067
|
+
return "running";
|
|
1068
|
+
}
|
|
1069
|
+
if (processStatus === "indeterminate") {
|
|
1070
|
+
return "indeterminate";
|
|
992
1071
|
}
|
|
1072
|
+
return "not-running";
|
|
993
1073
|
}
|
|
994
1074
|
async function isDaemonControlServerResponsive(timeoutMs = 1e3) {
|
|
995
1075
|
const state = await persistence.readDaemonState();
|
|
@@ -997,11 +1077,9 @@ async function isDaemonControlServerResponsive(timeoutMs = 1e3) {
|
|
|
997
1077
|
api.logger.debug("[DAEMON CONTROL] No daemon state or control port found for readiness check");
|
|
998
1078
|
return false;
|
|
999
1079
|
}
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
api.logger.debug("[DAEMON CONTROL] Daemon PID not running during readiness check, cleaning up state");
|
|
1004
|
-
await cleanupDaemonState();
|
|
1080
|
+
const processStatus = await inspectDaemonStateProcess(state);
|
|
1081
|
+
if (processStatus === "stale") {
|
|
1082
|
+
api.logger.debug("[DAEMON CONTROL] Daemon state became stale during readiness check");
|
|
1005
1083
|
return false;
|
|
1006
1084
|
}
|
|
1007
1085
|
try {
|
|
@@ -1017,7 +1095,7 @@ async function isDaemonControlServerResponsive(timeoutMs = 1e3) {
|
|
|
1017
1095
|
return false;
|
|
1018
1096
|
}
|
|
1019
1097
|
}
|
|
1020
|
-
async function isDaemonRunningCurrentlyInstalledHappyVersion() {
|
|
1098
|
+
async function isDaemonRunningCurrentlyInstalledHappyVersion(readinessProbeTimeoutMs = 1e3) {
|
|
1021
1099
|
api.logger.debug("[DAEMON CONTROL] Checking if daemon is running same version");
|
|
1022
1100
|
const runningDaemon = await checkIfDaemonRunningAndCleanupStaleState();
|
|
1023
1101
|
if (!runningDaemon) {
|
|
@@ -1030,14 +1108,14 @@ async function isDaemonRunningCurrentlyInstalledHappyVersion() {
|
|
|
1030
1108
|
return false;
|
|
1031
1109
|
}
|
|
1032
1110
|
try {
|
|
1033
|
-
const packageJsonPath = path.join(projectPath(), "package.json");
|
|
1034
|
-
const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, "utf-8"));
|
|
1111
|
+
const packageJsonPath = path$1.join(projectPath(), "package.json");
|
|
1112
|
+
const packageJson = JSON.parse(fs$1.readFileSync(packageJsonPath, "utf-8"));
|
|
1035
1113
|
const currentCliVersion = packageJson.version;
|
|
1036
1114
|
api.logger.debug(`[DAEMON CONTROL] Current CLI version: ${currentCliVersion}, Daemon started with version: ${state.startedWithCliVersion}`);
|
|
1037
1115
|
if (currentCliVersion !== state.startedWithCliVersion) {
|
|
1038
1116
|
return false;
|
|
1039
1117
|
}
|
|
1040
|
-
return await isDaemonControlServerResponsive();
|
|
1118
|
+
return await isDaemonControlServerResponsive(readinessProbeTimeoutMs);
|
|
1041
1119
|
} catch (error) {
|
|
1042
1120
|
api.logger.debug("[DAEMON CONTROL] Error checking daemon version", error);
|
|
1043
1121
|
return false;
|
|
@@ -1070,6 +1148,17 @@ async function stopDaemon() {
|
|
|
1070
1148
|
return;
|
|
1071
1149
|
}
|
|
1072
1150
|
api.logger.debug(`Stopping daemon with PID ${state.pid}`);
|
|
1151
|
+
const processStatus = await inspectDaemonStateProcess(state);
|
|
1152
|
+
if (processStatus === "stale") {
|
|
1153
|
+
api.logger.debug("Daemon state was stale while stopping, trying known control port/orphan cleanup");
|
|
1154
|
+
const stoppedByKnownPort = await stopDaemonOnKnownPort();
|
|
1155
|
+
if (stoppedByKnownPort) {
|
|
1156
|
+
api.logger.debug(`Requested daemon stop via known control port ${api.HAPPY_CLOUD_DAEMON_PORT}`);
|
|
1157
|
+
return;
|
|
1158
|
+
}
|
|
1159
|
+
await killOrphanDaemonProcesses();
|
|
1160
|
+
return;
|
|
1161
|
+
}
|
|
1073
1162
|
try {
|
|
1074
1163
|
await stopDaemonHttp();
|
|
1075
1164
|
await waitForProcessDeath(state.pid, 2e3);
|
|
@@ -1079,6 +1168,10 @@ async function stopDaemon() {
|
|
|
1079
1168
|
} catch (error) {
|
|
1080
1169
|
api.logger.debug("HTTP stop failed, will force kill", error);
|
|
1081
1170
|
}
|
|
1171
|
+
if (processStatus === "indeterminate") {
|
|
1172
|
+
api.logger.debug(`Skipping force kill for PID ${state.pid} because daemon identity is indeterminate`);
|
|
1173
|
+
return;
|
|
1174
|
+
}
|
|
1082
1175
|
try {
|
|
1083
1176
|
process.kill(state.pid, "SIGKILL");
|
|
1084
1177
|
await waitForProcessDeath(state.pid, 2e3).catch(() => {
|
|
@@ -1130,12 +1223,12 @@ function getEnvironmentInfo() {
|
|
|
1130
1223
|
terminal: process.env.TERM
|
|
1131
1224
|
};
|
|
1132
1225
|
}
|
|
1133
|
-
function resolveDaemonSpawnDiagnostics(projectRoot, fileExists =
|
|
1226
|
+
function resolveDaemonSpawnDiagnostics(projectRoot, fileExists = fs.existsSync) {
|
|
1134
1227
|
const wrapperCandidates = [
|
|
1135
|
-
|
|
1136
|
-
|
|
1228
|
+
path.join(projectRoot, "bin", "happy-cloud.mjs"),
|
|
1229
|
+
path.join(projectRoot, "bin", "happy.mjs")
|
|
1137
1230
|
];
|
|
1138
|
-
const cliEntrypoint =
|
|
1231
|
+
const cliEntrypoint = path.join(projectRoot, "dist", "index.mjs");
|
|
1139
1232
|
const wrapperPath = wrapperCandidates.find((candidate) => fileExists(candidate)) ?? wrapperCandidates[0];
|
|
1140
1233
|
return {
|
|
1141
1234
|
projectRoot,
|
|
@@ -1146,14 +1239,14 @@ function resolveDaemonSpawnDiagnostics(projectRoot, fileExists = node_fs.existsS
|
|
|
1146
1239
|
};
|
|
1147
1240
|
}
|
|
1148
1241
|
function getLogFiles(logDir) {
|
|
1149
|
-
if (!
|
|
1242
|
+
if (!fs.existsSync(logDir)) {
|
|
1150
1243
|
return [];
|
|
1151
1244
|
}
|
|
1152
1245
|
try {
|
|
1153
|
-
return
|
|
1154
|
-
const path =
|
|
1155
|
-
const stats =
|
|
1156
|
-
return { file, path, modified: stats.mtime };
|
|
1246
|
+
return fs.readdirSync(logDir).filter((file) => file.endsWith(".log")).map((file) => {
|
|
1247
|
+
const path$1 = path.join(logDir, file);
|
|
1248
|
+
const stats = fs.statSync(path$1);
|
|
1249
|
+
return { file, path: path$1, modified: stats.mtime };
|
|
1157
1250
|
}).sort((a, b) => b.modified.getTime() - a.modified.getTime());
|
|
1158
1251
|
} catch {
|
|
1159
1252
|
return [];
|
|
@@ -1212,9 +1305,9 @@ async function runDoctorCommand(filter) {
|
|
|
1212
1305
|
}
|
|
1213
1306
|
console.log(chalk.bold("\n\u{1F916} Daemon Status"));
|
|
1214
1307
|
try {
|
|
1215
|
-
const
|
|
1308
|
+
const daemonStatus = await getDaemonRuntimeStatus();
|
|
1216
1309
|
const state = await persistence.readDaemonState();
|
|
1217
|
-
if (
|
|
1310
|
+
if (daemonStatus === "running" && state) {
|
|
1218
1311
|
console.log(chalk.green("\u2713 Daemon is running"));
|
|
1219
1312
|
console.log(` PID: ${state.pid}`);
|
|
1220
1313
|
console.log(` Started: ${new Date(state.startTime).toLocaleString()}`);
|
|
@@ -1222,7 +1315,14 @@ async function runDoctorCommand(filter) {
|
|
|
1222
1315
|
if (state.httpPort) {
|
|
1223
1316
|
console.log(` HTTP Port: ${state.httpPort}`);
|
|
1224
1317
|
}
|
|
1225
|
-
} else if (
|
|
1318
|
+
} else if (daemonStatus === "indeterminate" && state) {
|
|
1319
|
+
console.log(chalk.yellow("\u26A0\uFE0F Daemon state exists, but process identity could not be confirmed"));
|
|
1320
|
+
console.log(` PID: ${state.pid}`);
|
|
1321
|
+
console.log(` CLI Version: ${state.startedWithCliVersion}`);
|
|
1322
|
+
if (state.httpPort) {
|
|
1323
|
+
console.log(` HTTP Port: ${state.httpPort}`);
|
|
1324
|
+
}
|
|
1325
|
+
} else if (state) {
|
|
1226
1326
|
console.log(chalk.yellow("\u26A0\uFE0F Daemon state exists but process not running (stale)"));
|
|
1227
1327
|
} else {
|
|
1228
1328
|
console.log(chalk.red("\u274C Daemon is not running"));
|
|
@@ -1244,6 +1344,7 @@ async function runDoctorCommand(filter) {
|
|
|
1244
1344
|
const typeLabels = {
|
|
1245
1345
|
"current": "\u{1F4CD} Current Process",
|
|
1246
1346
|
"daemon": "\u{1F916} Daemon",
|
|
1347
|
+
"daemon-indeterminate": "\u26A0\uFE0F Possible Daemon (identity unconfirmed)",
|
|
1247
1348
|
"daemon-version-check": "\u{1F50D} Daemon Version Check (stuck)",
|
|
1248
1349
|
"daemon-spawned-session": "\u{1F517} Daemon-Spawned Sessions",
|
|
1249
1350
|
"user-session": "\u{1F464} User Sessions",
|
|
@@ -1258,7 +1359,7 @@ async function runDoctorCommand(filter) {
|
|
|
1258
1359
|
console.log(chalk.blue(`
|
|
1259
1360
|
${typeLabels[type] || type}:`));
|
|
1260
1361
|
processes.forEach(({ pid, command }) => {
|
|
1261
|
-
const color = type === "current" ? chalk.green : type.startsWith("dev") ? chalk.cyan : type.includes("daemon") ? chalk.blue : chalk.gray;
|
|
1362
|
+
const color = type === "current" ? chalk.green : type === "daemon-indeterminate" ? chalk.yellow : type.startsWith("dev") ? chalk.cyan : type.includes("daemon") ? chalk.blue : chalk.gray;
|
|
1262
1363
|
console.log(` ${color(`PID ${pid}`)}: ${chalk.gray(command)}`);
|
|
1263
1364
|
});
|
|
1264
1365
|
});
|
|
@@ -1342,7 +1443,7 @@ const isBun = () => getRuntime() === "bun";
|
|
|
1342
1443
|
|
|
1343
1444
|
function spawnHappyCLI(args, options = {}) {
|
|
1344
1445
|
const projectRoot = projectPath();
|
|
1345
|
-
const entrypoint =
|
|
1446
|
+
const entrypoint = path.join(projectRoot, "dist", "index.mjs");
|
|
1346
1447
|
let directory;
|
|
1347
1448
|
if ("cwd" in options) {
|
|
1348
1449
|
directory = options.cwd;
|
|
@@ -1357,7 +1458,7 @@ function spawnHappyCLI(args, options = {}) {
|
|
|
1357
1458
|
entrypoint,
|
|
1358
1459
|
...args
|
|
1359
1460
|
];
|
|
1360
|
-
if (!
|
|
1461
|
+
if (!fs.existsSync(entrypoint)) {
|
|
1361
1462
|
const errorMessage = `Entrypoint ${entrypoint} does not exist`;
|
|
1362
1463
|
api.logger.debug(`[SPAWN HAPPY CLOUD CLI] ${errorMessage}`);
|
|
1363
1464
|
throw new Error(errorMessage);
|
|
@@ -2288,6 +2389,283 @@ function buildDaemonChildEnv(baseEnv, extraEnv) {
|
|
|
2288
2389
|
return childEnv;
|
|
2289
2390
|
}
|
|
2290
2391
|
|
|
2392
|
+
const DIFFERENT_DAEMON_RUNNING_MESSAGE = "A different daemon was started without killing us. We should kill ourselves.";
|
|
2393
|
+
function readProjectCliVersionFromDisk() {
|
|
2394
|
+
const packageJsonPath = path.join(projectPath(), "package.json");
|
|
2395
|
+
try {
|
|
2396
|
+
const parsedPackageJson = JSON.parse(fs.readFileSync(packageJsonPath, "utf-8"));
|
|
2397
|
+
if (typeof parsedPackageJson.version !== "string" || parsedPackageJson.version.trim().length === 0) {
|
|
2398
|
+
api.logger.warn(
|
|
2399
|
+
`[DAEMON RUN] Skipping daemon version check because ${packageJsonPath} does not contain a valid string version`
|
|
2400
|
+
);
|
|
2401
|
+
return null;
|
|
2402
|
+
}
|
|
2403
|
+
return parsedPackageJson.version;
|
|
2404
|
+
} catch (error) {
|
|
2405
|
+
api.logger.warn(
|
|
2406
|
+
`[DAEMON RUN] Skipping daemon version check because ${packageJsonPath} could not be read or parsed`,
|
|
2407
|
+
error instanceof Error ? error.message : String(error)
|
|
2408
|
+
);
|
|
2409
|
+
return null;
|
|
2410
|
+
}
|
|
2411
|
+
}
|
|
2412
|
+
async function runDaemonHealthCheck({
|
|
2413
|
+
trackedSessionPids,
|
|
2414
|
+
removeTrackedSession,
|
|
2415
|
+
currentCliVersion,
|
|
2416
|
+
readProjectCliVersion = readProjectCliVersionFromDisk,
|
|
2417
|
+
onDaemonOutdated,
|
|
2418
|
+
readDaemonState,
|
|
2419
|
+
daemonPid,
|
|
2420
|
+
requestShutdown,
|
|
2421
|
+
writeHeartbeat
|
|
2422
|
+
}) {
|
|
2423
|
+
for (const pid of trackedSessionPids) {
|
|
2424
|
+
try {
|
|
2425
|
+
process.kill(pid, 0);
|
|
2426
|
+
} catch {
|
|
2427
|
+
api.logger.debug(`[DAEMON RUN] Removing stale session with PID ${pid} (process no longer exists)`);
|
|
2428
|
+
removeTrackedSession(pid);
|
|
2429
|
+
}
|
|
2430
|
+
}
|
|
2431
|
+
const projectVersion = readProjectCliVersion();
|
|
2432
|
+
if (projectVersion && projectVersion !== currentCliVersion) {
|
|
2433
|
+
await onDaemonOutdated();
|
|
2434
|
+
return;
|
|
2435
|
+
}
|
|
2436
|
+
const daemonState = await readDaemonState();
|
|
2437
|
+
if (daemonState && daemonState.pid !== daemonPid) {
|
|
2438
|
+
api.logger.debug(`[DAEMON RUN] ${DIFFERENT_DAEMON_RUNNING_MESSAGE}`);
|
|
2439
|
+
requestShutdown("exception", DIFFERENT_DAEMON_RUNNING_MESSAGE);
|
|
2440
|
+
return;
|
|
2441
|
+
}
|
|
2442
|
+
await writeHeartbeat();
|
|
2443
|
+
}
|
|
2444
|
+
|
|
2445
|
+
async function atomicFileWrite(filePath, content) {
|
|
2446
|
+
const tmpFile = `${filePath}.${crypto.randomUUID()}.tmp`;
|
|
2447
|
+
try {
|
|
2448
|
+
await fs$2.writeFile(tmpFile, content);
|
|
2449
|
+
await fs$2.rename(tmpFile, filePath);
|
|
2450
|
+
} catch (error) {
|
|
2451
|
+
try {
|
|
2452
|
+
await fs$2.unlink(tmpFile);
|
|
2453
|
+
} catch {
|
|
2454
|
+
}
|
|
2455
|
+
throw error;
|
|
2456
|
+
}
|
|
2457
|
+
}
|
|
2458
|
+
|
|
2459
|
+
const SESSION_REGISTRY_VERSION = 1;
|
|
2460
|
+
const SESSION_REGISTRY_DIRNAME = "session-registry";
|
|
2461
|
+
const NON_SESSION_PROCESS_TYPES = /* @__PURE__ */ new Set([
|
|
2462
|
+
"current",
|
|
2463
|
+
"daemon",
|
|
2464
|
+
"daemon-indeterminate",
|
|
2465
|
+
"dev-daemon",
|
|
2466
|
+
"daemon-launcher",
|
|
2467
|
+
"dev-daemon-launcher",
|
|
2468
|
+
"daemon-version-check",
|
|
2469
|
+
"dev-daemon-version-check",
|
|
2470
|
+
"doctor",
|
|
2471
|
+
"dev-doctor"
|
|
2472
|
+
]);
|
|
2473
|
+
const registeredCleanupPids = /* @__PURE__ */ new Set();
|
|
2474
|
+
function getSessionRegistryDir() {
|
|
2475
|
+
return path.join(api.configuration.happyCloudHomeDir, SESSION_REGISTRY_DIRNAME);
|
|
2476
|
+
}
|
|
2477
|
+
function getSessionRegistryEntryPath(pid) {
|
|
2478
|
+
return path.join(getSessionRegistryDir(), `${pid}.json`);
|
|
2479
|
+
}
|
|
2480
|
+
function normalizeMetadataForPid(metadata, pid) {
|
|
2481
|
+
if (metadata.hostPid === pid) {
|
|
2482
|
+
return metadata;
|
|
2483
|
+
}
|
|
2484
|
+
return {
|
|
2485
|
+
...metadata,
|
|
2486
|
+
hostPid: pid
|
|
2487
|
+
};
|
|
2488
|
+
}
|
|
2489
|
+
function createTrackedSessionFromRegistryEntry(entry) {
|
|
2490
|
+
const metadata = normalizeMetadataForPid(entry.metadata, entry.pid);
|
|
2491
|
+
return {
|
|
2492
|
+
startedBy: metadata.startedBy === "daemon" ? "daemon" : "happy directly - likely by user from terminal",
|
|
2493
|
+
happySessionId: entry.sessionId,
|
|
2494
|
+
happySessionMetadataFromLocalWebhook: metadata,
|
|
2495
|
+
pid: entry.pid
|
|
2496
|
+
};
|
|
2497
|
+
}
|
|
2498
|
+
async function ensureSessionRegistryDir() {
|
|
2499
|
+
await promises.mkdir(getSessionRegistryDir(), { recursive: true });
|
|
2500
|
+
}
|
|
2501
|
+
async function removeRegistryEntryPath(path) {
|
|
2502
|
+
try {
|
|
2503
|
+
await promises.unlink(path);
|
|
2504
|
+
return true;
|
|
2505
|
+
} catch (error) {
|
|
2506
|
+
if (error?.code === "ENOENT") {
|
|
2507
|
+
return false;
|
|
2508
|
+
}
|
|
2509
|
+
api.logger.debug(`[SESSION REGISTRY] Failed to remove ${path}`, error);
|
|
2510
|
+
return false;
|
|
2511
|
+
}
|
|
2512
|
+
}
|
|
2513
|
+
function removeRegistryEntryPathSync(path) {
|
|
2514
|
+
try {
|
|
2515
|
+
fs.unlinkSync(path);
|
|
2516
|
+
} catch (error) {
|
|
2517
|
+
if (error?.code !== "ENOENT") {
|
|
2518
|
+
api.logger.debug(`[SESSION REGISTRY] Failed to remove ${path} during process cleanup`, error);
|
|
2519
|
+
}
|
|
2520
|
+
}
|
|
2521
|
+
}
|
|
2522
|
+
function installRegistryCleanup(pid) {
|
|
2523
|
+
if (registeredCleanupPids.has(pid)) {
|
|
2524
|
+
return;
|
|
2525
|
+
}
|
|
2526
|
+
registeredCleanupPids.add(pid);
|
|
2527
|
+
let cleaned = false;
|
|
2528
|
+
const cleanup = () => {
|
|
2529
|
+
if (cleaned) {
|
|
2530
|
+
return;
|
|
2531
|
+
}
|
|
2532
|
+
cleaned = true;
|
|
2533
|
+
removeRegistryEntryPathSync(getSessionRegistryEntryPath(pid));
|
|
2534
|
+
};
|
|
2535
|
+
process.once("exit", cleanup);
|
|
2536
|
+
}
|
|
2537
|
+
async function readRegistryEntry(path) {
|
|
2538
|
+
try {
|
|
2539
|
+
const raw = JSON.parse(await promises.readFile(path, "utf8"));
|
|
2540
|
+
if (raw.version !== SESSION_REGISTRY_VERSION || typeof raw.pid !== "number" || typeof raw.sessionId !== "string" || !raw.sessionId || !raw.metadata || typeof raw.metadata !== "object") {
|
|
2541
|
+
api.logger.debug(`[SESSION REGISTRY] Invalid entry schema at ${path}, pruning`);
|
|
2542
|
+
await removeRegistryEntryPath(path);
|
|
2543
|
+
return null;
|
|
2544
|
+
}
|
|
2545
|
+
return {
|
|
2546
|
+
version: SESSION_REGISTRY_VERSION,
|
|
2547
|
+
pid: raw.pid,
|
|
2548
|
+
sessionId: raw.sessionId,
|
|
2549
|
+
metadata: normalizeMetadataForPid(raw.metadata, raw.pid),
|
|
2550
|
+
updatedAt: typeof raw.updatedAt === "number" ? raw.updatedAt : Date.now()
|
|
2551
|
+
};
|
|
2552
|
+
} catch (error) {
|
|
2553
|
+
api.logger.debug(`[SESSION REGISTRY] Failed to read ${path}, pruning`, error);
|
|
2554
|
+
await removeRegistryEntryPath(path);
|
|
2555
|
+
return null;
|
|
2556
|
+
}
|
|
2557
|
+
}
|
|
2558
|
+
async function resolveLiveHappySessionPids(listLiveHappyProcesses) {
|
|
2559
|
+
try {
|
|
2560
|
+
const liveHappyProcesses = await listLiveHappyProcesses();
|
|
2561
|
+
if (liveHappyProcesses.length === 0) {
|
|
2562
|
+
return null;
|
|
2563
|
+
}
|
|
2564
|
+
const liveSessionProcesses = liveHappyProcesses.filter((proc) => !NON_SESSION_PROCESS_TYPES.has(proc.type));
|
|
2565
|
+
if (liveSessionProcesses.length === 0) {
|
|
2566
|
+
api.logger.debug(
|
|
2567
|
+
"[SESSION REGISTRY] Process discovery did not report any session processes, falling back to PID checks"
|
|
2568
|
+
);
|
|
2569
|
+
return null;
|
|
2570
|
+
}
|
|
2571
|
+
return new Set(
|
|
2572
|
+
liveSessionProcesses.map((proc) => proc.pid)
|
|
2573
|
+
);
|
|
2574
|
+
} catch (error) {
|
|
2575
|
+
api.logger.debug("[SESSION REGISTRY] Failed to list live Happy processes, falling back to PID checks", error);
|
|
2576
|
+
return null;
|
|
2577
|
+
}
|
|
2578
|
+
}
|
|
2579
|
+
async function persistLocalSessionRegistration(sessionId, metadata) {
|
|
2580
|
+
const pid = metadata.hostPid ?? process.pid;
|
|
2581
|
+
const normalizedMetadata = normalizeMetadataForPid(metadata, pid);
|
|
2582
|
+
await ensureSessionRegistryDir();
|
|
2583
|
+
await atomicFileWrite(
|
|
2584
|
+
getSessionRegistryEntryPath(pid),
|
|
2585
|
+
JSON.stringify({
|
|
2586
|
+
version: SESSION_REGISTRY_VERSION,
|
|
2587
|
+
pid,
|
|
2588
|
+
sessionId,
|
|
2589
|
+
metadata: normalizedMetadata,
|
|
2590
|
+
updatedAt: Date.now()
|
|
2591
|
+
}, null, 2)
|
|
2592
|
+
);
|
|
2593
|
+
}
|
|
2594
|
+
async function publishSessionRegistration(sessionId, metadata) {
|
|
2595
|
+
const pid = metadata.hostPid ?? process.pid;
|
|
2596
|
+
try {
|
|
2597
|
+
await persistLocalSessionRegistration(sessionId, metadata);
|
|
2598
|
+
installRegistryCleanup(pid);
|
|
2599
|
+
} catch (error) {
|
|
2600
|
+
api.logger.debug(`[SESSION REGISTRY] Failed to persist local registration for session ${sessionId}`, error);
|
|
2601
|
+
}
|
|
2602
|
+
try {
|
|
2603
|
+
const result = await notifyDaemonSessionStarted(sessionId, normalizeMetadataForPid(metadata, pid));
|
|
2604
|
+
if (result?.error) {
|
|
2605
|
+
api.logger.debug(`[SESSION REGISTRY] Failed to report session ${sessionId} to daemon`, result.error);
|
|
2606
|
+
}
|
|
2607
|
+
} catch (error) {
|
|
2608
|
+
api.logger.debug(`[SESSION REGISTRY] Failed to report session ${sessionId} to daemon`, error);
|
|
2609
|
+
}
|
|
2610
|
+
}
|
|
2611
|
+
async function recoverTrackedSessionsFromLocalRegistry({
|
|
2612
|
+
trackedSessionPids,
|
|
2613
|
+
trackSession,
|
|
2614
|
+
listLiveHappyProcesses = findAllHappyProcesses,
|
|
2615
|
+
lookupHappyProcessByPid = findHappyProcessByPid
|
|
2616
|
+
}) {
|
|
2617
|
+
const registryDir = getSessionRegistryDir();
|
|
2618
|
+
if (!fs.existsSync(registryDir)) {
|
|
2619
|
+
return { recoveredCount: 0, removedStaleCount: 0 };
|
|
2620
|
+
}
|
|
2621
|
+
const alreadyTracked = new Set(trackedSessionPids);
|
|
2622
|
+
const liveHappySessionPids = await resolveLiveHappySessionPids(listLiveHappyProcesses);
|
|
2623
|
+
let recoveredCount = 0;
|
|
2624
|
+
let removedStaleCount = 0;
|
|
2625
|
+
for (const entryFile of await promises.readdir(registryDir)) {
|
|
2626
|
+
if (!entryFile.endsWith(".json")) {
|
|
2627
|
+
continue;
|
|
2628
|
+
}
|
|
2629
|
+
const entryPath = path.join(registryDir, entryFile);
|
|
2630
|
+
const entry = await readRegistryEntry(entryPath);
|
|
2631
|
+
if (!entry) {
|
|
2632
|
+
continue;
|
|
2633
|
+
}
|
|
2634
|
+
let lookupResult;
|
|
2635
|
+
try {
|
|
2636
|
+
lookupResult = liveHappySessionPids?.has(entry.pid) ? { pid: entry.pid, type: "known-session" } : await lookupHappyProcessByPid(entry.pid);
|
|
2637
|
+
} catch (error) {
|
|
2638
|
+
api.logger.debug(
|
|
2639
|
+
`[SESSION REGISTRY] Failed to inspect PID ${entry.pid}, keeping registry entry until next recovery tick`,
|
|
2640
|
+
error
|
|
2641
|
+
);
|
|
2642
|
+
lookupResult = "indeterminate";
|
|
2643
|
+
}
|
|
2644
|
+
const sessionPidIsAlive = lookupResult !== null && lookupResult !== "indeterminate" && !NON_SESSION_PROCESS_TYPES.has(lookupResult.type);
|
|
2645
|
+
const shouldKeepEntryWithoutRecovery = lookupResult === "indeterminate";
|
|
2646
|
+
if (shouldKeepEntryWithoutRecovery) {
|
|
2647
|
+
api.logger.debug(
|
|
2648
|
+
`[SESSION REGISTRY] Keeping registry entry for PID ${entry.pid} because process identity is indeterminate`
|
|
2649
|
+
);
|
|
2650
|
+
continue;
|
|
2651
|
+
}
|
|
2652
|
+
if (!sessionPidIsAlive) {
|
|
2653
|
+
if (await removeRegistryEntryPath(entryPath)) {
|
|
2654
|
+
removedStaleCount++;
|
|
2655
|
+
}
|
|
2656
|
+
continue;
|
|
2657
|
+
}
|
|
2658
|
+
if (alreadyTracked.has(entry.pid)) {
|
|
2659
|
+
continue;
|
|
2660
|
+
}
|
|
2661
|
+
trackSession(entry.pid, createTrackedSessionFromRegistryEntry(entry));
|
|
2662
|
+
alreadyTracked.add(entry.pid);
|
|
2663
|
+
recoveredCount++;
|
|
2664
|
+
api.logger.debug(`[SESSION REGISTRY] Recovered tracked session ${entry.sessionId} for PID ${entry.pid}`);
|
|
2665
|
+
}
|
|
2666
|
+
return { recoveredCount, removedStaleCount };
|
|
2667
|
+
}
|
|
2668
|
+
|
|
2291
2669
|
const initialMachineMetadata = {
|
|
2292
2670
|
host: os$1.hostname(),
|
|
2293
2671
|
platform: os$1.platform(),
|
|
@@ -2417,7 +2795,7 @@ async function startDaemon() {
|
|
|
2417
2795
|
const { directory, sessionId, machineId: machineId2, approvedNewDirectoryCreation = true } = options;
|
|
2418
2796
|
let directoryCreated = false;
|
|
2419
2797
|
try {
|
|
2420
|
-
await fs$
|
|
2798
|
+
await fs$2.access(directory);
|
|
2421
2799
|
api.logger.debug(`[DAEMON RUN] Directory exists: ${directory}`);
|
|
2422
2800
|
} catch (error) {
|
|
2423
2801
|
api.logger.debug(`[DAEMON RUN] Directory doesn't exist, creating: ${directory}`);
|
|
@@ -2429,7 +2807,7 @@ async function startDaemon() {
|
|
|
2429
2807
|
};
|
|
2430
2808
|
}
|
|
2431
2809
|
try {
|
|
2432
|
-
await fs$
|
|
2810
|
+
await fs$2.mkdir(directory, { recursive: true });
|
|
2433
2811
|
api.logger.debug(`[DAEMON RUN] Successfully created directory: ${directory}`);
|
|
2434
2812
|
directoryCreated = true;
|
|
2435
2813
|
} catch (mkdirError) {
|
|
@@ -2458,7 +2836,7 @@ async function startDaemon() {
|
|
|
2458
2836
|
if (options.token) {
|
|
2459
2837
|
if (options.agent === "codex") {
|
|
2460
2838
|
const codexHomeDir = tmp__namespace.dirSync();
|
|
2461
|
-
fs$
|
|
2839
|
+
fs$2.writeFile(path$1.join(codexHomeDir.name, "auth.json"), options.token);
|
|
2462
2840
|
authEnv.CODEX_HOME = codexHomeDir.name;
|
|
2463
2841
|
} else {
|
|
2464
2842
|
authEnv.CLAUDE_CODE_OAUTH_TOKEN = options.token;
|
|
@@ -2511,20 +2889,18 @@ async function startDaemon() {
|
|
|
2511
2889
|
errorMessage: spawnError.errorMessage
|
|
2512
2890
|
};
|
|
2513
2891
|
}
|
|
2514
|
-
const tmuxAvailable = await isTmuxAvailable();
|
|
2515
|
-
let useTmux = tmuxAvailable;
|
|
2516
2892
|
let tmuxSessionName = extraEnv.TMUX_SESSION_NAME;
|
|
2517
|
-
|
|
2518
|
-
|
|
2519
|
-
|
|
2520
|
-
|
|
2521
|
-
|
|
2893
|
+
const tmuxRequested = tmuxSessionName !== void 0;
|
|
2894
|
+
const tmuxAvailable = tmuxRequested ? await isTmuxAvailable() : false;
|
|
2895
|
+
let useTmux = tmuxRequested && tmuxAvailable;
|
|
2896
|
+
if (tmuxRequested && !tmuxAvailable) {
|
|
2897
|
+
api.logger.debug(`[DAEMON RUN] tmux session name specified but tmux not available, falling back to regular spawning`);
|
|
2522
2898
|
}
|
|
2523
2899
|
if (useTmux && tmuxSessionName !== void 0) {
|
|
2524
2900
|
const sessionDesc = tmuxSessionName || "current/most recent session";
|
|
2525
2901
|
api.logger.debug(`[DAEMON RUN] Attempting to spawn session in tmux: ${sessionDesc}`);
|
|
2526
2902
|
const tmux = getTmuxUtilities(tmuxSessionName);
|
|
2527
|
-
const cliPath = path.join(projectPath(), "dist", "index.mjs");
|
|
2903
|
+
const cliPath = path$1.join(projectPath(), "dist", "index.mjs");
|
|
2528
2904
|
const agent = resolveDaemonSpawnAgent(options.agent);
|
|
2529
2905
|
const fullCommand = `node --no-warnings --no-deprecation ${cliPath} ${buildDaemonSpawnArgs(agent).join(" ")}`;
|
|
2530
2906
|
const windowName = `happy-${Date.now()}-${agent}`;
|
|
@@ -2723,6 +3099,17 @@ async function startDaemon() {
|
|
|
2723
3099
|
onHappySessionWebhook,
|
|
2724
3100
|
port: api.HAPPY_CLOUD_DAEMON_PORT
|
|
2725
3101
|
});
|
|
3102
|
+
const recoveryResult = await recoverTrackedSessionsFromLocalRegistry({
|
|
3103
|
+
trackedSessionPids: pidToTrackedSession.keys(),
|
|
3104
|
+
trackSession: (pid, trackedSession) => {
|
|
3105
|
+
pidToTrackedSession.set(pid, trackedSession);
|
|
3106
|
+
}
|
|
3107
|
+
});
|
|
3108
|
+
if (recoveryResult.recoveredCount > 0 || recoveryResult.removedStaleCount > 0) {
|
|
3109
|
+
api.logger.debug(
|
|
3110
|
+
`[DAEMON RUN] Session registry recovery completed: recovered=${recoveryResult.recoveredCount}, removedStale=${recoveryResult.removedStaleCount}`
|
|
3111
|
+
);
|
|
3112
|
+
}
|
|
2726
3113
|
const fileState = {
|
|
2727
3114
|
pid: process.pid,
|
|
2728
3115
|
httpPort: controlPort,
|
|
@@ -2762,52 +3149,69 @@ async function startDaemon() {
|
|
|
2762
3149
|
if (process.env.DEBUG) {
|
|
2763
3150
|
api.logger.debug(`[DAEMON RUN] Health check started at ${(/* @__PURE__ */ new Date()).toLocaleString()}`);
|
|
2764
3151
|
}
|
|
2765
|
-
for (const [pid, _] of pidToTrackedSession.entries()) {
|
|
2766
|
-
try {
|
|
2767
|
-
process.kill(pid, 0);
|
|
2768
|
-
} catch (error) {
|
|
2769
|
-
api.logger.debug(`[DAEMON RUN] Removing stale session with PID ${pid} (process no longer exists)`);
|
|
2770
|
-
pidToTrackedSession.delete(pid);
|
|
2771
|
-
}
|
|
2772
|
-
}
|
|
2773
|
-
const projectVersion = JSON.parse(fs.readFileSync(path.join(projectPath(), "package.json"), "utf-8")).version;
|
|
2774
|
-
if (projectVersion !== api.configuration.currentCliVersion) {
|
|
2775
|
-
api.logger.debug("[DAEMON RUN] Daemon is outdated, triggering self-restart with latest version, clearing heartbeat interval");
|
|
2776
|
-
clearInterval(restartOnStaleVersionAndHeartbeat);
|
|
2777
|
-
try {
|
|
2778
|
-
spawnHappyCLI(["daemon", "start"], {
|
|
2779
|
-
detached: true,
|
|
2780
|
-
stdio: "ignore"
|
|
2781
|
-
});
|
|
2782
|
-
} catch (error) {
|
|
2783
|
-
api.logger.debug("[DAEMON RUN] Failed to spawn new daemon, this is quite likely to happen during integration tests as we are cleaning out dist/ directory", error);
|
|
2784
|
-
}
|
|
2785
|
-
api.logger.debug("[DAEMON RUN] Hanging for a bit - waiting for CLI to kill us because we are running outdated version of the code");
|
|
2786
|
-
await new Promise((resolve) => setTimeout(resolve, 1e4));
|
|
2787
|
-
process.exit(0);
|
|
2788
|
-
}
|
|
2789
|
-
const daemonState = await persistence.readDaemonState();
|
|
2790
|
-
if (daemonState && daemonState.pid !== process.pid) {
|
|
2791
|
-
api.logger.debug("[DAEMON RUN] Somehow a different daemon was started without killing us. We should kill ourselves.");
|
|
2792
|
-
requestShutdown("exception", "A different daemon was started without killing us. We should kill ourselves.");
|
|
2793
|
-
}
|
|
2794
3152
|
try {
|
|
2795
|
-
const
|
|
2796
|
-
|
|
2797
|
-
|
|
2798
|
-
|
|
2799
|
-
|
|
2800
|
-
|
|
2801
|
-
|
|
2802
|
-
|
|
2803
|
-
|
|
2804
|
-
|
|
2805
|
-
api.logger.debug(`[DAEMON RUN] Health check completed at ${updatedState.lastHeartbeat}`);
|
|
3153
|
+
const recoveryResult2 = await recoverTrackedSessionsFromLocalRegistry({
|
|
3154
|
+
trackedSessionPids: pidToTrackedSession.keys(),
|
|
3155
|
+
trackSession: (pid, trackedSession) => {
|
|
3156
|
+
pidToTrackedSession.set(pid, trackedSession);
|
|
3157
|
+
}
|
|
3158
|
+
});
|
|
3159
|
+
if (recoveryResult2.recoveredCount > 0 || recoveryResult2.removedStaleCount > 0) {
|
|
3160
|
+
api.logger.debug(
|
|
3161
|
+
`[DAEMON RUN] Session registry recovery tick completed: recovered=${recoveryResult2.recoveredCount}, removedStale=${recoveryResult2.removedStaleCount}`
|
|
3162
|
+
);
|
|
2806
3163
|
}
|
|
3164
|
+
await runDaemonHealthCheck({
|
|
3165
|
+
trackedSessionPids: pidToTrackedSession.keys(),
|
|
3166
|
+
removeTrackedSession: (pid) => {
|
|
3167
|
+
pidToTrackedSession.delete(pid);
|
|
3168
|
+
},
|
|
3169
|
+
currentCliVersion: api.configuration.currentCliVersion,
|
|
3170
|
+
onDaemonOutdated: async () => {
|
|
3171
|
+
api.logger.debug("[DAEMON RUN] Daemon is outdated, triggering self-restart with latest version, clearing heartbeat interval");
|
|
3172
|
+
clearInterval(restartOnStaleVersionAndHeartbeat);
|
|
3173
|
+
try {
|
|
3174
|
+
spawnHappyCLI(["daemon", "start"], {
|
|
3175
|
+
detached: true,
|
|
3176
|
+
stdio: "ignore"
|
|
3177
|
+
});
|
|
3178
|
+
} catch (error) {
|
|
3179
|
+
api.logger.debug("[DAEMON RUN] Failed to spawn new daemon, this is quite likely to happen during integration tests as we are cleaning out dist/ directory", error);
|
|
3180
|
+
}
|
|
3181
|
+
api.logger.debug("[DAEMON RUN] Hanging for a bit - waiting for CLI to kill us because we are running outdated version of the code");
|
|
3182
|
+
await new Promise((resolve) => setTimeout(resolve, 1e4));
|
|
3183
|
+
process.exit(0);
|
|
3184
|
+
},
|
|
3185
|
+
readDaemonState: persistence.readDaemonState,
|
|
3186
|
+
daemonPid: process.pid,
|
|
3187
|
+
requestShutdown,
|
|
3188
|
+
writeHeartbeat: async () => {
|
|
3189
|
+
try {
|
|
3190
|
+
const updatedState = {
|
|
3191
|
+
pid: process.pid,
|
|
3192
|
+
httpPort: controlPort,
|
|
3193
|
+
startTime: fileState.startTime,
|
|
3194
|
+
startedWithCliVersion: api.packageJson.version,
|
|
3195
|
+
lastHeartbeat: (/* @__PURE__ */ new Date()).toLocaleString(),
|
|
3196
|
+
daemonLogPath: fileState.daemonLogPath
|
|
3197
|
+
};
|
|
3198
|
+
persistence.writeDaemonState(updatedState);
|
|
3199
|
+
if (process.env.DEBUG) {
|
|
3200
|
+
api.logger.debug(`[DAEMON RUN] Health check completed at ${updatedState.lastHeartbeat}`);
|
|
3201
|
+
}
|
|
3202
|
+
} catch (error) {
|
|
3203
|
+
api.logger.debug("[DAEMON RUN] Failed to write heartbeat", error);
|
|
3204
|
+
}
|
|
3205
|
+
}
|
|
3206
|
+
});
|
|
2807
3207
|
} catch (error) {
|
|
2808
|
-
api.logger.
|
|
3208
|
+
api.logger.warn(
|
|
3209
|
+
"[DAEMON RUN] Health check failed; keeping daemon alive until the next tick",
|
|
3210
|
+
error instanceof Error ? error.message : String(error)
|
|
3211
|
+
);
|
|
3212
|
+
} finally {
|
|
3213
|
+
heartbeatRunning = false;
|
|
2809
3214
|
}
|
|
2810
|
-
heartbeatRunning = false;
|
|
2811
3215
|
}, heartbeatIntervalMs);
|
|
2812
3216
|
const cleanupAndShutdown = async (source, errorMessage) => {
|
|
2813
3217
|
api.logger.debug(`[DAEMON RUN] Starting proper cleanup (source: ${source}, errorMessage: ${errorMessage})...`);
|
|
@@ -2863,7 +3267,7 @@ const PLIST_LABEL$1 = "com.happy-cloud.daemon";
|
|
|
2863
3267
|
const PLIST_FILE$1 = `/Library/LaunchDaemons/${PLIST_LABEL$1}.plist`;
|
|
2864
3268
|
async function install$1() {
|
|
2865
3269
|
try {
|
|
2866
|
-
if (fs.existsSync(PLIST_FILE$1)) {
|
|
3270
|
+
if (fs$1.existsSync(PLIST_FILE$1)) {
|
|
2867
3271
|
api.logger.info("Daemon plist already exists. Uninstalling first...");
|
|
2868
3272
|
child_process.execSync(`launchctl unload ${PLIST_FILE$1}`, { stdio: "inherit" });
|
|
2869
3273
|
}
|
|
@@ -2907,8 +3311,8 @@ async function install$1() {
|
|
|
2907
3311
|
</dict>
|
|
2908
3312
|
</plist>
|
|
2909
3313
|
`);
|
|
2910
|
-
fs.writeFileSync(PLIST_FILE$1, plistContent);
|
|
2911
|
-
fs.chmodSync(PLIST_FILE$1, 420);
|
|
3314
|
+
fs$1.writeFileSync(PLIST_FILE$1, plistContent);
|
|
3315
|
+
fs$1.chmodSync(PLIST_FILE$1, 420);
|
|
2912
3316
|
api.logger.info(`Created daemon plist at ${PLIST_FILE$1}`);
|
|
2913
3317
|
child_process.execSync(`launchctl load ${PLIST_FILE$1}`, { stdio: "inherit" });
|
|
2914
3318
|
api.logger.info("Daemon installed and started successfully");
|
|
@@ -2934,7 +3338,7 @@ const PLIST_LABEL = "com.happy-cli.daemon";
|
|
|
2934
3338
|
const PLIST_FILE = `/Library/LaunchDaemons/${PLIST_LABEL}.plist`;
|
|
2935
3339
|
async function uninstall$1() {
|
|
2936
3340
|
try {
|
|
2937
|
-
if (!fs.existsSync(PLIST_FILE)) {
|
|
3341
|
+
if (!fs$1.existsSync(PLIST_FILE)) {
|
|
2938
3342
|
api.logger.info("Daemon plist not found. Nothing to uninstall.");
|
|
2939
3343
|
return;
|
|
2940
3344
|
}
|
|
@@ -2944,7 +3348,7 @@ async function uninstall$1() {
|
|
|
2944
3348
|
} catch (error) {
|
|
2945
3349
|
api.logger.info("Failed to unload daemon (it might not be running)");
|
|
2946
3350
|
}
|
|
2947
|
-
fs.unlinkSync(PLIST_FILE);
|
|
3351
|
+
fs$1.unlinkSync(PLIST_FILE);
|
|
2948
3352
|
api.logger.info(`Removed daemon plist from ${PLIST_FILE}`);
|
|
2949
3353
|
api.logger.info("Daemon uninstalled successfully");
|
|
2950
3354
|
} catch (error) {
|
|
@@ -3084,8 +3488,8 @@ async function handleAuthLogout(args = []) {
|
|
|
3084
3488
|
console.log(chalk.gray("Stopped daemon"));
|
|
3085
3489
|
} catch {
|
|
3086
3490
|
}
|
|
3087
|
-
if (
|
|
3088
|
-
|
|
3491
|
+
if (fs.existsSync(happyDir)) {
|
|
3492
|
+
fs.rmSync(happyDir, { recursive: true, force: true });
|
|
3089
3493
|
}
|
|
3090
3494
|
console.log(chalk.green("\u2713 Successfully logged out"));
|
|
3091
3495
|
console.log(chalk.gray(' Run "hicloud auth login" to authenticate again'));
|
|
@@ -3120,9 +3524,11 @@ async function handleAuthStatus() {
|
|
|
3120
3524
|
console.log(chalk.gray(`
|
|
3121
3525
|
Data directory: ${api.configuration.happyCloudHomeDir}`));
|
|
3122
3526
|
try {
|
|
3123
|
-
const
|
|
3124
|
-
if (running) {
|
|
3527
|
+
const daemonStatus = await getDaemonRuntimeStatus();
|
|
3528
|
+
if (daemonStatus === "running") {
|
|
3125
3529
|
console.log(chalk.green("\u2713 Daemon running"));
|
|
3530
|
+
} else if (daemonStatus === "indeterminate") {
|
|
3531
|
+
console.log(chalk.yellow("\u26A0\uFE0F Daemon status indeterminate"));
|
|
3126
3532
|
} else {
|
|
3127
3533
|
console.log(chalk.gray("\u2717 Daemon not running"));
|
|
3128
3534
|
}
|
|
@@ -3758,10 +4164,10 @@ async function handleConnectStatus() {
|
|
|
3758
4164
|
}
|
|
3759
4165
|
function updateLocalGeminiCredentials(tokens) {
|
|
3760
4166
|
try {
|
|
3761
|
-
const geminiDir = path.join(os$1.homedir(), ".gemini");
|
|
3762
|
-
const credentialsPath = path.join(geminiDir, "oauth_creds.json");
|
|
3763
|
-
if (!fs.existsSync(geminiDir)) {
|
|
3764
|
-
fs.mkdirSync(geminiDir, { recursive: true });
|
|
4167
|
+
const geminiDir = path$1.join(os$1.homedir(), ".gemini");
|
|
4168
|
+
const credentialsPath = path$1.join(geminiDir, "oauth_creds.json");
|
|
4169
|
+
if (!fs$1.existsSync(geminiDir)) {
|
|
4170
|
+
fs$1.mkdirSync(geminiDir, { recursive: true });
|
|
3765
4171
|
}
|
|
3766
4172
|
const credentials = {
|
|
3767
4173
|
access_token: tokens.access_token,
|
|
@@ -3771,7 +4177,7 @@ function updateLocalGeminiCredentials(tokens) {
|
|
|
3771
4177
|
...tokens.id_token && { id_token: tokens.id_token },
|
|
3772
4178
|
...tokens.expires_in && { expires_in: tokens.expires_in }
|
|
3773
4179
|
};
|
|
3774
|
-
fs.writeFileSync(credentialsPath, JSON.stringify(credentials, null, 2), "utf-8");
|
|
4180
|
+
fs$1.writeFileSync(credentialsPath, JSON.stringify(credentials, null, 2), "utf-8");
|
|
3775
4181
|
console.log(chalk.gray(` Updated local credentials: ${credentialsPath}`));
|
|
3776
4182
|
} catch (error) {
|
|
3777
4183
|
console.log(chalk.yellow(` \u26A0\uFE0F Could not update local credentials: ${error}`));
|
|
@@ -3779,22 +4185,22 @@ function updateLocalGeminiCredentials(tokens) {
|
|
|
3779
4185
|
}
|
|
3780
4186
|
|
|
3781
4187
|
function getProjectPath(workingDirectory, claudeConfigDirOverride) {
|
|
3782
|
-
const projectId =
|
|
4188
|
+
const projectId = path.resolve(workingDirectory).replace(/[^a-zA-Z0-9-]/g, "-");
|
|
3783
4189
|
const claudeConfigDirRaw = process.env.CLAUDE_CONFIG_DIR ?? "";
|
|
3784
4190
|
const claudeConfigDirTrimmed = claudeConfigDirRaw.trim();
|
|
3785
|
-
const claudeConfigDir = claudeConfigDirTrimmed ? claudeConfigDirTrimmed :
|
|
3786
|
-
return
|
|
4191
|
+
const claudeConfigDir = claudeConfigDirTrimmed ? claudeConfigDirTrimmed : path.join(os.homedir(), ".claude");
|
|
4192
|
+
return path.join(claudeConfigDir, "projects", projectId);
|
|
3787
4193
|
}
|
|
3788
4194
|
|
|
3789
|
-
function claudeCheckSession(sessionId, path, transcriptPath) {
|
|
3790
|
-
const projectDir = getProjectPath(path);
|
|
3791
|
-
const sessionFile = transcriptPath ??
|
|
3792
|
-
const sessionExists =
|
|
4195
|
+
function claudeCheckSession(sessionId, path$1, transcriptPath) {
|
|
4196
|
+
const projectDir = getProjectPath(path$1);
|
|
4197
|
+
const sessionFile = transcriptPath ?? path.join(projectDir, `${sessionId}.jsonl`);
|
|
4198
|
+
const sessionExists = fs.existsSync(sessionFile);
|
|
3793
4199
|
if (!sessionExists) {
|
|
3794
4200
|
api.logger.debug(`[claudeCheckSession] Path ${sessionFile} does not exist`);
|
|
3795
4201
|
return false;
|
|
3796
4202
|
}
|
|
3797
|
-
const sessionData =
|
|
4203
|
+
const sessionData = fs.readFileSync(sessionFile, "utf-8").split("\n");
|
|
3798
4204
|
const hasGoodMessage = !!sessionData.find((v, index) => {
|
|
3799
4205
|
if (!v.trim()) return false;
|
|
3800
4206
|
try {
|
|
@@ -3815,7 +4221,7 @@ function claudeFindLastSession(workingDirectory) {
|
|
|
3815
4221
|
try {
|
|
3816
4222
|
const projectDir = getProjectPath(workingDirectory);
|
|
3817
4223
|
const uuidPattern = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
3818
|
-
const files =
|
|
4224
|
+
const files = fs.readdirSync(projectDir).filter((f) => f.endsWith(".jsonl")).map((f) => {
|
|
3819
4225
|
const sessionId = f.replace(".jsonl", "");
|
|
3820
4226
|
if (!uuidPattern.test(sessionId)) {
|
|
3821
4227
|
return null;
|
|
@@ -3824,7 +4230,7 @@ function claudeFindLastSession(workingDirectory) {
|
|
|
3824
4230
|
return {
|
|
3825
4231
|
name: f,
|
|
3826
4232
|
sessionId,
|
|
3827
|
-
mtime:
|
|
4233
|
+
mtime: fs.statSync(path.join(projectDir, f)).mtime.getTime()
|
|
3828
4234
|
};
|
|
3829
4235
|
}
|
|
3830
4236
|
return null;
|
|
@@ -3844,10 +4250,10 @@ class ExitCodeError extends Error {
|
|
|
3844
4250
|
this.exitCode = exitCode;
|
|
3845
4251
|
}
|
|
3846
4252
|
}
|
|
3847
|
-
const claudeCliPath =
|
|
4253
|
+
const claudeCliPath = path.resolve(path.join(projectPath(), "scripts", "claude_local_launcher.cjs"));
|
|
3848
4254
|
async function claudeLocal(opts) {
|
|
3849
4255
|
const projectDir = getProjectPath(opts.path);
|
|
3850
|
-
|
|
4256
|
+
fs.mkdirSync(projectDir, { recursive: true });
|
|
3851
4257
|
const hasContinueFlag = opts.claudeArgs?.includes("--continue");
|
|
3852
4258
|
const hasResumeFlag = opts.claudeArgs?.includes("--resume");
|
|
3853
4259
|
const hasUserSessionControl = hasContinueFlag || hasResumeFlag;
|
|
@@ -3966,7 +4372,7 @@ async function claudeLocal(opts) {
|
|
|
3966
4372
|
args.push("--settings", opts.hookSettingsPath);
|
|
3967
4373
|
api.logger.debug(`[ClaudeLocal] Using hook settings: ${opts.hookSettingsPath}`);
|
|
3968
4374
|
}
|
|
3969
|
-
if (!claudeCliPath || !
|
|
4375
|
+
if (!claudeCliPath || !fs.existsSync(claudeCliPath)) {
|
|
3970
4376
|
throw new Error("Claude local launcher not found. Please ensure HAPPY_PROJECT_ROOT is set correctly for development.");
|
|
3971
4377
|
}
|
|
3972
4378
|
const env = {
|
|
@@ -6314,17 +6720,17 @@ function readGeminiLocalConfig() {
|
|
|
6314
6720
|
let googleCloudProject = null;
|
|
6315
6721
|
let googleCloudProjectEmail = null;
|
|
6316
6722
|
const possiblePaths = [
|
|
6317
|
-
path.join(os$1.homedir(), ".gemini", "oauth_creds.json"),
|
|
6723
|
+
path$1.join(os$1.homedir(), ".gemini", "oauth_creds.json"),
|
|
6318
6724
|
// Main OAuth credentials file
|
|
6319
|
-
path.join(os$1.homedir(), ".gemini", "config.json"),
|
|
6320
|
-
path.join(os$1.homedir(), ".config", "gemini", "config.json"),
|
|
6321
|
-
path.join(os$1.homedir(), ".gemini", "auth.json"),
|
|
6322
|
-
path.join(os$1.homedir(), ".config", "gemini", "auth.json")
|
|
6725
|
+
path$1.join(os$1.homedir(), ".gemini", "config.json"),
|
|
6726
|
+
path$1.join(os$1.homedir(), ".config", "gemini", "config.json"),
|
|
6727
|
+
path$1.join(os$1.homedir(), ".gemini", "auth.json"),
|
|
6728
|
+
path$1.join(os$1.homedir(), ".config", "gemini", "auth.json")
|
|
6323
6729
|
];
|
|
6324
6730
|
for (const configPath of possiblePaths) {
|
|
6325
|
-
if (fs.existsSync(configPath)) {
|
|
6731
|
+
if (fs$1.existsSync(configPath)) {
|
|
6326
6732
|
try {
|
|
6327
|
-
const config = JSON.parse(fs.readFileSync(configPath, "utf-8"));
|
|
6733
|
+
const config = JSON.parse(fs$1.readFileSync(configPath, "utf-8"));
|
|
6328
6734
|
if (!token) {
|
|
6329
6735
|
const foundToken = config.access_token || config.token || config.apiKey || config.GEMINI_API_KEY;
|
|
6330
6736
|
if (foundToken && typeof foundToken === "string") {
|
|
@@ -6396,22 +6802,22 @@ function determineGeminiModel(explicitModel, localConfig) {
|
|
|
6396
6802
|
}
|
|
6397
6803
|
function saveGeminiModelToConfig(model) {
|
|
6398
6804
|
try {
|
|
6399
|
-
const configDir = path.join(os$1.homedir(), ".gemini");
|
|
6400
|
-
const configPath = path.join(configDir, "config.json");
|
|
6401
|
-
if (!fs.existsSync(configDir)) {
|
|
6402
|
-
fs.mkdirSync(configDir, { recursive: true });
|
|
6805
|
+
const configDir = path$1.join(os$1.homedir(), ".gemini");
|
|
6806
|
+
const configPath = path$1.join(configDir, "config.json");
|
|
6807
|
+
if (!fs$1.existsSync(configDir)) {
|
|
6808
|
+
fs$1.mkdirSync(configDir, { recursive: true });
|
|
6403
6809
|
}
|
|
6404
6810
|
let config = {};
|
|
6405
|
-
if (fs.existsSync(configPath)) {
|
|
6811
|
+
if (fs$1.existsSync(configPath)) {
|
|
6406
6812
|
try {
|
|
6407
|
-
config = JSON.parse(fs.readFileSync(configPath, "utf-8"));
|
|
6813
|
+
config = JSON.parse(fs$1.readFileSync(configPath, "utf-8"));
|
|
6408
6814
|
} catch (error) {
|
|
6409
6815
|
api.logger.debug(`[Gemini] Failed to read existing config, creating new one`);
|
|
6410
6816
|
config = {};
|
|
6411
6817
|
}
|
|
6412
6818
|
}
|
|
6413
6819
|
config.model = model;
|
|
6414
|
-
fs.writeFileSync(configPath, JSON.stringify(config, null, 2), "utf-8");
|
|
6820
|
+
fs$1.writeFileSync(configPath, JSON.stringify(config, null, 2), "utf-8");
|
|
6415
6821
|
api.logger.debug(`[Gemini] Saved model "${model}" to ${configPath}`);
|
|
6416
6822
|
} catch (error) {
|
|
6417
6823
|
api.logger.debug(`[Gemini] Failed to save model to config:`, error);
|
|
@@ -6419,15 +6825,15 @@ function saveGeminiModelToConfig(model) {
|
|
|
6419
6825
|
}
|
|
6420
6826
|
function saveGoogleCloudProjectToConfig(projectId, email) {
|
|
6421
6827
|
try {
|
|
6422
|
-
const configDir = path.join(os$1.homedir(), ".gemini");
|
|
6423
|
-
const configPath = path.join(configDir, "config.json");
|
|
6424
|
-
if (!fs.existsSync(configDir)) {
|
|
6425
|
-
fs.mkdirSync(configDir, { recursive: true });
|
|
6828
|
+
const configDir = path$1.join(os$1.homedir(), ".gemini");
|
|
6829
|
+
const configPath = path$1.join(configDir, "config.json");
|
|
6830
|
+
if (!fs$1.existsSync(configDir)) {
|
|
6831
|
+
fs$1.mkdirSync(configDir, { recursive: true });
|
|
6426
6832
|
}
|
|
6427
6833
|
let config = {};
|
|
6428
|
-
if (fs.existsSync(configPath)) {
|
|
6834
|
+
if (fs$1.existsSync(configPath)) {
|
|
6429
6835
|
try {
|
|
6430
|
-
config = JSON.parse(fs.readFileSync(configPath, "utf-8"));
|
|
6836
|
+
config = JSON.parse(fs$1.readFileSync(configPath, "utf-8"));
|
|
6431
6837
|
} catch {
|
|
6432
6838
|
config = {};
|
|
6433
6839
|
}
|
|
@@ -6436,7 +6842,7 @@ function saveGoogleCloudProjectToConfig(projectId, email) {
|
|
|
6436
6842
|
if (email) {
|
|
6437
6843
|
config.googleCloudProjectEmail = email;
|
|
6438
6844
|
}
|
|
6439
|
-
fs.writeFileSync(configPath, JSON.stringify(config, null, 2), "utf-8");
|
|
6845
|
+
fs$1.writeFileSync(configPath, JSON.stringify(config, null, 2), "utf-8");
|
|
6440
6846
|
api.logger.debug(`[Gemini] Saved Google Cloud Project "${projectId}"${email ? ` for ${email}` : ""} to ${configPath}`);
|
|
6441
6847
|
} catch (error) {
|
|
6442
6848
|
api.logger.debug(`[Gemini] Failed to save Google Cloud Project to config:`, error);
|
|
@@ -6544,11 +6950,11 @@ function readFirstEnv(...names) {
|
|
|
6544
6950
|
return "";
|
|
6545
6951
|
}
|
|
6546
6952
|
function normalizeCommandPath(command) {
|
|
6547
|
-
if (
|
|
6953
|
+
if (path.isAbsolute(command)) {
|
|
6548
6954
|
return command;
|
|
6549
6955
|
}
|
|
6550
|
-
const resolved =
|
|
6551
|
-
return
|
|
6956
|
+
const resolved = path.resolve(process.cwd(), command);
|
|
6957
|
+
return fs.existsSync(resolved) ? resolved : command;
|
|
6552
6958
|
}
|
|
6553
6959
|
function resolveCommandOnPath(command) {
|
|
6554
6960
|
const pathValue = typeof process.env.PATH === "string" ? process.env.PATH : "";
|
|
@@ -6556,13 +6962,13 @@ function resolveCommandOnPath(command) {
|
|
|
6556
6962
|
return null;
|
|
6557
6963
|
}
|
|
6558
6964
|
const extensions = process.platform === "win32" ? (process.env.PATHEXT || ".COM;.EXE;.BAT;.CMD").split(";").map((value) => value.trim().toLowerCase()).filter(Boolean) : [""];
|
|
6559
|
-
for (const dir of pathValue.split(
|
|
6965
|
+
for (const dir of pathValue.split(path.delimiter)) {
|
|
6560
6966
|
const trimmedDir = dir.trim();
|
|
6561
6967
|
if (!trimmedDir) {
|
|
6562
6968
|
continue;
|
|
6563
6969
|
}
|
|
6564
|
-
const directCandidate =
|
|
6565
|
-
if (
|
|
6970
|
+
const directCandidate = path.join(trimmedDir, command);
|
|
6971
|
+
if (fs.existsSync(directCandidate)) {
|
|
6566
6972
|
return directCandidate;
|
|
6567
6973
|
}
|
|
6568
6974
|
if (process.platform !== "win32") {
|
|
@@ -6573,8 +6979,8 @@ function resolveCommandOnPath(command) {
|
|
|
6573
6979
|
continue;
|
|
6574
6980
|
}
|
|
6575
6981
|
for (const extension of extensions) {
|
|
6576
|
-
const candidate =
|
|
6577
|
-
if (
|
|
6982
|
+
const candidate = path.join(trimmedDir, `${command}${extension.toLowerCase()}`);
|
|
6983
|
+
if (fs.existsSync(candidate)) {
|
|
6578
6984
|
return candidate;
|
|
6579
6985
|
}
|
|
6580
6986
|
}
|
|
@@ -6673,8 +7079,8 @@ function validateCodexAcpSpawn(options = {}) {
|
|
|
6673
7079
|
const normalizedCommand = spawn.command.trim();
|
|
6674
7080
|
const commandLower = normalizedCommand.toLowerCase();
|
|
6675
7081
|
const npxMode = readCodexAcpNpxMode();
|
|
6676
|
-
if (
|
|
6677
|
-
if (!
|
|
7082
|
+
if (path.isAbsolute(normalizedCommand)) {
|
|
7083
|
+
if (!fs.existsSync(normalizedCommand)) {
|
|
6678
7084
|
return {
|
|
6679
7085
|
ok: false,
|
|
6680
7086
|
errorMessage: `Codex ACP is enabled, but the resolved command does not exist: ${normalizedCommand}`
|
|
@@ -6703,6 +7109,189 @@ function validateCodexAcpSpawn(options = {}) {
|
|
|
6703
7109
|
return { ok: true, spawn };
|
|
6704
7110
|
}
|
|
6705
7111
|
|
|
7112
|
+
function firstExistingPath(candidates) {
|
|
7113
|
+
for (const candidate of candidates) {
|
|
7114
|
+
try {
|
|
7115
|
+
if (fs.existsSync(candidate)) {
|
|
7116
|
+
return candidate;
|
|
7117
|
+
}
|
|
7118
|
+
} catch {
|
|
7119
|
+
}
|
|
7120
|
+
}
|
|
7121
|
+
return null;
|
|
7122
|
+
}
|
|
7123
|
+
function resolveCodexExecutable() {
|
|
7124
|
+
if (process.platform === "win32") {
|
|
7125
|
+
const appData = process.env.APPDATA || path.join(os.homedir(), "AppData", "Roaming");
|
|
7126
|
+
const npmGlobalBin = path.join(appData, "npm");
|
|
7127
|
+
const resolved = firstExistingPath([
|
|
7128
|
+
path.join(npmGlobalBin, "codex.cmd"),
|
|
7129
|
+
path.join(npmGlobalBin, "codex.ps1"),
|
|
7130
|
+
path.join(npmGlobalBin, "codex")
|
|
7131
|
+
]);
|
|
7132
|
+
if (resolved) {
|
|
7133
|
+
return resolved;
|
|
7134
|
+
}
|
|
7135
|
+
}
|
|
7136
|
+
return "codex";
|
|
7137
|
+
}
|
|
7138
|
+
|
|
7139
|
+
function getCodexPlatformTarget(platform, arch) {
|
|
7140
|
+
if (platform === "win32" && arch === "x64") {
|
|
7141
|
+
return {
|
|
7142
|
+
packageName: "codex-win32-x64",
|
|
7143
|
+
targetTriple: "x86_64-pc-windows-msvc"
|
|
7144
|
+
};
|
|
7145
|
+
}
|
|
7146
|
+
if (platform === "win32" && arch === "arm64") {
|
|
7147
|
+
return {
|
|
7148
|
+
packageName: "codex-win32-arm64",
|
|
7149
|
+
targetTriple: "aarch64-pc-windows-msvc"
|
|
7150
|
+
};
|
|
7151
|
+
}
|
|
7152
|
+
if ((platform === "linux" || platform === "android") && arch === "x64") {
|
|
7153
|
+
return {
|
|
7154
|
+
packageName: "codex-linux-x64",
|
|
7155
|
+
targetTriple: "x86_64-unknown-linux-musl"
|
|
7156
|
+
};
|
|
7157
|
+
}
|
|
7158
|
+
if ((platform === "linux" || platform === "android") && arch === "arm64") {
|
|
7159
|
+
return {
|
|
7160
|
+
packageName: "codex-linux-arm64",
|
|
7161
|
+
targetTriple: "aarch64-unknown-linux-musl"
|
|
7162
|
+
};
|
|
7163
|
+
}
|
|
7164
|
+
if (platform === "darwin" && arch === "x64") {
|
|
7165
|
+
return {
|
|
7166
|
+
packageName: "codex-darwin-x64",
|
|
7167
|
+
targetTriple: "x86_64-apple-darwin"
|
|
7168
|
+
};
|
|
7169
|
+
}
|
|
7170
|
+
if (platform === "darwin" && arch === "arm64") {
|
|
7171
|
+
return {
|
|
7172
|
+
packageName: "codex-darwin-arm64",
|
|
7173
|
+
targetTriple: "aarch64-apple-darwin"
|
|
7174
|
+
};
|
|
7175
|
+
}
|
|
7176
|
+
return null;
|
|
7177
|
+
}
|
|
7178
|
+
function dedupePaths(paths, platform) {
|
|
7179
|
+
const seen = /* @__PURE__ */ new Set();
|
|
7180
|
+
const unique = [];
|
|
7181
|
+
for (const entry of paths) {
|
|
7182
|
+
const normalized = path.normalize(entry);
|
|
7183
|
+
const key = platform === "win32" ? normalized.toLowerCase() : normalized;
|
|
7184
|
+
if (seen.has(key)) {
|
|
7185
|
+
continue;
|
|
7186
|
+
}
|
|
7187
|
+
seen.add(key);
|
|
7188
|
+
unique.push(entry);
|
|
7189
|
+
}
|
|
7190
|
+
return unique;
|
|
7191
|
+
}
|
|
7192
|
+
function resolveCodexShimPath({
|
|
7193
|
+
platform = process.platform,
|
|
7194
|
+
exists = fs.existsSync,
|
|
7195
|
+
resolveExecutable = resolveCodexExecutable,
|
|
7196
|
+
resolveOnPath = resolveCommandOnPath
|
|
7197
|
+
}) {
|
|
7198
|
+
const resolvedExecutable = resolveExecutable();
|
|
7199
|
+
if (path.isAbsolute(resolvedExecutable) && exists(resolvedExecutable)) {
|
|
7200
|
+
return resolvedExecutable;
|
|
7201
|
+
}
|
|
7202
|
+
const commandNames = platform === "win32" ? ["codex.cmd", "codex.ps1", "codex"] : ["codex"];
|
|
7203
|
+
for (const commandName of commandNames) {
|
|
7204
|
+
const resolved = resolveOnPath(commandName);
|
|
7205
|
+
if (resolved) {
|
|
7206
|
+
return resolved;
|
|
7207
|
+
}
|
|
7208
|
+
}
|
|
7209
|
+
return null;
|
|
7210
|
+
}
|
|
7211
|
+
function resolveCodexPackageRoots(executablePath, {
|
|
7212
|
+
platform = process.platform,
|
|
7213
|
+
exists = fs.existsSync,
|
|
7214
|
+
realpath = fs.realpathSync
|
|
7215
|
+
}) {
|
|
7216
|
+
const executableDir = path.dirname(executablePath);
|
|
7217
|
+
const candidates = [
|
|
7218
|
+
path.join(executableDir, "node_modules", "@openai", "codex"),
|
|
7219
|
+
path.join(executableDir, "..", "lib", "node_modules", "@openai", "codex")
|
|
7220
|
+
];
|
|
7221
|
+
try {
|
|
7222
|
+
const realExecutablePath = realpath(executablePath);
|
|
7223
|
+
candidates.push(path.join(path.dirname(realExecutablePath), ".."));
|
|
7224
|
+
} catch {
|
|
7225
|
+
}
|
|
7226
|
+
return dedupePaths(
|
|
7227
|
+
candidates.filter((candidate) => exists(candidate)),
|
|
7228
|
+
platform
|
|
7229
|
+
);
|
|
7230
|
+
}
|
|
7231
|
+
function resolvePathEnvKey(env) {
|
|
7232
|
+
return Object.prototype.hasOwnProperty.call(env, "Path") && !Object.prototype.hasOwnProperty.call(env, "PATH") ? "Path" : "PATH";
|
|
7233
|
+
}
|
|
7234
|
+
function resolveBundledCodexToolPathDirs(options = {}) {
|
|
7235
|
+
const platform = options.platform ?? process.platform;
|
|
7236
|
+
const arch = options.arch ?? process.arch;
|
|
7237
|
+
const codexTarget = getCodexPlatformTarget(platform, arch);
|
|
7238
|
+
if (!codexTarget) {
|
|
7239
|
+
return [];
|
|
7240
|
+
}
|
|
7241
|
+
const codexShimPath = resolveCodexShimPath(options);
|
|
7242
|
+
if (!codexShimPath) {
|
|
7243
|
+
return [];
|
|
7244
|
+
}
|
|
7245
|
+
const packageRoots = resolveCodexPackageRoots(codexShimPath, options);
|
|
7246
|
+
if (packageRoots.length === 0) {
|
|
7247
|
+
return [];
|
|
7248
|
+
}
|
|
7249
|
+
const exists = options.exists ?? fs.existsSync;
|
|
7250
|
+
const candidates = packageRoots.flatMap((packageRoot) => [
|
|
7251
|
+
path.join(packageRoot, "vendor", codexTarget.targetTriple, "path"),
|
|
7252
|
+
path.join(
|
|
7253
|
+
packageRoot,
|
|
7254
|
+
"node_modules",
|
|
7255
|
+
"@openai",
|
|
7256
|
+
codexTarget.packageName,
|
|
7257
|
+
"vendor",
|
|
7258
|
+
codexTarget.targetTriple,
|
|
7259
|
+
"path"
|
|
7260
|
+
)
|
|
7261
|
+
]);
|
|
7262
|
+
return dedupePaths(
|
|
7263
|
+
candidates.filter((candidate) => exists(candidate)),
|
|
7264
|
+
platform
|
|
7265
|
+
);
|
|
7266
|
+
}
|
|
7267
|
+
function buildCodexAcpEnv(overrides = {}, options = {}) {
|
|
7268
|
+
const platform = options.platform ?? process.platform;
|
|
7269
|
+
const env = {
|
|
7270
|
+
...process.env,
|
|
7271
|
+
...overrides
|
|
7272
|
+
};
|
|
7273
|
+
const pathKey = resolvePathEnvKey(env);
|
|
7274
|
+
const alternatePathKey = pathKey === "PATH" ? "Path" : "PATH";
|
|
7275
|
+
const currentPathValue = env[pathKey] ?? env[alternatePathKey] ?? "";
|
|
7276
|
+
const currentPathEntries = currentPathValue.split(path.delimiter).filter(Boolean);
|
|
7277
|
+
const codexToolDirs = resolveBundledCodexToolPathDirs(options);
|
|
7278
|
+
const mergedPathEntries = dedupePaths(
|
|
7279
|
+
[...codexToolDirs, ...currentPathEntries],
|
|
7280
|
+
platform
|
|
7281
|
+
);
|
|
7282
|
+
if (mergedPathEntries.length > 0) {
|
|
7283
|
+
env[pathKey] = mergedPathEntries.join(path.delimiter);
|
|
7284
|
+
}
|
|
7285
|
+
delete env[alternatePathKey];
|
|
7286
|
+
const stringEnvEntries = [];
|
|
7287
|
+
for (const [key, value] of Object.entries(env)) {
|
|
7288
|
+
if (typeof value === "string") {
|
|
7289
|
+
stringEnvEntries.push([key, value]);
|
|
7290
|
+
}
|
|
7291
|
+
}
|
|
7292
|
+
return Object.fromEntries(stringEnvEntries);
|
|
7293
|
+
}
|
|
7294
|
+
|
|
6706
7295
|
class CodexAcpTransport extends CodexTransport {
|
|
6707
7296
|
constructor(initTimeoutMs) {
|
|
6708
7297
|
super();
|
|
@@ -6732,10 +7321,10 @@ function createCodexBackend(options) {
|
|
|
6732
7321
|
cwd: options.cwd,
|
|
6733
7322
|
command: spawn.command,
|
|
6734
7323
|
args: spawn.args,
|
|
6735
|
-
env: {
|
|
7324
|
+
env: buildCodexAcpEnv({
|
|
6736
7325
|
...options.env,
|
|
6737
7326
|
NODE_ENV: "production"
|
|
6738
|
-
},
|
|
7327
|
+
}),
|
|
6739
7328
|
permissionHandler: options.permissionHandler,
|
|
6740
7329
|
selectionHandler: options.selectionHandler,
|
|
6741
7330
|
transportHandler: resolveCodexTransport(spawn.command)
|
|
@@ -6885,6 +7474,24 @@ function createDefaultRuntimeShell() {
|
|
|
6885
7474
|
return new RuntimeShell();
|
|
6886
7475
|
}
|
|
6887
7476
|
|
|
7477
|
+
const DAEMON_STARTUP_TIMEOUT_MS = 3e4;
|
|
7478
|
+
const DAEMON_STARTUP_POLL_INTERVAL_MS = 250;
|
|
7479
|
+
async function waitForDaemonReady(timeoutMs = DAEMON_STARTUP_TIMEOUT_MS, pollIntervalMs = DAEMON_STARTUP_POLL_INTERVAL_MS) {
|
|
7480
|
+
const deadline = Date.now() + timeoutMs;
|
|
7481
|
+
while (Date.now() < deadline) {
|
|
7482
|
+
const remainingMs = deadline - Date.now();
|
|
7483
|
+
if (await isDaemonRunningCurrentlyInstalledHappyVersion(Math.max(1, remainingMs))) {
|
|
7484
|
+
return true;
|
|
7485
|
+
}
|
|
7486
|
+
const sleepMs = Math.min(pollIntervalMs, deadline - Date.now());
|
|
7487
|
+
if (sleepMs <= 0) {
|
|
7488
|
+
break;
|
|
7489
|
+
}
|
|
7490
|
+
await new Promise((resolve) => setTimeout(resolve, sleepMs));
|
|
7491
|
+
}
|
|
7492
|
+
return false;
|
|
7493
|
+
}
|
|
7494
|
+
|
|
6888
7495
|
function isRuntimeProvider(value) {
|
|
6889
7496
|
return value === "claude" || value === "codex" || value === "gemini" || value === "cursor";
|
|
6890
7497
|
}
|
|
@@ -6929,26 +7536,20 @@ async function ensureUnifiedDaemonStarted() {
|
|
|
6929
7536
|
env: process.env
|
|
6930
7537
|
});
|
|
6931
7538
|
daemonProcess.unref();
|
|
6932
|
-
|
|
6933
|
-
|
|
6934
|
-
return;
|
|
6935
|
-
}
|
|
6936
|
-
if (await isDaemonControlServerResponsive(500)) {
|
|
6937
|
-
return;
|
|
6938
|
-
}
|
|
6939
|
-
await new Promise((resolve) => setTimeout(resolve, 100));
|
|
7539
|
+
if (await waitForDaemonReady()) {
|
|
7540
|
+
return;
|
|
6940
7541
|
}
|
|
6941
7542
|
throw new Error("Failed to start Happy background service.");
|
|
6942
7543
|
}
|
|
6943
7544
|
async function executeUnifiedProvider(opts) {
|
|
6944
7545
|
const credentials = await ensureUnifiedRuntimePrerequisites(opts.credentials);
|
|
6945
7546
|
if (opts.provider === "claude") {
|
|
6946
|
-
const { runClaude } = await Promise.resolve().then(function () { return require('./runClaude-
|
|
7547
|
+
const { runClaude } = await Promise.resolve().then(function () { return require('./runClaude-C1W_Nw0C.cjs'); });
|
|
6947
7548
|
await runClaude(credentials, opts.claudeOptions ?? {});
|
|
6948
7549
|
return;
|
|
6949
7550
|
}
|
|
6950
7551
|
if (opts.provider === "codex") {
|
|
6951
|
-
const { runCodex } = await Promise.resolve().then(function () { return require('./runCodex-
|
|
7552
|
+
const { runCodex } = await Promise.resolve().then(function () { return require('./runCodex-D2VCWVEK.cjs'); });
|
|
6952
7553
|
await runCodex({
|
|
6953
7554
|
credentials,
|
|
6954
7555
|
startedBy: opts.startedBy,
|
|
@@ -6958,7 +7559,7 @@ async function executeUnifiedProvider(opts) {
|
|
|
6958
7559
|
return;
|
|
6959
7560
|
}
|
|
6960
7561
|
if (opts.provider === "gemini") {
|
|
6961
|
-
const { runGemini } = await Promise.resolve().then(function () { return require('./runGemini-
|
|
7562
|
+
const { runGemini } = await Promise.resolve().then(function () { return require('./runGemini-CiGnjflq.cjs'); });
|
|
6962
7563
|
await runGemini({
|
|
6963
7564
|
credentials,
|
|
6964
7565
|
startedBy: opts.startedBy
|
|
@@ -7000,7 +7601,7 @@ function shouldRunMainClaudeFlow(opts) {
|
|
|
7000
7601
|
return;
|
|
7001
7602
|
} else if (subcommand === "runtime") {
|
|
7002
7603
|
if (args[1] === "providers") {
|
|
7003
|
-
const { renderRuntimeProviders } = await Promise.resolve().then(function () { return require('./command-
|
|
7604
|
+
const { renderRuntimeProviders } = await Promise.resolve().then(function () { return require('./command-Df7u5eAT.cjs'); });
|
|
7004
7605
|
console.log(renderRuntimeProviders());
|
|
7005
7606
|
return;
|
|
7006
7607
|
}
|
|
@@ -7178,8 +7779,8 @@ function shouldRunMainClaudeFlow(opts) {
|
|
|
7178
7779
|
const projectId = args[3];
|
|
7179
7780
|
try {
|
|
7180
7781
|
const { saveGoogleCloudProjectToConfig } = await Promise.resolve().then(function () { return config; });
|
|
7181
|
-
const { readCredentials: readCredentials2 } = await Promise.resolve().then(function () { return require('./persistence-
|
|
7182
|
-
const { ApiClient: ApiClient2 } = await Promise.resolve().then(function () { return require('./api-
|
|
7782
|
+
const { readCredentials: readCredentials2 } = await Promise.resolve().then(function () { return require('./persistence-D7JtnrYA.cjs'); });
|
|
7783
|
+
const { ApiClient: ApiClient2 } = await Promise.resolve().then(function () { return require('./api-DUE5TJBE.cjs'); }).then(function (n) { return n.api; });
|
|
7183
7784
|
let userEmail = void 0;
|
|
7184
7785
|
try {
|
|
7185
7786
|
const credentials = await readCredentials2();
|
|
@@ -7326,14 +7927,7 @@ function shouldRunMainClaudeFlow(opts) {
|
|
|
7326
7927
|
env: process.env
|
|
7327
7928
|
});
|
|
7328
7929
|
child.unref();
|
|
7329
|
-
|
|
7330
|
-
for (let i = 0; i < 100; i++) {
|
|
7331
|
-
if (await checkIfDaemonRunningAndCleanupStaleState() && await isDaemonControlServerResponsive(500)) {
|
|
7332
|
-
started = true;
|
|
7333
|
-
break;
|
|
7334
|
-
}
|
|
7335
|
-
await new Promise((resolve) => setTimeout(resolve, 100));
|
|
7336
|
-
}
|
|
7930
|
+
const started = await waitForDaemonReady();
|
|
7337
7931
|
if (started) {
|
|
7338
7932
|
console.log("Daemon started successfully");
|
|
7339
7933
|
} else {
|
|
@@ -7342,7 +7936,7 @@ function shouldRunMainClaudeFlow(opts) {
|
|
|
7342
7936
|
if (latest) {
|
|
7343
7937
|
console.error(`Latest daemon log: ${latest.path}`);
|
|
7344
7938
|
try {
|
|
7345
|
-
const logContent =
|
|
7939
|
+
const logContent = fs.readFileSync(latest.path, "utf-8");
|
|
7346
7940
|
if (logContent.includes("EADDRINUSE")) {
|
|
7347
7941
|
console.error("Daemon control port is already in use. Retry after stopping the stale daemon or run `hicloud doctor clean`.");
|
|
7348
7942
|
}
|
|
@@ -7615,8 +8209,8 @@ exports.getInitialGeminiModel = getInitialGeminiModel;
|
|
|
7615
8209
|
exports.getProjectPath = getProjectPath;
|
|
7616
8210
|
exports.initialMachineMetadata = initialMachineMetadata;
|
|
7617
8211
|
exports.isBun = isBun;
|
|
7618
|
-
exports.notifyDaemonSessionStarted = notifyDaemonSessionStarted;
|
|
7619
8212
|
exports.projectPath = projectPath;
|
|
8213
|
+
exports.publishSessionRegistration = publishSessionRegistration;
|
|
7620
8214
|
exports.readGeminiLocalConfig = readGeminiLocalConfig;
|
|
7621
8215
|
exports.saveGeminiModelToConfig = saveGeminiModelToConfig;
|
|
7622
8216
|
exports.startCaffeinate = startCaffeinate;
|