@hermespilot/link 0.4.4 → 0.4.6
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/{chunk-2CHGHWCY.js → chunk-DUQDO2LQ.js} +635 -129
- package/dist/cli/index.js +1 -1
- package/dist/http/app.js +1 -1
- package/package.json +1 -1
|
@@ -2080,6 +2080,14 @@ async function ensureHermesApiServerConfig(profileName = "default", configPath =
|
|
|
2080
2080
|
}
|
|
2081
2081
|
return ensureHermesApiServerConfigUnlocked(profileName, configPath);
|
|
2082
2082
|
}
|
|
2083
|
+
async function repairHermesApiServerConfig(profileName = "default", configPath = resolveHermesConfigPath(profileName)) {
|
|
2084
|
+
if (profileName !== "default") {
|
|
2085
|
+
return withProfileApiServerPortAssignmentLock(
|
|
2086
|
+
() => repairHermesApiServerConfigUnlocked(profileName, configPath)
|
|
2087
|
+
);
|
|
2088
|
+
}
|
|
2089
|
+
return repairHermesApiServerConfigUnlocked(profileName, configPath);
|
|
2090
|
+
}
|
|
2083
2091
|
async function ensureHermesApiServerConfigUnlocked(profileName = "default", configPath = resolveHermesConfigPath(profileName)) {
|
|
2084
2092
|
const existingRaw = await readFile2(configPath, "utf8").catch(
|
|
2085
2093
|
(error) => {
|
|
@@ -2096,47 +2104,60 @@ async function ensureHermesApiServerConfigUnlocked(profileName = "default", conf
|
|
|
2096
2104
|
const extra = ensureRecord(apiServer, "extra");
|
|
2097
2105
|
const configOnly = readApiServerConfig(apiServer);
|
|
2098
2106
|
const envOverrides = await readHermesApiServerEnvOverrides(profileName);
|
|
2099
|
-
const
|
|
2100
|
-
const
|
|
2101
|
-
const
|
|
2102
|
-
const
|
|
2103
|
-
const
|
|
2107
|
+
const configKey = configOnly.key?.trim() ? configOnly.key : null;
|
|
2108
|
+
const envKey = envOverrides.key?.trim() ? envOverrides.key : null;
|
|
2109
|
+
const configHost = configOnly.host?.trim() ? configOnly.host : null;
|
|
2110
|
+
const envHost = envOverrides.host?.trim() ? envOverrides.host : null;
|
|
2111
|
+
const configPort = readApiServerPort(configOnly.port);
|
|
2112
|
+
const envPort = readApiServerPort(envOverrides.port);
|
|
2113
|
+
const desiredHost = configHost ?? envHost ?? DEFAULT_HERMES_API_SERVER_HOST;
|
|
2114
|
+
const desiredPort = await resolveDesiredApiServerPort({
|
|
2115
|
+
profileName,
|
|
2116
|
+
configPort,
|
|
2117
|
+
envPort
|
|
2118
|
+
});
|
|
2119
|
+
const desiredKey = configKey ?? envKey ?? randomBytes(32).toString("base64url");
|
|
2104
2120
|
let changed = false;
|
|
2105
2121
|
let enabledAdded = false;
|
|
2106
2122
|
let hostAdded = false;
|
|
2107
2123
|
let portAdded = false;
|
|
2108
|
-
|
|
2109
|
-
if (!beforeEnabled) {
|
|
2124
|
+
if (apiServer.enabled !== true) {
|
|
2110
2125
|
apiServer.enabled = true;
|
|
2111
2126
|
enabledAdded = true;
|
|
2112
2127
|
changed = true;
|
|
2113
2128
|
}
|
|
2114
|
-
if (
|
|
2115
|
-
extra.host =
|
|
2116
|
-
hostAdded =
|
|
2129
|
+
if (configHost !== desiredHost) {
|
|
2130
|
+
extra.host = desiredHost;
|
|
2131
|
+
hostAdded = !configHost;
|
|
2117
2132
|
changed = true;
|
|
2118
2133
|
}
|
|
2119
|
-
if (
|
|
2120
|
-
|
|
2121
|
-
extra.port = assignedPort;
|
|
2122
|
-
portAdded = true;
|
|
2123
|
-
changed = true;
|
|
2124
|
-
} else if (!beforePort) {
|
|
2125
|
-
assignedPort = DEFAULT_HERMES_API_SERVER_PORT;
|
|
2126
|
-
extra.port = assignedPort;
|
|
2134
|
+
if (configPort !== desiredPort) {
|
|
2135
|
+
extra.port = desiredPort;
|
|
2127
2136
|
portAdded = true;
|
|
2128
2137
|
changed = true;
|
|
2129
2138
|
}
|
|
2130
|
-
if (
|
|
2131
|
-
extra.key =
|
|
2139
|
+
if (configKey !== desiredKey) {
|
|
2140
|
+
extra.key = desiredKey;
|
|
2132
2141
|
changed = true;
|
|
2133
2142
|
}
|
|
2134
|
-
|
|
2143
|
+
const desiredApiServer = readApiServerConfig(apiServer, true);
|
|
2144
|
+
const backupPath = changed && existingRaw ? `${configPath}.bak.${Date.now()}` : null;
|
|
2145
|
+
if (backupPath && existingRaw !== null) {
|
|
2146
|
+
await atomicWriteFilePreservingMetadata(backupPath, existingRaw, {
|
|
2147
|
+
metadataSourcePath: configPath
|
|
2148
|
+
});
|
|
2149
|
+
}
|
|
2150
|
+
if (changed) {
|
|
2151
|
+
document.contents = document.createNode(config);
|
|
2152
|
+
await atomicWriteFilePreservingMetadata(configPath, document.toString());
|
|
2153
|
+
}
|
|
2154
|
+
const envChanged = await writeHermesApiServerEnv(profileName, desiredApiServer);
|
|
2155
|
+
if (!changed && !envChanged) {
|
|
2135
2156
|
return {
|
|
2136
2157
|
configPath,
|
|
2137
2158
|
apiServer: applyEnvOverrides(
|
|
2138
2159
|
readApiServerConfig(apiServer, true),
|
|
2139
|
-
|
|
2160
|
+
await readHermesApiServerEnvOverrides(profileName),
|
|
2140
2161
|
true
|
|
2141
2162
|
),
|
|
2142
2163
|
changed: false,
|
|
@@ -2148,36 +2169,79 @@ async function ensureHermesApiServerConfigUnlocked(profileName = "default", conf
|
|
|
2148
2169
|
notice: null
|
|
2149
2170
|
};
|
|
2150
2171
|
}
|
|
2151
|
-
const backupPath = existingRaw ? `${configPath}.bak.${Date.now()}` : null;
|
|
2152
|
-
if (backupPath && existingRaw !== null) {
|
|
2153
|
-
await atomicWriteFilePreservingMetadata(backupPath, existingRaw, {
|
|
2154
|
-
metadataSourcePath: configPath
|
|
2155
|
-
});
|
|
2156
|
-
}
|
|
2157
|
-
document.contents = document.createNode(config);
|
|
2158
|
-
await atomicWriteFilePreservingMetadata(configPath, document.toString());
|
|
2159
2172
|
return {
|
|
2160
2173
|
configPath,
|
|
2161
2174
|
apiServer: applyEnvOverrides(
|
|
2162
2175
|
readApiServerConfig(apiServer, true),
|
|
2163
|
-
|
|
2176
|
+
await readHermesApiServerEnvOverrides(profileName),
|
|
2164
2177
|
true
|
|
2165
2178
|
),
|
|
2166
2179
|
changed: true,
|
|
2167
|
-
keyAdded: !
|
|
2180
|
+
keyAdded: !configKey,
|
|
2168
2181
|
enabledAdded,
|
|
2169
2182
|
hostAdded,
|
|
2170
2183
|
portAdded,
|
|
2171
2184
|
backupPath,
|
|
2172
2185
|
notice: buildNotice({
|
|
2173
|
-
keyAdded: !
|
|
2186
|
+
keyAdded: !configKey,
|
|
2174
2187
|
enabledAdded,
|
|
2175
2188
|
hostAdded,
|
|
2176
2189
|
portAdded,
|
|
2177
|
-
port:
|
|
2190
|
+
port: desiredPort
|
|
2178
2191
|
})
|
|
2179
2192
|
};
|
|
2180
2193
|
}
|
|
2194
|
+
async function repairHermesApiServerConfigUnlocked(profileName = "default", configPath = resolveHermesConfigPath(profileName)) {
|
|
2195
|
+
const existingRaw = await readFile2(configPath, "utf8").catch(
|
|
2196
|
+
(error) => {
|
|
2197
|
+
if (isNodeError3(error, "ENOENT")) {
|
|
2198
|
+
return null;
|
|
2199
|
+
}
|
|
2200
|
+
throw error;
|
|
2201
|
+
}
|
|
2202
|
+
);
|
|
2203
|
+
const document = existingRaw ? YAML.parseDocument(existingRaw) : new YAML.Document({});
|
|
2204
|
+
const config = toRecord(document.toJSON());
|
|
2205
|
+
const platforms = ensureRecord(config, "platforms");
|
|
2206
|
+
const apiServer = ensureRecord(platforms, "api_server");
|
|
2207
|
+
const extra = ensureRecord(apiServer, "extra");
|
|
2208
|
+
const previous = applyEnvOverrides(
|
|
2209
|
+
readApiServerConfig(apiServer, true),
|
|
2210
|
+
await readHermesApiServerEnvOverrides(profileName),
|
|
2211
|
+
true
|
|
2212
|
+
);
|
|
2213
|
+
const freshPort = await nextProfileApiServerPort(profileName);
|
|
2214
|
+
const freshKey = randomBytes(32).toString("base64url");
|
|
2215
|
+
apiServer.enabled = true;
|
|
2216
|
+
extra.host = DEFAULT_HERMES_API_SERVER_HOST;
|
|
2217
|
+
extra.port = freshPort;
|
|
2218
|
+
extra.key = freshKey;
|
|
2219
|
+
const backupPath = existingRaw ? `${configPath}.bak.${Date.now()}` : null;
|
|
2220
|
+
if (backupPath && existingRaw !== null) {
|
|
2221
|
+
await atomicWriteFilePreservingMetadata(backupPath, existingRaw, {
|
|
2222
|
+
metadataSourcePath: configPath
|
|
2223
|
+
});
|
|
2224
|
+
}
|
|
2225
|
+
document.contents = document.createNode(config);
|
|
2226
|
+
await atomicWriteFilePreservingMetadata(configPath, document.toString());
|
|
2227
|
+
const apiServerConfig = readApiServerConfig(apiServer, true);
|
|
2228
|
+
await writeHermesApiServerEnv(profileName, apiServerConfig, { force: true });
|
|
2229
|
+
return {
|
|
2230
|
+
configPath,
|
|
2231
|
+
apiServer: applyEnvOverrides(
|
|
2232
|
+
apiServerConfig,
|
|
2233
|
+
await readHermesApiServerEnvOverrides(profileName),
|
|
2234
|
+
true
|
|
2235
|
+
),
|
|
2236
|
+
changed: true,
|
|
2237
|
+
keyAdded: !previous.key,
|
|
2238
|
+
enabledAdded: previous.enabled !== true,
|
|
2239
|
+
hostAdded: previous.host !== DEFAULT_HERMES_API_SERVER_HOST,
|
|
2240
|
+
portAdded: previous.port !== freshPort,
|
|
2241
|
+
backupPath,
|
|
2242
|
+
notice: "\u5DF2\u4E3A Hermes API Server \u91CD\u65B0\u5206\u914D\u672C\u673A\u7AEF\u53E3\u5E76\u8F6E\u6362 key\uFF0C\u7528\u4E8E\u4FEE\u590D\u65E7 Gateway \u6216 key \u4E0D\u4E00\u81F4\u5BFC\u81F4\u7684 401\u3002"
|
|
2243
|
+
};
|
|
2244
|
+
}
|
|
2181
2245
|
async function readHermesConfigDocument(configPath) {
|
|
2182
2246
|
const existingRaw = await readFile2(configPath, "utf8").catch(
|
|
2183
2247
|
(error) => {
|
|
@@ -3511,6 +3575,21 @@ function shouldAssignDedicatedProfileApiServerPort(profileName, configuredPort)
|
|
|
3511
3575
|
}
|
|
3512
3576
|
return configuredPort === void 0 || configuredPort === DEFAULT_HERMES_API_SERVER_PORT;
|
|
3513
3577
|
}
|
|
3578
|
+
function readApiServerPort(value) {
|
|
3579
|
+
return typeof value === "number" && Number.isFinite(value) && value > 0 ? value : null;
|
|
3580
|
+
}
|
|
3581
|
+
async function resolveDesiredApiServerPort(input) {
|
|
3582
|
+
if (shouldAssignDedicatedProfileApiServerPort(
|
|
3583
|
+
input.profileName,
|
|
3584
|
+
input.configPort ?? void 0
|
|
3585
|
+
)) {
|
|
3586
|
+
if (input.profileName !== "default" && input.envPort !== null && input.envPort !== DEFAULT_HERMES_API_SERVER_PORT) {
|
|
3587
|
+
return input.envPort;
|
|
3588
|
+
}
|
|
3589
|
+
return nextProfileApiServerPort(input.profileName);
|
|
3590
|
+
}
|
|
3591
|
+
return input.configPort ?? input.envPort ?? DEFAULT_HERMES_API_SERVER_PORT;
|
|
3592
|
+
}
|
|
3514
3593
|
async function nextProfileApiServerPort(profileName) {
|
|
3515
3594
|
const usedPorts = await readConfiguredApiServerPorts(profileName);
|
|
3516
3595
|
for (let port = PROFILE_API_SERVER_PORT_START; port <= PROFILE_API_SERVER_PORT_END; port += 1) {
|
|
@@ -3602,17 +3681,6 @@ function isEnvValueConfigured(value) {
|
|
|
3602
3681
|
}
|
|
3603
3682
|
async function readHermesApiServerEnvOverrides(profileName) {
|
|
3604
3683
|
const values = await readHermesEnvFile(profileName);
|
|
3605
|
-
for (const key of [
|
|
3606
|
-
"API_SERVER_ENABLED",
|
|
3607
|
-
"API_SERVER_HOST",
|
|
3608
|
-
"API_SERVER_PORT",
|
|
3609
|
-
"API_SERVER_KEY"
|
|
3610
|
-
]) {
|
|
3611
|
-
const value = process.env[key];
|
|
3612
|
-
if (typeof value === "string" && value.trim()) {
|
|
3613
|
-
values[key] = value;
|
|
3614
|
-
}
|
|
3615
|
-
}
|
|
3616
3684
|
const port = Number.parseInt(values.API_SERVER_PORT ?? "", 10);
|
|
3617
3685
|
return {
|
|
3618
3686
|
enabled: parseEnvBoolean(values.API_SERVER_ENABLED),
|
|
@@ -3681,14 +3749,82 @@ async function writeHermesEnvValue(profileName, key, value) {
|
|
|
3681
3749
|
}
|
|
3682
3750
|
await atomicWriteFilePreservingMetadata(envPath, nextRaw);
|
|
3683
3751
|
}
|
|
3752
|
+
async function writeHermesApiServerEnv(profileName, config, options = {}) {
|
|
3753
|
+
const envPath = path3.join(resolveHermesProfileDir(profileName), ".env");
|
|
3754
|
+
const existingRaw = await readFile2(envPath, "utf8").catch(
|
|
3755
|
+
(error) => {
|
|
3756
|
+
if (isNodeError3(error, "ENOENT")) {
|
|
3757
|
+
return null;
|
|
3758
|
+
}
|
|
3759
|
+
throw error;
|
|
3760
|
+
}
|
|
3761
|
+
);
|
|
3762
|
+
if (existingRaw === null && profileName === "default" && !options.force) {
|
|
3763
|
+
return false;
|
|
3764
|
+
}
|
|
3765
|
+
return writeHermesEnvValues(
|
|
3766
|
+
profileName,
|
|
3767
|
+
[
|
|
3768
|
+
["API_SERVER_ENABLED", config.enabled === false ? "false" : "true"],
|
|
3769
|
+
["API_SERVER_HOST", config.host ?? DEFAULT_HERMES_API_SERVER_HOST],
|
|
3770
|
+
["API_SERVER_PORT", String(config.port ?? DEFAULT_HERMES_API_SERVER_PORT)],
|
|
3771
|
+
["API_SERVER_KEY", config.key ?? ""]
|
|
3772
|
+
],
|
|
3773
|
+
existingRaw ?? ""
|
|
3774
|
+
);
|
|
3775
|
+
}
|
|
3776
|
+
async function writeHermesEnvValues(profileName, values, existingRaw) {
|
|
3777
|
+
const envPath = path3.join(resolveHermesProfileDir(profileName), ".env");
|
|
3778
|
+
const raw = existingRaw ?? await readFile2(envPath, "utf8").catch((error) => {
|
|
3779
|
+
if (isNodeError3(error, "ENOENT")) {
|
|
3780
|
+
return "";
|
|
3781
|
+
}
|
|
3782
|
+
throw error;
|
|
3783
|
+
});
|
|
3784
|
+
const lines = raw ? raw.split(/\r?\n/u) : [];
|
|
3785
|
+
const remaining = new Map(values);
|
|
3786
|
+
const nextLines = lines.map((line) => {
|
|
3787
|
+
const trimmed = line.trim();
|
|
3788
|
+
for (const [key, value] of remaining) {
|
|
3789
|
+
const keyPattern = new RegExp(
|
|
3790
|
+
`^(?:export\\s+)?${escapeRegExp(key)}=`,
|
|
3791
|
+
"u"
|
|
3792
|
+
);
|
|
3793
|
+
if (keyPattern.test(trimmed)) {
|
|
3794
|
+
remaining.delete(key);
|
|
3795
|
+
return `${key}=${formatEnvValue(value)}`;
|
|
3796
|
+
}
|
|
3797
|
+
}
|
|
3798
|
+
return line;
|
|
3799
|
+
});
|
|
3800
|
+
if (remaining.size > 0 && nextLines.length > 0 && nextLines.at(-1) !== "") {
|
|
3801
|
+
nextLines.push("");
|
|
3802
|
+
}
|
|
3803
|
+
for (const [key, value] of remaining) {
|
|
3804
|
+
nextLines.push(`${key}=${formatEnvValue(value)}`);
|
|
3805
|
+
}
|
|
3806
|
+
const nextRaw = nextLines.join("\n").replace(/\n*$/u, "\n");
|
|
3807
|
+
if (nextRaw === raw) {
|
|
3808
|
+
return false;
|
|
3809
|
+
}
|
|
3810
|
+
if (raw) {
|
|
3811
|
+
await atomicWriteFilePreservingMetadata(
|
|
3812
|
+
`${envPath}.bak.${Date.now()}`,
|
|
3813
|
+
raw,
|
|
3814
|
+
{ metadataSourcePath: envPath }
|
|
3815
|
+
);
|
|
3816
|
+
}
|
|
3817
|
+
await atomicWriteFilePreservingMetadata(envPath, nextRaw);
|
|
3818
|
+
return true;
|
|
3819
|
+
}
|
|
3684
3820
|
function applyEnvOverrides(config, env, withDefaults) {
|
|
3685
|
-
const host =
|
|
3686
|
-
const port =
|
|
3821
|
+
const host = env.host ?? config.host;
|
|
3822
|
+
const port = env.port ?? config.port;
|
|
3687
3823
|
return {
|
|
3688
|
-
enabled:
|
|
3824
|
+
enabled: env.enabled ?? config.enabled,
|
|
3689
3825
|
host: withDefaults ? host ?? DEFAULT_HERMES_API_SERVER_HOST : host,
|
|
3690
3826
|
port: withDefaults ? port ?? DEFAULT_HERMES_API_SERVER_PORT : port,
|
|
3691
|
-
key:
|
|
3827
|
+
key: env.key ?? config.key
|
|
3692
3828
|
};
|
|
3693
3829
|
}
|
|
3694
3830
|
function parseEnvBoolean(value) {
|
|
@@ -3996,7 +4132,7 @@ import os2 from "os";
|
|
|
3996
4132
|
import path5 from "path";
|
|
3997
4133
|
|
|
3998
4134
|
// src/constants.ts
|
|
3999
|
-
var LINK_VERSION = "0.4.
|
|
4135
|
+
var LINK_VERSION = "0.4.6";
|
|
4000
4136
|
var LINK_COMMAND = "hermeslink";
|
|
4001
4137
|
var LINK_DEFAULT_PORT = 52379;
|
|
4002
4138
|
var LINK_RUNTIME_DIR_NAME = ".hermeslink";
|
|
@@ -4496,6 +4632,7 @@ var DASHBOARD_STATUS_URL = "http://127.0.0.1:9119/api/status";
|
|
|
4496
4632
|
var DASHBOARD_STATUS_TIMEOUT_MS = 1500;
|
|
4497
4633
|
var DEFAULT_VERSION_CACHE_TTL_MS = 6e4;
|
|
4498
4634
|
var MAX_VERSION_LOG_OUTPUT_LENGTH = 1200;
|
|
4635
|
+
var HERMES_GATEWAY_ENV_BLOCKLIST_PREFIXES = ["API_SERVER_"];
|
|
4499
4636
|
var gatewayStartInFlightByProfile = /* @__PURE__ */ new Map();
|
|
4500
4637
|
var hermesVersionCache = /* @__PURE__ */ new Map();
|
|
4501
4638
|
var hermesVersionInFlight = /* @__PURE__ */ new Map();
|
|
@@ -4558,6 +4695,47 @@ async function ensureHermesApiServerAvailable(options = {}) {
|
|
|
4558
4695
|
});
|
|
4559
4696
|
return { available: true, configResult, started: true, start, health };
|
|
4560
4697
|
}
|
|
4698
|
+
if (health.authInvalid) {
|
|
4699
|
+
void options.logger?.warn("gateway_api_server_auth_repair_requested", {
|
|
4700
|
+
profile: profileName,
|
|
4701
|
+
port: configResult.apiServer.port ?? null,
|
|
4702
|
+
issue: health.issue ?? "auth_invalid"
|
|
4703
|
+
});
|
|
4704
|
+
const repairedConfigResult = await repairHermesApiServerConfig(profileName);
|
|
4705
|
+
const repairedStart = await startHermesGatewayOnce(
|
|
4706
|
+
options.paths ?? resolveRuntimePaths(),
|
|
4707
|
+
profileName,
|
|
4708
|
+
options.logger
|
|
4709
|
+
);
|
|
4710
|
+
health = await waitForHermesApiHealth(
|
|
4711
|
+
repairedConfigResult.apiServer,
|
|
4712
|
+
fetcher,
|
|
4713
|
+
options.timeoutMs ?? DEFAULT_START_TIMEOUT_MS
|
|
4714
|
+
);
|
|
4715
|
+
if (health.healthy) {
|
|
4716
|
+
void options.logger?.info("gateway_api_server_auth_repair_succeeded", {
|
|
4717
|
+
profile: profileName,
|
|
4718
|
+
pid: repairedStart.pid,
|
|
4719
|
+
port: repairedConfigResult.apiServer.port ?? null,
|
|
4720
|
+
log_path: repairedStart.logPath,
|
|
4721
|
+
hermes_version: repairedStart.version?.version ?? null
|
|
4722
|
+
});
|
|
4723
|
+
return {
|
|
4724
|
+
available: true,
|
|
4725
|
+
configResult: repairedConfigResult,
|
|
4726
|
+
started: true,
|
|
4727
|
+
start: repairedStart,
|
|
4728
|
+
health
|
|
4729
|
+
};
|
|
4730
|
+
}
|
|
4731
|
+
void options.logger?.error("gateway_api_server_auth_repair_failed", {
|
|
4732
|
+
profile: profileName,
|
|
4733
|
+
pid: repairedStart.pid,
|
|
4734
|
+
port: repairedConfigResult.apiServer.port ?? null,
|
|
4735
|
+
log_path: repairedStart.logPath,
|
|
4736
|
+
issue: health.issue ?? "unknown"
|
|
4737
|
+
});
|
|
4738
|
+
}
|
|
4561
4739
|
const logHint = await readRecentGatewayLogHint(start.logPath);
|
|
4562
4740
|
void options.logger?.error("gateway_auto_start_failed", {
|
|
4563
4741
|
profile: profileName,
|
|
@@ -4648,12 +4826,9 @@ async function readHermesVersion(options = {}) {
|
|
|
4648
4826
|
}
|
|
4649
4827
|
async function execHermesVersion(hermesBin, logger) {
|
|
4650
4828
|
const failures = [];
|
|
4651
|
-
for (const args of [["version"], ["
|
|
4829
|
+
for (const args of [["--version"], ["version"]]) {
|
|
4652
4830
|
try {
|
|
4653
|
-
return await
|
|
4654
|
-
timeout: 5e3,
|
|
4655
|
-
windowsHide: true
|
|
4656
|
-
});
|
|
4831
|
+
return await spawnHermesVersionCommand(hermesBin, args, 5e3);
|
|
4657
4832
|
} catch (error) {
|
|
4658
4833
|
const failure = describeVersionCommandFailure(hermesBin, args, error);
|
|
4659
4834
|
failures.push(failure);
|
|
@@ -4669,6 +4844,85 @@ async function execHermesVersion(hermesBin, logger) {
|
|
|
4669
4844
|
});
|
|
4670
4845
|
throw new Error(summary);
|
|
4671
4846
|
}
|
|
4847
|
+
async function spawnHermesVersionCommand(hermesBin, args, timeoutMs) {
|
|
4848
|
+
return await new Promise((resolve, reject) => {
|
|
4849
|
+
const child = spawn(hermesBin, args, {
|
|
4850
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
4851
|
+
windowsHide: true
|
|
4852
|
+
});
|
|
4853
|
+
let stdout = "";
|
|
4854
|
+
let stderr = "";
|
|
4855
|
+
let settled = false;
|
|
4856
|
+
const timer = setTimeout(() => {
|
|
4857
|
+
finish(() => {
|
|
4858
|
+
reject(
|
|
4859
|
+
createVersionCommandError(
|
|
4860
|
+
`${hermesBin} ${args.join(" ")} timed out after ${timeoutMs}ms`,
|
|
4861
|
+
{ stdout, stderr, killed: true }
|
|
4862
|
+
)
|
|
4863
|
+
);
|
|
4864
|
+
}, true);
|
|
4865
|
+
}, timeoutMs);
|
|
4866
|
+
const finish = (callback, kill = false) => {
|
|
4867
|
+
if (settled) {
|
|
4868
|
+
return;
|
|
4869
|
+
}
|
|
4870
|
+
settled = true;
|
|
4871
|
+
clearTimeout(timer);
|
|
4872
|
+
if (kill && child.pid && !child.killed) {
|
|
4873
|
+
child.kill();
|
|
4874
|
+
}
|
|
4875
|
+
callback();
|
|
4876
|
+
};
|
|
4877
|
+
const resolveIfVersionPrinted = () => {
|
|
4878
|
+
if (parseHermesVersion(stdout)) {
|
|
4879
|
+
finish(() => resolve({ stdout: `${stdout}
|
|
4880
|
+
${stderr}`.trim() }), true);
|
|
4881
|
+
}
|
|
4882
|
+
};
|
|
4883
|
+
child.stdout?.on("data", (chunk) => {
|
|
4884
|
+
stdout += chunk.toString();
|
|
4885
|
+
resolveIfVersionPrinted();
|
|
4886
|
+
});
|
|
4887
|
+
child.stderr?.on("data", (chunk) => {
|
|
4888
|
+
stderr += chunk.toString();
|
|
4889
|
+
});
|
|
4890
|
+
child.once("error", (error) => {
|
|
4891
|
+
finish(() => {
|
|
4892
|
+
reject(
|
|
4893
|
+
createVersionCommandError(error.message, {
|
|
4894
|
+
stdout,
|
|
4895
|
+
stderr,
|
|
4896
|
+
cause: error
|
|
4897
|
+
})
|
|
4898
|
+
);
|
|
4899
|
+
});
|
|
4900
|
+
});
|
|
4901
|
+
child.once("close", (code, signal) => {
|
|
4902
|
+
finish(() => {
|
|
4903
|
+
const raw = `${stdout}
|
|
4904
|
+
${stderr}`.trim();
|
|
4905
|
+
const version = parseHermesVersion(raw);
|
|
4906
|
+
if (code === 0 && (raw || version)) {
|
|
4907
|
+
resolve({ stdout: raw });
|
|
4908
|
+
return;
|
|
4909
|
+
}
|
|
4910
|
+
reject(
|
|
4911
|
+
createVersionCommandError(
|
|
4912
|
+
`${hermesBin} ${args.join(" ")} exited with code ${code ?? "null"}`,
|
|
4913
|
+
{
|
|
4914
|
+
stdout,
|
|
4915
|
+
stderr,
|
|
4916
|
+
code,
|
|
4917
|
+
signal,
|
|
4918
|
+
killed: child.killed
|
|
4919
|
+
}
|
|
4920
|
+
)
|
|
4921
|
+
);
|
|
4922
|
+
});
|
|
4923
|
+
});
|
|
4924
|
+
});
|
|
4925
|
+
}
|
|
4672
4926
|
function assertHermesRunsApiSupported(version, status) {
|
|
4673
4927
|
if (status !== 404) {
|
|
4674
4928
|
return;
|
|
@@ -4720,7 +4974,7 @@ async function startHermesGateway(paths, profileName, logger) {
|
|
|
4720
4974
|
try {
|
|
4721
4975
|
const child = spawn(resolveHermesBin(), gatewayRunArgs(profileName), {
|
|
4722
4976
|
detached: true,
|
|
4723
|
-
env:
|
|
4977
|
+
env: buildHermesGatewayChildEnv(),
|
|
4724
4978
|
stdio: ["ignore", "pipe", "pipe"],
|
|
4725
4979
|
windowsHide: true
|
|
4726
4980
|
});
|
|
@@ -4786,6 +5040,7 @@ async function restartHermesGatewayServiceIfAvailable(options) {
|
|
|
4786
5040
|
}
|
|
4787
5041
|
try {
|
|
4788
5042
|
await execFileAsync2(resolveHermesBin(), ["gateway", "restart"], {
|
|
5043
|
+
env: buildHermesGatewayChildEnv(),
|
|
4789
5044
|
timeout: options.timeoutMs ?? DEFAULT_START_TIMEOUT_MS,
|
|
4790
5045
|
windowsHide: true
|
|
4791
5046
|
});
|
|
@@ -4840,6 +5095,17 @@ function gatewayRunArgs(profileName) {
|
|
|
4840
5095
|
function formatHermesGatewayRunCommand(profileName) {
|
|
4841
5096
|
return `${resolveHermesBin()} ${gatewayRunArgs(profileName).join(" ")}`;
|
|
4842
5097
|
}
|
|
5098
|
+
function buildHermesGatewayChildEnv() {
|
|
5099
|
+
const env = { ...process.env };
|
|
5100
|
+
for (const key of Object.keys(env)) {
|
|
5101
|
+
if (HERMES_GATEWAY_ENV_BLOCKLIST_PREFIXES.some(
|
|
5102
|
+
(prefix) => key.startsWith(prefix)
|
|
5103
|
+
)) {
|
|
5104
|
+
delete env[key];
|
|
5105
|
+
}
|
|
5106
|
+
}
|
|
5107
|
+
return env;
|
|
5108
|
+
}
|
|
4843
5109
|
async function fileExists(filePath) {
|
|
4844
5110
|
return access(filePath).then(() => true).catch(() => false);
|
|
4845
5111
|
}
|
|
@@ -4875,6 +5141,10 @@ async function readHermesApiServerHealth(config, fetcher = fetch) {
|
|
|
4875
5141
|
}
|
|
4876
5142
|
const issue = describeDetailedHealthIssue(record);
|
|
4877
5143
|
const terminal = isTerminalDetailedHealth(record);
|
|
5144
|
+
const authIssue = issue ? null : await probeHermesApiServerAuth(resolvedConfig, fetcher);
|
|
5145
|
+
if (authIssue) {
|
|
5146
|
+
return authIssue;
|
|
5147
|
+
}
|
|
4878
5148
|
return {
|
|
4879
5149
|
healthy: !issue,
|
|
4880
5150
|
terminal,
|
|
@@ -4910,22 +5180,9 @@ async function readHermesApiServerHealth(config, fetcher = fetch) {
|
|
|
4910
5180
|
detailed: record
|
|
4911
5181
|
};
|
|
4912
5182
|
}
|
|
4913
|
-
|
|
4914
|
-
|
|
4915
|
-
|
|
4916
|
-
{
|
|
4917
|
-
method: "GET",
|
|
4918
|
-
headers: authHeaders(resolvedConfig)
|
|
4919
|
-
},
|
|
4920
|
-
fetcher
|
|
4921
|
-
);
|
|
4922
|
-
if (authProbe?.status === 401) {
|
|
4923
|
-
return {
|
|
4924
|
-
healthy: false,
|
|
4925
|
-
authInvalid: true,
|
|
4926
|
-
issue: "Hermes API Server \u8FD4\u56DE 401\uFF0C\u5F53\u524D\u7AEF\u53E3\u53EF\u80FD\u5C5E\u4E8E\u53E6\u4E00\u4E2A Profile\uFF0C\u6216 API Server key \u5DF2\u53D8\u66F4\u3002"
|
|
4927
|
-
};
|
|
4928
|
-
}
|
|
5183
|
+
const authIssue = await probeHermesApiServerAuth(resolvedConfig, fetcher);
|
|
5184
|
+
if (authIssue) {
|
|
5185
|
+
return authIssue;
|
|
4929
5186
|
}
|
|
4930
5187
|
return { healthy: true };
|
|
4931
5188
|
}
|
|
@@ -4941,6 +5198,36 @@ async function readHermesApiServerHealth(config, fetcher = fetch) {
|
|
|
4941
5198
|
issue: describePortHealthFailure(resolvedConfig.port)
|
|
4942
5199
|
};
|
|
4943
5200
|
}
|
|
5201
|
+
async function probeHermesApiServerAuth(config, fetcher) {
|
|
5202
|
+
const response = await fetchWithTimeout(
|
|
5203
|
+
`http://127.0.0.1:${config.port}/v1/models`,
|
|
5204
|
+
{
|
|
5205
|
+
method: "GET",
|
|
5206
|
+
headers: authHeaders(config)
|
|
5207
|
+
},
|
|
5208
|
+
fetcher
|
|
5209
|
+
);
|
|
5210
|
+
if (!response) {
|
|
5211
|
+
return {
|
|
5212
|
+
healthy: false,
|
|
5213
|
+
issue: "Hermes API Server \u9274\u6743\u63A2\u6D4B\u65E0\u54CD\u5E94\uFF1A/v1/models \u6CA1\u6709\u5728\u8D85\u65F6\u65F6\u95F4\u5185\u8FD4\u56DE\u3002"
|
|
5214
|
+
};
|
|
5215
|
+
}
|
|
5216
|
+
if (response?.status === 401) {
|
|
5217
|
+
return {
|
|
5218
|
+
healthy: false,
|
|
5219
|
+
authInvalid: true,
|
|
5220
|
+
issue: "Hermes API Server \u8FD4\u56DE 401\uFF0C\u5F53\u524D\u7AEF\u53E3\u53EF\u80FD\u5C5E\u4E8E\u53E6\u4E00\u4E2A Profile\uFF0C\u6216 API Server key \u5DF2\u53D8\u66F4\u3002"
|
|
5221
|
+
};
|
|
5222
|
+
}
|
|
5223
|
+
if (response && !response.ok) {
|
|
5224
|
+
return {
|
|
5225
|
+
healthy: false,
|
|
5226
|
+
issue: `Hermes API Server \u9274\u6743\u63A2\u6D4B\u5931\u8D25\uFF1A/v1/models \u8FD4\u56DE HTTP ${response.status}\u3002`
|
|
5227
|
+
};
|
|
5228
|
+
}
|
|
5229
|
+
return null;
|
|
5230
|
+
}
|
|
4944
5231
|
function fetchWithTimeout(input, init, fetcher) {
|
|
4945
5232
|
const controller = new AbortController();
|
|
4946
5233
|
const timer = setTimeout(() => controller.abort(), HEALTH_TIMEOUT_MS);
|
|
@@ -5122,6 +5409,21 @@ async function probeHermesVersion(hermesBin, options) {
|
|
|
5122
5409
|
}
|
|
5123
5410
|
}
|
|
5124
5411
|
}
|
|
5412
|
+
function createVersionCommandError(message, details) {
|
|
5413
|
+
const error = new Error(message, { cause: details.cause });
|
|
5414
|
+
error.stdout = details.stdout;
|
|
5415
|
+
error.stderr = details.stderr;
|
|
5416
|
+
if (details.code !== void 0) {
|
|
5417
|
+
error.code = details.code;
|
|
5418
|
+
}
|
|
5419
|
+
if (details.signal !== void 0) {
|
|
5420
|
+
error.signal = details.signal;
|
|
5421
|
+
}
|
|
5422
|
+
if (details.killed !== void 0) {
|
|
5423
|
+
error.killed = details.killed;
|
|
5424
|
+
}
|
|
5425
|
+
return error;
|
|
5426
|
+
}
|
|
5125
5427
|
function describeVersionCommandFailure(hermesBin, args, error) {
|
|
5126
5428
|
const message = error instanceof Error ? error.message : String(error);
|
|
5127
5429
|
const details = readExecErrorDetails(error, message);
|
|
@@ -8979,24 +9281,15 @@ var ConversationQueryCoordinator = class {
|
|
|
8979
9281
|
async listConversationPage(options = {}) {
|
|
8980
9282
|
const limit = normalizeConversationListPageLimit(options.limit);
|
|
8981
9283
|
const cursor = decodeConversationListCursor(options.cursor);
|
|
8982
|
-
|
|
9284
|
+
return this.listIndexedConversationPage({
|
|
8983
9285
|
limit,
|
|
8984
|
-
cursor
|
|
8985
|
-
|
|
8986
|
-
|
|
8987
|
-
return this.listConversationPageFromStore({ limit, cursor });
|
|
8988
|
-
}
|
|
8989
|
-
const summaries = await this.summarizeIndexedConversations(
|
|
8990
|
-
indexedPage.records
|
|
8991
|
-
);
|
|
8992
|
-
return {
|
|
8993
|
-
conversations: summaries,
|
|
8994
|
-
page: {
|
|
9286
|
+
cursor,
|
|
9287
|
+
fallback: () => this.listConversationPageFromStore({ limit, cursor }),
|
|
9288
|
+
listPage: (pageCursor) => listConversationStatsPage(this.deps.paths, {
|
|
8995
9289
|
limit,
|
|
8996
|
-
|
|
8997
|
-
|
|
8998
|
-
|
|
8999
|
-
};
|
|
9290
|
+
cursor: pageCursor
|
|
9291
|
+
})
|
|
9292
|
+
});
|
|
9000
9293
|
}
|
|
9001
9294
|
async searchConversationPage(options = {}) {
|
|
9002
9295
|
const query = normalizeConversationSearchQuery(options.query);
|
|
@@ -9005,20 +9298,72 @@ var ConversationQueryCoordinator = class {
|
|
|
9005
9298
|
}
|
|
9006
9299
|
const limit = normalizeConversationListPageLimit(options.limit);
|
|
9007
9300
|
const cursor = decodeConversationListCursor(options.cursor);
|
|
9008
|
-
|
|
9301
|
+
return this.listIndexedConversationPage({
|
|
9009
9302
|
limit,
|
|
9010
9303
|
cursor,
|
|
9011
|
-
|
|
9304
|
+
listPage: (pageCursor) => searchConversationStatsPage(this.deps.paths, {
|
|
9305
|
+
limit,
|
|
9306
|
+
cursor: pageCursor,
|
|
9307
|
+
query
|
|
9308
|
+
})
|
|
9012
9309
|
});
|
|
9013
|
-
|
|
9014
|
-
|
|
9015
|
-
|
|
9310
|
+
}
|
|
9311
|
+
async listIndexedConversationPage(input) {
|
|
9312
|
+
const collected = [];
|
|
9313
|
+
const seenConversationIds = /* @__PURE__ */ new Set();
|
|
9314
|
+
let scanCursor = input.cursor;
|
|
9315
|
+
let usedIndex = false;
|
|
9316
|
+
while (collected.length <= input.limit) {
|
|
9317
|
+
const indexedPage = await input.listPage(scanCursor);
|
|
9318
|
+
if (indexedPage.records.length === 0) {
|
|
9319
|
+
if (!usedIndex && input.fallback) {
|
|
9320
|
+
return input.fallback();
|
|
9321
|
+
}
|
|
9322
|
+
return this.buildConversationListPage({
|
|
9323
|
+
limit: input.limit,
|
|
9324
|
+
conversations: collected,
|
|
9325
|
+
hasMore: false
|
|
9326
|
+
});
|
|
9327
|
+
}
|
|
9328
|
+
usedIndex = true;
|
|
9329
|
+
const summaries = await this.summarizeIndexedConversations(
|
|
9330
|
+
indexedPage.records
|
|
9331
|
+
);
|
|
9332
|
+
for (const summary of summaries) {
|
|
9333
|
+
if (!seenConversationIds.add(summary.id)) {
|
|
9334
|
+
continue;
|
|
9335
|
+
}
|
|
9336
|
+
collected.push(summary);
|
|
9337
|
+
if (collected.length > input.limit) {
|
|
9338
|
+
break;
|
|
9339
|
+
}
|
|
9340
|
+
}
|
|
9341
|
+
if (collected.length > input.limit) {
|
|
9342
|
+
break;
|
|
9343
|
+
}
|
|
9344
|
+
if (!indexedPage.hasMore) {
|
|
9345
|
+
return this.buildConversationListPage({
|
|
9346
|
+
limit: input.limit,
|
|
9347
|
+
conversations: collected,
|
|
9348
|
+
hasMore: false
|
|
9349
|
+
});
|
|
9350
|
+
}
|
|
9351
|
+
scanCursor = conversationListCursorFromRecord(indexedPage.records.at(-1));
|
|
9352
|
+
}
|
|
9353
|
+
return this.buildConversationListPage({
|
|
9354
|
+
limit: input.limit,
|
|
9355
|
+
conversations: collected,
|
|
9356
|
+
hasMore: true
|
|
9357
|
+
});
|
|
9358
|
+
}
|
|
9359
|
+
buildConversationListPage(input) {
|
|
9360
|
+
const conversations = input.conversations.slice(0, input.limit);
|
|
9016
9361
|
return {
|
|
9017
|
-
conversations
|
|
9362
|
+
conversations,
|
|
9018
9363
|
page: {
|
|
9019
|
-
limit,
|
|
9020
|
-
has_more:
|
|
9021
|
-
next_cursor:
|
|
9364
|
+
limit: input.limit,
|
|
9365
|
+
has_more: input.hasMore,
|
|
9366
|
+
next_cursor: input.hasMore && conversations.length > 0 ? encodeConversationListCursorFromSummary(conversations.at(-1)) : null
|
|
9022
9367
|
}
|
|
9023
9368
|
};
|
|
9024
9369
|
}
|
|
@@ -9177,6 +9522,12 @@ function encodeConversationListCursor(record) {
|
|
|
9177
9522
|
"utf8"
|
|
9178
9523
|
).toString("base64url");
|
|
9179
9524
|
}
|
|
9525
|
+
function conversationListCursorFromRecord(record) {
|
|
9526
|
+
return {
|
|
9527
|
+
updatedAt: record.updatedAt,
|
|
9528
|
+
conversationId: record.conversationId
|
|
9529
|
+
};
|
|
9530
|
+
}
|
|
9180
9531
|
function encodeConversationListCursorFromSummary(summary) {
|
|
9181
9532
|
return encodeConversationListCursor({
|
|
9182
9533
|
conversationId: summary.id,
|
|
@@ -11668,6 +12019,46 @@ async function callHermesApi(path26, init, options) {
|
|
|
11668
12019
|
startedAt,
|
|
11669
12020
|
response
|
|
11670
12021
|
);
|
|
12022
|
+
if (response.status !== 401) {
|
|
12023
|
+
return response;
|
|
12024
|
+
}
|
|
12025
|
+
void options.logger?.warn("hermes_api_request_repairing_after_401", {
|
|
12026
|
+
method,
|
|
12027
|
+
path: path26,
|
|
12028
|
+
profile: options.profileName ?? "default",
|
|
12029
|
+
port: config.port ?? null,
|
|
12030
|
+
duration_ms: Date.now() - startedAt
|
|
12031
|
+
});
|
|
12032
|
+
const profileName = options.profileName ?? "default";
|
|
12033
|
+
const repairedConfig = await repairHermesApiServerConfig(profileName);
|
|
12034
|
+
const repairedAvailability = await ensureHermesApiServerAvailable({
|
|
12035
|
+
fetchImpl: options.fetchImpl,
|
|
12036
|
+
forceRestart: true,
|
|
12037
|
+
logger: options.logger,
|
|
12038
|
+
profileName
|
|
12039
|
+
});
|
|
12040
|
+
config = repairedAvailability.configResult.apiServer ?? repairedConfig.apiServer;
|
|
12041
|
+
try {
|
|
12042
|
+
response = await request();
|
|
12043
|
+
} catch (error) {
|
|
12044
|
+
logHermesApiError(
|
|
12045
|
+
options.logger,
|
|
12046
|
+
method,
|
|
12047
|
+
path26,
|
|
12048
|
+
options.profileName,
|
|
12049
|
+
startedAt,
|
|
12050
|
+
error
|
|
12051
|
+
);
|
|
12052
|
+
throw error;
|
|
12053
|
+
}
|
|
12054
|
+
logHermesApiResponse(
|
|
12055
|
+
options.logger,
|
|
12056
|
+
method,
|
|
12057
|
+
path26,
|
|
12058
|
+
options.profileName,
|
|
12059
|
+
startedAt,
|
|
12060
|
+
response
|
|
12061
|
+
);
|
|
11671
12062
|
return response;
|
|
11672
12063
|
}
|
|
11673
12064
|
async function fetchHermesApi(fetcher, config, path26, init, options) {
|
|
@@ -15948,11 +16339,15 @@ function createHttpErrorMiddleware(logger) {
|
|
|
15948
16339
|
}
|
|
15949
16340
|
|
|
15950
16341
|
// src/hermes/profiles.ts
|
|
15951
|
-
import {
|
|
16342
|
+
import { execFile as execFile4 } from "child_process";
|
|
16343
|
+
import { readdir as readdir9, readFile as readFile12, rename as rename3, stat as stat12 } from "fs/promises";
|
|
15952
16344
|
import path18 from "path";
|
|
16345
|
+
import { promisify as promisify4 } from "util";
|
|
15953
16346
|
import YAML2 from "yaml";
|
|
15954
16347
|
var DEFAULT_PROFILE = "default";
|
|
15955
16348
|
var PROFILE_NAME_PATTERN4 = /^[a-zA-Z0-9._-]{1,64}$/;
|
|
16349
|
+
var PROFILE_DELETE_TIMEOUT_MS = 3e4;
|
|
16350
|
+
var execFileAsync4 = promisify4(execFile4);
|
|
15956
16351
|
async function listHermesProfiles(paths = resolveRuntimePaths()) {
|
|
15957
16352
|
const profiles = /* @__PURE__ */ new Map();
|
|
15958
16353
|
profiles.set(DEFAULT_PROFILE, await profileInfo(DEFAULT_PROFILE, paths));
|
|
@@ -16024,12 +16419,7 @@ async function updateHermesProfileMetadata(name, metadata, paths = resolveRuntim
|
|
|
16024
16419
|
async function deleteHermesProfile(name, paths = resolveRuntimePaths()) {
|
|
16025
16420
|
assertMutableProfile(name);
|
|
16026
16421
|
const profile = await profileInfo(name, paths);
|
|
16027
|
-
const exists = await
|
|
16028
|
-
if (isNodeError14(error, "ENOENT")) {
|
|
16029
|
-
return false;
|
|
16030
|
-
}
|
|
16031
|
-
throw error;
|
|
16032
|
-
});
|
|
16422
|
+
const exists = await pathExists(profile.path);
|
|
16033
16423
|
if (!exists) {
|
|
16034
16424
|
throw new LinkHttpError(
|
|
16035
16425
|
404,
|
|
@@ -16037,7 +16427,14 @@ async function deleteHermesProfile(name, paths = resolveRuntimePaths()) {
|
|
|
16037
16427
|
`Profile "${name}" does not exist`
|
|
16038
16428
|
);
|
|
16039
16429
|
}
|
|
16040
|
-
await
|
|
16430
|
+
await deleteHermesProfileWithCli(name);
|
|
16431
|
+
if (await pathExists(profile.path)) {
|
|
16432
|
+
throw new LinkHttpError(
|
|
16433
|
+
502,
|
|
16434
|
+
"hermes_profile_delete_incomplete",
|
|
16435
|
+
`Hermes CLI reported success, but Profile "${name}" still exists at ${profile.path}`
|
|
16436
|
+
);
|
|
16437
|
+
}
|
|
16041
16438
|
await deleteProfileIdentity(paths, {
|
|
16042
16439
|
profileName: profile.name,
|
|
16043
16440
|
profileUid: profile.uid
|
|
@@ -16093,6 +16490,52 @@ function assertProfileName(name) {
|
|
|
16093
16490
|
throw new LinkHttpError(400, "invalid_profile_name", "invalid profile name");
|
|
16094
16491
|
}
|
|
16095
16492
|
}
|
|
16493
|
+
async function pathExists(targetPath) {
|
|
16494
|
+
return await stat12(targetPath).then(() => true).catch((error) => {
|
|
16495
|
+
if (isNodeError14(error, "ENOENT")) {
|
|
16496
|
+
return false;
|
|
16497
|
+
}
|
|
16498
|
+
throw error;
|
|
16499
|
+
});
|
|
16500
|
+
}
|
|
16501
|
+
async function deleteHermesProfileWithCli(name) {
|
|
16502
|
+
try {
|
|
16503
|
+
await execFileAsync4(resolveHermesBin(), ["profile", "delete", name, "--yes"], {
|
|
16504
|
+
timeout: PROFILE_DELETE_TIMEOUT_MS,
|
|
16505
|
+
windowsHide: true
|
|
16506
|
+
});
|
|
16507
|
+
} catch (error) {
|
|
16508
|
+
throw new LinkHttpError(
|
|
16509
|
+
502,
|
|
16510
|
+
"hermes_profile_delete_failed",
|
|
16511
|
+
`Hermes CLI could not delete Profile "${name}": ${formatExecError(error)}`
|
|
16512
|
+
);
|
|
16513
|
+
}
|
|
16514
|
+
}
|
|
16515
|
+
function formatExecError(error) {
|
|
16516
|
+
if (!(error instanceof Error)) {
|
|
16517
|
+
return String(error);
|
|
16518
|
+
}
|
|
16519
|
+
const output = readExecErrorOutput2(error);
|
|
16520
|
+
return output ? `${error.message}
|
|
16521
|
+
${output}` : error.message;
|
|
16522
|
+
}
|
|
16523
|
+
function readExecErrorOutput2(error) {
|
|
16524
|
+
const parts = [];
|
|
16525
|
+
if ("stdout" in error && error.stdout != null) {
|
|
16526
|
+
const stdout = String(error.stdout).trim();
|
|
16527
|
+
if (stdout) {
|
|
16528
|
+
parts.push(stdout);
|
|
16529
|
+
}
|
|
16530
|
+
}
|
|
16531
|
+
if ("stderr" in error && error.stderr != null) {
|
|
16532
|
+
const stderr = String(error.stderr).trim();
|
|
16533
|
+
if (stderr) {
|
|
16534
|
+
parts.push(stderr);
|
|
16535
|
+
}
|
|
16536
|
+
}
|
|
16537
|
+
return parts.join("\n");
|
|
16538
|
+
}
|
|
16096
16539
|
function isNodeError14(error, code) {
|
|
16097
16540
|
return typeof error === "object" && error !== null && "code" in error && error.code === code;
|
|
16098
16541
|
}
|
|
@@ -16806,7 +17249,7 @@ import {
|
|
|
16806
17249
|
cp,
|
|
16807
17250
|
mkdir as mkdir10,
|
|
16808
17251
|
readFile as readFile13,
|
|
16809
|
-
rm as
|
|
17252
|
+
rm as rm6,
|
|
16810
17253
|
stat as stat13
|
|
16811
17254
|
} from "fs/promises";
|
|
16812
17255
|
import path19 from "path";
|
|
@@ -17107,7 +17550,7 @@ async function generateProfileName(displayName, paths) {
|
|
|
17107
17550
|
for (let attempt = 0; attempt < 100; attempt += 1) {
|
|
17108
17551
|
const suffix = randomSuffix(attempt);
|
|
17109
17552
|
const candidate = `${base}-${suffix}`.slice(0, 64).replace(/[-_]+$/u, "");
|
|
17110
|
-
if (!existing.has(candidate) && !await
|
|
17553
|
+
if (!existing.has(candidate) && !await pathExists2(resolveHermesProfileDir(candidate))) {
|
|
17111
17554
|
return candidate;
|
|
17112
17555
|
}
|
|
17113
17556
|
}
|
|
@@ -17133,7 +17576,7 @@ function randomSuffix(attempt) {
|
|
|
17133
17576
|
}
|
|
17134
17577
|
async function applyProfileCreationPostSteps(input) {
|
|
17135
17578
|
const profilePath = resolveHermesProfileDir(input.profileName);
|
|
17136
|
-
if (!await
|
|
17579
|
+
if (!await pathExists2(profilePath)) {
|
|
17137
17580
|
await ensureDirectoryWithInheritedMetadata(profilePath, 448);
|
|
17138
17581
|
}
|
|
17139
17582
|
if (input.sourceProfile && input.copyScopes.length > 0) {
|
|
@@ -17299,10 +17742,10 @@ async function writeEnvValues(profileName, values) {
|
|
|
17299
17742
|
async function copySkills(sourceProfile, targetProfile) {
|
|
17300
17743
|
const sourceSkills = path19.join(resolveHermesProfileDir(sourceProfile), "skills");
|
|
17301
17744
|
const targetSkills = path19.join(resolveHermesProfileDir(targetProfile), "skills");
|
|
17302
|
-
if (!await
|
|
17745
|
+
if (!await pathExists2(sourceSkills)) {
|
|
17303
17746
|
return;
|
|
17304
17747
|
}
|
|
17305
|
-
await
|
|
17748
|
+
await rm6(targetSkills, { recursive: true, force: true });
|
|
17306
17749
|
await cp(sourceSkills, targetSkills, {
|
|
17307
17750
|
recursive: true,
|
|
17308
17751
|
force: true,
|
|
@@ -17350,7 +17793,7 @@ async function failProfileCreation(input) {
|
|
|
17350
17793
|
await input.writer.write(`
|
|
17351
17794
|
Rolling back ${input.rollbackProfileName}...
|
|
17352
17795
|
`);
|
|
17353
|
-
await
|
|
17796
|
+
await rm6(resolveHermesProfileDir(input.rollbackProfileName), {
|
|
17354
17797
|
recursive: true,
|
|
17355
17798
|
force: true
|
|
17356
17799
|
}).catch(() => void 0);
|
|
@@ -17401,14 +17844,14 @@ function profileCreationLogPath(paths) {
|
|
|
17401
17844
|
async function clearProfileCreationLogFiles(paths) {
|
|
17402
17845
|
const primary = profileCreationLogPath(paths);
|
|
17403
17846
|
await Promise.all([
|
|
17404
|
-
|
|
17847
|
+
rm6(primary, { force: true }).catch(() => void 0),
|
|
17405
17848
|
...Array.from(
|
|
17406
17849
|
{ length: PROFILE_CREATE_LOG_MAX_FILES },
|
|
17407
|
-
(_, index) =>
|
|
17850
|
+
(_, index) => rm6(`${primary}.${index + 1}`, { force: true }).catch(() => void 0)
|
|
17408
17851
|
)
|
|
17409
17852
|
]);
|
|
17410
17853
|
}
|
|
17411
|
-
async function
|
|
17854
|
+
async function pathExists2(targetPath) {
|
|
17412
17855
|
return await stat13(targetPath).then(() => true).catch((error) => {
|
|
17413
17856
|
if (isNodeError15(error, "ENOENT")) {
|
|
17414
17857
|
return false;
|
|
@@ -20260,7 +20703,7 @@ function readModelList(payload) {
|
|
|
20260
20703
|
// src/hermes/updates.ts
|
|
20261
20704
|
import { EventEmitter as EventEmitter3 } from "events";
|
|
20262
20705
|
import { spawn as spawn3 } from "child_process";
|
|
20263
|
-
import { mkdir as mkdir11, readFile as readFile16, rm as
|
|
20706
|
+
import { mkdir as mkdir11, readFile as readFile16, rm as rm7 } from "fs/promises";
|
|
20264
20707
|
import path22 from "path";
|
|
20265
20708
|
var SERVER_HERMES_RELEASES_LATEST_PATH = "/api/v1/hermes-agent/releases/latest";
|
|
20266
20709
|
var RELEASE_CACHE_TTL_MS = 6 * 60 * 60 * 1e3;
|
|
@@ -20550,10 +20993,10 @@ function updateLogPath(paths) {
|
|
|
20550
20993
|
async function clearUpdateLogFiles(paths) {
|
|
20551
20994
|
const primary = updateLogPath(paths);
|
|
20552
20995
|
await Promise.all([
|
|
20553
|
-
|
|
20996
|
+
rm7(primary, { force: true }).catch(() => void 0),
|
|
20554
20997
|
...Array.from(
|
|
20555
20998
|
{ length: UPDATE_LOG_MAX_FILES },
|
|
20556
|
-
(_, index) =>
|
|
20999
|
+
(_, index) => rm7(`${primary}.${index + 1}`, { force: true }).catch(() => void 0)
|
|
20557
21000
|
)
|
|
20558
21001
|
]);
|
|
20559
21002
|
}
|
|
@@ -20645,20 +21088,22 @@ function readString17(payload, key) {
|
|
|
20645
21088
|
// src/link/updates.ts
|
|
20646
21089
|
import { spawn as spawn5 } from "child_process";
|
|
20647
21090
|
import { EventEmitter as EventEmitter4 } from "events";
|
|
20648
|
-
import { mkdir as mkdir14, readFile as readFile18, rm as
|
|
21091
|
+
import { mkdir as mkdir14, readFile as readFile18, rm as rm10 } from "fs/promises";
|
|
20649
21092
|
import path24 from "path";
|
|
20650
21093
|
|
|
20651
21094
|
// src/daemon/process.ts
|
|
20652
21095
|
import { spawn as spawn4 } from "child_process";
|
|
20653
|
-
import { mkdir as mkdir13, readFile as readFile17, rm as
|
|
21096
|
+
import { mkdir as mkdir13, readFile as readFile17, rm as rm9 } from "fs/promises";
|
|
20654
21097
|
import path23 from "path";
|
|
20655
21098
|
|
|
20656
21099
|
// src/daemon/service.ts
|
|
20657
21100
|
import { createServer } from "http";
|
|
20658
|
-
import { mkdir as mkdir12, rm as
|
|
21101
|
+
import { mkdir as mkdir12, rm as rm8, writeFile as writeFile3 } from "fs/promises";
|
|
20659
21102
|
|
|
20660
21103
|
// src/relay/control-client.ts
|
|
20661
21104
|
import WebSocket from "ws";
|
|
21105
|
+
var RELAY_SSE_BATCH_FLUSH_INTERVAL_MS = 50;
|
|
21106
|
+
var RELAY_SSE_BATCH_FLUSH_BYTES = 2 * 1024;
|
|
20662
21107
|
function connectRelayControl(options) {
|
|
20663
21108
|
const wsUrl = new URL(`${options.relayBaseUrl.replace(/\/+$/u, "")}/api/v1/relay/link/connect`);
|
|
20664
21109
|
wsUrl.protocol = wsUrl.protocol === "https:" ? "wss:" : "ws:";
|
|
@@ -20794,6 +21239,7 @@ async function handleFrame(socket, raw, localPort, abortControllers) {
|
|
|
20794
21239
|
}
|
|
20795
21240
|
const abortController = new AbortController();
|
|
20796
21241
|
abortControllers.set(frame.id, abortController);
|
|
21242
|
+
let sseBatcher = null;
|
|
20797
21243
|
try {
|
|
20798
21244
|
const response = await fetch(`http://127.0.0.1:${localPort}${frame.path}`, {
|
|
20799
21245
|
method: frame.method,
|
|
@@ -20805,14 +21251,16 @@ async function handleFrame(socket, raw, localPort, abortControllers) {
|
|
|
20805
21251
|
const contentType = response.headers.get("content-type") ?? "";
|
|
20806
21252
|
if (response.body && contentType.includes("text/event-stream")) {
|
|
20807
21253
|
socket.send(JSON.stringify({ type: "http.stream.start", id: frame.id, status: response.status, headers }));
|
|
21254
|
+
sseBatcher = createRelayStreamChunkBatcher(socket, frame.id);
|
|
20808
21255
|
const reader = response.body.getReader();
|
|
20809
21256
|
while (true) {
|
|
20810
21257
|
const next = await reader.read();
|
|
20811
21258
|
if (next.done) {
|
|
20812
21259
|
break;
|
|
20813
21260
|
}
|
|
20814
|
-
|
|
21261
|
+
sseBatcher.push(next.value);
|
|
20815
21262
|
}
|
|
21263
|
+
sseBatcher.flush();
|
|
20816
21264
|
socket.send(JSON.stringify({ type: "http.stream.end", id: frame.id }));
|
|
20817
21265
|
return;
|
|
20818
21266
|
}
|
|
@@ -20822,15 +21270,73 @@ async function handleFrame(socket, raw, localPort, abortControllers) {
|
|
|
20822
21270
|
if (abortController.signal.aborted || isAbortError2(error)) {
|
|
20823
21271
|
return;
|
|
20824
21272
|
}
|
|
21273
|
+
sseBatcher?.flush();
|
|
20825
21274
|
const message = error instanceof Error ? error.message : "Relay request failed";
|
|
20826
21275
|
socket.send(JSON.stringify({ type: "http.error", id: frame.id, status: 502, message }));
|
|
20827
21276
|
} finally {
|
|
21277
|
+
sseBatcher?.dispose();
|
|
20828
21278
|
abortControllers.delete(frame.id);
|
|
20829
21279
|
}
|
|
20830
21280
|
}
|
|
20831
21281
|
function isAbortError2(error) {
|
|
20832
21282
|
return typeof error === "object" && error !== null && "name" in error && error.name === "AbortError";
|
|
20833
21283
|
}
|
|
21284
|
+
function createRelayStreamChunkBatcher(socket, id) {
|
|
21285
|
+
let chunks = [];
|
|
21286
|
+
let totalBytes = 0;
|
|
21287
|
+
let flushTimer = null;
|
|
21288
|
+
const clearFlushTimer = () => {
|
|
21289
|
+
if (flushTimer == null) {
|
|
21290
|
+
return;
|
|
21291
|
+
}
|
|
21292
|
+
clearTimeout(flushTimer);
|
|
21293
|
+
flushTimer = null;
|
|
21294
|
+
};
|
|
21295
|
+
const flush = () => {
|
|
21296
|
+
clearFlushTimer();
|
|
21297
|
+
if (totalBytes <= 0) {
|
|
21298
|
+
return;
|
|
21299
|
+
}
|
|
21300
|
+
const bodyBase64 = Buffer.concat(chunks, totalBytes).toString("base64");
|
|
21301
|
+
chunks = [];
|
|
21302
|
+
totalBytes = 0;
|
|
21303
|
+
if (socket.readyState !== WebSocket.OPEN) {
|
|
21304
|
+
return;
|
|
21305
|
+
}
|
|
21306
|
+
socket.send(JSON.stringify({ type: "http.stream.chunk", id, bodyBase64 }));
|
|
21307
|
+
};
|
|
21308
|
+
const scheduleFlush = () => {
|
|
21309
|
+
if (flushTimer != null) {
|
|
21310
|
+
return;
|
|
21311
|
+
}
|
|
21312
|
+
flushTimer = setTimeout(() => {
|
|
21313
|
+
flushTimer = null;
|
|
21314
|
+
flush();
|
|
21315
|
+
}, RELAY_SSE_BATCH_FLUSH_INTERVAL_MS);
|
|
21316
|
+
flushTimer.unref?.();
|
|
21317
|
+
};
|
|
21318
|
+
return {
|
|
21319
|
+
push(chunk) {
|
|
21320
|
+
if (chunk.byteLength <= 0) {
|
|
21321
|
+
return;
|
|
21322
|
+
}
|
|
21323
|
+
const buffer = Buffer.from(chunk);
|
|
21324
|
+
chunks.push(buffer);
|
|
21325
|
+
totalBytes += buffer.byteLength;
|
|
21326
|
+
if (totalBytes >= RELAY_SSE_BATCH_FLUSH_BYTES) {
|
|
21327
|
+
flush();
|
|
21328
|
+
return;
|
|
21329
|
+
}
|
|
21330
|
+
scheduleFlush();
|
|
21331
|
+
},
|
|
21332
|
+
flush,
|
|
21333
|
+
dispose() {
|
|
21334
|
+
clearFlushTimer();
|
|
21335
|
+
chunks = [];
|
|
21336
|
+
totalBytes = 0;
|
|
21337
|
+
}
|
|
21338
|
+
};
|
|
21339
|
+
}
|
|
20834
21340
|
|
|
20835
21341
|
// src/runtime/system-info.ts
|
|
20836
21342
|
import { execFileSync } from "child_process";
|
|
@@ -21705,7 +22211,7 @@ async function startLinkService(options = {}) {
|
|
|
21705
22211
|
await logger.info("service_stopped");
|
|
21706
22212
|
await logger.flush();
|
|
21707
22213
|
if (options.writePidFile) {
|
|
21708
|
-
await
|
|
22214
|
+
await rm8(pidFilePath(paths), { force: true }).catch(() => void 0);
|
|
21709
22215
|
}
|
|
21710
22216
|
}
|
|
21711
22217
|
};
|
|
@@ -21897,7 +22403,7 @@ async function stopDaemonProcess(paths = resolveRuntimePaths()) {
|
|
|
21897
22403
|
try {
|
|
21898
22404
|
process.kill(status.pid, "SIGTERM");
|
|
21899
22405
|
} catch {
|
|
21900
|
-
await
|
|
22406
|
+
await rm9(pidFilePath(paths), { force: true }).catch(() => void 0);
|
|
21901
22407
|
return await getDaemonStatus(paths);
|
|
21902
22408
|
}
|
|
21903
22409
|
for (let index = 0; index < 20; index += 1) {
|
|
@@ -21919,7 +22425,7 @@ async function stopDaemonProcess(paths = resolveRuntimePaths()) {
|
|
|
21919
22425
|
}
|
|
21920
22426
|
}
|
|
21921
22427
|
if (!isProcessAlive3(status.pid) || !await pidBackedServiceIsReachable(paths)) {
|
|
21922
|
-
await
|
|
22428
|
+
await rm9(pidFilePath(paths), { force: true }).catch(() => void 0);
|
|
21923
22429
|
}
|
|
21924
22430
|
return await getDaemonStatus(paths);
|
|
21925
22431
|
}
|
|
@@ -21927,7 +22433,7 @@ async function getDaemonStatus(paths = resolveRuntimePaths()) {
|
|
|
21927
22433
|
const pidFile = pidFilePath(paths);
|
|
21928
22434
|
const pid = await readPid(pidFile);
|
|
21929
22435
|
if (pid && !isProcessAlive3(pid)) {
|
|
21930
|
-
await
|
|
22436
|
+
await rm9(pidFile, { force: true }).catch(() => void 0);
|
|
21931
22437
|
return {
|
|
21932
22438
|
running: false,
|
|
21933
22439
|
pid: null,
|
|
@@ -22382,10 +22888,10 @@ function updateLogPath2(paths) {
|
|
|
22382
22888
|
async function clearUpdateLogFiles2(paths) {
|
|
22383
22889
|
const primary = updateLogPath2(paths);
|
|
22384
22890
|
await Promise.all([
|
|
22385
|
-
|
|
22891
|
+
rm10(primary, { force: true }).catch(() => void 0),
|
|
22386
22892
|
...Array.from(
|
|
22387
22893
|
{ length: UPDATE_LOG_MAX_FILES2 },
|
|
22388
|
-
(_, index) =>
|
|
22894
|
+
(_, index) => rm10(`${primary}.${index + 1}`, { force: true }).catch(() => void 0)
|
|
22389
22895
|
)
|
|
22390
22896
|
]);
|
|
22391
22897
|
}
|
|
@@ -22442,7 +22948,7 @@ function readString18(payload, key) {
|
|
|
22442
22948
|
|
|
22443
22949
|
// src/pairing/pairing.ts
|
|
22444
22950
|
import path25 from "path";
|
|
22445
|
-
import { rm as
|
|
22951
|
+
import { rm as rm11 } from "fs/promises";
|
|
22446
22952
|
|
|
22447
22953
|
// src/relay/bootstrap.ts
|
|
22448
22954
|
var RelayNetworkError = class extends Error {
|
|
@@ -22704,7 +23210,7 @@ async function readPairingClaim(sessionId, paths = resolveRuntimePaths()) {
|
|
|
22704
23210
|
};
|
|
22705
23211
|
}
|
|
22706
23212
|
async function clearPairingClaim(sessionId, paths = resolveRuntimePaths()) {
|
|
22707
|
-
await
|
|
23213
|
+
await rm11(pairingClaimPath(sessionId, paths), { force: true }).catch(() => void 0);
|
|
22708
23214
|
}
|
|
22709
23215
|
async function claimPairing(input) {
|
|
22710
23216
|
const paths = input.paths ?? resolveRuntimePaths();
|
package/dist/cli/index.js
CHANGED
package/dist/http/app.js
CHANGED