@hermespilot/link 0.6.6 → 0.6.7
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-4OXUJNO6.js → chunk-FWX7ZUP4.js} +266 -15
- package/dist/cli/index.js +47 -2
- package/dist/http/app.d.ts +3 -0
- package/dist/http/app.js +1 -1
- package/package.json +1 -1
|
@@ -1465,6 +1465,9 @@ var messages = {
|
|
|
1465
1465
|
"config.notice.autoFilled": "Hermes API Server was auto-filled with {fields}; existing port/host/key were not overwritten.",
|
|
1466
1466
|
"config.notice.repairDefault": "Hermes API Server kept the default port and rotated the key to repair an old Gateway or a key mismatch that caused 401.",
|
|
1467
1467
|
"config.notice.repairProfile": "Hermes API Server was reassigned a local port and had its key rotated to repair an old Gateway or a key mismatch that caused 401.",
|
|
1468
|
+
"config.yaml.invalid": "Hermes Profile config is not valid YAML, so Link cannot write settings yet. Fix {path}, then try again.{details}{more}",
|
|
1469
|
+
"config.yaml.details": " YAML error: {errors}",
|
|
1470
|
+
"config.yaml.more": " and {count} more error(s).",
|
|
1468
1471
|
"daemon.description": "Run Hermes Link in the foreground",
|
|
1469
1472
|
"daemon.foreground": "Hermes Link foreground daemon is running. Press Ctrl+C to stop.",
|
|
1470
1473
|
"logs.description": "Show or follow Hermes Link logs",
|
|
@@ -1520,6 +1523,7 @@ var messages = {
|
|
|
1520
1523
|
"pair.autostartFailed": "Pairing succeeded, but boot autostart could not be enabled: {message}",
|
|
1521
1524
|
"doctor.description": "Run local diagnostics",
|
|
1522
1525
|
"doctor.installOnly": "only check npm global command and PATH setup",
|
|
1526
|
+
"doctor.noProfiles": "skip Hermes Profile diagnostics",
|
|
1523
1527
|
"doctor.installHeader": "Install/PATH diagnostics:",
|
|
1524
1528
|
"doctor.installNpmPrefix": "npm global prefix: {value}",
|
|
1525
1529
|
"doctor.installGlobalBin": "npm global bin directory: {value}",
|
|
@@ -1549,6 +1553,13 @@ var messages = {
|
|
|
1549
1553
|
"doctor.apiReady": "Hermes API Server: ready",
|
|
1550
1554
|
"doctor.apiStarted": "Hermes API Server: started and ready",
|
|
1551
1555
|
"doctor.apiUnavailable": "Hermes API Server: unavailable. {message}",
|
|
1556
|
+
"doctor.profilesHeader": "Hermes Profiles:",
|
|
1557
|
+
"doctor.profilesNone": "- no profiles found",
|
|
1558
|
+
"doctor.profileLine": "- {profile}: {endpoint}; {state}",
|
|
1559
|
+
"doctor.profileReady": "ready",
|
|
1560
|
+
"doctor.profileNotRunning": "not running ({message})",
|
|
1561
|
+
"doctor.profilePreparedState": "prepared config; {state}",
|
|
1562
|
+
"doctor.profilePrepareFailed": "prepare failed ({message})",
|
|
1552
1563
|
"doctor.apiUnavailable.summary": "Hermes API Server: unavailable",
|
|
1553
1564
|
"doctor.apiUnavailable.profile": "Profile: {profile}; port: {port}",
|
|
1554
1565
|
"doctor.apiUnavailable.diagnosisDivider": "----- API Server diagnosis -----",
|
|
@@ -1668,6 +1679,9 @@ var messages = {
|
|
|
1668
1679
|
"config.notice.autoFilled": "\u5DF2\u4E3A Hermes API Server \u81EA\u52A8\u8865\u5145 {fields}\uFF1B\u672A\u8986\u76D6\u5DF2\u6709 port/host/key\u3002",
|
|
1669
1680
|
"config.notice.repairDefault": "\u5DF2\u4E3A Hermes API Server \u4FDD\u6301\u9ED8\u8BA4\u7AEF\u53E3\u5E76\u8F6E\u6362 key\uFF0C\u7528\u4E8E\u4FEE\u590D\u65E7 Gateway \u6216 key \u4E0D\u4E00\u81F4\u5BFC\u81F4\u7684 401\u3002",
|
|
1670
1681
|
"config.notice.repairProfile": "\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",
|
|
1682
|
+
"config.yaml.invalid": "Hermes Profile \u914D\u7F6E\u6587\u4EF6\u4E0D\u662F\u6709\u6548\u7684 YAML\uFF0C\u6682\u65F6\u65E0\u6CD5\u5199\u5165\u914D\u7F6E\u3002\u8BF7\u5148\u4FEE\u590D {path} \u540E\u91CD\u8BD5\u3002{details}{more}",
|
|
1683
|
+
"config.yaml.details": " YAML \u9519\u8BEF\uFF1A{errors}",
|
|
1684
|
+
"config.yaml.more": " \u7B49 {count} \u5904\u9519\u8BEF\u3002",
|
|
1671
1685
|
"daemon.description": "\u4EE5\u524D\u53F0\u65B9\u5F0F\u8FD0\u884C Hermes Link",
|
|
1672
1686
|
"daemon.foreground": "Hermes Link \u524D\u53F0\u670D\u52A1\u6B63\u5728\u8FD0\u884C\u3002\u6309 Ctrl+C \u505C\u6B62\u3002",
|
|
1673
1687
|
"logs.description": "\u663E\u793A\u6216\u8DDF\u8E2A Hermes Link \u65E5\u5FD7",
|
|
@@ -1723,6 +1737,7 @@ var messages = {
|
|
|
1723
1737
|
"pair.autostartFailed": "\u914D\u5BF9\u5DF2\u6210\u529F\uFF0C\u4F46\u542F\u7528\u5F00\u673A\u81EA\u542F\u5931\u8D25\uFF1A{message}",
|
|
1724
1738
|
"doctor.description": "\u8FD0\u884C\u672C\u673A\u8BCA\u65AD",
|
|
1725
1739
|
"doctor.installOnly": "\u53EA\u68C0\u67E5 npm \u5168\u5C40\u547D\u4EE4\u548C PATH \u8BBE\u7F6E",
|
|
1740
|
+
"doctor.noProfiles": "\u8DF3\u8FC7 Hermes Profile \u8BCA\u65AD",
|
|
1726
1741
|
"doctor.installHeader": "\u5B89\u88C5 / PATH \u8BCA\u65AD\uFF1A",
|
|
1727
1742
|
"doctor.installNpmPrefix": "npm \u5168\u5C40 prefix\uFF1A{value}",
|
|
1728
1743
|
"doctor.installGlobalBin": "npm \u5168\u5C40 bin \u76EE\u5F55\uFF1A{value}",
|
|
@@ -1752,6 +1767,13 @@ var messages = {
|
|
|
1752
1767
|
"doctor.apiReady": "Hermes API Server\uFF1A\u5DF2\u5C31\u7EEA",
|
|
1753
1768
|
"doctor.apiStarted": "Hermes API Server\uFF1A\u5DF2\u81EA\u52A8\u542F\u52A8\u5E76\u5C31\u7EEA",
|
|
1754
1769
|
"doctor.apiUnavailable": "Hermes API Server\uFF1A\u4E0D\u53EF\u7528\u3002{message}",
|
|
1770
|
+
"doctor.profilesHeader": "Hermes Profiles\uFF1A",
|
|
1771
|
+
"doctor.profilesNone": "- \u6CA1\u6709\u627E\u5230 Profile",
|
|
1772
|
+
"doctor.profileLine": "- {profile}\uFF1A{endpoint}\uFF1B{state}",
|
|
1773
|
+
"doctor.profileReady": "\u5DF2\u5C31\u7EEA",
|
|
1774
|
+
"doctor.profileNotRunning": "\u5C1A\u672A\u8FD0\u884C\uFF08{message}\uFF09",
|
|
1775
|
+
"doctor.profilePreparedState": "\u5DF2\u81EA\u52A8\u51C6\u5907\u914D\u7F6E\uFF1B{state}",
|
|
1776
|
+
"doctor.profilePrepareFailed": "\u51C6\u5907\u5931\u8D25\uFF08{message}\uFF09",
|
|
1755
1777
|
"doctor.apiUnavailable.summary": "Hermes API Server\uFF1A\u4E0D\u53EF\u7528",
|
|
1756
1778
|
"doctor.apiUnavailable.profile": "Profile\uFF1A{profile}\uFF1B\u7AEF\u53E3\uFF1A{port}",
|
|
1757
1779
|
"doctor.apiUnavailable.diagnosisDivider": "----- API Server \u8BCA\u65AD -----",
|
|
@@ -1894,6 +1916,17 @@ var MODEL_CONFIG_RESTART_HINT = "\u6A21\u578B\u914D\u7F6E\u5DF2\u4FDD\u5B58\u300
|
|
|
1894
1916
|
var MODEL_DEFAULTS_APPLIED_HINT = "\u9ED8\u8BA4\u6A21\u578B\u8BBE\u7F6E\u5DF2\u4FDD\u5B58\u3002\u65B0\u7684 Run \u4F1A\u76F4\u63A5\u8BFB\u53D6\u6700\u65B0\u914D\u7F6E\uFF0C\u65E0\u9700\u91CD\u8F7D Hermes Gateway\u3002";
|
|
1895
1917
|
var PROFILE_PERMISSIONS_RESTART_HINT = "\u6743\u9650\u914D\u7F6E\u5DF2\u4FDD\u5B58\u3002\u540E\u7EED\u4EE5\u8BE5 Profile \u53D1\u8D77\u7684\u65B0 Run \u4F1A\u8BFB\u53D6\u6700\u65B0\u914D\u7F6E\uFF1B\u5982\u679C\u8BE5 Profile \u7684 Gateway \u5DF2\u7ECF\u5728\u8FD0\u884C\uFF0C\u9700\u8981\u91CD\u8F7D\u5BF9\u5E94 Gateway\u3002";
|
|
1896
1918
|
var PROFILE_TOOL_CONFIG_RESTART_HINT = "\u5DE5\u5177\u540E\u7AEF\u914D\u7F6E\u5DF2\u4FDD\u5B58\u3002\u540E\u7EED\u4EE5\u8BE5 Profile \u53D1\u8D77\u7684\u65B0 Run \u4F1A\u8BFB\u53D6\u6700\u65B0\u914D\u7F6E\uFF1B\u5982\u679C\u8BE5 Profile \u7684 Gateway \u5DF2\u7ECF\u5728\u8FD0\u884C\uFF0C\u9700\u8981\u91CD\u8F7D\u5BF9\u5E94 Gateway\u3002";
|
|
1919
|
+
var HermesConfigYamlError = class extends Error {
|
|
1920
|
+
constructor(configPath, errors, language) {
|
|
1921
|
+
super(buildHermesConfigYamlErrorMessage(configPath, errors, language));
|
|
1922
|
+
this.configPath = configPath;
|
|
1923
|
+
this.errors = errors;
|
|
1924
|
+
this.name = "HermesConfigYamlError";
|
|
1925
|
+
}
|
|
1926
|
+
configPath;
|
|
1927
|
+
errors;
|
|
1928
|
+
code = "hermes_config_yaml_invalid";
|
|
1929
|
+
};
|
|
1897
1930
|
var REASONING_EFFORTS = [
|
|
1898
1931
|
"none",
|
|
1899
1932
|
"minimal",
|
|
@@ -2805,12 +2838,12 @@ async function saveHermesProfilePermissions(profileName, input, configPath = res
|
|
|
2805
2838
|
restartHint: PROFILE_PERMISSIONS_RESTART_HINT
|
|
2806
2839
|
};
|
|
2807
2840
|
}
|
|
2808
|
-
async function addHermesCommandAllowlistEntry(profileName, entry, configPath = resolveHermesConfigPath(profileName)) {
|
|
2841
|
+
async function addHermesCommandAllowlistEntry(profileName, entry, configPath = resolveHermesConfigPath(profileName), language) {
|
|
2809
2842
|
const normalizedEntry = entry.trim();
|
|
2810
2843
|
if (!normalizedEntry) {
|
|
2811
2844
|
throw new Error("command_allowlist entry must be non-empty");
|
|
2812
2845
|
}
|
|
2813
|
-
const { document, config, existingRaw } = await readHermesConfigDocument(configPath);
|
|
2846
|
+
const { document, config, existingRaw } = await readHermesConfigDocument(configPath, language);
|
|
2814
2847
|
const current = readStringList(config.command_allowlist);
|
|
2815
2848
|
if (current.includes(normalizedEntry)) {
|
|
2816
2849
|
return {
|
|
@@ -3132,7 +3165,7 @@ async function repairHermesApiServerConfigUnlocked(profileName = "default", conf
|
|
|
3132
3165
|
notice: buildRepairNotice(language, profileName)
|
|
3133
3166
|
};
|
|
3134
3167
|
}
|
|
3135
|
-
async function readHermesConfigDocument(configPath) {
|
|
3168
|
+
async function readHermesConfigDocument(configPath, language) {
|
|
3136
3169
|
const existingRaw = await readFile2(configPath, "utf8").catch(
|
|
3137
3170
|
(error) => {
|
|
3138
3171
|
if (isNodeError3(error, "ENOENT")) {
|
|
@@ -3142,12 +3175,34 @@ async function readHermesConfigDocument(configPath) {
|
|
|
3142
3175
|
}
|
|
3143
3176
|
);
|
|
3144
3177
|
const document = existingRaw ? YAML.parseDocument(existingRaw) : new YAML.Document({});
|
|
3178
|
+
assertValidHermesConfigDocument(configPath, document, language);
|
|
3145
3179
|
return {
|
|
3146
3180
|
document,
|
|
3147
3181
|
config: toRecord(document.toJSON()),
|
|
3148
3182
|
existingRaw
|
|
3149
3183
|
};
|
|
3150
3184
|
}
|
|
3185
|
+
function assertValidHermesConfigDocument(configPath, document, language) {
|
|
3186
|
+
const errors = document.errors.map((error) => error.message.trim()).filter((message) => message.length > 0);
|
|
3187
|
+
if (errors.length > 0) {
|
|
3188
|
+
throw new HermesConfigYamlError(configPath, errors, language);
|
|
3189
|
+
}
|
|
3190
|
+
}
|
|
3191
|
+
function buildHermesConfigYamlErrorMessage(configPath, errors, language) {
|
|
3192
|
+
const firstErrors = errors.slice(0, 3);
|
|
3193
|
+
const resolvedLanguage = language ?? "zh-CN";
|
|
3194
|
+
const details = firstErrors.length > 0 ? translate(resolvedLanguage, "config.yaml.details", {
|
|
3195
|
+
errors: firstErrors.join("; ")
|
|
3196
|
+
}) : "";
|
|
3197
|
+
const more = errors.length > firstErrors.length ? translate(resolvedLanguage, "config.yaml.more", {
|
|
3198
|
+
count: errors.length - firstErrors.length
|
|
3199
|
+
}) : "";
|
|
3200
|
+
return translate(resolvedLanguage, "config.yaml.invalid", {
|
|
3201
|
+
path: configPath,
|
|
3202
|
+
details,
|
|
3203
|
+
more
|
|
3204
|
+
});
|
|
3205
|
+
}
|
|
3151
3206
|
async function writeHermesConfigDocument(input) {
|
|
3152
3207
|
const backupPath = input.existingRaw ? `${input.configPath}.bak.${Date.now()}` : null;
|
|
3153
3208
|
if (backupPath) {
|
|
@@ -4894,12 +4949,24 @@ async function addConfiguredApiServerPort(ports, profileName, excludedProfileNam
|
|
|
4894
4949
|
throw error;
|
|
4895
4950
|
});
|
|
4896
4951
|
if (!raw.trim()) {
|
|
4952
|
+
const envPort2 = readApiServerPort(
|
|
4953
|
+
(await readHermesApiServerEnvOverrides(profileName)).port
|
|
4954
|
+
);
|
|
4955
|
+
if (envPort2 !== null) {
|
|
4956
|
+
ports.add(envPort2);
|
|
4957
|
+
}
|
|
4897
4958
|
return;
|
|
4898
4959
|
}
|
|
4899
4960
|
const config = toRecord(YAML.parse(raw));
|
|
4900
4961
|
const apiServer = toRecord(toRecord(config.platforms).api_server);
|
|
4901
|
-
const
|
|
4902
|
-
|
|
4962
|
+
const envPort = readApiServerPort(
|
|
4963
|
+
(await readHermesApiServerEnvOverrides(profileName)).port
|
|
4964
|
+
);
|
|
4965
|
+
const configPort = readApiServerPort(readApiServerConfig(apiServer).port);
|
|
4966
|
+
for (const port of [envPort, configPort]) {
|
|
4967
|
+
if (port === null) {
|
|
4968
|
+
continue;
|
|
4969
|
+
}
|
|
4903
4970
|
ports.add(port);
|
|
4904
4971
|
}
|
|
4905
4972
|
}
|
|
@@ -5418,7 +5485,7 @@ import os2 from "os";
|
|
|
5418
5485
|
import path5 from "path";
|
|
5419
5486
|
|
|
5420
5487
|
// src/constants.ts
|
|
5421
|
-
var LINK_VERSION = "0.6.
|
|
5488
|
+
var LINK_VERSION = "0.6.7";
|
|
5422
5489
|
var LINK_COMMAND = "hermeslink";
|
|
5423
5490
|
var LINK_DEFAULT_PORT = 52379;
|
|
5424
5491
|
var LINK_RUNTIME_DIR_NAME = ".hermeslink";
|
|
@@ -18816,8 +18883,15 @@ var ConversationService = class {
|
|
|
18816
18883
|
);
|
|
18817
18884
|
const result = await addHermesCommandAllowlistEntry(
|
|
18818
18885
|
profileName,
|
|
18819
|
-
patternKey
|
|
18820
|
-
|
|
18886
|
+
patternKey,
|
|
18887
|
+
void 0,
|
|
18888
|
+
input.language
|
|
18889
|
+
).catch((error) => {
|
|
18890
|
+
if (error instanceof HermesConfigYamlError) {
|
|
18891
|
+
throw new LinkHttpError(409, error.code, error.message);
|
|
18892
|
+
}
|
|
18893
|
+
throw error;
|
|
18894
|
+
});
|
|
18821
18895
|
commandAllowlistUpdated = result.changed;
|
|
18822
18896
|
configPath = result.configPath;
|
|
18823
18897
|
requiresGatewayReload = result.requiresGatewayReload;
|
|
@@ -19529,6 +19603,36 @@ function readString16(body, key) {
|
|
|
19529
19603
|
const value = body[key];
|
|
19530
19604
|
return typeof value === "string" && value.trim() ? value.trim() : null;
|
|
19531
19605
|
}
|
|
19606
|
+
function readPreferredLanguage(ctx) {
|
|
19607
|
+
const candidates = [
|
|
19608
|
+
ctx.get("x-hermespilot-language"),
|
|
19609
|
+
ctx.get("x-app-language"),
|
|
19610
|
+
ctx.get("accept-language")
|
|
19611
|
+
];
|
|
19612
|
+
for (const candidate of candidates) {
|
|
19613
|
+
for (const part of candidate.split(",")) {
|
|
19614
|
+
const normalized = part.split(";")[0]?.trim();
|
|
19615
|
+
if (!normalized) {
|
|
19616
|
+
continue;
|
|
19617
|
+
}
|
|
19618
|
+
const language = readSupportedLanguage(normalized);
|
|
19619
|
+
if (language) {
|
|
19620
|
+
return language;
|
|
19621
|
+
}
|
|
19622
|
+
}
|
|
19623
|
+
}
|
|
19624
|
+
return resolveLanguage();
|
|
19625
|
+
}
|
|
19626
|
+
function readSupportedLanguage(value) {
|
|
19627
|
+
const normalized = value.trim().replace("_", "-").toLowerCase();
|
|
19628
|
+
if (normalized.startsWith("zh")) {
|
|
19629
|
+
return "zh-CN";
|
|
19630
|
+
}
|
|
19631
|
+
if (normalized.startsWith("en")) {
|
|
19632
|
+
return "en";
|
|
19633
|
+
}
|
|
19634
|
+
return null;
|
|
19635
|
+
}
|
|
19532
19636
|
function readOptionalProfileName(body) {
|
|
19533
19637
|
return readString16(body, "profile") ?? readString16(body, "profile_name") ?? readString16(body, "profileName") ?? void 0;
|
|
19534
19638
|
}
|
|
@@ -20209,7 +20313,8 @@ function registerConversationRoutes(router, options) {
|
|
|
20209
20313
|
...await conversations.resolveApproval({
|
|
20210
20314
|
conversationId: ctx.params.conversationId,
|
|
20211
20315
|
approvalId: ctx.params.approvalId,
|
|
20212
|
-
decision: scope
|
|
20316
|
+
decision: scope,
|
|
20317
|
+
language: readPreferredLanguage(ctx)
|
|
20213
20318
|
})
|
|
20214
20319
|
};
|
|
20215
20320
|
}
|
|
@@ -20223,7 +20328,8 @@ function registerConversationRoutes(router, options) {
|
|
|
20223
20328
|
...await conversations.resolveApproval({
|
|
20224
20329
|
conversationId: ctx.params.conversationId,
|
|
20225
20330
|
approvalId: ctx.params.approvalId,
|
|
20226
|
-
decision: "deny"
|
|
20331
|
+
decision: "deny",
|
|
20332
|
+
language: readPreferredLanguage(ctx)
|
|
20227
20333
|
})
|
|
20228
20334
|
};
|
|
20229
20335
|
}
|
|
@@ -20448,7 +20554,9 @@ var PROFILE_DELETE_VERIFY_INTERVAL_MS = 150;
|
|
|
20448
20554
|
var execFileAsync4 = promisify4(execFile4);
|
|
20449
20555
|
async function listHermesProfiles(paths = resolveRuntimePaths()) {
|
|
20450
20556
|
const profiles = /* @__PURE__ */ new Map();
|
|
20451
|
-
|
|
20557
|
+
if (await hasDefaultProfileConfigSource()) {
|
|
20558
|
+
profiles.set(DEFAULT_PROFILE, await profileInfo(DEFAULT_PROFILE, paths));
|
|
20559
|
+
}
|
|
20452
20560
|
const profilesDir = resolveHermesProfilesDir();
|
|
20453
20561
|
const entries = await readdir9(profilesDir, { withFileTypes: true }).catch(
|
|
20454
20562
|
(error) => {
|
|
@@ -20473,6 +20581,36 @@ async function listHermesProfiles(paths = resolveRuntimePaths()) {
|
|
|
20473
20581
|
return left.name.localeCompare(right.name);
|
|
20474
20582
|
});
|
|
20475
20583
|
}
|
|
20584
|
+
async function prepareHermesProfilesForUse(paths = resolveRuntimePaths()) {
|
|
20585
|
+
const profiles = await listHermesProfiles(paths);
|
|
20586
|
+
const prepared = [];
|
|
20587
|
+
for (const profile of profiles) {
|
|
20588
|
+
try {
|
|
20589
|
+
const result = await ensureHermesApiServerKey(
|
|
20590
|
+
profile.name,
|
|
20591
|
+
profile.configPath
|
|
20592
|
+
);
|
|
20593
|
+
prepared.push({
|
|
20594
|
+
profile,
|
|
20595
|
+
apiServer: result.apiServer,
|
|
20596
|
+
changed: result.changed,
|
|
20597
|
+
backupPath: result.backupPath,
|
|
20598
|
+
notice: result.notice,
|
|
20599
|
+
error: null
|
|
20600
|
+
});
|
|
20601
|
+
} catch (error) {
|
|
20602
|
+
prepared.push({
|
|
20603
|
+
profile,
|
|
20604
|
+
apiServer: null,
|
|
20605
|
+
changed: false,
|
|
20606
|
+
backupPath: null,
|
|
20607
|
+
notice: null,
|
|
20608
|
+
error: errorMessage(error)
|
|
20609
|
+
});
|
|
20610
|
+
}
|
|
20611
|
+
}
|
|
20612
|
+
return prepared;
|
|
20613
|
+
}
|
|
20476
20614
|
async function getHermesProfileStatus(name, paths = resolveRuntimePaths()) {
|
|
20477
20615
|
assertProfileName(name);
|
|
20478
20616
|
const profile = await profileInfo(name, paths);
|
|
@@ -20596,6 +20734,19 @@ async function pathExists(targetPath) {
|
|
|
20596
20734
|
throw error;
|
|
20597
20735
|
});
|
|
20598
20736
|
}
|
|
20737
|
+
async function hasDefaultProfileConfigSource() {
|
|
20738
|
+
const profilePath = resolveHermesProfileDir(DEFAULT_PROFILE);
|
|
20739
|
+
const profileStat = await stat13(profilePath).catch((error) => {
|
|
20740
|
+
if (isNodeError15(error, "ENOENT")) {
|
|
20741
|
+
return null;
|
|
20742
|
+
}
|
|
20743
|
+
throw error;
|
|
20744
|
+
});
|
|
20745
|
+
if (!profileStat?.isDirectory()) {
|
|
20746
|
+
return false;
|
|
20747
|
+
}
|
|
20748
|
+
return await pathExists(resolveHermesConfigPath(DEFAULT_PROFILE)) || await pathExists(path20.join(profilePath, ".env"));
|
|
20749
|
+
}
|
|
20599
20750
|
async function deleteHermesProfileWithCli(name) {
|
|
20600
20751
|
try {
|
|
20601
20752
|
await execFileAsync4(resolveHermesBin(), ["profile", "delete", name, "--yes"], {
|
|
@@ -20747,6 +20898,9 @@ function readExecErrorOutput2(error) {
|
|
|
20747
20898
|
function isNodeError15(error, code) {
|
|
20748
20899
|
return typeof error === "object" && error !== null && "code" in error && error.code === code;
|
|
20749
20900
|
}
|
|
20901
|
+
function errorMessage(error) {
|
|
20902
|
+
return error instanceof Error ? error.message : String(error);
|
|
20903
|
+
}
|
|
20750
20904
|
function escapeRegExp2(value) {
|
|
20751
20905
|
return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
20752
20906
|
}
|
|
@@ -21473,7 +21627,11 @@ function registerProfileCatalogRoutes(router, options) {
|
|
|
21473
21627
|
});
|
|
21474
21628
|
}
|
|
21475
21629
|
async function readProfileCatalogItem(profile) {
|
|
21476
|
-
const [capabilities, permissions, modelConfigs] = await Promise.all([
|
|
21630
|
+
const [apiServer, capabilities, permissions, modelConfigs] = await Promise.all([
|
|
21631
|
+
readCatalogField(
|
|
21632
|
+
"apiServer",
|
|
21633
|
+
() => readHermesApiServerConfig(profile.name, profile.configPath)
|
|
21634
|
+
),
|
|
21477
21635
|
readCatalogField(
|
|
21478
21636
|
"capabilities",
|
|
21479
21637
|
() => readHermesProfileCapabilities(profile.name)
|
|
@@ -21486,10 +21644,17 @@ async function readProfileCatalogItem(profile) {
|
|
|
21486
21644
|
]);
|
|
21487
21645
|
return {
|
|
21488
21646
|
profile,
|
|
21647
|
+
apiServer: apiServer.value ? {
|
|
21648
|
+
host: apiServer.value.host ?? null,
|
|
21649
|
+
port: apiServer.value.port ?? null,
|
|
21650
|
+
configured: Boolean(apiServer.value.key),
|
|
21651
|
+
changed: false
|
|
21652
|
+
} : null,
|
|
21489
21653
|
capabilities: capabilities.value,
|
|
21490
21654
|
permissions: permissions.value,
|
|
21491
21655
|
modelConfigs: modelConfigs.value,
|
|
21492
21656
|
errors: [
|
|
21657
|
+
...apiServer.errors,
|
|
21493
21658
|
...capabilities.errors,
|
|
21494
21659
|
...permissions.errors,
|
|
21495
21660
|
...modelConfigs.errors
|
|
@@ -21502,11 +21667,11 @@ async function readCatalogField(field, load) {
|
|
|
21502
21667
|
} catch (error) {
|
|
21503
21668
|
return {
|
|
21504
21669
|
value: null,
|
|
21505
|
-
errors: [{ field, message:
|
|
21670
|
+
errors: [{ field, message: errorMessage2(error) }]
|
|
21506
21671
|
};
|
|
21507
21672
|
}
|
|
21508
21673
|
}
|
|
21509
|
-
function
|
|
21674
|
+
function errorMessage2(error) {
|
|
21510
21675
|
return error instanceof Error ? error.message : String(error);
|
|
21511
21676
|
}
|
|
21512
21677
|
|
|
@@ -21619,7 +21784,7 @@ async function ensureHermesLinkSkillInstalledForProfiles(options = {}) {
|
|
|
21619
21784
|
const skillChanged = await writeHermesLinkSkill(skillPath);
|
|
21620
21785
|
const profiles = await listHermesProfiles(paths);
|
|
21621
21786
|
const results = [];
|
|
21622
|
-
for (const profile of profiles) {
|
|
21787
|
+
for (const profile of withDefaultProfilePlaceholder(profiles)) {
|
|
21623
21788
|
try {
|
|
21624
21789
|
results.push(await ensureProfileUsesExternalSkillDir(profile, externalDir));
|
|
21625
21790
|
} catch (error) {
|
|
@@ -21673,6 +21838,25 @@ async function ensureHermesLinkSkillInstalledBestEffort(options = {}) {
|
|
|
21673
21838
|
});
|
|
21674
21839
|
}
|
|
21675
21840
|
}
|
|
21841
|
+
function withDefaultProfilePlaceholder(profiles) {
|
|
21842
|
+
if (profiles.some((profile) => profile.name === "default")) {
|
|
21843
|
+
return profiles;
|
|
21844
|
+
}
|
|
21845
|
+
return [
|
|
21846
|
+
{
|
|
21847
|
+
uid: "default",
|
|
21848
|
+
name: "default",
|
|
21849
|
+
active: false,
|
|
21850
|
+
path: resolveHermesProfileDir("default"),
|
|
21851
|
+
configPath: resolveHermesConfigPath("default"),
|
|
21852
|
+
displayName: null,
|
|
21853
|
+
description: null,
|
|
21854
|
+
avatarType: "default",
|
|
21855
|
+
avatarUrl: null
|
|
21856
|
+
},
|
|
21857
|
+
...profiles
|
|
21858
|
+
];
|
|
21859
|
+
}
|
|
21676
21860
|
function resolveHermesLinkSkillExternalDir(paths = resolveRuntimePaths()) {
|
|
21677
21861
|
return path21.join(paths.homeDir, HERMES_LINK_SKILL_ROOT_DIR);
|
|
21678
21862
|
}
|
|
@@ -25153,6 +25337,16 @@ function registerProfileRoutes(router, options) {
|
|
|
25153
25337
|
profiles: await listHermesProfiles(paths)
|
|
25154
25338
|
};
|
|
25155
25339
|
});
|
|
25340
|
+
router.post("/api/v1/profiles/prepare", async (ctx) => {
|
|
25341
|
+
await authenticateRequest(ctx, paths);
|
|
25342
|
+
ctx.set("cache-control", "no-store");
|
|
25343
|
+
ctx.body = {
|
|
25344
|
+
ok: true,
|
|
25345
|
+
profiles: (await prepareHermesProfilesForUse(paths)).map(
|
|
25346
|
+
formatProfilePreparation
|
|
25347
|
+
)
|
|
25348
|
+
};
|
|
25349
|
+
});
|
|
25156
25350
|
router.get("/api/v1/profile-creation/status", async (ctx) => {
|
|
25157
25351
|
await authenticateRequest(ctx, paths);
|
|
25158
25352
|
ctx.set("cache-control", "no-store");
|
|
@@ -25252,6 +25446,20 @@ function registerProfileRoutes(router, options) {
|
|
|
25252
25446
|
ctx.status = 204;
|
|
25253
25447
|
});
|
|
25254
25448
|
}
|
|
25449
|
+
function formatProfilePreparation(item) {
|
|
25450
|
+
return {
|
|
25451
|
+
profile: item.profile,
|
|
25452
|
+
apiServer: item.apiServer ? {
|
|
25453
|
+
host: item.apiServer.host ?? null,
|
|
25454
|
+
port: item.apiServer.port ?? null,
|
|
25455
|
+
configured: Boolean(item.apiServer.key),
|
|
25456
|
+
changed: item.changed
|
|
25457
|
+
} : null,
|
|
25458
|
+
changed: item.changed,
|
|
25459
|
+
notice: item.notice,
|
|
25460
|
+
error: item.error
|
|
25461
|
+
};
|
|
25462
|
+
}
|
|
25255
25463
|
function readProfileName(body) {
|
|
25256
25464
|
if (typeof body.name !== "string") {
|
|
25257
25465
|
throw new Error("invalid profile name");
|
|
@@ -27413,6 +27621,15 @@ async function startLinkService(options = {}) {
|
|
|
27413
27621
|
});
|
|
27414
27622
|
return streamBatchPolicy;
|
|
27415
27623
|
};
|
|
27624
|
+
let profilePreparation = Promise.resolve();
|
|
27625
|
+
const triggerProfilePreparation = (source) => {
|
|
27626
|
+
profilePreparation = profilePreparation.catch(() => void 0).then(() => prepareHermesProfilesForUse(paths)).then((profiles) => logProfilePreparationResult(logger, source, profiles)).catch((error) => {
|
|
27627
|
+
void logger.warn("profile_preparation_failed", {
|
|
27628
|
+
source,
|
|
27629
|
+
error: error instanceof Error ? error.message : String(error)
|
|
27630
|
+
});
|
|
27631
|
+
});
|
|
27632
|
+
};
|
|
27416
27633
|
let hermesSessionSync = Promise.resolve();
|
|
27417
27634
|
const triggerHermesSessionSync = () => {
|
|
27418
27635
|
hermesSessionSync = Promise.resolve().then(() => conversations.syncHermesSessions()).then(() => void 0).catch((error) => {
|
|
@@ -27432,6 +27649,7 @@ async function startLinkService(options = {}) {
|
|
|
27432
27649
|
logger,
|
|
27433
27650
|
source: "pairing_claimed"
|
|
27434
27651
|
});
|
|
27652
|
+
triggerProfilePreparation("pairing_claimed");
|
|
27435
27653
|
triggerHermesSessionSync();
|
|
27436
27654
|
void loadRelayStreamBatchPolicy("pairing_claimed");
|
|
27437
27655
|
await options.onPairingClaimed?.();
|
|
@@ -27492,6 +27710,9 @@ async function startLinkService(options = {}) {
|
|
|
27492
27710
|
port: config.port,
|
|
27493
27711
|
link_id: identity?.link_id ?? null
|
|
27494
27712
|
});
|
|
27713
|
+
if (identity?.link_id) {
|
|
27714
|
+
triggerProfilePreparation("service_startup");
|
|
27715
|
+
}
|
|
27495
27716
|
triggerHermesSessionSync();
|
|
27496
27717
|
const scheduler = startCronDeliveryScheduler({
|
|
27497
27718
|
paths,
|
|
@@ -27576,6 +27797,7 @@ async function startLinkService(options = {}) {
|
|
|
27576
27797
|
scheduler.close(),
|
|
27577
27798
|
hermesSessionSyncScheduler.close(),
|
|
27578
27799
|
lanIpMonitor?.close(),
|
|
27800
|
+
profilePreparation.catch(() => void 0),
|
|
27579
27801
|
hermesSessionSync.catch(() => void 0)
|
|
27580
27802
|
]);
|
|
27581
27803
|
await logger.info("service_stopped");
|
|
@@ -27601,6 +27823,32 @@ function waitForRelayReadyTimeout(timeoutMs) {
|
|
|
27601
27823
|
timer.unref?.();
|
|
27602
27824
|
});
|
|
27603
27825
|
}
|
|
27826
|
+
async function logProfilePreparationResult(logger, source, profiles) {
|
|
27827
|
+
const changed = profiles.filter((profile) => profile.changed);
|
|
27828
|
+
const failed = profiles.filter((profile) => profile.error);
|
|
27829
|
+
const summary = {
|
|
27830
|
+
source,
|
|
27831
|
+
total: profiles.length,
|
|
27832
|
+
prepared: profiles.length - failed.length,
|
|
27833
|
+
changed: changed.length,
|
|
27834
|
+
failed: failed.length,
|
|
27835
|
+
changed_profiles: changed.map((profile) => profile.profile.name)
|
|
27836
|
+
};
|
|
27837
|
+
if (changed.length > 0 || failed.length > 0) {
|
|
27838
|
+
await logger.warn("profiles_prepared", summary);
|
|
27839
|
+
} else {
|
|
27840
|
+
await logger.info("profiles_prepared", summary);
|
|
27841
|
+
}
|
|
27842
|
+
if (failed.length > 0) {
|
|
27843
|
+
await logger.warn("profile_preparation_partial_failure", {
|
|
27844
|
+
source,
|
|
27845
|
+
failed_profiles: failed.map((profile) => ({
|
|
27846
|
+
profile: profile.profile.name,
|
|
27847
|
+
error: profile.error
|
|
27848
|
+
}))
|
|
27849
|
+
});
|
|
27850
|
+
}
|
|
27851
|
+
}
|
|
27604
27852
|
function pidFilePath(paths = resolveRuntimePaths()) {
|
|
27605
27853
|
return `${paths.runDir}/hermeslink.pid`;
|
|
27606
27854
|
}
|
|
@@ -30585,6 +30833,7 @@ export {
|
|
|
30585
30833
|
resolveLanguage,
|
|
30586
30834
|
translate,
|
|
30587
30835
|
localizeErrorMessage,
|
|
30836
|
+
DEFAULT_HERMES_API_SERVER_PORT,
|
|
30588
30837
|
resolveHermesProfileDir,
|
|
30589
30838
|
resolveHermesConfigPath,
|
|
30590
30839
|
ensureHermesApiServerConfig,
|
|
@@ -30599,6 +30848,7 @@ export {
|
|
|
30599
30848
|
HermesApiServerUnavailableError,
|
|
30600
30849
|
ensureHermesApiServerAvailable,
|
|
30601
30850
|
readHermesVersion,
|
|
30851
|
+
readHermesApiServerHealth,
|
|
30602
30852
|
defaultLinkConfig,
|
|
30603
30853
|
loadConfig,
|
|
30604
30854
|
saveConfig,
|
|
@@ -30609,6 +30859,7 @@ export {
|
|
|
30609
30859
|
getIdentityStatus,
|
|
30610
30860
|
ConversationService,
|
|
30611
30861
|
hasActiveDevices,
|
|
30862
|
+
prepareHermesProfilesForUse,
|
|
30612
30863
|
ensureHermesLinkSkillInstalledBestEffort,
|
|
30613
30864
|
detectRuntimeEnvironment,
|
|
30614
30865
|
preparePairing,
|
package/dist/cli/index.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import {
|
|
3
3
|
ConversationService,
|
|
4
|
+
DEFAULT_HERMES_API_SERVER_PORT,
|
|
4
5
|
HermesApiServerUnavailableError,
|
|
5
6
|
LINK_COMMAND,
|
|
6
7
|
LINK_VERSION,
|
|
@@ -29,8 +30,10 @@ import {
|
|
|
29
30
|
localizeErrorMessage,
|
|
30
31
|
normalizeLanHost,
|
|
31
32
|
parseLogLevel,
|
|
33
|
+
prepareHermesProfilesForUse,
|
|
32
34
|
preparePairing,
|
|
33
35
|
probeLocalLinkService,
|
|
36
|
+
readHermesApiServerHealth,
|
|
34
37
|
readHermesVersion,
|
|
35
38
|
readPairingClaim,
|
|
36
39
|
readRecentGatewayLogEntries,
|
|
@@ -48,7 +51,7 @@ import {
|
|
|
48
51
|
startLinkService,
|
|
49
52
|
stopDaemonProcess,
|
|
50
53
|
translate
|
|
51
|
-
} from "../chunk-
|
|
54
|
+
} from "../chunk-FWX7ZUP4.js";
|
|
52
55
|
|
|
53
56
|
// src/cli/index.ts
|
|
54
57
|
import { Command } from "commander";
|
|
@@ -1158,7 +1161,7 @@ logsCommand.command("flush").description(helpText("logs.flush.description")).opt
|
|
|
1158
1161
|
})
|
|
1159
1162
|
);
|
|
1160
1163
|
});
|
|
1161
|
-
program.command("doctor").option("--install", helpText("doctor.installOnly")).description(helpText("doctor.description")).action(async (options) => {
|
|
1164
|
+
program.command("doctor").option("--install", helpText("doctor.installOnly")).option("--no-profiles", helpText("doctor.noProfiles")).description(helpText("doctor.description")).action(async (options) => {
|
|
1162
1165
|
const installInfo = readInstallPathInfo();
|
|
1163
1166
|
const installLanguage = await loadCliLanguage().catch(() => detectSystemLanguage());
|
|
1164
1167
|
const installT = translate.bind(null, installLanguage);
|
|
@@ -1200,6 +1203,9 @@ program.command("doctor").option("--install", helpText("doctor.installOnly")).de
|
|
|
1200
1203
|
} catch (error) {
|
|
1201
1204
|
console.log(formatHermesApiServerUnavailable(error, language));
|
|
1202
1205
|
}
|
|
1206
|
+
if (options.profiles !== false) {
|
|
1207
|
+
await printProfileDiagnostics(t);
|
|
1208
|
+
}
|
|
1203
1209
|
});
|
|
1204
1210
|
if (isCliEntrypoint()) {
|
|
1205
1211
|
program.parseAsync(process.argv).catch(async (error) => {
|
|
@@ -1212,6 +1218,45 @@ async function loadCliLanguage() {
|
|
|
1212
1218
|
const config = await loadConfig();
|
|
1213
1219
|
return resolveLanguage(config.language);
|
|
1214
1220
|
}
|
|
1221
|
+
async function printProfileDiagnostics(t) {
|
|
1222
|
+
console.log(t("doctor.profilesHeader"));
|
|
1223
|
+
const profiles = await prepareHermesProfilesForUse();
|
|
1224
|
+
if (profiles.length === 0) {
|
|
1225
|
+
console.log(t("doctor.profilesNone"));
|
|
1226
|
+
return;
|
|
1227
|
+
}
|
|
1228
|
+
for (const profile of profiles) {
|
|
1229
|
+
console.log(await formatProfileDiagnosticLine(profile, t));
|
|
1230
|
+
}
|
|
1231
|
+
}
|
|
1232
|
+
async function formatProfileDiagnosticLine(profile, t) {
|
|
1233
|
+
if (profile.error || !profile.apiServer) {
|
|
1234
|
+
return t("doctor.profileLine", {
|
|
1235
|
+
profile: profile.profile.name,
|
|
1236
|
+
endpoint: t("status.unknown"),
|
|
1237
|
+
state: t("doctor.profilePrepareFailed", {
|
|
1238
|
+
message: profile.error ?? t("status.unknown")
|
|
1239
|
+
})
|
|
1240
|
+
});
|
|
1241
|
+
}
|
|
1242
|
+
const host = profile.apiServer.host ?? "127.0.0.1";
|
|
1243
|
+
const port = profile.apiServer.port ?? DEFAULT_HERMES_API_SERVER_PORT;
|
|
1244
|
+
const health = await readHermesApiServerHealth(profile.apiServer).catch(
|
|
1245
|
+
(error) => ({
|
|
1246
|
+
healthy: false,
|
|
1247
|
+
issue: error instanceof Error ? error.message : String(error)
|
|
1248
|
+
})
|
|
1249
|
+
);
|
|
1250
|
+
const baseState = health.healthy ? t("doctor.profileReady") : t("doctor.profileNotRunning", {
|
|
1251
|
+
message: health.issue ?? t("status.unknown")
|
|
1252
|
+
});
|
|
1253
|
+
const state = profile.changed ? t("doctor.profilePreparedState", { state: baseState }) : baseState;
|
|
1254
|
+
return t("doctor.profileLine", {
|
|
1255
|
+
profile: profile.profile.name,
|
|
1256
|
+
endpoint: `${host}:${port}`,
|
|
1257
|
+
state
|
|
1258
|
+
});
|
|
1259
|
+
}
|
|
1215
1260
|
function buildRelayStatusPayload(input) {
|
|
1216
1261
|
if (!input.paired) {
|
|
1217
1262
|
return emptyRelayStatus(input.relayConfigured, "not_paired");
|
package/dist/http/app.d.ts
CHANGED
|
@@ -381,6 +381,8 @@ declare class FileLogger {
|
|
|
381
381
|
private rotateIfNeeded;
|
|
382
382
|
}
|
|
383
383
|
|
|
384
|
+
type SupportedLanguage = 'zh-CN' | 'en';
|
|
385
|
+
|
|
384
386
|
interface HermesSessionSyncResult {
|
|
385
387
|
scanned_profiles: number;
|
|
386
388
|
scanned_sessions: number;
|
|
@@ -510,6 +512,7 @@ declare class ConversationService {
|
|
|
510
512
|
conversationId: string;
|
|
511
513
|
approvalId: string;
|
|
512
514
|
decision: LinkApprovalDecision;
|
|
515
|
+
language?: SupportedLanguage;
|
|
513
516
|
}): Promise<{
|
|
514
517
|
conversation_id: string;
|
|
515
518
|
message_id: string;
|
package/dist/http/app.js
CHANGED