happy-imou-cloud 2.0.8 → 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-D-uiH_TF.mjs → api-BjxmW-0W.mjs} +2 -2
- package/dist/{api-CN-WqYd_.cjs → api-DUE5TJBE.cjs} +43 -43
- package/dist/{command-DGFsZx58.mjs → command-ComOeFLY.mjs} +3 -3
- package/dist/{command-DjIfRZQS.cjs → command-Df7u5eAT.cjs} +3 -3
- package/dist/{index-DM6z3aeG.cjs → index-Cuvfa15L.cjs} +808 -201
- package/dist/{index-DhheEtRl.mjs → index-CzvgPwr1.mjs} +732 -125
- 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-BjEof0E2.mjs → names-CicSgRNg.mjs} +2 -2
- package/dist/{names-BnV67N_O.cjs → names-yJNZoTv_.cjs} +4 -4
- package/dist/{persistence-DiNg1DPF.mjs → persistence-BxP6Jw1f.mjs} +1 -1
- package/dist/{persistence-DBGkO8gB.cjs → persistence-D7JtnrYA.cjs} +29 -29
- package/dist/{registerKillSessionHandler-Cu9rHGsI.mjs → registerKillSessionHandler-ARQrPvnT.mjs} +2 -2
- package/dist/{registerKillSessionHandler-CYc0SIjF.cjs → registerKillSessionHandler-DCMFiXyA.cjs} +2 -2
- package/dist/{runClaude-DAR_hw3C.mjs → runClaude-B_fTMxc4.mjs} +6 -15
- package/dist/{runClaude-CPhWaFrX.cjs → runClaude-C1W_Nw0C.cjs} +32 -41
- package/dist/{runCodex-B4QAb-Go.mjs → runCodex-CUgOiIEz.mjs} +12 -13
- package/dist/{runCodex--QLrOs8X.cjs → runCodex-D2VCWVEK.cjs} +12 -13
- package/dist/{runGemini-DqowSR2w.mjs → runGemini-2_FEtJYa.mjs} +7 -16
- package/dist/{runGemini-CDyhCucw.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
|
-
} catch {
|
|
989
|
-
api.logger.debug("[DAEMON RUN] Daemon PID not running, cleaning up state");
|
|
990
|
-
await cleanupDaemonState();
|
|
991
|
-
return false;
|
|
1065
|
+
const processStatus = await inspectDaemonStateProcess(state);
|
|
1066
|
+
if (processStatus === "confirmed") {
|
|
1067
|
+
return "running";
|
|
992
1068
|
}
|
|
1069
|
+
if (processStatus === "indeterminate") {
|
|
1070
|
+
return "indeterminate";
|
|
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,15 +1223,30 @@ function getEnvironmentInfo() {
|
|
|
1130
1223
|
terminal: process.env.TERM
|
|
1131
1224
|
};
|
|
1132
1225
|
}
|
|
1226
|
+
function resolveDaemonSpawnDiagnostics(projectRoot, fileExists = fs.existsSync) {
|
|
1227
|
+
const wrapperCandidates = [
|
|
1228
|
+
path.join(projectRoot, "bin", "happy-cloud.mjs"),
|
|
1229
|
+
path.join(projectRoot, "bin", "happy.mjs")
|
|
1230
|
+
];
|
|
1231
|
+
const cliEntrypoint = path.join(projectRoot, "dist", "index.mjs");
|
|
1232
|
+
const wrapperPath = wrapperCandidates.find((candidate) => fileExists(candidate)) ?? wrapperCandidates[0];
|
|
1233
|
+
return {
|
|
1234
|
+
projectRoot,
|
|
1235
|
+
wrapperPath,
|
|
1236
|
+
cliEntrypoint,
|
|
1237
|
+
wrapperExists: fileExists(wrapperPath),
|
|
1238
|
+
cliEntrypointExists: fileExists(cliEntrypoint)
|
|
1239
|
+
};
|
|
1240
|
+
}
|
|
1133
1241
|
function getLogFiles(logDir) {
|
|
1134
|
-
if (!
|
|
1242
|
+
if (!fs.existsSync(logDir)) {
|
|
1135
1243
|
return [];
|
|
1136
1244
|
}
|
|
1137
1245
|
try {
|
|
1138
|
-
return
|
|
1139
|
-
const path =
|
|
1140
|
-
const stats =
|
|
1141
|
-
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 };
|
|
1142
1250
|
}).sort((a, b) => b.modified.getTime() - a.modified.getTime());
|
|
1143
1251
|
} catch {
|
|
1144
1252
|
return [];
|
|
@@ -1156,14 +1264,12 @@ async function runDoctorCommand(filter) {
|
|
|
1156
1264
|
console.log(`Node.js Version: ${chalk.green(process.version)}`);
|
|
1157
1265
|
console.log("");
|
|
1158
1266
|
console.log(chalk.bold("\u{1F527} Daemon Spawn Diagnostics"));
|
|
1159
|
-
const
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
|
-
console.log(`
|
|
1163
|
-
console.log(`Wrapper
|
|
1164
|
-
console.log(`CLI
|
|
1165
|
-
console.log(`Wrapper Exists: ${node_fs.existsSync(wrapperPath) ? chalk.green("\u2713 Yes") : chalk.red("\u274C No")}`);
|
|
1166
|
-
console.log(`CLI Exists: ${node_fs.existsSync(cliEntrypoint) ? chalk.green("\u2713 Yes") : chalk.red("\u274C No")}`);
|
|
1267
|
+
const diagnostics = resolveDaemonSpawnDiagnostics(projectPath());
|
|
1268
|
+
console.log(`Project Root: ${chalk.blue(diagnostics.projectRoot)}`);
|
|
1269
|
+
console.log(`Wrapper Script: ${chalk.blue(diagnostics.wrapperPath)}`);
|
|
1270
|
+
console.log(`CLI Entrypoint: ${chalk.blue(diagnostics.cliEntrypoint)}`);
|
|
1271
|
+
console.log(`Wrapper Exists: ${diagnostics.wrapperExists ? chalk.green("\u2713 Yes") : chalk.red("\u274C No")}`);
|
|
1272
|
+
console.log(`CLI Exists: ${diagnostics.cliEntrypointExists ? chalk.green("\u2713 Yes") : chalk.red("\u274C No")}`);
|
|
1167
1273
|
console.log("");
|
|
1168
1274
|
console.log(chalk.bold("\u2699\uFE0F Configuration"));
|
|
1169
1275
|
console.log(`Happy Home: ${chalk.blue(api.configuration.happyCloudHomeDir)}`);
|
|
@@ -1199,9 +1305,9 @@ async function runDoctorCommand(filter) {
|
|
|
1199
1305
|
}
|
|
1200
1306
|
console.log(chalk.bold("\n\u{1F916} Daemon Status"));
|
|
1201
1307
|
try {
|
|
1202
|
-
const
|
|
1308
|
+
const daemonStatus = await getDaemonRuntimeStatus();
|
|
1203
1309
|
const state = await persistence.readDaemonState();
|
|
1204
|
-
if (
|
|
1310
|
+
if (daemonStatus === "running" && state) {
|
|
1205
1311
|
console.log(chalk.green("\u2713 Daemon is running"));
|
|
1206
1312
|
console.log(` PID: ${state.pid}`);
|
|
1207
1313
|
console.log(` Started: ${new Date(state.startTime).toLocaleString()}`);
|
|
@@ -1209,7 +1315,14 @@ async function runDoctorCommand(filter) {
|
|
|
1209
1315
|
if (state.httpPort) {
|
|
1210
1316
|
console.log(` HTTP Port: ${state.httpPort}`);
|
|
1211
1317
|
}
|
|
1212
|
-
} 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) {
|
|
1213
1326
|
console.log(chalk.yellow("\u26A0\uFE0F Daemon state exists but process not running (stale)"));
|
|
1214
1327
|
} else {
|
|
1215
1328
|
console.log(chalk.red("\u274C Daemon is not running"));
|
|
@@ -1231,6 +1344,7 @@ async function runDoctorCommand(filter) {
|
|
|
1231
1344
|
const typeLabels = {
|
|
1232
1345
|
"current": "\u{1F4CD} Current Process",
|
|
1233
1346
|
"daemon": "\u{1F916} Daemon",
|
|
1347
|
+
"daemon-indeterminate": "\u26A0\uFE0F Possible Daemon (identity unconfirmed)",
|
|
1234
1348
|
"daemon-version-check": "\u{1F50D} Daemon Version Check (stuck)",
|
|
1235
1349
|
"daemon-spawned-session": "\u{1F517} Daemon-Spawned Sessions",
|
|
1236
1350
|
"user-session": "\u{1F464} User Sessions",
|
|
@@ -1245,7 +1359,7 @@ async function runDoctorCommand(filter) {
|
|
|
1245
1359
|
console.log(chalk.blue(`
|
|
1246
1360
|
${typeLabels[type] || type}:`));
|
|
1247
1361
|
processes.forEach(({ pid, command }) => {
|
|
1248
|
-
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;
|
|
1249
1363
|
console.log(` ${color(`PID ${pid}`)}: ${chalk.gray(command)}`);
|
|
1250
1364
|
});
|
|
1251
1365
|
});
|
|
@@ -1329,7 +1443,7 @@ const isBun = () => getRuntime() === "bun";
|
|
|
1329
1443
|
|
|
1330
1444
|
function spawnHappyCLI(args, options = {}) {
|
|
1331
1445
|
const projectRoot = projectPath();
|
|
1332
|
-
const entrypoint =
|
|
1446
|
+
const entrypoint = path.join(projectRoot, "dist", "index.mjs");
|
|
1333
1447
|
let directory;
|
|
1334
1448
|
if ("cwd" in options) {
|
|
1335
1449
|
directory = options.cwd;
|
|
@@ -1344,7 +1458,7 @@ function spawnHappyCLI(args, options = {}) {
|
|
|
1344
1458
|
entrypoint,
|
|
1345
1459
|
...args
|
|
1346
1460
|
];
|
|
1347
|
-
if (!
|
|
1461
|
+
if (!fs.existsSync(entrypoint)) {
|
|
1348
1462
|
const errorMessage = `Entrypoint ${entrypoint} does not exist`;
|
|
1349
1463
|
api.logger.debug(`[SPAWN HAPPY CLOUD CLI] ${errorMessage}`);
|
|
1350
1464
|
throw new Error(errorMessage);
|
|
@@ -2275,6 +2389,283 @@ function buildDaemonChildEnv(baseEnv, extraEnv) {
|
|
|
2275
2389
|
return childEnv;
|
|
2276
2390
|
}
|
|
2277
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
|
+
|
|
2278
2669
|
const initialMachineMetadata = {
|
|
2279
2670
|
host: os$1.hostname(),
|
|
2280
2671
|
platform: os$1.platform(),
|
|
@@ -2404,7 +2795,7 @@ async function startDaemon() {
|
|
|
2404
2795
|
const { directory, sessionId, machineId: machineId2, approvedNewDirectoryCreation = true } = options;
|
|
2405
2796
|
let directoryCreated = false;
|
|
2406
2797
|
try {
|
|
2407
|
-
await fs$
|
|
2798
|
+
await fs$2.access(directory);
|
|
2408
2799
|
api.logger.debug(`[DAEMON RUN] Directory exists: ${directory}`);
|
|
2409
2800
|
} catch (error) {
|
|
2410
2801
|
api.logger.debug(`[DAEMON RUN] Directory doesn't exist, creating: ${directory}`);
|
|
@@ -2416,7 +2807,7 @@ async function startDaemon() {
|
|
|
2416
2807
|
};
|
|
2417
2808
|
}
|
|
2418
2809
|
try {
|
|
2419
|
-
await fs$
|
|
2810
|
+
await fs$2.mkdir(directory, { recursive: true });
|
|
2420
2811
|
api.logger.debug(`[DAEMON RUN] Successfully created directory: ${directory}`);
|
|
2421
2812
|
directoryCreated = true;
|
|
2422
2813
|
} catch (mkdirError) {
|
|
@@ -2445,7 +2836,7 @@ async function startDaemon() {
|
|
|
2445
2836
|
if (options.token) {
|
|
2446
2837
|
if (options.agent === "codex") {
|
|
2447
2838
|
const codexHomeDir = tmp__namespace.dirSync();
|
|
2448
|
-
fs$
|
|
2839
|
+
fs$2.writeFile(path$1.join(codexHomeDir.name, "auth.json"), options.token);
|
|
2449
2840
|
authEnv.CODEX_HOME = codexHomeDir.name;
|
|
2450
2841
|
} else {
|
|
2451
2842
|
authEnv.CLAUDE_CODE_OAUTH_TOKEN = options.token;
|
|
@@ -2498,20 +2889,18 @@ async function startDaemon() {
|
|
|
2498
2889
|
errorMessage: spawnError.errorMessage
|
|
2499
2890
|
};
|
|
2500
2891
|
}
|
|
2501
|
-
const tmuxAvailable = await isTmuxAvailable();
|
|
2502
|
-
let useTmux = tmuxAvailable;
|
|
2503
2892
|
let tmuxSessionName = extraEnv.TMUX_SESSION_NAME;
|
|
2504
|
-
|
|
2505
|
-
|
|
2506
|
-
|
|
2507
|
-
|
|
2508
|
-
|
|
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`);
|
|
2509
2898
|
}
|
|
2510
2899
|
if (useTmux && tmuxSessionName !== void 0) {
|
|
2511
2900
|
const sessionDesc = tmuxSessionName || "current/most recent session";
|
|
2512
2901
|
api.logger.debug(`[DAEMON RUN] Attempting to spawn session in tmux: ${sessionDesc}`);
|
|
2513
2902
|
const tmux = getTmuxUtilities(tmuxSessionName);
|
|
2514
|
-
const cliPath = path.join(projectPath(), "dist", "index.mjs");
|
|
2903
|
+
const cliPath = path$1.join(projectPath(), "dist", "index.mjs");
|
|
2515
2904
|
const agent = resolveDaemonSpawnAgent(options.agent);
|
|
2516
2905
|
const fullCommand = `node --no-warnings --no-deprecation ${cliPath} ${buildDaemonSpawnArgs(agent).join(" ")}`;
|
|
2517
2906
|
const windowName = `happy-${Date.now()}-${agent}`;
|
|
@@ -2710,6 +3099,17 @@ async function startDaemon() {
|
|
|
2710
3099
|
onHappySessionWebhook,
|
|
2711
3100
|
port: api.HAPPY_CLOUD_DAEMON_PORT
|
|
2712
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
|
+
}
|
|
2713
3113
|
const fileState = {
|
|
2714
3114
|
pid: process.pid,
|
|
2715
3115
|
httpPort: controlPort,
|
|
@@ -2749,52 +3149,69 @@ async function startDaemon() {
|
|
|
2749
3149
|
if (process.env.DEBUG) {
|
|
2750
3150
|
api.logger.debug(`[DAEMON RUN] Health check started at ${(/* @__PURE__ */ new Date()).toLocaleString()}`);
|
|
2751
3151
|
}
|
|
2752
|
-
for (const [pid, _] of pidToTrackedSession.entries()) {
|
|
2753
|
-
try {
|
|
2754
|
-
process.kill(pid, 0);
|
|
2755
|
-
} catch (error) {
|
|
2756
|
-
api.logger.debug(`[DAEMON RUN] Removing stale session with PID ${pid} (process no longer exists)`);
|
|
2757
|
-
pidToTrackedSession.delete(pid);
|
|
2758
|
-
}
|
|
2759
|
-
}
|
|
2760
|
-
const projectVersion = JSON.parse(fs.readFileSync(path.join(projectPath(), "package.json"), "utf-8")).version;
|
|
2761
|
-
if (projectVersion !== api.configuration.currentCliVersion) {
|
|
2762
|
-
api.logger.debug("[DAEMON RUN] Daemon is outdated, triggering self-restart with latest version, clearing heartbeat interval");
|
|
2763
|
-
clearInterval(restartOnStaleVersionAndHeartbeat);
|
|
2764
|
-
try {
|
|
2765
|
-
spawnHappyCLI(["daemon", "start"], {
|
|
2766
|
-
detached: true,
|
|
2767
|
-
stdio: "ignore"
|
|
2768
|
-
});
|
|
2769
|
-
} catch (error) {
|
|
2770
|
-
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);
|
|
2771
|
-
}
|
|
2772
|
-
api.logger.debug("[DAEMON RUN] Hanging for a bit - waiting for CLI to kill us because we are running outdated version of the code");
|
|
2773
|
-
await new Promise((resolve) => setTimeout(resolve, 1e4));
|
|
2774
|
-
process.exit(0);
|
|
2775
|
-
}
|
|
2776
|
-
const daemonState = await persistence.readDaemonState();
|
|
2777
|
-
if (daemonState && daemonState.pid !== process.pid) {
|
|
2778
|
-
api.logger.debug("[DAEMON RUN] Somehow a different daemon was started without killing us. We should kill ourselves.");
|
|
2779
|
-
requestShutdown("exception", "A different daemon was started without killing us. We should kill ourselves.");
|
|
2780
|
-
}
|
|
2781
3152
|
try {
|
|
2782
|
-
const
|
|
2783
|
-
|
|
2784
|
-
|
|
2785
|
-
|
|
2786
|
-
|
|
2787
|
-
|
|
2788
|
-
|
|
2789
|
-
|
|
2790
|
-
|
|
2791
|
-
|
|
2792
|
-
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
|
+
);
|
|
2793
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
|
+
});
|
|
2794
3207
|
} catch (error) {
|
|
2795
|
-
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;
|
|
2796
3214
|
}
|
|
2797
|
-
heartbeatRunning = false;
|
|
2798
3215
|
}, heartbeatIntervalMs);
|
|
2799
3216
|
const cleanupAndShutdown = async (source, errorMessage) => {
|
|
2800
3217
|
api.logger.debug(`[DAEMON RUN] Starting proper cleanup (source: ${source}, errorMessage: ${errorMessage})...`);
|
|
@@ -2850,7 +3267,7 @@ const PLIST_LABEL$1 = "com.happy-cloud.daemon";
|
|
|
2850
3267
|
const PLIST_FILE$1 = `/Library/LaunchDaemons/${PLIST_LABEL$1}.plist`;
|
|
2851
3268
|
async function install$1() {
|
|
2852
3269
|
try {
|
|
2853
|
-
if (fs.existsSync(PLIST_FILE$1)) {
|
|
3270
|
+
if (fs$1.existsSync(PLIST_FILE$1)) {
|
|
2854
3271
|
api.logger.info("Daemon plist already exists. Uninstalling first...");
|
|
2855
3272
|
child_process.execSync(`launchctl unload ${PLIST_FILE$1}`, { stdio: "inherit" });
|
|
2856
3273
|
}
|
|
@@ -2894,8 +3311,8 @@ async function install$1() {
|
|
|
2894
3311
|
</dict>
|
|
2895
3312
|
</plist>
|
|
2896
3313
|
`);
|
|
2897
|
-
fs.writeFileSync(PLIST_FILE$1, plistContent);
|
|
2898
|
-
fs.chmodSync(PLIST_FILE$1, 420);
|
|
3314
|
+
fs$1.writeFileSync(PLIST_FILE$1, plistContent);
|
|
3315
|
+
fs$1.chmodSync(PLIST_FILE$1, 420);
|
|
2899
3316
|
api.logger.info(`Created daemon plist at ${PLIST_FILE$1}`);
|
|
2900
3317
|
child_process.execSync(`launchctl load ${PLIST_FILE$1}`, { stdio: "inherit" });
|
|
2901
3318
|
api.logger.info("Daemon installed and started successfully");
|
|
@@ -2921,7 +3338,7 @@ const PLIST_LABEL = "com.happy-cli.daemon";
|
|
|
2921
3338
|
const PLIST_FILE = `/Library/LaunchDaemons/${PLIST_LABEL}.plist`;
|
|
2922
3339
|
async function uninstall$1() {
|
|
2923
3340
|
try {
|
|
2924
|
-
if (!fs.existsSync(PLIST_FILE)) {
|
|
3341
|
+
if (!fs$1.existsSync(PLIST_FILE)) {
|
|
2925
3342
|
api.logger.info("Daemon plist not found. Nothing to uninstall.");
|
|
2926
3343
|
return;
|
|
2927
3344
|
}
|
|
@@ -2931,7 +3348,7 @@ async function uninstall$1() {
|
|
|
2931
3348
|
} catch (error) {
|
|
2932
3349
|
api.logger.info("Failed to unload daemon (it might not be running)");
|
|
2933
3350
|
}
|
|
2934
|
-
fs.unlinkSync(PLIST_FILE);
|
|
3351
|
+
fs$1.unlinkSync(PLIST_FILE);
|
|
2935
3352
|
api.logger.info(`Removed daemon plist from ${PLIST_FILE}`);
|
|
2936
3353
|
api.logger.info("Daemon uninstalled successfully");
|
|
2937
3354
|
} catch (error) {
|
|
@@ -3071,8 +3488,8 @@ async function handleAuthLogout(args = []) {
|
|
|
3071
3488
|
console.log(chalk.gray("Stopped daemon"));
|
|
3072
3489
|
} catch {
|
|
3073
3490
|
}
|
|
3074
|
-
if (
|
|
3075
|
-
|
|
3491
|
+
if (fs.existsSync(happyDir)) {
|
|
3492
|
+
fs.rmSync(happyDir, { recursive: true, force: true });
|
|
3076
3493
|
}
|
|
3077
3494
|
console.log(chalk.green("\u2713 Successfully logged out"));
|
|
3078
3495
|
console.log(chalk.gray(' Run "hicloud auth login" to authenticate again'));
|
|
@@ -3107,9 +3524,11 @@ async function handleAuthStatus() {
|
|
|
3107
3524
|
console.log(chalk.gray(`
|
|
3108
3525
|
Data directory: ${api.configuration.happyCloudHomeDir}`));
|
|
3109
3526
|
try {
|
|
3110
|
-
const
|
|
3111
|
-
if (running) {
|
|
3527
|
+
const daemonStatus = await getDaemonRuntimeStatus();
|
|
3528
|
+
if (daemonStatus === "running") {
|
|
3112
3529
|
console.log(chalk.green("\u2713 Daemon running"));
|
|
3530
|
+
} else if (daemonStatus === "indeterminate") {
|
|
3531
|
+
console.log(chalk.yellow("\u26A0\uFE0F Daemon status indeterminate"));
|
|
3113
3532
|
} else {
|
|
3114
3533
|
console.log(chalk.gray("\u2717 Daemon not running"));
|
|
3115
3534
|
}
|
|
@@ -3745,10 +4164,10 @@ async function handleConnectStatus() {
|
|
|
3745
4164
|
}
|
|
3746
4165
|
function updateLocalGeminiCredentials(tokens) {
|
|
3747
4166
|
try {
|
|
3748
|
-
const geminiDir = path.join(os$1.homedir(), ".gemini");
|
|
3749
|
-
const credentialsPath = path.join(geminiDir, "oauth_creds.json");
|
|
3750
|
-
if (!fs.existsSync(geminiDir)) {
|
|
3751
|
-
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 });
|
|
3752
4171
|
}
|
|
3753
4172
|
const credentials = {
|
|
3754
4173
|
access_token: tokens.access_token,
|
|
@@ -3758,7 +4177,7 @@ function updateLocalGeminiCredentials(tokens) {
|
|
|
3758
4177
|
...tokens.id_token && { id_token: tokens.id_token },
|
|
3759
4178
|
...tokens.expires_in && { expires_in: tokens.expires_in }
|
|
3760
4179
|
};
|
|
3761
|
-
fs.writeFileSync(credentialsPath, JSON.stringify(credentials, null, 2), "utf-8");
|
|
4180
|
+
fs$1.writeFileSync(credentialsPath, JSON.stringify(credentials, null, 2), "utf-8");
|
|
3762
4181
|
console.log(chalk.gray(` Updated local credentials: ${credentialsPath}`));
|
|
3763
4182
|
} catch (error) {
|
|
3764
4183
|
console.log(chalk.yellow(` \u26A0\uFE0F Could not update local credentials: ${error}`));
|
|
@@ -3766,22 +4185,22 @@ function updateLocalGeminiCredentials(tokens) {
|
|
|
3766
4185
|
}
|
|
3767
4186
|
|
|
3768
4187
|
function getProjectPath(workingDirectory, claudeConfigDirOverride) {
|
|
3769
|
-
const projectId =
|
|
4188
|
+
const projectId = path.resolve(workingDirectory).replace(/[^a-zA-Z0-9-]/g, "-");
|
|
3770
4189
|
const claudeConfigDirRaw = process.env.CLAUDE_CONFIG_DIR ?? "";
|
|
3771
4190
|
const claudeConfigDirTrimmed = claudeConfigDirRaw.trim();
|
|
3772
|
-
const claudeConfigDir = claudeConfigDirTrimmed ? claudeConfigDirTrimmed :
|
|
3773
|
-
return
|
|
4191
|
+
const claudeConfigDir = claudeConfigDirTrimmed ? claudeConfigDirTrimmed : path.join(os.homedir(), ".claude");
|
|
4192
|
+
return path.join(claudeConfigDir, "projects", projectId);
|
|
3774
4193
|
}
|
|
3775
4194
|
|
|
3776
|
-
function claudeCheckSession(sessionId, path, transcriptPath) {
|
|
3777
|
-
const projectDir = getProjectPath(path);
|
|
3778
|
-
const sessionFile = transcriptPath ??
|
|
3779
|
-
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);
|
|
3780
4199
|
if (!sessionExists) {
|
|
3781
4200
|
api.logger.debug(`[claudeCheckSession] Path ${sessionFile} does not exist`);
|
|
3782
4201
|
return false;
|
|
3783
4202
|
}
|
|
3784
|
-
const sessionData =
|
|
4203
|
+
const sessionData = fs.readFileSync(sessionFile, "utf-8").split("\n");
|
|
3785
4204
|
const hasGoodMessage = !!sessionData.find((v, index) => {
|
|
3786
4205
|
if (!v.trim()) return false;
|
|
3787
4206
|
try {
|
|
@@ -3802,7 +4221,7 @@ function claudeFindLastSession(workingDirectory) {
|
|
|
3802
4221
|
try {
|
|
3803
4222
|
const projectDir = getProjectPath(workingDirectory);
|
|
3804
4223
|
const uuidPattern = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
3805
|
-
const files =
|
|
4224
|
+
const files = fs.readdirSync(projectDir).filter((f) => f.endsWith(".jsonl")).map((f) => {
|
|
3806
4225
|
const sessionId = f.replace(".jsonl", "");
|
|
3807
4226
|
if (!uuidPattern.test(sessionId)) {
|
|
3808
4227
|
return null;
|
|
@@ -3811,7 +4230,7 @@ function claudeFindLastSession(workingDirectory) {
|
|
|
3811
4230
|
return {
|
|
3812
4231
|
name: f,
|
|
3813
4232
|
sessionId,
|
|
3814
|
-
mtime:
|
|
4233
|
+
mtime: fs.statSync(path.join(projectDir, f)).mtime.getTime()
|
|
3815
4234
|
};
|
|
3816
4235
|
}
|
|
3817
4236
|
return null;
|
|
@@ -3831,10 +4250,10 @@ class ExitCodeError extends Error {
|
|
|
3831
4250
|
this.exitCode = exitCode;
|
|
3832
4251
|
}
|
|
3833
4252
|
}
|
|
3834
|
-
const claudeCliPath =
|
|
4253
|
+
const claudeCliPath = path.resolve(path.join(projectPath(), "scripts", "claude_local_launcher.cjs"));
|
|
3835
4254
|
async function claudeLocal(opts) {
|
|
3836
4255
|
const projectDir = getProjectPath(opts.path);
|
|
3837
|
-
|
|
4256
|
+
fs.mkdirSync(projectDir, { recursive: true });
|
|
3838
4257
|
const hasContinueFlag = opts.claudeArgs?.includes("--continue");
|
|
3839
4258
|
const hasResumeFlag = opts.claudeArgs?.includes("--resume");
|
|
3840
4259
|
const hasUserSessionControl = hasContinueFlag || hasResumeFlag;
|
|
@@ -3953,7 +4372,7 @@ async function claudeLocal(opts) {
|
|
|
3953
4372
|
args.push("--settings", opts.hookSettingsPath);
|
|
3954
4373
|
api.logger.debug(`[ClaudeLocal] Using hook settings: ${opts.hookSettingsPath}`);
|
|
3955
4374
|
}
|
|
3956
|
-
if (!claudeCliPath || !
|
|
4375
|
+
if (!claudeCliPath || !fs.existsSync(claudeCliPath)) {
|
|
3957
4376
|
throw new Error("Claude local launcher not found. Please ensure HAPPY_PROJECT_ROOT is set correctly for development.");
|
|
3958
4377
|
}
|
|
3959
4378
|
const env = {
|
|
@@ -6301,17 +6720,17 @@ function readGeminiLocalConfig() {
|
|
|
6301
6720
|
let googleCloudProject = null;
|
|
6302
6721
|
let googleCloudProjectEmail = null;
|
|
6303
6722
|
const possiblePaths = [
|
|
6304
|
-
path.join(os$1.homedir(), ".gemini", "oauth_creds.json"),
|
|
6723
|
+
path$1.join(os$1.homedir(), ".gemini", "oauth_creds.json"),
|
|
6305
6724
|
// Main OAuth credentials file
|
|
6306
|
-
path.join(os$1.homedir(), ".gemini", "config.json"),
|
|
6307
|
-
path.join(os$1.homedir(), ".config", "gemini", "config.json"),
|
|
6308
|
-
path.join(os$1.homedir(), ".gemini", "auth.json"),
|
|
6309
|
-
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")
|
|
6310
6729
|
];
|
|
6311
6730
|
for (const configPath of possiblePaths) {
|
|
6312
|
-
if (fs.existsSync(configPath)) {
|
|
6731
|
+
if (fs$1.existsSync(configPath)) {
|
|
6313
6732
|
try {
|
|
6314
|
-
const config = JSON.parse(fs.readFileSync(configPath, "utf-8"));
|
|
6733
|
+
const config = JSON.parse(fs$1.readFileSync(configPath, "utf-8"));
|
|
6315
6734
|
if (!token) {
|
|
6316
6735
|
const foundToken = config.access_token || config.token || config.apiKey || config.GEMINI_API_KEY;
|
|
6317
6736
|
if (foundToken && typeof foundToken === "string") {
|
|
@@ -6383,22 +6802,22 @@ function determineGeminiModel(explicitModel, localConfig) {
|
|
|
6383
6802
|
}
|
|
6384
6803
|
function saveGeminiModelToConfig(model) {
|
|
6385
6804
|
try {
|
|
6386
|
-
const configDir = path.join(os$1.homedir(), ".gemini");
|
|
6387
|
-
const configPath = path.join(configDir, "config.json");
|
|
6388
|
-
if (!fs.existsSync(configDir)) {
|
|
6389
|
-
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 });
|
|
6390
6809
|
}
|
|
6391
6810
|
let config = {};
|
|
6392
|
-
if (fs.existsSync(configPath)) {
|
|
6811
|
+
if (fs$1.existsSync(configPath)) {
|
|
6393
6812
|
try {
|
|
6394
|
-
config = JSON.parse(fs.readFileSync(configPath, "utf-8"));
|
|
6813
|
+
config = JSON.parse(fs$1.readFileSync(configPath, "utf-8"));
|
|
6395
6814
|
} catch (error) {
|
|
6396
6815
|
api.logger.debug(`[Gemini] Failed to read existing config, creating new one`);
|
|
6397
6816
|
config = {};
|
|
6398
6817
|
}
|
|
6399
6818
|
}
|
|
6400
6819
|
config.model = model;
|
|
6401
|
-
fs.writeFileSync(configPath, JSON.stringify(config, null, 2), "utf-8");
|
|
6820
|
+
fs$1.writeFileSync(configPath, JSON.stringify(config, null, 2), "utf-8");
|
|
6402
6821
|
api.logger.debug(`[Gemini] Saved model "${model}" to ${configPath}`);
|
|
6403
6822
|
} catch (error) {
|
|
6404
6823
|
api.logger.debug(`[Gemini] Failed to save model to config:`, error);
|
|
@@ -6406,15 +6825,15 @@ function saveGeminiModelToConfig(model) {
|
|
|
6406
6825
|
}
|
|
6407
6826
|
function saveGoogleCloudProjectToConfig(projectId, email) {
|
|
6408
6827
|
try {
|
|
6409
|
-
const configDir = path.join(os$1.homedir(), ".gemini");
|
|
6410
|
-
const configPath = path.join(configDir, "config.json");
|
|
6411
|
-
if (!fs.existsSync(configDir)) {
|
|
6412
|
-
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 });
|
|
6413
6832
|
}
|
|
6414
6833
|
let config = {};
|
|
6415
|
-
if (fs.existsSync(configPath)) {
|
|
6834
|
+
if (fs$1.existsSync(configPath)) {
|
|
6416
6835
|
try {
|
|
6417
|
-
config = JSON.parse(fs.readFileSync(configPath, "utf-8"));
|
|
6836
|
+
config = JSON.parse(fs$1.readFileSync(configPath, "utf-8"));
|
|
6418
6837
|
} catch {
|
|
6419
6838
|
config = {};
|
|
6420
6839
|
}
|
|
@@ -6423,7 +6842,7 @@ function saveGoogleCloudProjectToConfig(projectId, email) {
|
|
|
6423
6842
|
if (email) {
|
|
6424
6843
|
config.googleCloudProjectEmail = email;
|
|
6425
6844
|
}
|
|
6426
|
-
fs.writeFileSync(configPath, JSON.stringify(config, null, 2), "utf-8");
|
|
6845
|
+
fs$1.writeFileSync(configPath, JSON.stringify(config, null, 2), "utf-8");
|
|
6427
6846
|
api.logger.debug(`[Gemini] Saved Google Cloud Project "${projectId}"${email ? ` for ${email}` : ""} to ${configPath}`);
|
|
6428
6847
|
} catch (error) {
|
|
6429
6848
|
api.logger.debug(`[Gemini] Failed to save Google Cloud Project to config:`, error);
|
|
@@ -6531,11 +6950,11 @@ function readFirstEnv(...names) {
|
|
|
6531
6950
|
return "";
|
|
6532
6951
|
}
|
|
6533
6952
|
function normalizeCommandPath(command) {
|
|
6534
|
-
if (
|
|
6953
|
+
if (path.isAbsolute(command)) {
|
|
6535
6954
|
return command;
|
|
6536
6955
|
}
|
|
6537
|
-
const resolved =
|
|
6538
|
-
return
|
|
6956
|
+
const resolved = path.resolve(process.cwd(), command);
|
|
6957
|
+
return fs.existsSync(resolved) ? resolved : command;
|
|
6539
6958
|
}
|
|
6540
6959
|
function resolveCommandOnPath(command) {
|
|
6541
6960
|
const pathValue = typeof process.env.PATH === "string" ? process.env.PATH : "";
|
|
@@ -6543,13 +6962,13 @@ function resolveCommandOnPath(command) {
|
|
|
6543
6962
|
return null;
|
|
6544
6963
|
}
|
|
6545
6964
|
const extensions = process.platform === "win32" ? (process.env.PATHEXT || ".COM;.EXE;.BAT;.CMD").split(";").map((value) => value.trim().toLowerCase()).filter(Boolean) : [""];
|
|
6546
|
-
for (const dir of pathValue.split(
|
|
6965
|
+
for (const dir of pathValue.split(path.delimiter)) {
|
|
6547
6966
|
const trimmedDir = dir.trim();
|
|
6548
6967
|
if (!trimmedDir) {
|
|
6549
6968
|
continue;
|
|
6550
6969
|
}
|
|
6551
|
-
const directCandidate =
|
|
6552
|
-
if (
|
|
6970
|
+
const directCandidate = path.join(trimmedDir, command);
|
|
6971
|
+
if (fs.existsSync(directCandidate)) {
|
|
6553
6972
|
return directCandidate;
|
|
6554
6973
|
}
|
|
6555
6974
|
if (process.platform !== "win32") {
|
|
@@ -6560,8 +6979,8 @@ function resolveCommandOnPath(command) {
|
|
|
6560
6979
|
continue;
|
|
6561
6980
|
}
|
|
6562
6981
|
for (const extension of extensions) {
|
|
6563
|
-
const candidate =
|
|
6564
|
-
if (
|
|
6982
|
+
const candidate = path.join(trimmedDir, `${command}${extension.toLowerCase()}`);
|
|
6983
|
+
if (fs.existsSync(candidate)) {
|
|
6565
6984
|
return candidate;
|
|
6566
6985
|
}
|
|
6567
6986
|
}
|
|
@@ -6660,8 +7079,8 @@ function validateCodexAcpSpawn(options = {}) {
|
|
|
6660
7079
|
const normalizedCommand = spawn.command.trim();
|
|
6661
7080
|
const commandLower = normalizedCommand.toLowerCase();
|
|
6662
7081
|
const npxMode = readCodexAcpNpxMode();
|
|
6663
|
-
if (
|
|
6664
|
-
if (!
|
|
7082
|
+
if (path.isAbsolute(normalizedCommand)) {
|
|
7083
|
+
if (!fs.existsSync(normalizedCommand)) {
|
|
6665
7084
|
return {
|
|
6666
7085
|
ok: false,
|
|
6667
7086
|
errorMessage: `Codex ACP is enabled, but the resolved command does not exist: ${normalizedCommand}`
|
|
@@ -6690,6 +7109,189 @@ function validateCodexAcpSpawn(options = {}) {
|
|
|
6690
7109
|
return { ok: true, spawn };
|
|
6691
7110
|
}
|
|
6692
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
|
+
|
|
6693
7295
|
class CodexAcpTransport extends CodexTransport {
|
|
6694
7296
|
constructor(initTimeoutMs) {
|
|
6695
7297
|
super();
|
|
@@ -6719,10 +7321,10 @@ function createCodexBackend(options) {
|
|
|
6719
7321
|
cwd: options.cwd,
|
|
6720
7322
|
command: spawn.command,
|
|
6721
7323
|
args: spawn.args,
|
|
6722
|
-
env: {
|
|
7324
|
+
env: buildCodexAcpEnv({
|
|
6723
7325
|
...options.env,
|
|
6724
7326
|
NODE_ENV: "production"
|
|
6725
|
-
},
|
|
7327
|
+
}),
|
|
6726
7328
|
permissionHandler: options.permissionHandler,
|
|
6727
7329
|
selectionHandler: options.selectionHandler,
|
|
6728
7330
|
transportHandler: resolveCodexTransport(spawn.command)
|
|
@@ -6872,6 +7474,24 @@ function createDefaultRuntimeShell() {
|
|
|
6872
7474
|
return new RuntimeShell();
|
|
6873
7475
|
}
|
|
6874
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
|
+
|
|
6875
7495
|
function isRuntimeProvider(value) {
|
|
6876
7496
|
return value === "claude" || value === "codex" || value === "gemini" || value === "cursor";
|
|
6877
7497
|
}
|
|
@@ -6916,26 +7536,20 @@ async function ensureUnifiedDaemonStarted() {
|
|
|
6916
7536
|
env: process.env
|
|
6917
7537
|
});
|
|
6918
7538
|
daemonProcess.unref();
|
|
6919
|
-
|
|
6920
|
-
|
|
6921
|
-
return;
|
|
6922
|
-
}
|
|
6923
|
-
if (await isDaemonControlServerResponsive(500)) {
|
|
6924
|
-
return;
|
|
6925
|
-
}
|
|
6926
|
-
await new Promise((resolve) => setTimeout(resolve, 100));
|
|
7539
|
+
if (await waitForDaemonReady()) {
|
|
7540
|
+
return;
|
|
6927
7541
|
}
|
|
6928
7542
|
throw new Error("Failed to start Happy background service.");
|
|
6929
7543
|
}
|
|
6930
7544
|
async function executeUnifiedProvider(opts) {
|
|
6931
7545
|
const credentials = await ensureUnifiedRuntimePrerequisites(opts.credentials);
|
|
6932
7546
|
if (opts.provider === "claude") {
|
|
6933
|
-
const { runClaude } = await Promise.resolve().then(function () { return require('./runClaude-
|
|
7547
|
+
const { runClaude } = await Promise.resolve().then(function () { return require('./runClaude-C1W_Nw0C.cjs'); });
|
|
6934
7548
|
await runClaude(credentials, opts.claudeOptions ?? {});
|
|
6935
7549
|
return;
|
|
6936
7550
|
}
|
|
6937
7551
|
if (opts.provider === "codex") {
|
|
6938
|
-
const { runCodex } = await Promise.resolve().then(function () { return require('./runCodex
|
|
7552
|
+
const { runCodex } = await Promise.resolve().then(function () { return require('./runCodex-D2VCWVEK.cjs'); });
|
|
6939
7553
|
await runCodex({
|
|
6940
7554
|
credentials,
|
|
6941
7555
|
startedBy: opts.startedBy,
|
|
@@ -6945,7 +7559,7 @@ async function executeUnifiedProvider(opts) {
|
|
|
6945
7559
|
return;
|
|
6946
7560
|
}
|
|
6947
7561
|
if (opts.provider === "gemini") {
|
|
6948
|
-
const { runGemini } = await Promise.resolve().then(function () { return require('./runGemini-
|
|
7562
|
+
const { runGemini } = await Promise.resolve().then(function () { return require('./runGemini-CiGnjflq.cjs'); });
|
|
6949
7563
|
await runGemini({
|
|
6950
7564
|
credentials,
|
|
6951
7565
|
startedBy: opts.startedBy
|
|
@@ -6987,7 +7601,7 @@ function shouldRunMainClaudeFlow(opts) {
|
|
|
6987
7601
|
return;
|
|
6988
7602
|
} else if (subcommand === "runtime") {
|
|
6989
7603
|
if (args[1] === "providers") {
|
|
6990
|
-
const { renderRuntimeProviders } = await Promise.resolve().then(function () { return require('./command-
|
|
7604
|
+
const { renderRuntimeProviders } = await Promise.resolve().then(function () { return require('./command-Df7u5eAT.cjs'); });
|
|
6991
7605
|
console.log(renderRuntimeProviders());
|
|
6992
7606
|
return;
|
|
6993
7607
|
}
|
|
@@ -7165,8 +7779,8 @@ function shouldRunMainClaudeFlow(opts) {
|
|
|
7165
7779
|
const projectId = args[3];
|
|
7166
7780
|
try {
|
|
7167
7781
|
const { saveGoogleCloudProjectToConfig } = await Promise.resolve().then(function () { return config; });
|
|
7168
|
-
const { readCredentials: readCredentials2 } = await Promise.resolve().then(function () { return require('./persistence-
|
|
7169
|
-
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; });
|
|
7170
7784
|
let userEmail = void 0;
|
|
7171
7785
|
try {
|
|
7172
7786
|
const credentials = await readCredentials2();
|
|
@@ -7313,14 +7927,7 @@ function shouldRunMainClaudeFlow(opts) {
|
|
|
7313
7927
|
env: process.env
|
|
7314
7928
|
});
|
|
7315
7929
|
child.unref();
|
|
7316
|
-
|
|
7317
|
-
for (let i = 0; i < 100; i++) {
|
|
7318
|
-
if (await checkIfDaemonRunningAndCleanupStaleState() && await isDaemonControlServerResponsive(500)) {
|
|
7319
|
-
started = true;
|
|
7320
|
-
break;
|
|
7321
|
-
}
|
|
7322
|
-
await new Promise((resolve) => setTimeout(resolve, 100));
|
|
7323
|
-
}
|
|
7930
|
+
const started = await waitForDaemonReady();
|
|
7324
7931
|
if (started) {
|
|
7325
7932
|
console.log("Daemon started successfully");
|
|
7326
7933
|
} else {
|
|
@@ -7329,7 +7936,7 @@ function shouldRunMainClaudeFlow(opts) {
|
|
|
7329
7936
|
if (latest) {
|
|
7330
7937
|
console.error(`Latest daemon log: ${latest.path}`);
|
|
7331
7938
|
try {
|
|
7332
|
-
const logContent =
|
|
7939
|
+
const logContent = fs.readFileSync(latest.path, "utf-8");
|
|
7333
7940
|
if (logContent.includes("EADDRINUSE")) {
|
|
7334
7941
|
console.error("Daemon control port is already in use. Retry after stopping the stale daemon or run `hicloud doctor clean`.");
|
|
7335
7942
|
}
|
|
@@ -7602,8 +8209,8 @@ exports.getInitialGeminiModel = getInitialGeminiModel;
|
|
|
7602
8209
|
exports.getProjectPath = getProjectPath;
|
|
7603
8210
|
exports.initialMachineMetadata = initialMachineMetadata;
|
|
7604
8211
|
exports.isBun = isBun;
|
|
7605
|
-
exports.notifyDaemonSessionStarted = notifyDaemonSessionStarted;
|
|
7606
8212
|
exports.projectPath = projectPath;
|
|
8213
|
+
exports.publishSessionRegistration = publishSessionRegistration;
|
|
7607
8214
|
exports.readGeminiLocalConfig = readGeminiLocalConfig;
|
|
7608
8215
|
exports.saveGeminiModelToConfig = saveGeminiModelToConfig;
|
|
7609
8216
|
exports.startCaffeinate = startCaffeinate;
|