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,34 +1,34 @@
|
|
|
1
1
|
import{createRequire as _pkgrollCR}from"node:module";const require=_pkgrollCR(import.meta.url);import chalk from 'chalk';
|
|
2
|
-
import { l as logger, e as encodeBase64, c as configuration, h as buildAuthenticatedHeaders, S as SigningBootstrapRequiredError, j as SIGNING_BOOTSTRAP_REQUIRED_MESSAGE, k as encodeBase64Url, f as delay, m as buildClientHeaders, n as decodeBase64, H as HAPPY_CLOUD_DAEMON_PORT, p as packageJson, A as ApiClient, o as getLatestDaemonLog } from './api-
|
|
3
|
-
import { writeCredentialsLegacy, writeCredentialsDataKey, readCredentials, readSettings, updateSettings, readDaemonState, clearDaemonState, acquireDaemonLock, writeDaemonState, releaseDaemonLock, validateProfileForAgent, getProfileEnvironmentVariables, clearCredentials, clearMachineId } from './persistence-
|
|
2
|
+
import { l as logger, e as encodeBase64, c as configuration, h as buildAuthenticatedHeaders, S as SigningBootstrapRequiredError, j as SIGNING_BOOTSTRAP_REQUIRED_MESSAGE, k as encodeBase64Url, f as delay, m as buildClientHeaders, n as decodeBase64, H as HAPPY_CLOUD_DAEMON_PORT, p as packageJson, A as ApiClient, o as getLatestDaemonLog } from './api-BjxmW-0W.mjs';
|
|
3
|
+
import { writeCredentialsLegacy, writeCredentialsDataKey, readCredentials, readSettings, updateSettings, readDaemonState, clearDaemonState, acquireDaemonLock, writeDaemonState, releaseDaemonLock, validateProfileForAgent, getProfileEnvironmentVariables, clearCredentials, clearMachineId } from './persistence-BxP6Jw1f.mjs';
|
|
4
4
|
import { z } from 'zod';
|
|
5
|
-
import fs from 'fs/promises';
|
|
5
|
+
import fs, { writeFile as writeFile$1, rename, unlink as unlink$1 } from 'fs/promises';
|
|
6
6
|
import os, { homedir } from 'os';
|
|
7
7
|
import * as tmp from 'tmp';
|
|
8
8
|
import { randomUUID, randomBytes } from 'node:crypto';
|
|
9
9
|
import tweetnacl from 'tweetnacl';
|
|
10
10
|
import axios from 'axios';
|
|
11
11
|
import qrcode from 'qrcode-terminal';
|
|
12
|
-
import { writeFile, unlink } from 'node:fs/promises';
|
|
12
|
+
import { writeFile, unlink, readdir, readFile, mkdir } from 'node:fs/promises';
|
|
13
13
|
import { createRequire } from 'node:module';
|
|
14
14
|
import os$1, { tmpdir, homedir as homedir$1 } from 'node:os';
|
|
15
|
-
import { join, resolve as resolve$1, isAbsolute, delimiter } from 'node:path';
|
|
15
|
+
import path, { join, resolve as resolve$1, isAbsolute, delimiter, normalize, dirname as dirname$1 } from 'node:path';
|
|
16
16
|
import open from 'open';
|
|
17
17
|
import React, { useState } from 'react';
|
|
18
18
|
import { useInput, Box, Text, render } from 'ink';
|
|
19
19
|
import { spawn, execSync, exec } from 'child_process';
|
|
20
20
|
import { dirname, resolve, join as join$1 } from 'path';
|
|
21
21
|
import { fileURLToPath } from 'url';
|
|
22
|
-
import { readFileSync as readFileSync$1, existsSync as existsSync$1, writeFileSync, chmodSync, unlinkSync, mkdirSync } from 'fs';
|
|
22
|
+
import { readFileSync as readFileSync$1, existsSync as existsSync$1, writeFileSync, chmodSync, unlinkSync as unlinkSync$1, mkdirSync } from 'fs';
|
|
23
23
|
import { execFileSync, spawn as spawn$2 } from 'node:child_process';
|
|
24
24
|
import psList from 'ps-list';
|
|
25
25
|
import spawn$1 from 'cross-spawn';
|
|
26
|
-
import { existsSync, readFileSync, readdirSync, statSync, rmSync, mkdirSync as mkdirSync$1 } from 'node:fs';
|
|
26
|
+
import fs$1, { existsSync, readFileSync, readdirSync, statSync, unlinkSync, rmSync, mkdirSync as mkdirSync$1, realpathSync } from 'node:fs';
|
|
27
27
|
import fastify from 'fastify';
|
|
28
28
|
import { validatorCompiler, serializerCompiler } from 'fastify-type-provider-zod';
|
|
29
|
+
import { randomUUID as randomUUID$1, randomBytes as randomBytes$1, createHash } from 'crypto';
|
|
29
30
|
import { createInterface } from 'node:readline';
|
|
30
31
|
import { createServer } from 'http';
|
|
31
|
-
import { randomBytes as randomBytes$1, createHash } from 'crypto';
|
|
32
32
|
import { promisify } from 'util';
|
|
33
33
|
import { ndJsonStream, ClientSideConnection } from '@agentclientprotocol/sdk';
|
|
34
34
|
|
|
@@ -677,6 +677,16 @@ function projectPath() {
|
|
|
677
677
|
return path;
|
|
678
678
|
}
|
|
679
679
|
|
|
680
|
+
function classifyHappyProcessLookup(proc) {
|
|
681
|
+
const match = classifyHappyProcess(proc);
|
|
682
|
+
if (match) {
|
|
683
|
+
return match;
|
|
684
|
+
}
|
|
685
|
+
if (process.platform === "win32" && isWindowsCliHostProcess(proc.name) && proc.cmd.trim().length === 0) {
|
|
686
|
+
return "indeterminate";
|
|
687
|
+
}
|
|
688
|
+
return null;
|
|
689
|
+
}
|
|
680
690
|
function getDaemonPid() {
|
|
681
691
|
try {
|
|
682
692
|
if (!existsSync(configuration.daemonStateFile)) {
|
|
@@ -757,7 +767,7 @@ function findWindowsDaemonProcess(processes) {
|
|
|
757
767
|
return {
|
|
758
768
|
pid: daemonPid,
|
|
759
769
|
command: `${daemonProcess.name || "unknown"} (PID ${daemonPid})`,
|
|
760
|
-
type: "daemon"
|
|
770
|
+
type: "daemon-indeterminate"
|
|
761
771
|
};
|
|
762
772
|
}
|
|
763
773
|
function findWindowsHappyProcesses(processes) {
|
|
@@ -774,6 +784,40 @@ function findWindowsHappyProcesses(processes) {
|
|
|
774
784
|
}
|
|
775
785
|
return Array.from(matches.values());
|
|
776
786
|
}
|
|
787
|
+
function toProcessSnapshot(proc) {
|
|
788
|
+
return {
|
|
789
|
+
pid: proc.pid,
|
|
790
|
+
name: proc.name || "",
|
|
791
|
+
cmd: proc.cmd || ""
|
|
792
|
+
};
|
|
793
|
+
}
|
|
794
|
+
async function findHappyProcessByPid(pid) {
|
|
795
|
+
try {
|
|
796
|
+
if (process.platform === "win32") {
|
|
797
|
+
const windowsProcesses = getWindowsProcessSnapshots();
|
|
798
|
+
if (windowsProcesses.length > 0) {
|
|
799
|
+
const proc3 = windowsProcesses.find((candidate) => candidate.pid === pid);
|
|
800
|
+
if (!proc3) {
|
|
801
|
+
return null;
|
|
802
|
+
}
|
|
803
|
+
return classifyHappyProcessLookup(proc3);
|
|
804
|
+
}
|
|
805
|
+
const fallbackProcesses = (await psList()).map(toProcessSnapshot);
|
|
806
|
+
const proc2 = fallbackProcesses.find((candidate) => candidate.pid === pid);
|
|
807
|
+
if (!proc2) {
|
|
808
|
+
return null;
|
|
809
|
+
}
|
|
810
|
+
return classifyHappyProcessLookup(proc2);
|
|
811
|
+
}
|
|
812
|
+
const proc = (await psList()).map(toProcessSnapshot).find((candidate) => candidate.pid === pid);
|
|
813
|
+
if (!proc) {
|
|
814
|
+
return null;
|
|
815
|
+
}
|
|
816
|
+
return classifyHappyProcess(proc);
|
|
817
|
+
} catch (error) {
|
|
818
|
+
return "indeterminate";
|
|
819
|
+
}
|
|
820
|
+
}
|
|
777
821
|
async function findAllHappyProcesses() {
|
|
778
822
|
try {
|
|
779
823
|
if (process.platform === "win32") {
|
|
@@ -781,15 +825,8 @@ async function findAllHappyProcesses() {
|
|
|
781
825
|
if (windowsProcesses.length > 0) {
|
|
782
826
|
return findWindowsHappyProcesses(windowsProcesses);
|
|
783
827
|
}
|
|
784
|
-
const fallbackProcesses = await psList();
|
|
785
|
-
|
|
786
|
-
fallbackProcesses.map((proc) => ({
|
|
787
|
-
pid: proc.pid,
|
|
788
|
-
name: proc.name || "",
|
|
789
|
-
cmd: proc.cmd || ""
|
|
790
|
-
}))
|
|
791
|
-
);
|
|
792
|
-
return fallbackDaemon ? [fallbackDaemon] : [];
|
|
828
|
+
const fallbackProcesses = (await psList()).map(toProcessSnapshot);
|
|
829
|
+
return findWindowsHappyProcesses(fallbackProcesses);
|
|
793
830
|
}
|
|
794
831
|
const processes = await psList();
|
|
795
832
|
const allProcesses = [];
|
|
@@ -846,6 +883,46 @@ async function killRunawayHappyProcesses() {
|
|
|
846
883
|
return { killed, errors };
|
|
847
884
|
}
|
|
848
885
|
|
|
886
|
+
const CONFIRMED_DAEMON_PROCESS_TYPES = /* @__PURE__ */ new Set([
|
|
887
|
+
"daemon",
|
|
888
|
+
"dev-daemon"
|
|
889
|
+
]);
|
|
890
|
+
function currentProcessLooksLikeDaemon() {
|
|
891
|
+
return process.argv.join(" ").includes("daemon start-sync");
|
|
892
|
+
}
|
|
893
|
+
async function inspectDaemonStateProcess(state) {
|
|
894
|
+
if (!state) {
|
|
895
|
+
return "stale";
|
|
896
|
+
}
|
|
897
|
+
try {
|
|
898
|
+
process.kill(state.pid, 0);
|
|
899
|
+
} catch {
|
|
900
|
+
logger.debug("[DAEMON RUN] Daemon PID not running, cleaning up state");
|
|
901
|
+
await cleanupDaemonState();
|
|
902
|
+
return "stale";
|
|
903
|
+
}
|
|
904
|
+
const processInfo = await findHappyProcessByPid(state.pid);
|
|
905
|
+
if (processInfo === "indeterminate") {
|
|
906
|
+
logger.debug(`[DAEMON RUN] Daemon PID ${state.pid} is alive but process identity is indeterminate`);
|
|
907
|
+
return "indeterminate";
|
|
908
|
+
}
|
|
909
|
+
if (!processInfo) {
|
|
910
|
+
logger.debug(`[DAEMON RUN] PID ${state.pid} is alive but is not a Happy daemon process, cleaning up state`);
|
|
911
|
+
await cleanupDaemonState();
|
|
912
|
+
return "stale";
|
|
913
|
+
}
|
|
914
|
+
if (processInfo.type === "current" && currentProcessLooksLikeDaemon()) {
|
|
915
|
+
return "confirmed";
|
|
916
|
+
}
|
|
917
|
+
if (!CONFIRMED_DAEMON_PROCESS_TYPES.has(processInfo.type)) {
|
|
918
|
+
logger.debug(
|
|
919
|
+
`[DAEMON RUN] PID ${state.pid} resolved to Happy process type ${processInfo.type}, not a daemon; cleaning up state`
|
|
920
|
+
);
|
|
921
|
+
await cleanupDaemonState();
|
|
922
|
+
return "stale";
|
|
923
|
+
}
|
|
924
|
+
return "confirmed";
|
|
925
|
+
}
|
|
849
926
|
async function daemonPost(path, body) {
|
|
850
927
|
const state = await readDaemonState();
|
|
851
928
|
if (!state?.httpPort) {
|
|
@@ -855,9 +932,8 @@ async function daemonPost(path, body) {
|
|
|
855
932
|
error: errorMessage
|
|
856
933
|
};
|
|
857
934
|
}
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
} catch (error) {
|
|
935
|
+
const processStatus = await inspectDaemonStateProcess(state);
|
|
936
|
+
if (processStatus === "stale") {
|
|
861
937
|
const errorMessage = "Daemon is not running, file is stale";
|
|
862
938
|
logger.debug(`[CONTROL CLIENT] ${errorMessage}`);
|
|
863
939
|
return {
|
|
@@ -956,18 +1032,22 @@ async function stopDaemonHttp() {
|
|
|
956
1032
|
await daemonPost("/stop");
|
|
957
1033
|
}
|
|
958
1034
|
async function checkIfDaemonRunningAndCleanupStaleState() {
|
|
1035
|
+
const runtimeStatus = await getDaemonRuntimeStatus();
|
|
1036
|
+
return runtimeStatus !== "not-running";
|
|
1037
|
+
}
|
|
1038
|
+
async function getDaemonRuntimeStatus() {
|
|
959
1039
|
const state = await readDaemonState();
|
|
960
1040
|
if (!state) {
|
|
961
|
-
return
|
|
1041
|
+
return "not-running";
|
|
962
1042
|
}
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
return
|
|
966
|
-
} catch {
|
|
967
|
-
logger.debug("[DAEMON RUN] Daemon PID not running, cleaning up state");
|
|
968
|
-
await cleanupDaemonState();
|
|
969
|
-
return false;
|
|
1043
|
+
const processStatus = await inspectDaemonStateProcess(state);
|
|
1044
|
+
if (processStatus === "confirmed") {
|
|
1045
|
+
return "running";
|
|
970
1046
|
}
|
|
1047
|
+
if (processStatus === "indeterminate") {
|
|
1048
|
+
return "indeterminate";
|
|
1049
|
+
}
|
|
1050
|
+
return "not-running";
|
|
971
1051
|
}
|
|
972
1052
|
async function isDaemonControlServerResponsive(timeoutMs = 1e3) {
|
|
973
1053
|
const state = await readDaemonState();
|
|
@@ -975,11 +1055,9 @@ async function isDaemonControlServerResponsive(timeoutMs = 1e3) {
|
|
|
975
1055
|
logger.debug("[DAEMON CONTROL] No daemon state or control port found for readiness check");
|
|
976
1056
|
return false;
|
|
977
1057
|
}
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
logger.debug("[DAEMON CONTROL] Daemon PID not running during readiness check, cleaning up state");
|
|
982
|
-
await cleanupDaemonState();
|
|
1058
|
+
const processStatus = await inspectDaemonStateProcess(state);
|
|
1059
|
+
if (processStatus === "stale") {
|
|
1060
|
+
logger.debug("[DAEMON CONTROL] Daemon state became stale during readiness check");
|
|
983
1061
|
return false;
|
|
984
1062
|
}
|
|
985
1063
|
try {
|
|
@@ -995,7 +1073,7 @@ async function isDaemonControlServerResponsive(timeoutMs = 1e3) {
|
|
|
995
1073
|
return false;
|
|
996
1074
|
}
|
|
997
1075
|
}
|
|
998
|
-
async function isDaemonRunningCurrentlyInstalledHappyVersion() {
|
|
1076
|
+
async function isDaemonRunningCurrentlyInstalledHappyVersion(readinessProbeTimeoutMs = 1e3) {
|
|
999
1077
|
logger.debug("[DAEMON CONTROL] Checking if daemon is running same version");
|
|
1000
1078
|
const runningDaemon = await checkIfDaemonRunningAndCleanupStaleState();
|
|
1001
1079
|
if (!runningDaemon) {
|
|
@@ -1015,7 +1093,7 @@ async function isDaemonRunningCurrentlyInstalledHappyVersion() {
|
|
|
1015
1093
|
if (currentCliVersion !== state.startedWithCliVersion) {
|
|
1016
1094
|
return false;
|
|
1017
1095
|
}
|
|
1018
|
-
return await isDaemonControlServerResponsive();
|
|
1096
|
+
return await isDaemonControlServerResponsive(readinessProbeTimeoutMs);
|
|
1019
1097
|
} catch (error) {
|
|
1020
1098
|
logger.debug("[DAEMON CONTROL] Error checking daemon version", error);
|
|
1021
1099
|
return false;
|
|
@@ -1048,6 +1126,17 @@ async function stopDaemon() {
|
|
|
1048
1126
|
return;
|
|
1049
1127
|
}
|
|
1050
1128
|
logger.debug(`Stopping daemon with PID ${state.pid}`);
|
|
1129
|
+
const processStatus = await inspectDaemonStateProcess(state);
|
|
1130
|
+
if (processStatus === "stale") {
|
|
1131
|
+
logger.debug("Daemon state was stale while stopping, trying known control port/orphan cleanup");
|
|
1132
|
+
const stoppedByKnownPort = await stopDaemonOnKnownPort();
|
|
1133
|
+
if (stoppedByKnownPort) {
|
|
1134
|
+
logger.debug(`Requested daemon stop via known control port ${HAPPY_CLOUD_DAEMON_PORT}`);
|
|
1135
|
+
return;
|
|
1136
|
+
}
|
|
1137
|
+
await killOrphanDaemonProcesses();
|
|
1138
|
+
return;
|
|
1139
|
+
}
|
|
1051
1140
|
try {
|
|
1052
1141
|
await stopDaemonHttp();
|
|
1053
1142
|
await waitForProcessDeath(state.pid, 2e3);
|
|
@@ -1057,6 +1146,10 @@ async function stopDaemon() {
|
|
|
1057
1146
|
} catch (error) {
|
|
1058
1147
|
logger.debug("HTTP stop failed, will force kill", error);
|
|
1059
1148
|
}
|
|
1149
|
+
if (processStatus === "indeterminate") {
|
|
1150
|
+
logger.debug(`Skipping force kill for PID ${state.pid} because daemon identity is indeterminate`);
|
|
1151
|
+
return;
|
|
1152
|
+
}
|
|
1060
1153
|
try {
|
|
1061
1154
|
process.kill(state.pid, "SIGKILL");
|
|
1062
1155
|
await waitForProcessDeath(state.pid, 2e3).catch(() => {
|
|
@@ -1108,6 +1201,21 @@ function getEnvironmentInfo() {
|
|
|
1108
1201
|
terminal: process.env.TERM
|
|
1109
1202
|
};
|
|
1110
1203
|
}
|
|
1204
|
+
function resolveDaemonSpawnDiagnostics(projectRoot, fileExists = existsSync) {
|
|
1205
|
+
const wrapperCandidates = [
|
|
1206
|
+
join(projectRoot, "bin", "happy-cloud.mjs"),
|
|
1207
|
+
join(projectRoot, "bin", "happy.mjs")
|
|
1208
|
+
];
|
|
1209
|
+
const cliEntrypoint = join(projectRoot, "dist", "index.mjs");
|
|
1210
|
+
const wrapperPath = wrapperCandidates.find((candidate) => fileExists(candidate)) ?? wrapperCandidates[0];
|
|
1211
|
+
return {
|
|
1212
|
+
projectRoot,
|
|
1213
|
+
wrapperPath,
|
|
1214
|
+
cliEntrypoint,
|
|
1215
|
+
wrapperExists: fileExists(wrapperPath),
|
|
1216
|
+
cliEntrypointExists: fileExists(cliEntrypoint)
|
|
1217
|
+
};
|
|
1218
|
+
}
|
|
1111
1219
|
function getLogFiles(logDir) {
|
|
1112
1220
|
if (!existsSync(logDir)) {
|
|
1113
1221
|
return [];
|
|
@@ -1134,14 +1242,12 @@ async function runDoctorCommand(filter) {
|
|
|
1134
1242
|
console.log(`Node.js Version: ${chalk.green(process.version)}`);
|
|
1135
1243
|
console.log("");
|
|
1136
1244
|
console.log(chalk.bold("\u{1F527} Daemon Spawn Diagnostics"));
|
|
1137
|
-
const
|
|
1138
|
-
|
|
1139
|
-
|
|
1140
|
-
console.log(`
|
|
1141
|
-
console.log(`Wrapper
|
|
1142
|
-
console.log(`CLI
|
|
1143
|
-
console.log(`Wrapper Exists: ${existsSync(wrapperPath) ? chalk.green("\u2713 Yes") : chalk.red("\u274C No")}`);
|
|
1144
|
-
console.log(`CLI Exists: ${existsSync(cliEntrypoint) ? chalk.green("\u2713 Yes") : chalk.red("\u274C No")}`);
|
|
1245
|
+
const diagnostics = resolveDaemonSpawnDiagnostics(projectPath());
|
|
1246
|
+
console.log(`Project Root: ${chalk.blue(diagnostics.projectRoot)}`);
|
|
1247
|
+
console.log(`Wrapper Script: ${chalk.blue(diagnostics.wrapperPath)}`);
|
|
1248
|
+
console.log(`CLI Entrypoint: ${chalk.blue(diagnostics.cliEntrypoint)}`);
|
|
1249
|
+
console.log(`Wrapper Exists: ${diagnostics.wrapperExists ? chalk.green("\u2713 Yes") : chalk.red("\u274C No")}`);
|
|
1250
|
+
console.log(`CLI Exists: ${diagnostics.cliEntrypointExists ? chalk.green("\u2713 Yes") : chalk.red("\u274C No")}`);
|
|
1145
1251
|
console.log("");
|
|
1146
1252
|
console.log(chalk.bold("\u2699\uFE0F Configuration"));
|
|
1147
1253
|
console.log(`Happy Home: ${chalk.blue(configuration.happyCloudHomeDir)}`);
|
|
@@ -1177,9 +1283,9 @@ async function runDoctorCommand(filter) {
|
|
|
1177
1283
|
}
|
|
1178
1284
|
console.log(chalk.bold("\n\u{1F916} Daemon Status"));
|
|
1179
1285
|
try {
|
|
1180
|
-
const
|
|
1286
|
+
const daemonStatus = await getDaemonRuntimeStatus();
|
|
1181
1287
|
const state = await readDaemonState();
|
|
1182
|
-
if (
|
|
1288
|
+
if (daemonStatus === "running" && state) {
|
|
1183
1289
|
console.log(chalk.green("\u2713 Daemon is running"));
|
|
1184
1290
|
console.log(` PID: ${state.pid}`);
|
|
1185
1291
|
console.log(` Started: ${new Date(state.startTime).toLocaleString()}`);
|
|
@@ -1187,7 +1293,14 @@ async function runDoctorCommand(filter) {
|
|
|
1187
1293
|
if (state.httpPort) {
|
|
1188
1294
|
console.log(` HTTP Port: ${state.httpPort}`);
|
|
1189
1295
|
}
|
|
1190
|
-
} else if (
|
|
1296
|
+
} else if (daemonStatus === "indeterminate" && state) {
|
|
1297
|
+
console.log(chalk.yellow("\u26A0\uFE0F Daemon state exists, but process identity could not be confirmed"));
|
|
1298
|
+
console.log(` PID: ${state.pid}`);
|
|
1299
|
+
console.log(` CLI Version: ${state.startedWithCliVersion}`);
|
|
1300
|
+
if (state.httpPort) {
|
|
1301
|
+
console.log(` HTTP Port: ${state.httpPort}`);
|
|
1302
|
+
}
|
|
1303
|
+
} else if (state) {
|
|
1191
1304
|
console.log(chalk.yellow("\u26A0\uFE0F Daemon state exists but process not running (stale)"));
|
|
1192
1305
|
} else {
|
|
1193
1306
|
console.log(chalk.red("\u274C Daemon is not running"));
|
|
@@ -1209,6 +1322,7 @@ async function runDoctorCommand(filter) {
|
|
|
1209
1322
|
const typeLabels = {
|
|
1210
1323
|
"current": "\u{1F4CD} Current Process",
|
|
1211
1324
|
"daemon": "\u{1F916} Daemon",
|
|
1325
|
+
"daemon-indeterminate": "\u26A0\uFE0F Possible Daemon (identity unconfirmed)",
|
|
1212
1326
|
"daemon-version-check": "\u{1F50D} Daemon Version Check (stuck)",
|
|
1213
1327
|
"daemon-spawned-session": "\u{1F517} Daemon-Spawned Sessions",
|
|
1214
1328
|
"user-session": "\u{1F464} User Sessions",
|
|
@@ -1223,7 +1337,7 @@ async function runDoctorCommand(filter) {
|
|
|
1223
1337
|
console.log(chalk.blue(`
|
|
1224
1338
|
${typeLabels[type] || type}:`));
|
|
1225
1339
|
processes.forEach(({ pid, command }) => {
|
|
1226
|
-
const color = type === "current" ? chalk.green : type.startsWith("dev") ? chalk.cyan : type.includes("daemon") ? chalk.blue : chalk.gray;
|
|
1340
|
+
const color = type === "current" ? chalk.green : type === "daemon-indeterminate" ? chalk.yellow : type.startsWith("dev") ? chalk.cyan : type.includes("daemon") ? chalk.blue : chalk.gray;
|
|
1227
1341
|
console.log(` ${color(`PID ${pid}`)}: ${chalk.gray(command)}`);
|
|
1228
1342
|
});
|
|
1229
1343
|
});
|
|
@@ -2253,6 +2367,283 @@ function buildDaemonChildEnv(baseEnv, extraEnv) {
|
|
|
2253
2367
|
return childEnv;
|
|
2254
2368
|
}
|
|
2255
2369
|
|
|
2370
|
+
const DIFFERENT_DAEMON_RUNNING_MESSAGE = "A different daemon was started without killing us. We should kill ourselves.";
|
|
2371
|
+
function readProjectCliVersionFromDisk() {
|
|
2372
|
+
const packageJsonPath = join(projectPath(), "package.json");
|
|
2373
|
+
try {
|
|
2374
|
+
const parsedPackageJson = JSON.parse(readFileSync(packageJsonPath, "utf-8"));
|
|
2375
|
+
if (typeof parsedPackageJson.version !== "string" || parsedPackageJson.version.trim().length === 0) {
|
|
2376
|
+
logger.warn(
|
|
2377
|
+
`[DAEMON RUN] Skipping daemon version check because ${packageJsonPath} does not contain a valid string version`
|
|
2378
|
+
);
|
|
2379
|
+
return null;
|
|
2380
|
+
}
|
|
2381
|
+
return parsedPackageJson.version;
|
|
2382
|
+
} catch (error) {
|
|
2383
|
+
logger.warn(
|
|
2384
|
+
`[DAEMON RUN] Skipping daemon version check because ${packageJsonPath} could not be read or parsed`,
|
|
2385
|
+
error instanceof Error ? error.message : String(error)
|
|
2386
|
+
);
|
|
2387
|
+
return null;
|
|
2388
|
+
}
|
|
2389
|
+
}
|
|
2390
|
+
async function runDaemonHealthCheck({
|
|
2391
|
+
trackedSessionPids,
|
|
2392
|
+
removeTrackedSession,
|
|
2393
|
+
currentCliVersion,
|
|
2394
|
+
readProjectCliVersion = readProjectCliVersionFromDisk,
|
|
2395
|
+
onDaemonOutdated,
|
|
2396
|
+
readDaemonState,
|
|
2397
|
+
daemonPid,
|
|
2398
|
+
requestShutdown,
|
|
2399
|
+
writeHeartbeat
|
|
2400
|
+
}) {
|
|
2401
|
+
for (const pid of trackedSessionPids) {
|
|
2402
|
+
try {
|
|
2403
|
+
process.kill(pid, 0);
|
|
2404
|
+
} catch {
|
|
2405
|
+
logger.debug(`[DAEMON RUN] Removing stale session with PID ${pid} (process no longer exists)`);
|
|
2406
|
+
removeTrackedSession(pid);
|
|
2407
|
+
}
|
|
2408
|
+
}
|
|
2409
|
+
const projectVersion = readProjectCliVersion();
|
|
2410
|
+
if (projectVersion && projectVersion !== currentCliVersion) {
|
|
2411
|
+
await onDaemonOutdated();
|
|
2412
|
+
return;
|
|
2413
|
+
}
|
|
2414
|
+
const daemonState = await readDaemonState();
|
|
2415
|
+
if (daemonState && daemonState.pid !== daemonPid) {
|
|
2416
|
+
logger.debug(`[DAEMON RUN] ${DIFFERENT_DAEMON_RUNNING_MESSAGE}`);
|
|
2417
|
+
requestShutdown("exception", DIFFERENT_DAEMON_RUNNING_MESSAGE);
|
|
2418
|
+
return;
|
|
2419
|
+
}
|
|
2420
|
+
await writeHeartbeat();
|
|
2421
|
+
}
|
|
2422
|
+
|
|
2423
|
+
async function atomicFileWrite(filePath, content) {
|
|
2424
|
+
const tmpFile = `${filePath}.${randomUUID$1()}.tmp`;
|
|
2425
|
+
try {
|
|
2426
|
+
await writeFile$1(tmpFile, content);
|
|
2427
|
+
await rename(tmpFile, filePath);
|
|
2428
|
+
} catch (error) {
|
|
2429
|
+
try {
|
|
2430
|
+
await unlink$1(tmpFile);
|
|
2431
|
+
} catch {
|
|
2432
|
+
}
|
|
2433
|
+
throw error;
|
|
2434
|
+
}
|
|
2435
|
+
}
|
|
2436
|
+
|
|
2437
|
+
const SESSION_REGISTRY_VERSION = 1;
|
|
2438
|
+
const SESSION_REGISTRY_DIRNAME = "session-registry";
|
|
2439
|
+
const NON_SESSION_PROCESS_TYPES = /* @__PURE__ */ new Set([
|
|
2440
|
+
"current",
|
|
2441
|
+
"daemon",
|
|
2442
|
+
"daemon-indeterminate",
|
|
2443
|
+
"dev-daemon",
|
|
2444
|
+
"daemon-launcher",
|
|
2445
|
+
"dev-daemon-launcher",
|
|
2446
|
+
"daemon-version-check",
|
|
2447
|
+
"dev-daemon-version-check",
|
|
2448
|
+
"doctor",
|
|
2449
|
+
"dev-doctor"
|
|
2450
|
+
]);
|
|
2451
|
+
const registeredCleanupPids = /* @__PURE__ */ new Set();
|
|
2452
|
+
function getSessionRegistryDir() {
|
|
2453
|
+
return join(configuration.happyCloudHomeDir, SESSION_REGISTRY_DIRNAME);
|
|
2454
|
+
}
|
|
2455
|
+
function getSessionRegistryEntryPath(pid) {
|
|
2456
|
+
return join(getSessionRegistryDir(), `${pid}.json`);
|
|
2457
|
+
}
|
|
2458
|
+
function normalizeMetadataForPid(metadata, pid) {
|
|
2459
|
+
if (metadata.hostPid === pid) {
|
|
2460
|
+
return metadata;
|
|
2461
|
+
}
|
|
2462
|
+
return {
|
|
2463
|
+
...metadata,
|
|
2464
|
+
hostPid: pid
|
|
2465
|
+
};
|
|
2466
|
+
}
|
|
2467
|
+
function createTrackedSessionFromRegistryEntry(entry) {
|
|
2468
|
+
const metadata = normalizeMetadataForPid(entry.metadata, entry.pid);
|
|
2469
|
+
return {
|
|
2470
|
+
startedBy: metadata.startedBy === "daemon" ? "daemon" : "happy directly - likely by user from terminal",
|
|
2471
|
+
happySessionId: entry.sessionId,
|
|
2472
|
+
happySessionMetadataFromLocalWebhook: metadata,
|
|
2473
|
+
pid: entry.pid
|
|
2474
|
+
};
|
|
2475
|
+
}
|
|
2476
|
+
async function ensureSessionRegistryDir() {
|
|
2477
|
+
await mkdir(getSessionRegistryDir(), { recursive: true });
|
|
2478
|
+
}
|
|
2479
|
+
async function removeRegistryEntryPath(path) {
|
|
2480
|
+
try {
|
|
2481
|
+
await unlink(path);
|
|
2482
|
+
return true;
|
|
2483
|
+
} catch (error) {
|
|
2484
|
+
if (error?.code === "ENOENT") {
|
|
2485
|
+
return false;
|
|
2486
|
+
}
|
|
2487
|
+
logger.debug(`[SESSION REGISTRY] Failed to remove ${path}`, error);
|
|
2488
|
+
return false;
|
|
2489
|
+
}
|
|
2490
|
+
}
|
|
2491
|
+
function removeRegistryEntryPathSync(path) {
|
|
2492
|
+
try {
|
|
2493
|
+
unlinkSync(path);
|
|
2494
|
+
} catch (error) {
|
|
2495
|
+
if (error?.code !== "ENOENT") {
|
|
2496
|
+
logger.debug(`[SESSION REGISTRY] Failed to remove ${path} during process cleanup`, error);
|
|
2497
|
+
}
|
|
2498
|
+
}
|
|
2499
|
+
}
|
|
2500
|
+
function installRegistryCleanup(pid) {
|
|
2501
|
+
if (registeredCleanupPids.has(pid)) {
|
|
2502
|
+
return;
|
|
2503
|
+
}
|
|
2504
|
+
registeredCleanupPids.add(pid);
|
|
2505
|
+
let cleaned = false;
|
|
2506
|
+
const cleanup = () => {
|
|
2507
|
+
if (cleaned) {
|
|
2508
|
+
return;
|
|
2509
|
+
}
|
|
2510
|
+
cleaned = true;
|
|
2511
|
+
removeRegistryEntryPathSync(getSessionRegistryEntryPath(pid));
|
|
2512
|
+
};
|
|
2513
|
+
process.once("exit", cleanup);
|
|
2514
|
+
}
|
|
2515
|
+
async function readRegistryEntry(path) {
|
|
2516
|
+
try {
|
|
2517
|
+
const raw = JSON.parse(await readFile(path, "utf8"));
|
|
2518
|
+
if (raw.version !== SESSION_REGISTRY_VERSION || typeof raw.pid !== "number" || typeof raw.sessionId !== "string" || !raw.sessionId || !raw.metadata || typeof raw.metadata !== "object") {
|
|
2519
|
+
logger.debug(`[SESSION REGISTRY] Invalid entry schema at ${path}, pruning`);
|
|
2520
|
+
await removeRegistryEntryPath(path);
|
|
2521
|
+
return null;
|
|
2522
|
+
}
|
|
2523
|
+
return {
|
|
2524
|
+
version: SESSION_REGISTRY_VERSION,
|
|
2525
|
+
pid: raw.pid,
|
|
2526
|
+
sessionId: raw.sessionId,
|
|
2527
|
+
metadata: normalizeMetadataForPid(raw.metadata, raw.pid),
|
|
2528
|
+
updatedAt: typeof raw.updatedAt === "number" ? raw.updatedAt : Date.now()
|
|
2529
|
+
};
|
|
2530
|
+
} catch (error) {
|
|
2531
|
+
logger.debug(`[SESSION REGISTRY] Failed to read ${path}, pruning`, error);
|
|
2532
|
+
await removeRegistryEntryPath(path);
|
|
2533
|
+
return null;
|
|
2534
|
+
}
|
|
2535
|
+
}
|
|
2536
|
+
async function resolveLiveHappySessionPids(listLiveHappyProcesses) {
|
|
2537
|
+
try {
|
|
2538
|
+
const liveHappyProcesses = await listLiveHappyProcesses();
|
|
2539
|
+
if (liveHappyProcesses.length === 0) {
|
|
2540
|
+
return null;
|
|
2541
|
+
}
|
|
2542
|
+
const liveSessionProcesses = liveHappyProcesses.filter((proc) => !NON_SESSION_PROCESS_TYPES.has(proc.type));
|
|
2543
|
+
if (liveSessionProcesses.length === 0) {
|
|
2544
|
+
logger.debug(
|
|
2545
|
+
"[SESSION REGISTRY] Process discovery did not report any session processes, falling back to PID checks"
|
|
2546
|
+
);
|
|
2547
|
+
return null;
|
|
2548
|
+
}
|
|
2549
|
+
return new Set(
|
|
2550
|
+
liveSessionProcesses.map((proc) => proc.pid)
|
|
2551
|
+
);
|
|
2552
|
+
} catch (error) {
|
|
2553
|
+
logger.debug("[SESSION REGISTRY] Failed to list live Happy processes, falling back to PID checks", error);
|
|
2554
|
+
return null;
|
|
2555
|
+
}
|
|
2556
|
+
}
|
|
2557
|
+
async function persistLocalSessionRegistration(sessionId, metadata) {
|
|
2558
|
+
const pid = metadata.hostPid ?? process.pid;
|
|
2559
|
+
const normalizedMetadata = normalizeMetadataForPid(metadata, pid);
|
|
2560
|
+
await ensureSessionRegistryDir();
|
|
2561
|
+
await atomicFileWrite(
|
|
2562
|
+
getSessionRegistryEntryPath(pid),
|
|
2563
|
+
JSON.stringify({
|
|
2564
|
+
version: SESSION_REGISTRY_VERSION,
|
|
2565
|
+
pid,
|
|
2566
|
+
sessionId,
|
|
2567
|
+
metadata: normalizedMetadata,
|
|
2568
|
+
updatedAt: Date.now()
|
|
2569
|
+
}, null, 2)
|
|
2570
|
+
);
|
|
2571
|
+
}
|
|
2572
|
+
async function publishSessionRegistration(sessionId, metadata) {
|
|
2573
|
+
const pid = metadata.hostPid ?? process.pid;
|
|
2574
|
+
try {
|
|
2575
|
+
await persistLocalSessionRegistration(sessionId, metadata);
|
|
2576
|
+
installRegistryCleanup(pid);
|
|
2577
|
+
} catch (error) {
|
|
2578
|
+
logger.debug(`[SESSION REGISTRY] Failed to persist local registration for session ${sessionId}`, error);
|
|
2579
|
+
}
|
|
2580
|
+
try {
|
|
2581
|
+
const result = await notifyDaemonSessionStarted(sessionId, normalizeMetadataForPid(metadata, pid));
|
|
2582
|
+
if (result?.error) {
|
|
2583
|
+
logger.debug(`[SESSION REGISTRY] Failed to report session ${sessionId} to daemon`, result.error);
|
|
2584
|
+
}
|
|
2585
|
+
} catch (error) {
|
|
2586
|
+
logger.debug(`[SESSION REGISTRY] Failed to report session ${sessionId} to daemon`, error);
|
|
2587
|
+
}
|
|
2588
|
+
}
|
|
2589
|
+
async function recoverTrackedSessionsFromLocalRegistry({
|
|
2590
|
+
trackedSessionPids,
|
|
2591
|
+
trackSession,
|
|
2592
|
+
listLiveHappyProcesses = findAllHappyProcesses,
|
|
2593
|
+
lookupHappyProcessByPid = findHappyProcessByPid
|
|
2594
|
+
}) {
|
|
2595
|
+
const registryDir = getSessionRegistryDir();
|
|
2596
|
+
if (!existsSync(registryDir)) {
|
|
2597
|
+
return { recoveredCount: 0, removedStaleCount: 0 };
|
|
2598
|
+
}
|
|
2599
|
+
const alreadyTracked = new Set(trackedSessionPids);
|
|
2600
|
+
const liveHappySessionPids = await resolveLiveHappySessionPids(listLiveHappyProcesses);
|
|
2601
|
+
let recoveredCount = 0;
|
|
2602
|
+
let removedStaleCount = 0;
|
|
2603
|
+
for (const entryFile of await readdir(registryDir)) {
|
|
2604
|
+
if (!entryFile.endsWith(".json")) {
|
|
2605
|
+
continue;
|
|
2606
|
+
}
|
|
2607
|
+
const entryPath = join(registryDir, entryFile);
|
|
2608
|
+
const entry = await readRegistryEntry(entryPath);
|
|
2609
|
+
if (!entry) {
|
|
2610
|
+
continue;
|
|
2611
|
+
}
|
|
2612
|
+
let lookupResult;
|
|
2613
|
+
try {
|
|
2614
|
+
lookupResult = liveHappySessionPids?.has(entry.pid) ? { pid: entry.pid, type: "known-session" } : await lookupHappyProcessByPid(entry.pid);
|
|
2615
|
+
} catch (error) {
|
|
2616
|
+
logger.debug(
|
|
2617
|
+
`[SESSION REGISTRY] Failed to inspect PID ${entry.pid}, keeping registry entry until next recovery tick`,
|
|
2618
|
+
error
|
|
2619
|
+
);
|
|
2620
|
+
lookupResult = "indeterminate";
|
|
2621
|
+
}
|
|
2622
|
+
const sessionPidIsAlive = lookupResult !== null && lookupResult !== "indeterminate" && !NON_SESSION_PROCESS_TYPES.has(lookupResult.type);
|
|
2623
|
+
const shouldKeepEntryWithoutRecovery = lookupResult === "indeterminate";
|
|
2624
|
+
if (shouldKeepEntryWithoutRecovery) {
|
|
2625
|
+
logger.debug(
|
|
2626
|
+
`[SESSION REGISTRY] Keeping registry entry for PID ${entry.pid} because process identity is indeterminate`
|
|
2627
|
+
);
|
|
2628
|
+
continue;
|
|
2629
|
+
}
|
|
2630
|
+
if (!sessionPidIsAlive) {
|
|
2631
|
+
if (await removeRegistryEntryPath(entryPath)) {
|
|
2632
|
+
removedStaleCount++;
|
|
2633
|
+
}
|
|
2634
|
+
continue;
|
|
2635
|
+
}
|
|
2636
|
+
if (alreadyTracked.has(entry.pid)) {
|
|
2637
|
+
continue;
|
|
2638
|
+
}
|
|
2639
|
+
trackSession(entry.pid, createTrackedSessionFromRegistryEntry(entry));
|
|
2640
|
+
alreadyTracked.add(entry.pid);
|
|
2641
|
+
recoveredCount++;
|
|
2642
|
+
logger.debug(`[SESSION REGISTRY] Recovered tracked session ${entry.sessionId} for PID ${entry.pid}`);
|
|
2643
|
+
}
|
|
2644
|
+
return { recoveredCount, removedStaleCount };
|
|
2645
|
+
}
|
|
2646
|
+
|
|
2256
2647
|
const initialMachineMetadata = {
|
|
2257
2648
|
host: os.hostname(),
|
|
2258
2649
|
platform: os.platform(),
|
|
@@ -2476,14 +2867,12 @@ async function startDaemon() {
|
|
|
2476
2867
|
errorMessage: spawnError.errorMessage
|
|
2477
2868
|
};
|
|
2478
2869
|
}
|
|
2479
|
-
const tmuxAvailable = await isTmuxAvailable();
|
|
2480
|
-
let useTmux = tmuxAvailable;
|
|
2481
2870
|
let tmuxSessionName = extraEnv.TMUX_SESSION_NAME;
|
|
2482
|
-
|
|
2483
|
-
|
|
2484
|
-
|
|
2485
|
-
|
|
2486
|
-
|
|
2871
|
+
const tmuxRequested = tmuxSessionName !== void 0;
|
|
2872
|
+
const tmuxAvailable = tmuxRequested ? await isTmuxAvailable() : false;
|
|
2873
|
+
let useTmux = tmuxRequested && tmuxAvailable;
|
|
2874
|
+
if (tmuxRequested && !tmuxAvailable) {
|
|
2875
|
+
logger.debug(`[DAEMON RUN] tmux session name specified but tmux not available, falling back to regular spawning`);
|
|
2487
2876
|
}
|
|
2488
2877
|
if (useTmux && tmuxSessionName !== void 0) {
|
|
2489
2878
|
const sessionDesc = tmuxSessionName || "current/most recent session";
|
|
@@ -2688,6 +3077,17 @@ async function startDaemon() {
|
|
|
2688
3077
|
onHappySessionWebhook,
|
|
2689
3078
|
port: HAPPY_CLOUD_DAEMON_PORT
|
|
2690
3079
|
});
|
|
3080
|
+
const recoveryResult = await recoverTrackedSessionsFromLocalRegistry({
|
|
3081
|
+
trackedSessionPids: pidToTrackedSession.keys(),
|
|
3082
|
+
trackSession: (pid, trackedSession) => {
|
|
3083
|
+
pidToTrackedSession.set(pid, trackedSession);
|
|
3084
|
+
}
|
|
3085
|
+
});
|
|
3086
|
+
if (recoveryResult.recoveredCount > 0 || recoveryResult.removedStaleCount > 0) {
|
|
3087
|
+
logger.debug(
|
|
3088
|
+
`[DAEMON RUN] Session registry recovery completed: recovered=${recoveryResult.recoveredCount}, removedStale=${recoveryResult.removedStaleCount}`
|
|
3089
|
+
);
|
|
3090
|
+
}
|
|
2691
3091
|
const fileState = {
|
|
2692
3092
|
pid: process.pid,
|
|
2693
3093
|
httpPort: controlPort,
|
|
@@ -2727,52 +3127,69 @@ async function startDaemon() {
|
|
|
2727
3127
|
if (process.env.DEBUG) {
|
|
2728
3128
|
logger.debug(`[DAEMON RUN] Health check started at ${(/* @__PURE__ */ new Date()).toLocaleString()}`);
|
|
2729
3129
|
}
|
|
2730
|
-
for (const [pid, _] of pidToTrackedSession.entries()) {
|
|
2731
|
-
try {
|
|
2732
|
-
process.kill(pid, 0);
|
|
2733
|
-
} catch (error) {
|
|
2734
|
-
logger.debug(`[DAEMON RUN] Removing stale session with PID ${pid} (process no longer exists)`);
|
|
2735
|
-
pidToTrackedSession.delete(pid);
|
|
2736
|
-
}
|
|
2737
|
-
}
|
|
2738
|
-
const projectVersion = JSON.parse(readFileSync$1(join$1(projectPath(), "package.json"), "utf-8")).version;
|
|
2739
|
-
if (projectVersion !== configuration.currentCliVersion) {
|
|
2740
|
-
logger.debug("[DAEMON RUN] Daemon is outdated, triggering self-restart with latest version, clearing heartbeat interval");
|
|
2741
|
-
clearInterval(restartOnStaleVersionAndHeartbeat);
|
|
2742
|
-
try {
|
|
2743
|
-
spawnHappyCLI(["daemon", "start"], {
|
|
2744
|
-
detached: true,
|
|
2745
|
-
stdio: "ignore"
|
|
2746
|
-
});
|
|
2747
|
-
} catch (error) {
|
|
2748
|
-
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);
|
|
2749
|
-
}
|
|
2750
|
-
logger.debug("[DAEMON RUN] Hanging for a bit - waiting for CLI to kill us because we are running outdated version of the code");
|
|
2751
|
-
await new Promise((resolve) => setTimeout(resolve, 1e4));
|
|
2752
|
-
process.exit(0);
|
|
2753
|
-
}
|
|
2754
|
-
const daemonState = await readDaemonState();
|
|
2755
|
-
if (daemonState && daemonState.pid !== process.pid) {
|
|
2756
|
-
logger.debug("[DAEMON RUN] Somehow a different daemon was started without killing us. We should kill ourselves.");
|
|
2757
|
-
requestShutdown("exception", "A different daemon was started without killing us. We should kill ourselves.");
|
|
2758
|
-
}
|
|
2759
3130
|
try {
|
|
2760
|
-
const
|
|
2761
|
-
|
|
2762
|
-
|
|
2763
|
-
|
|
2764
|
-
|
|
2765
|
-
|
|
2766
|
-
|
|
2767
|
-
|
|
2768
|
-
|
|
2769
|
-
|
|
2770
|
-
logger.debug(`[DAEMON RUN] Health check completed at ${updatedState.lastHeartbeat}`);
|
|
3131
|
+
const recoveryResult2 = await recoverTrackedSessionsFromLocalRegistry({
|
|
3132
|
+
trackedSessionPids: pidToTrackedSession.keys(),
|
|
3133
|
+
trackSession: (pid, trackedSession) => {
|
|
3134
|
+
pidToTrackedSession.set(pid, trackedSession);
|
|
3135
|
+
}
|
|
3136
|
+
});
|
|
3137
|
+
if (recoveryResult2.recoveredCount > 0 || recoveryResult2.removedStaleCount > 0) {
|
|
3138
|
+
logger.debug(
|
|
3139
|
+
`[DAEMON RUN] Session registry recovery tick completed: recovered=${recoveryResult2.recoveredCount}, removedStale=${recoveryResult2.removedStaleCount}`
|
|
3140
|
+
);
|
|
2771
3141
|
}
|
|
3142
|
+
await runDaemonHealthCheck({
|
|
3143
|
+
trackedSessionPids: pidToTrackedSession.keys(),
|
|
3144
|
+
removeTrackedSession: (pid) => {
|
|
3145
|
+
pidToTrackedSession.delete(pid);
|
|
3146
|
+
},
|
|
3147
|
+
currentCliVersion: configuration.currentCliVersion,
|
|
3148
|
+
onDaemonOutdated: async () => {
|
|
3149
|
+
logger.debug("[DAEMON RUN] Daemon is outdated, triggering self-restart with latest version, clearing heartbeat interval");
|
|
3150
|
+
clearInterval(restartOnStaleVersionAndHeartbeat);
|
|
3151
|
+
try {
|
|
3152
|
+
spawnHappyCLI(["daemon", "start"], {
|
|
3153
|
+
detached: true,
|
|
3154
|
+
stdio: "ignore"
|
|
3155
|
+
});
|
|
3156
|
+
} catch (error) {
|
|
3157
|
+
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);
|
|
3158
|
+
}
|
|
3159
|
+
logger.debug("[DAEMON RUN] Hanging for a bit - waiting for CLI to kill us because we are running outdated version of the code");
|
|
3160
|
+
await new Promise((resolve) => setTimeout(resolve, 1e4));
|
|
3161
|
+
process.exit(0);
|
|
3162
|
+
},
|
|
3163
|
+
readDaemonState,
|
|
3164
|
+
daemonPid: process.pid,
|
|
3165
|
+
requestShutdown,
|
|
3166
|
+
writeHeartbeat: async () => {
|
|
3167
|
+
try {
|
|
3168
|
+
const updatedState = {
|
|
3169
|
+
pid: process.pid,
|
|
3170
|
+
httpPort: controlPort,
|
|
3171
|
+
startTime: fileState.startTime,
|
|
3172
|
+
startedWithCliVersion: packageJson.version,
|
|
3173
|
+
lastHeartbeat: (/* @__PURE__ */ new Date()).toLocaleString(),
|
|
3174
|
+
daemonLogPath: fileState.daemonLogPath
|
|
3175
|
+
};
|
|
3176
|
+
writeDaemonState(updatedState);
|
|
3177
|
+
if (process.env.DEBUG) {
|
|
3178
|
+
logger.debug(`[DAEMON RUN] Health check completed at ${updatedState.lastHeartbeat}`);
|
|
3179
|
+
}
|
|
3180
|
+
} catch (error) {
|
|
3181
|
+
logger.debug("[DAEMON RUN] Failed to write heartbeat", error);
|
|
3182
|
+
}
|
|
3183
|
+
}
|
|
3184
|
+
});
|
|
2772
3185
|
} catch (error) {
|
|
2773
|
-
logger.
|
|
3186
|
+
logger.warn(
|
|
3187
|
+
"[DAEMON RUN] Health check failed; keeping daemon alive until the next tick",
|
|
3188
|
+
error instanceof Error ? error.message : String(error)
|
|
3189
|
+
);
|
|
3190
|
+
} finally {
|
|
3191
|
+
heartbeatRunning = false;
|
|
2774
3192
|
}
|
|
2775
|
-
heartbeatRunning = false;
|
|
2776
3193
|
}, heartbeatIntervalMs);
|
|
2777
3194
|
const cleanupAndShutdown = async (source, errorMessage) => {
|
|
2778
3195
|
logger.debug(`[DAEMON RUN] Starting proper cleanup (source: ${source}, errorMessage: ${errorMessage})...`);
|
|
@@ -2909,7 +3326,7 @@ async function uninstall$1() {
|
|
|
2909
3326
|
} catch (error) {
|
|
2910
3327
|
logger.info("Failed to unload daemon (it might not be running)");
|
|
2911
3328
|
}
|
|
2912
|
-
unlinkSync(PLIST_FILE);
|
|
3329
|
+
unlinkSync$1(PLIST_FILE);
|
|
2913
3330
|
logger.info(`Removed daemon plist from ${PLIST_FILE}`);
|
|
2914
3331
|
logger.info("Daemon uninstalled successfully");
|
|
2915
3332
|
} catch (error) {
|
|
@@ -3085,9 +3502,11 @@ async function handleAuthStatus() {
|
|
|
3085
3502
|
console.log(chalk.gray(`
|
|
3086
3503
|
Data directory: ${configuration.happyCloudHomeDir}`));
|
|
3087
3504
|
try {
|
|
3088
|
-
const
|
|
3089
|
-
if (running) {
|
|
3505
|
+
const daemonStatus = await getDaemonRuntimeStatus();
|
|
3506
|
+
if (daemonStatus === "running") {
|
|
3090
3507
|
console.log(chalk.green("\u2713 Daemon running"));
|
|
3508
|
+
} else if (daemonStatus === "indeterminate") {
|
|
3509
|
+
console.log(chalk.yellow("\u26A0\uFE0F Daemon status indeterminate"));
|
|
3091
3510
|
} else {
|
|
3092
3511
|
console.log(chalk.gray("\u2717 Daemon not running"));
|
|
3093
3512
|
}
|
|
@@ -6668,6 +7087,189 @@ function validateCodexAcpSpawn(options = {}) {
|
|
|
6668
7087
|
return { ok: true, spawn };
|
|
6669
7088
|
}
|
|
6670
7089
|
|
|
7090
|
+
function firstExistingPath(candidates) {
|
|
7091
|
+
for (const candidate of candidates) {
|
|
7092
|
+
try {
|
|
7093
|
+
if (fs$1.existsSync(candidate)) {
|
|
7094
|
+
return candidate;
|
|
7095
|
+
}
|
|
7096
|
+
} catch {
|
|
7097
|
+
}
|
|
7098
|
+
}
|
|
7099
|
+
return null;
|
|
7100
|
+
}
|
|
7101
|
+
function resolveCodexExecutable() {
|
|
7102
|
+
if (process.platform === "win32") {
|
|
7103
|
+
const appData = process.env.APPDATA || path.join(os$1.homedir(), "AppData", "Roaming");
|
|
7104
|
+
const npmGlobalBin = path.join(appData, "npm");
|
|
7105
|
+
const resolved = firstExistingPath([
|
|
7106
|
+
path.join(npmGlobalBin, "codex.cmd"),
|
|
7107
|
+
path.join(npmGlobalBin, "codex.ps1"),
|
|
7108
|
+
path.join(npmGlobalBin, "codex")
|
|
7109
|
+
]);
|
|
7110
|
+
if (resolved) {
|
|
7111
|
+
return resolved;
|
|
7112
|
+
}
|
|
7113
|
+
}
|
|
7114
|
+
return "codex";
|
|
7115
|
+
}
|
|
7116
|
+
|
|
7117
|
+
function getCodexPlatformTarget(platform, arch) {
|
|
7118
|
+
if (platform === "win32" && arch === "x64") {
|
|
7119
|
+
return {
|
|
7120
|
+
packageName: "codex-win32-x64",
|
|
7121
|
+
targetTriple: "x86_64-pc-windows-msvc"
|
|
7122
|
+
};
|
|
7123
|
+
}
|
|
7124
|
+
if (platform === "win32" && arch === "arm64") {
|
|
7125
|
+
return {
|
|
7126
|
+
packageName: "codex-win32-arm64",
|
|
7127
|
+
targetTriple: "aarch64-pc-windows-msvc"
|
|
7128
|
+
};
|
|
7129
|
+
}
|
|
7130
|
+
if ((platform === "linux" || platform === "android") && arch === "x64") {
|
|
7131
|
+
return {
|
|
7132
|
+
packageName: "codex-linux-x64",
|
|
7133
|
+
targetTriple: "x86_64-unknown-linux-musl"
|
|
7134
|
+
};
|
|
7135
|
+
}
|
|
7136
|
+
if ((platform === "linux" || platform === "android") && arch === "arm64") {
|
|
7137
|
+
return {
|
|
7138
|
+
packageName: "codex-linux-arm64",
|
|
7139
|
+
targetTriple: "aarch64-unknown-linux-musl"
|
|
7140
|
+
};
|
|
7141
|
+
}
|
|
7142
|
+
if (platform === "darwin" && arch === "x64") {
|
|
7143
|
+
return {
|
|
7144
|
+
packageName: "codex-darwin-x64",
|
|
7145
|
+
targetTriple: "x86_64-apple-darwin"
|
|
7146
|
+
};
|
|
7147
|
+
}
|
|
7148
|
+
if (platform === "darwin" && arch === "arm64") {
|
|
7149
|
+
return {
|
|
7150
|
+
packageName: "codex-darwin-arm64",
|
|
7151
|
+
targetTriple: "aarch64-apple-darwin"
|
|
7152
|
+
};
|
|
7153
|
+
}
|
|
7154
|
+
return null;
|
|
7155
|
+
}
|
|
7156
|
+
function dedupePaths(paths, platform) {
|
|
7157
|
+
const seen = /* @__PURE__ */ new Set();
|
|
7158
|
+
const unique = [];
|
|
7159
|
+
for (const entry of paths) {
|
|
7160
|
+
const normalized = normalize(entry);
|
|
7161
|
+
const key = platform === "win32" ? normalized.toLowerCase() : normalized;
|
|
7162
|
+
if (seen.has(key)) {
|
|
7163
|
+
continue;
|
|
7164
|
+
}
|
|
7165
|
+
seen.add(key);
|
|
7166
|
+
unique.push(entry);
|
|
7167
|
+
}
|
|
7168
|
+
return unique;
|
|
7169
|
+
}
|
|
7170
|
+
function resolveCodexShimPath({
|
|
7171
|
+
platform = process.platform,
|
|
7172
|
+
exists = existsSync,
|
|
7173
|
+
resolveExecutable = resolveCodexExecutable,
|
|
7174
|
+
resolveOnPath = resolveCommandOnPath
|
|
7175
|
+
}) {
|
|
7176
|
+
const resolvedExecutable = resolveExecutable();
|
|
7177
|
+
if (isAbsolute(resolvedExecutable) && exists(resolvedExecutable)) {
|
|
7178
|
+
return resolvedExecutable;
|
|
7179
|
+
}
|
|
7180
|
+
const commandNames = platform === "win32" ? ["codex.cmd", "codex.ps1", "codex"] : ["codex"];
|
|
7181
|
+
for (const commandName of commandNames) {
|
|
7182
|
+
const resolved = resolveOnPath(commandName);
|
|
7183
|
+
if (resolved) {
|
|
7184
|
+
return resolved;
|
|
7185
|
+
}
|
|
7186
|
+
}
|
|
7187
|
+
return null;
|
|
7188
|
+
}
|
|
7189
|
+
function resolveCodexPackageRoots(executablePath, {
|
|
7190
|
+
platform = process.platform,
|
|
7191
|
+
exists = existsSync,
|
|
7192
|
+
realpath = realpathSync
|
|
7193
|
+
}) {
|
|
7194
|
+
const executableDir = dirname$1(executablePath);
|
|
7195
|
+
const candidates = [
|
|
7196
|
+
join(executableDir, "node_modules", "@openai", "codex"),
|
|
7197
|
+
join(executableDir, "..", "lib", "node_modules", "@openai", "codex")
|
|
7198
|
+
];
|
|
7199
|
+
try {
|
|
7200
|
+
const realExecutablePath = realpath(executablePath);
|
|
7201
|
+
candidates.push(join(dirname$1(realExecutablePath), ".."));
|
|
7202
|
+
} catch {
|
|
7203
|
+
}
|
|
7204
|
+
return dedupePaths(
|
|
7205
|
+
candidates.filter((candidate) => exists(candidate)),
|
|
7206
|
+
platform
|
|
7207
|
+
);
|
|
7208
|
+
}
|
|
7209
|
+
function resolvePathEnvKey(env) {
|
|
7210
|
+
return Object.prototype.hasOwnProperty.call(env, "Path") && !Object.prototype.hasOwnProperty.call(env, "PATH") ? "Path" : "PATH";
|
|
7211
|
+
}
|
|
7212
|
+
function resolveBundledCodexToolPathDirs(options = {}) {
|
|
7213
|
+
const platform = options.platform ?? process.platform;
|
|
7214
|
+
const arch = options.arch ?? process.arch;
|
|
7215
|
+
const codexTarget = getCodexPlatformTarget(platform, arch);
|
|
7216
|
+
if (!codexTarget) {
|
|
7217
|
+
return [];
|
|
7218
|
+
}
|
|
7219
|
+
const codexShimPath = resolveCodexShimPath(options);
|
|
7220
|
+
if (!codexShimPath) {
|
|
7221
|
+
return [];
|
|
7222
|
+
}
|
|
7223
|
+
const packageRoots = resolveCodexPackageRoots(codexShimPath, options);
|
|
7224
|
+
if (packageRoots.length === 0) {
|
|
7225
|
+
return [];
|
|
7226
|
+
}
|
|
7227
|
+
const exists = options.exists ?? existsSync;
|
|
7228
|
+
const candidates = packageRoots.flatMap((packageRoot) => [
|
|
7229
|
+
join(packageRoot, "vendor", codexTarget.targetTriple, "path"),
|
|
7230
|
+
join(
|
|
7231
|
+
packageRoot,
|
|
7232
|
+
"node_modules",
|
|
7233
|
+
"@openai",
|
|
7234
|
+
codexTarget.packageName,
|
|
7235
|
+
"vendor",
|
|
7236
|
+
codexTarget.targetTriple,
|
|
7237
|
+
"path"
|
|
7238
|
+
)
|
|
7239
|
+
]);
|
|
7240
|
+
return dedupePaths(
|
|
7241
|
+
candidates.filter((candidate) => exists(candidate)),
|
|
7242
|
+
platform
|
|
7243
|
+
);
|
|
7244
|
+
}
|
|
7245
|
+
function buildCodexAcpEnv(overrides = {}, options = {}) {
|
|
7246
|
+
const platform = options.platform ?? process.platform;
|
|
7247
|
+
const env = {
|
|
7248
|
+
...process.env,
|
|
7249
|
+
...overrides
|
|
7250
|
+
};
|
|
7251
|
+
const pathKey = resolvePathEnvKey(env);
|
|
7252
|
+
const alternatePathKey = pathKey === "PATH" ? "Path" : "PATH";
|
|
7253
|
+
const currentPathValue = env[pathKey] ?? env[alternatePathKey] ?? "";
|
|
7254
|
+
const currentPathEntries = currentPathValue.split(delimiter).filter(Boolean);
|
|
7255
|
+
const codexToolDirs = resolveBundledCodexToolPathDirs(options);
|
|
7256
|
+
const mergedPathEntries = dedupePaths(
|
|
7257
|
+
[...codexToolDirs, ...currentPathEntries],
|
|
7258
|
+
platform
|
|
7259
|
+
);
|
|
7260
|
+
if (mergedPathEntries.length > 0) {
|
|
7261
|
+
env[pathKey] = mergedPathEntries.join(delimiter);
|
|
7262
|
+
}
|
|
7263
|
+
delete env[alternatePathKey];
|
|
7264
|
+
const stringEnvEntries = [];
|
|
7265
|
+
for (const [key, value] of Object.entries(env)) {
|
|
7266
|
+
if (typeof value === "string") {
|
|
7267
|
+
stringEnvEntries.push([key, value]);
|
|
7268
|
+
}
|
|
7269
|
+
}
|
|
7270
|
+
return Object.fromEntries(stringEnvEntries);
|
|
7271
|
+
}
|
|
7272
|
+
|
|
6671
7273
|
class CodexAcpTransport extends CodexTransport {
|
|
6672
7274
|
constructor(initTimeoutMs) {
|
|
6673
7275
|
super();
|
|
@@ -6697,10 +7299,10 @@ function createCodexBackend(options) {
|
|
|
6697
7299
|
cwd: options.cwd,
|
|
6698
7300
|
command: spawn.command,
|
|
6699
7301
|
args: spawn.args,
|
|
6700
|
-
env: {
|
|
7302
|
+
env: buildCodexAcpEnv({
|
|
6701
7303
|
...options.env,
|
|
6702
7304
|
NODE_ENV: "production"
|
|
6703
|
-
},
|
|
7305
|
+
}),
|
|
6704
7306
|
permissionHandler: options.permissionHandler,
|
|
6705
7307
|
selectionHandler: options.selectionHandler,
|
|
6706
7308
|
transportHandler: resolveCodexTransport(spawn.command)
|
|
@@ -6850,6 +7452,24 @@ function createDefaultRuntimeShell() {
|
|
|
6850
7452
|
return new RuntimeShell();
|
|
6851
7453
|
}
|
|
6852
7454
|
|
|
7455
|
+
const DAEMON_STARTUP_TIMEOUT_MS = 3e4;
|
|
7456
|
+
const DAEMON_STARTUP_POLL_INTERVAL_MS = 250;
|
|
7457
|
+
async function waitForDaemonReady(timeoutMs = DAEMON_STARTUP_TIMEOUT_MS, pollIntervalMs = DAEMON_STARTUP_POLL_INTERVAL_MS) {
|
|
7458
|
+
const deadline = Date.now() + timeoutMs;
|
|
7459
|
+
while (Date.now() < deadline) {
|
|
7460
|
+
const remainingMs = deadline - Date.now();
|
|
7461
|
+
if (await isDaemonRunningCurrentlyInstalledHappyVersion(Math.max(1, remainingMs))) {
|
|
7462
|
+
return true;
|
|
7463
|
+
}
|
|
7464
|
+
const sleepMs = Math.min(pollIntervalMs, deadline - Date.now());
|
|
7465
|
+
if (sleepMs <= 0) {
|
|
7466
|
+
break;
|
|
7467
|
+
}
|
|
7468
|
+
await new Promise((resolve) => setTimeout(resolve, sleepMs));
|
|
7469
|
+
}
|
|
7470
|
+
return false;
|
|
7471
|
+
}
|
|
7472
|
+
|
|
6853
7473
|
function isRuntimeProvider(value) {
|
|
6854
7474
|
return value === "claude" || value === "codex" || value === "gemini" || value === "cursor";
|
|
6855
7475
|
}
|
|
@@ -6894,26 +7514,20 @@ async function ensureUnifiedDaemonStarted() {
|
|
|
6894
7514
|
env: process.env
|
|
6895
7515
|
});
|
|
6896
7516
|
daemonProcess.unref();
|
|
6897
|
-
|
|
6898
|
-
|
|
6899
|
-
return;
|
|
6900
|
-
}
|
|
6901
|
-
if (await isDaemonControlServerResponsive(500)) {
|
|
6902
|
-
return;
|
|
6903
|
-
}
|
|
6904
|
-
await new Promise((resolve) => setTimeout(resolve, 100));
|
|
7517
|
+
if (await waitForDaemonReady()) {
|
|
7518
|
+
return;
|
|
6905
7519
|
}
|
|
6906
7520
|
throw new Error("Failed to start Happy background service.");
|
|
6907
7521
|
}
|
|
6908
7522
|
async function executeUnifiedProvider(opts) {
|
|
6909
7523
|
const credentials = await ensureUnifiedRuntimePrerequisites(opts.credentials);
|
|
6910
7524
|
if (opts.provider === "claude") {
|
|
6911
|
-
const { runClaude } = await import('./runClaude-
|
|
7525
|
+
const { runClaude } = await import('./runClaude-B_fTMxc4.mjs');
|
|
6912
7526
|
await runClaude(credentials, opts.claudeOptions ?? {});
|
|
6913
7527
|
return;
|
|
6914
7528
|
}
|
|
6915
7529
|
if (opts.provider === "codex") {
|
|
6916
|
-
const { runCodex } = await import('./runCodex-
|
|
7530
|
+
const { runCodex } = await import('./runCodex-CUgOiIEz.mjs');
|
|
6917
7531
|
await runCodex({
|
|
6918
7532
|
credentials,
|
|
6919
7533
|
startedBy: opts.startedBy,
|
|
@@ -6923,7 +7537,7 @@ async function executeUnifiedProvider(opts) {
|
|
|
6923
7537
|
return;
|
|
6924
7538
|
}
|
|
6925
7539
|
if (opts.provider === "gemini") {
|
|
6926
|
-
const { runGemini } = await import('./runGemini-
|
|
7540
|
+
const { runGemini } = await import('./runGemini-2_FEtJYa.mjs');
|
|
6927
7541
|
await runGemini({
|
|
6928
7542
|
credentials,
|
|
6929
7543
|
startedBy: opts.startedBy
|
|
@@ -6965,7 +7579,7 @@ function shouldRunMainClaudeFlow(opts) {
|
|
|
6965
7579
|
return;
|
|
6966
7580
|
} else if (subcommand === "runtime") {
|
|
6967
7581
|
if (args[1] === "providers") {
|
|
6968
|
-
const { renderRuntimeProviders } = await import('./command-
|
|
7582
|
+
const { renderRuntimeProviders } = await import('./command-ComOeFLY.mjs');
|
|
6969
7583
|
console.log(renderRuntimeProviders());
|
|
6970
7584
|
return;
|
|
6971
7585
|
}
|
|
@@ -7143,8 +7757,8 @@ function shouldRunMainClaudeFlow(opts) {
|
|
|
7143
7757
|
const projectId = args[3];
|
|
7144
7758
|
try {
|
|
7145
7759
|
const { saveGoogleCloudProjectToConfig } = await Promise.resolve().then(function () { return config; });
|
|
7146
|
-
const { readCredentials: readCredentials2 } = await import('./persistence-
|
|
7147
|
-
const { ApiClient: ApiClient2 } = await import('./api-
|
|
7760
|
+
const { readCredentials: readCredentials2 } = await import('./persistence-BxP6Jw1f.mjs');
|
|
7761
|
+
const { ApiClient: ApiClient2 } = await import('./api-BjxmW-0W.mjs').then(function (n) { return n.q; });
|
|
7148
7762
|
let userEmail = void 0;
|
|
7149
7763
|
try {
|
|
7150
7764
|
const credentials = await readCredentials2();
|
|
@@ -7291,14 +7905,7 @@ function shouldRunMainClaudeFlow(opts) {
|
|
|
7291
7905
|
env: process.env
|
|
7292
7906
|
});
|
|
7293
7907
|
child.unref();
|
|
7294
|
-
|
|
7295
|
-
for (let i = 0; i < 100; i++) {
|
|
7296
|
-
if (await checkIfDaemonRunningAndCleanupStaleState() && await isDaemonControlServerResponsive(500)) {
|
|
7297
|
-
started = true;
|
|
7298
|
-
break;
|
|
7299
|
-
}
|
|
7300
|
-
await new Promise((resolve) => setTimeout(resolve, 100));
|
|
7301
|
-
}
|
|
7908
|
+
const started = await waitForDaemonReady();
|
|
7302
7909
|
if (started) {
|
|
7303
7910
|
console.log("Daemon started successfully");
|
|
7304
7911
|
} else {
|
|
@@ -7567,4 +8174,4 @@ ${chalk.bold("Examples:")}
|
|
|
7567
8174
|
}
|
|
7568
8175
|
}
|
|
7569
8176
|
|
|
7570
|
-
export { ExitCodeError as E, GEMINI_MODEL_ENV as G, createGeminiBackend as a, stopCaffeinate as b, createDefaultRuntimeShell as c,
|
|
8177
|
+
export { ExitCodeError as E, GEMINI_MODEL_ENV as G, createGeminiBackend as a, stopCaffeinate as b, createDefaultRuntimeShell as c, projectPath as d, createCodexBackend as e, formatDisplayMessage as f, getInitialGeminiModel as g, getProjectPath as h, initialMachineMetadata as i, claudeLocal as j, isBun as k, trimIdent as l, claudeCheckSession as m, getEnvironmentInfo as n, startCaffeinate as o, publishSessionRegistration as p, readGeminiLocalConfig as r, saveGeminiModelToConfig as s, truncateDisplayMessage as t, validateCodexAcpSpawn as v };
|