@hermespilot/link 0.6.6 → 0.6.8
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-57ZJLOQA.js} +477 -51
- 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
|
@@ -1359,6 +1359,7 @@ function isNodeError(error, code) {
|
|
|
1359
1359
|
}
|
|
1360
1360
|
|
|
1361
1361
|
// src/storage/atomic-json.ts
|
|
1362
|
+
var jsonUpdateQueues = /* @__PURE__ */ new Map();
|
|
1362
1363
|
async function readJsonFile(filePath) {
|
|
1363
1364
|
try {
|
|
1364
1365
|
const raw = await readFile(filePath, "utf8");
|
|
@@ -1375,6 +1376,24 @@ async function writeJsonFile(filePath, value, mode = 384) {
|
|
|
1375
1376
|
`;
|
|
1376
1377
|
await atomicWriteFilePreservingMetadata(filePath, payload, { mode });
|
|
1377
1378
|
}
|
|
1379
|
+
async function updateJsonFile(filePath, update, mode = 384) {
|
|
1380
|
+
const previous = jsonUpdateQueues.get(filePath) ?? Promise.resolve();
|
|
1381
|
+
let next;
|
|
1382
|
+
const operation = previous.catch(() => void 0).then(async () => {
|
|
1383
|
+
const current = await readJsonFile(filePath);
|
|
1384
|
+
next = await update(current);
|
|
1385
|
+
await writeJsonFile(filePath, next, mode);
|
|
1386
|
+
});
|
|
1387
|
+
const queued = operation.catch(() => void 0);
|
|
1388
|
+
jsonUpdateQueues.set(filePath, queued);
|
|
1389
|
+
void queued.finally(() => {
|
|
1390
|
+
if (jsonUpdateQueues.get(filePath) === queued) {
|
|
1391
|
+
jsonUpdateQueues.delete(filePath);
|
|
1392
|
+
}
|
|
1393
|
+
});
|
|
1394
|
+
await operation;
|
|
1395
|
+
return next;
|
|
1396
|
+
}
|
|
1378
1397
|
function isNodeError2(error, code) {
|
|
1379
1398
|
return typeof error === "object" && error !== null && "code" in error && error.code === code;
|
|
1380
1399
|
}
|
|
@@ -1465,6 +1484,9 @@ var messages = {
|
|
|
1465
1484
|
"config.notice.autoFilled": "Hermes API Server was auto-filled with {fields}; existing port/host/key were not overwritten.",
|
|
1466
1485
|
"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
1486
|
"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.",
|
|
1487
|
+
"config.yaml.invalid": "Hermes Profile config is not valid YAML, so Link cannot write settings yet. Fix {path}, then try again.{details}{more}",
|
|
1488
|
+
"config.yaml.details": " YAML error: {errors}",
|
|
1489
|
+
"config.yaml.more": " and {count} more error(s).",
|
|
1468
1490
|
"daemon.description": "Run Hermes Link in the foreground",
|
|
1469
1491
|
"daemon.foreground": "Hermes Link foreground daemon is running. Press Ctrl+C to stop.",
|
|
1470
1492
|
"logs.description": "Show or follow Hermes Link logs",
|
|
@@ -1520,6 +1542,7 @@ var messages = {
|
|
|
1520
1542
|
"pair.autostartFailed": "Pairing succeeded, but boot autostart could not be enabled: {message}",
|
|
1521
1543
|
"doctor.description": "Run local diagnostics",
|
|
1522
1544
|
"doctor.installOnly": "only check npm global command and PATH setup",
|
|
1545
|
+
"doctor.noProfiles": "skip Hermes Profile diagnostics",
|
|
1523
1546
|
"doctor.installHeader": "Install/PATH diagnostics:",
|
|
1524
1547
|
"doctor.installNpmPrefix": "npm global prefix: {value}",
|
|
1525
1548
|
"doctor.installGlobalBin": "npm global bin directory: {value}",
|
|
@@ -1549,6 +1572,13 @@ var messages = {
|
|
|
1549
1572
|
"doctor.apiReady": "Hermes API Server: ready",
|
|
1550
1573
|
"doctor.apiStarted": "Hermes API Server: started and ready",
|
|
1551
1574
|
"doctor.apiUnavailable": "Hermes API Server: unavailable. {message}",
|
|
1575
|
+
"doctor.profilesHeader": "Hermes Profiles:",
|
|
1576
|
+
"doctor.profilesNone": "- no profiles found",
|
|
1577
|
+
"doctor.profileLine": "- {profile}: {endpoint}; {state}",
|
|
1578
|
+
"doctor.profileReady": "ready",
|
|
1579
|
+
"doctor.profileNotRunning": "not running ({message})",
|
|
1580
|
+
"doctor.profilePreparedState": "prepared config; {state}",
|
|
1581
|
+
"doctor.profilePrepareFailed": "prepare failed ({message})",
|
|
1552
1582
|
"doctor.apiUnavailable.summary": "Hermes API Server: unavailable",
|
|
1553
1583
|
"doctor.apiUnavailable.profile": "Profile: {profile}; port: {port}",
|
|
1554
1584
|
"doctor.apiUnavailable.diagnosisDivider": "----- API Server diagnosis -----",
|
|
@@ -1668,6 +1698,9 @@ var messages = {
|
|
|
1668
1698
|
"config.notice.autoFilled": "\u5DF2\u4E3A Hermes API Server \u81EA\u52A8\u8865\u5145 {fields}\uFF1B\u672A\u8986\u76D6\u5DF2\u6709 port/host/key\u3002",
|
|
1669
1699
|
"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
1700
|
"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",
|
|
1701
|
+
"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}",
|
|
1702
|
+
"config.yaml.details": " YAML \u9519\u8BEF\uFF1A{errors}",
|
|
1703
|
+
"config.yaml.more": " \u7B49 {count} \u5904\u9519\u8BEF\u3002",
|
|
1671
1704
|
"daemon.description": "\u4EE5\u524D\u53F0\u65B9\u5F0F\u8FD0\u884C Hermes Link",
|
|
1672
1705
|
"daemon.foreground": "Hermes Link \u524D\u53F0\u670D\u52A1\u6B63\u5728\u8FD0\u884C\u3002\u6309 Ctrl+C \u505C\u6B62\u3002",
|
|
1673
1706
|
"logs.description": "\u663E\u793A\u6216\u8DDF\u8E2A Hermes Link \u65E5\u5FD7",
|
|
@@ -1723,6 +1756,7 @@ var messages = {
|
|
|
1723
1756
|
"pair.autostartFailed": "\u914D\u5BF9\u5DF2\u6210\u529F\uFF0C\u4F46\u542F\u7528\u5F00\u673A\u81EA\u542F\u5931\u8D25\uFF1A{message}",
|
|
1724
1757
|
"doctor.description": "\u8FD0\u884C\u672C\u673A\u8BCA\u65AD",
|
|
1725
1758
|
"doctor.installOnly": "\u53EA\u68C0\u67E5 npm \u5168\u5C40\u547D\u4EE4\u548C PATH \u8BBE\u7F6E",
|
|
1759
|
+
"doctor.noProfiles": "\u8DF3\u8FC7 Hermes Profile \u8BCA\u65AD",
|
|
1726
1760
|
"doctor.installHeader": "\u5B89\u88C5 / PATH \u8BCA\u65AD\uFF1A",
|
|
1727
1761
|
"doctor.installNpmPrefix": "npm \u5168\u5C40 prefix\uFF1A{value}",
|
|
1728
1762
|
"doctor.installGlobalBin": "npm \u5168\u5C40 bin \u76EE\u5F55\uFF1A{value}",
|
|
@@ -1752,6 +1786,13 @@ var messages = {
|
|
|
1752
1786
|
"doctor.apiReady": "Hermes API Server\uFF1A\u5DF2\u5C31\u7EEA",
|
|
1753
1787
|
"doctor.apiStarted": "Hermes API Server\uFF1A\u5DF2\u81EA\u52A8\u542F\u52A8\u5E76\u5C31\u7EEA",
|
|
1754
1788
|
"doctor.apiUnavailable": "Hermes API Server\uFF1A\u4E0D\u53EF\u7528\u3002{message}",
|
|
1789
|
+
"doctor.profilesHeader": "Hermes Profiles\uFF1A",
|
|
1790
|
+
"doctor.profilesNone": "- \u6CA1\u6709\u627E\u5230 Profile",
|
|
1791
|
+
"doctor.profileLine": "- {profile}\uFF1A{endpoint}\uFF1B{state}",
|
|
1792
|
+
"doctor.profileReady": "\u5DF2\u5C31\u7EEA",
|
|
1793
|
+
"doctor.profileNotRunning": "\u5C1A\u672A\u8FD0\u884C\uFF08{message}\uFF09",
|
|
1794
|
+
"doctor.profilePreparedState": "\u5DF2\u81EA\u52A8\u51C6\u5907\u914D\u7F6E\uFF1B{state}",
|
|
1795
|
+
"doctor.profilePrepareFailed": "\u51C6\u5907\u5931\u8D25\uFF08{message}\uFF09",
|
|
1755
1796
|
"doctor.apiUnavailable.summary": "Hermes API Server\uFF1A\u4E0D\u53EF\u7528",
|
|
1756
1797
|
"doctor.apiUnavailable.profile": "Profile\uFF1A{profile}\uFF1B\u7AEF\u53E3\uFF1A{port}",
|
|
1757
1798
|
"doctor.apiUnavailable.diagnosisDivider": "----- API Server \u8BCA\u65AD -----",
|
|
@@ -1894,6 +1935,17 @@ var MODEL_CONFIG_RESTART_HINT = "\u6A21\u578B\u914D\u7F6E\u5DF2\u4FDD\u5B58\u300
|
|
|
1894
1935
|
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
1936
|
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
1937
|
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";
|
|
1938
|
+
var HermesConfigYamlError = class extends Error {
|
|
1939
|
+
constructor(configPath, errors, language) {
|
|
1940
|
+
super(buildHermesConfigYamlErrorMessage(configPath, errors, language));
|
|
1941
|
+
this.configPath = configPath;
|
|
1942
|
+
this.errors = errors;
|
|
1943
|
+
this.name = "HermesConfigYamlError";
|
|
1944
|
+
}
|
|
1945
|
+
configPath;
|
|
1946
|
+
errors;
|
|
1947
|
+
code = "hermes_config_yaml_invalid";
|
|
1948
|
+
};
|
|
1897
1949
|
var REASONING_EFFORTS = [
|
|
1898
1950
|
"none",
|
|
1899
1951
|
"minimal",
|
|
@@ -2805,12 +2857,12 @@ async function saveHermesProfilePermissions(profileName, input, configPath = res
|
|
|
2805
2857
|
restartHint: PROFILE_PERMISSIONS_RESTART_HINT
|
|
2806
2858
|
};
|
|
2807
2859
|
}
|
|
2808
|
-
async function addHermesCommandAllowlistEntry(profileName, entry, configPath = resolveHermesConfigPath(profileName)) {
|
|
2860
|
+
async function addHermesCommandAllowlistEntry(profileName, entry, configPath = resolveHermesConfigPath(profileName), language) {
|
|
2809
2861
|
const normalizedEntry = entry.trim();
|
|
2810
2862
|
if (!normalizedEntry) {
|
|
2811
2863
|
throw new Error("command_allowlist entry must be non-empty");
|
|
2812
2864
|
}
|
|
2813
|
-
const { document, config, existingRaw } = await readHermesConfigDocument(configPath);
|
|
2865
|
+
const { document, config, existingRaw } = await readHermesConfigDocument(configPath, language);
|
|
2814
2866
|
const current = readStringList(config.command_allowlist);
|
|
2815
2867
|
if (current.includes(normalizedEntry)) {
|
|
2816
2868
|
return {
|
|
@@ -3132,7 +3184,7 @@ async function repairHermesApiServerConfigUnlocked(profileName = "default", conf
|
|
|
3132
3184
|
notice: buildRepairNotice(language, profileName)
|
|
3133
3185
|
};
|
|
3134
3186
|
}
|
|
3135
|
-
async function readHermesConfigDocument(configPath) {
|
|
3187
|
+
async function readHermesConfigDocument(configPath, language) {
|
|
3136
3188
|
const existingRaw = await readFile2(configPath, "utf8").catch(
|
|
3137
3189
|
(error) => {
|
|
3138
3190
|
if (isNodeError3(error, "ENOENT")) {
|
|
@@ -3142,12 +3194,34 @@ async function readHermesConfigDocument(configPath) {
|
|
|
3142
3194
|
}
|
|
3143
3195
|
);
|
|
3144
3196
|
const document = existingRaw ? YAML.parseDocument(existingRaw) : new YAML.Document({});
|
|
3197
|
+
assertValidHermesConfigDocument(configPath, document, language);
|
|
3145
3198
|
return {
|
|
3146
3199
|
document,
|
|
3147
3200
|
config: toRecord(document.toJSON()),
|
|
3148
3201
|
existingRaw
|
|
3149
3202
|
};
|
|
3150
3203
|
}
|
|
3204
|
+
function assertValidHermesConfigDocument(configPath, document, language) {
|
|
3205
|
+
const errors = document.errors.map((error) => error.message.trim()).filter((message) => message.length > 0);
|
|
3206
|
+
if (errors.length > 0) {
|
|
3207
|
+
throw new HermesConfigYamlError(configPath, errors, language);
|
|
3208
|
+
}
|
|
3209
|
+
}
|
|
3210
|
+
function buildHermesConfigYamlErrorMessage(configPath, errors, language) {
|
|
3211
|
+
const firstErrors = errors.slice(0, 3);
|
|
3212
|
+
const resolvedLanguage = language ?? "zh-CN";
|
|
3213
|
+
const details = firstErrors.length > 0 ? translate(resolvedLanguage, "config.yaml.details", {
|
|
3214
|
+
errors: firstErrors.join("; ")
|
|
3215
|
+
}) : "";
|
|
3216
|
+
const more = errors.length > firstErrors.length ? translate(resolvedLanguage, "config.yaml.more", {
|
|
3217
|
+
count: errors.length - firstErrors.length
|
|
3218
|
+
}) : "";
|
|
3219
|
+
return translate(resolvedLanguage, "config.yaml.invalid", {
|
|
3220
|
+
path: configPath,
|
|
3221
|
+
details,
|
|
3222
|
+
more
|
|
3223
|
+
});
|
|
3224
|
+
}
|
|
3151
3225
|
async function writeHermesConfigDocument(input) {
|
|
3152
3226
|
const backupPath = input.existingRaw ? `${input.configPath}.bak.${Date.now()}` : null;
|
|
3153
3227
|
if (backupPath) {
|
|
@@ -4894,12 +4968,24 @@ async function addConfiguredApiServerPort(ports, profileName, excludedProfileNam
|
|
|
4894
4968
|
throw error;
|
|
4895
4969
|
});
|
|
4896
4970
|
if (!raw.trim()) {
|
|
4971
|
+
const envPort2 = readApiServerPort(
|
|
4972
|
+
(await readHermesApiServerEnvOverrides(profileName)).port
|
|
4973
|
+
);
|
|
4974
|
+
if (envPort2 !== null) {
|
|
4975
|
+
ports.add(envPort2);
|
|
4976
|
+
}
|
|
4897
4977
|
return;
|
|
4898
4978
|
}
|
|
4899
4979
|
const config = toRecord(YAML.parse(raw));
|
|
4900
4980
|
const apiServer = toRecord(toRecord(config.platforms).api_server);
|
|
4901
|
-
const
|
|
4902
|
-
|
|
4981
|
+
const envPort = readApiServerPort(
|
|
4982
|
+
(await readHermesApiServerEnvOverrides(profileName)).port
|
|
4983
|
+
);
|
|
4984
|
+
const configPort = readApiServerPort(readApiServerConfig(apiServer).port);
|
|
4985
|
+
for (const port of [envPort, configPort]) {
|
|
4986
|
+
if (port === null) {
|
|
4987
|
+
continue;
|
|
4988
|
+
}
|
|
4903
4989
|
ports.add(port);
|
|
4904
4990
|
}
|
|
4905
4991
|
}
|
|
@@ -5418,7 +5504,7 @@ import os2 from "os";
|
|
|
5418
5504
|
import path5 from "path";
|
|
5419
5505
|
|
|
5420
5506
|
// src/constants.ts
|
|
5421
|
-
var LINK_VERSION = "0.6.
|
|
5507
|
+
var LINK_VERSION = "0.6.8";
|
|
5422
5508
|
var LINK_COMMAND = "hermeslink";
|
|
5423
5509
|
var LINK_DEFAULT_PORT = 52379;
|
|
5424
5510
|
var LINK_RUNTIME_DIR_NAME = ".hermeslink";
|
|
@@ -9056,6 +9142,7 @@ var ConversationMaintenanceCoordinator = class {
|
|
|
9056
9142
|
clearPlans;
|
|
9057
9143
|
archivePlans;
|
|
9058
9144
|
async prepareClearAllConversationPlan(targetStatus = "active") {
|
|
9145
|
+
assertArchivedClearPlanTarget(targetStatus);
|
|
9059
9146
|
const targets = [];
|
|
9060
9147
|
for (const conversationId of await this.deps.store.listConversationIds()) {
|
|
9061
9148
|
const manifest = await this.deps.store.readManifest(conversationId).catch(() => null);
|
|
@@ -9078,6 +9165,7 @@ var ConversationMaintenanceCoordinator = class {
|
|
|
9078
9165
|
}
|
|
9079
9166
|
async executeClearAllConversationPlan(planId) {
|
|
9080
9167
|
let plan = await this.clearPlans.read(planId);
|
|
9168
|
+
assertArchivedClearPlanTarget(plan.target_status ?? "active");
|
|
9081
9169
|
if (plan.status === "completed") {
|
|
9082
9170
|
return plan;
|
|
9083
9171
|
}
|
|
@@ -9140,6 +9228,7 @@ var ConversationMaintenanceCoordinator = class {
|
|
|
9140
9228
|
}
|
|
9141
9229
|
async startClearAllConversationPlan(planId) {
|
|
9142
9230
|
const plan = await this.clearPlans.read(planId);
|
|
9231
|
+
assertArchivedClearPlanTarget(plan.target_status ?? "active");
|
|
9143
9232
|
if (plan.status === "completed" || plan.status === "executing") {
|
|
9144
9233
|
return plan;
|
|
9145
9234
|
}
|
|
@@ -9621,6 +9710,16 @@ var ConversationMaintenanceCoordinator = class {
|
|
|
9621
9710
|
return plan;
|
|
9622
9711
|
}
|
|
9623
9712
|
};
|
|
9713
|
+
function assertArchivedClearPlanTarget(targetStatus) {
|
|
9714
|
+
if (targetStatus === "archived") {
|
|
9715
|
+
return;
|
|
9716
|
+
}
|
|
9717
|
+
throw new LinkHttpError(
|
|
9718
|
+
409,
|
|
9719
|
+
"active_conversation_clear_plan_disabled",
|
|
9720
|
+
"Bulk deletion of active conversations is disabled. Archive active conversations first, or delete explicitly selected conversations."
|
|
9721
|
+
);
|
|
9722
|
+
}
|
|
9624
9723
|
function isVoiceAttachmentInput(attachment) {
|
|
9625
9724
|
return attachment.kind === "voice" || attachment.type === "voice" || attachment.is_voice_note === true || attachment.isVoiceNote === true;
|
|
9626
9725
|
}
|
|
@@ -18816,8 +18915,15 @@ var ConversationService = class {
|
|
|
18816
18915
|
);
|
|
18817
18916
|
const result = await addHermesCommandAllowlistEntry(
|
|
18818
18917
|
profileName,
|
|
18819
|
-
patternKey
|
|
18820
|
-
|
|
18918
|
+
patternKey,
|
|
18919
|
+
void 0,
|
|
18920
|
+
input.language
|
|
18921
|
+
).catch((error) => {
|
|
18922
|
+
if (error instanceof HermesConfigYamlError) {
|
|
18923
|
+
throw new LinkHttpError(409, error.code, error.message);
|
|
18924
|
+
}
|
|
18925
|
+
throw error;
|
|
18926
|
+
});
|
|
18821
18927
|
commandAllowlistUpdated = result.changed;
|
|
18822
18928
|
configPath = result.configPath;
|
|
18823
18929
|
requiresGatewayReload = result.requiresGatewayReload;
|
|
@@ -19529,6 +19635,36 @@ function readString16(body, key) {
|
|
|
19529
19635
|
const value = body[key];
|
|
19530
19636
|
return typeof value === "string" && value.trim() ? value.trim() : null;
|
|
19531
19637
|
}
|
|
19638
|
+
function readPreferredLanguage(ctx) {
|
|
19639
|
+
const candidates = [
|
|
19640
|
+
ctx.get("x-hermespilot-language"),
|
|
19641
|
+
ctx.get("x-app-language"),
|
|
19642
|
+
ctx.get("accept-language")
|
|
19643
|
+
];
|
|
19644
|
+
for (const candidate of candidates) {
|
|
19645
|
+
for (const part of candidate.split(",")) {
|
|
19646
|
+
const normalized = part.split(";")[0]?.trim();
|
|
19647
|
+
if (!normalized) {
|
|
19648
|
+
continue;
|
|
19649
|
+
}
|
|
19650
|
+
const language = readSupportedLanguage(normalized);
|
|
19651
|
+
if (language) {
|
|
19652
|
+
return language;
|
|
19653
|
+
}
|
|
19654
|
+
}
|
|
19655
|
+
}
|
|
19656
|
+
return resolveLanguage();
|
|
19657
|
+
}
|
|
19658
|
+
function readSupportedLanguage(value) {
|
|
19659
|
+
const normalized = value.trim().replace("_", "-").toLowerCase();
|
|
19660
|
+
if (normalized.startsWith("zh")) {
|
|
19661
|
+
return "zh-CN";
|
|
19662
|
+
}
|
|
19663
|
+
if (normalized.startsWith("en")) {
|
|
19664
|
+
return "en";
|
|
19665
|
+
}
|
|
19666
|
+
return null;
|
|
19667
|
+
}
|
|
19532
19668
|
function readOptionalProfileName(body) {
|
|
19533
19669
|
return readString16(body, "profile") ?? readString16(body, "profile_name") ?? readString16(body, "profileName") ?? void 0;
|
|
19534
19670
|
}
|
|
@@ -19875,7 +20011,7 @@ function isExpectedClientDisconnectError(error) {
|
|
|
19875
20011
|
|
|
19876
20012
|
// src/http/routes/conversations.ts
|
|
19877
20013
|
function registerConversationRoutes(router, options) {
|
|
19878
|
-
const { paths, conversations } = options;
|
|
20014
|
+
const { paths, logger, conversations } = options;
|
|
19879
20015
|
router.get("/api/v1/conversations", async (ctx) => {
|
|
19880
20016
|
await authenticateRequest(ctx, paths);
|
|
19881
20017
|
ctx.set("cache-control", "no-store");
|
|
@@ -20081,12 +20217,20 @@ function registerConversationRoutes(router, options) {
|
|
|
20081
20217
|
ctx.body = { ok: true };
|
|
20082
20218
|
});
|
|
20083
20219
|
router.post("/api/v1/conversations/clear-plans", async (ctx) => {
|
|
20084
|
-
await authenticateRequest(ctx, paths);
|
|
20220
|
+
const auth = await authenticateRequest(ctx, paths);
|
|
20085
20221
|
const body = await readJsonBody(ctx.req);
|
|
20086
20222
|
const targetStatus = readConversationClearPlanTargetStatus(body);
|
|
20087
20223
|
const plan = await conversations.prepareClearAllConversationPlan(
|
|
20088
20224
|
targetStatus
|
|
20089
20225
|
);
|
|
20226
|
+
void logger.warn(
|
|
20227
|
+
"conversation_clear_plan_prepared",
|
|
20228
|
+
conversationMutationAuditFields(ctx, auth, {
|
|
20229
|
+
plan_id: plan.id,
|
|
20230
|
+
target_status: plan.target_status,
|
|
20231
|
+
total_count: plan.total_count
|
|
20232
|
+
})
|
|
20233
|
+
);
|
|
20090
20234
|
ctx.status = 201;
|
|
20091
20235
|
ctx.body = {
|
|
20092
20236
|
ok: true,
|
|
@@ -20104,10 +20248,19 @@ function registerConversationRoutes(router, options) {
|
|
|
20104
20248
|
router.post(
|
|
20105
20249
|
"/api/v1/conversations/clear-plans/:planId/execute",
|
|
20106
20250
|
async (ctx) => {
|
|
20107
|
-
await authenticateRequest(ctx, paths);
|
|
20251
|
+
const auth = await authenticateRequest(ctx, paths);
|
|
20108
20252
|
const plan = await conversations.startClearAllConversationPlan(
|
|
20109
20253
|
ctx.params.planId
|
|
20110
20254
|
);
|
|
20255
|
+
void logger.warn(
|
|
20256
|
+
"conversation_clear_plan_execute_requested",
|
|
20257
|
+
conversationMutationAuditFields(ctx, auth, {
|
|
20258
|
+
plan_id: plan.id,
|
|
20259
|
+
target_status: plan.target_status,
|
|
20260
|
+
total_count: plan.total_count,
|
|
20261
|
+
status: plan.status
|
|
20262
|
+
})
|
|
20263
|
+
);
|
|
20111
20264
|
ctx.status = plan.status === "completed" ? 200 : 202;
|
|
20112
20265
|
ctx.body = {
|
|
20113
20266
|
ok: true,
|
|
@@ -20156,7 +20309,7 @@ function registerConversationRoutes(router, options) {
|
|
|
20156
20309
|
}
|
|
20157
20310
|
);
|
|
20158
20311
|
router.delete("/api/v1/conversations", async (ctx) => {
|
|
20159
|
-
await authenticateRequest(ctx, paths);
|
|
20312
|
+
const auth = await authenticateRequest(ctx, paths);
|
|
20160
20313
|
const body = await readJsonBody(ctx.req);
|
|
20161
20314
|
const conversationIds = readStringArray(
|
|
20162
20315
|
body,
|
|
@@ -20171,6 +20324,14 @@ function registerConversationRoutes(router, options) {
|
|
|
20171
20324
|
);
|
|
20172
20325
|
}
|
|
20173
20326
|
const deleted = await conversations.deleteConversations(conversationIds);
|
|
20327
|
+
void logger.warn(
|
|
20328
|
+
"conversation_bulk_delete_requested",
|
|
20329
|
+
conversationMutationAuditFields(ctx, auth, {
|
|
20330
|
+
requested_count: conversationIds.length,
|
|
20331
|
+
deleted_count: deleted.deleted_count,
|
|
20332
|
+
failed_count: deleted.failed_count
|
|
20333
|
+
})
|
|
20334
|
+
);
|
|
20174
20335
|
const ok = deleted.failed_count === 0;
|
|
20175
20336
|
ctx.status = ok ? 200 : 409;
|
|
20176
20337
|
ctx.body = {
|
|
@@ -20209,7 +20370,8 @@ function registerConversationRoutes(router, options) {
|
|
|
20209
20370
|
...await conversations.resolveApproval({
|
|
20210
20371
|
conversationId: ctx.params.conversationId,
|
|
20211
20372
|
approvalId: ctx.params.approvalId,
|
|
20212
|
-
decision: scope
|
|
20373
|
+
decision: scope,
|
|
20374
|
+
language: readPreferredLanguage(ctx)
|
|
20213
20375
|
})
|
|
20214
20376
|
};
|
|
20215
20377
|
}
|
|
@@ -20223,7 +20385,8 @@ function registerConversationRoutes(router, options) {
|
|
|
20223
20385
|
...await conversations.resolveApproval({
|
|
20224
20386
|
conversationId: ctx.params.conversationId,
|
|
20225
20387
|
approvalId: ctx.params.approvalId,
|
|
20226
|
-
decision: "deny"
|
|
20388
|
+
decision: "deny",
|
|
20389
|
+
language: readPreferredLanguage(ctx)
|
|
20227
20390
|
})
|
|
20228
20391
|
};
|
|
20229
20392
|
}
|
|
@@ -20248,10 +20411,21 @@ function registerConversationRoutes(router, options) {
|
|
|
20248
20411
|
}
|
|
20249
20412
|
);
|
|
20250
20413
|
router.delete("/api/v1/conversations/:conversationId", async (ctx) => {
|
|
20251
|
-
await authenticateRequest(ctx, paths);
|
|
20414
|
+
const auth = await authenticateRequest(ctx, paths);
|
|
20415
|
+
const result = await conversations.deleteConversation(
|
|
20416
|
+
ctx.params.conversationId
|
|
20417
|
+
);
|
|
20418
|
+
void logger.warn(
|
|
20419
|
+
"conversation_delete_requested",
|
|
20420
|
+
conversationMutationAuditFields(ctx, auth, {
|
|
20421
|
+
conversation_id: result.conversation_id,
|
|
20422
|
+
hermes_deleted: result.hermes_deleted,
|
|
20423
|
+
hermes_session_count: result.hermes_session_ids?.length ?? 0
|
|
20424
|
+
})
|
|
20425
|
+
);
|
|
20252
20426
|
ctx.body = {
|
|
20253
20427
|
ok: true,
|
|
20254
|
-
...
|
|
20428
|
+
...result,
|
|
20255
20429
|
blob_gc_completed: true
|
|
20256
20430
|
};
|
|
20257
20431
|
});
|
|
@@ -20314,6 +20488,21 @@ function readConversationClearPlanTargetStatus(body) {
|
|
|
20314
20488
|
"Conversation clear plan target status is invalid"
|
|
20315
20489
|
);
|
|
20316
20490
|
}
|
|
20491
|
+
function conversationMutationAuditFields(ctx, auth, fields) {
|
|
20492
|
+
return {
|
|
20493
|
+
method: ctx.method,
|
|
20494
|
+
path: ctx.path,
|
|
20495
|
+
auth_kind: auth.kind,
|
|
20496
|
+
device_id: auth.device?.id ?? null,
|
|
20497
|
+
device_label: auth.device?.label ?? null,
|
|
20498
|
+
device_platform: auth.device?.platform ?? null,
|
|
20499
|
+
device_model: auth.device?.model ?? null,
|
|
20500
|
+
account_id: auth.accountId ?? null,
|
|
20501
|
+
app_instance_id: auth.appInstanceId ?? null,
|
|
20502
|
+
user_agent: ctx.get("user-agent") || null,
|
|
20503
|
+
...fields
|
|
20504
|
+
};
|
|
20505
|
+
}
|
|
20317
20506
|
function readNonNegativeIntegerHeader(value) {
|
|
20318
20507
|
const raw = Array.isArray(value) ? value[0] : value;
|
|
20319
20508
|
if (!raw) {
|
|
@@ -20448,7 +20637,9 @@ var PROFILE_DELETE_VERIFY_INTERVAL_MS = 150;
|
|
|
20448
20637
|
var execFileAsync4 = promisify4(execFile4);
|
|
20449
20638
|
async function listHermesProfiles(paths = resolveRuntimePaths()) {
|
|
20450
20639
|
const profiles = /* @__PURE__ */ new Map();
|
|
20451
|
-
|
|
20640
|
+
if (await hasDefaultProfileConfigSource()) {
|
|
20641
|
+
profiles.set(DEFAULT_PROFILE, await profileInfo(DEFAULT_PROFILE, paths));
|
|
20642
|
+
}
|
|
20452
20643
|
const profilesDir = resolveHermesProfilesDir();
|
|
20453
20644
|
const entries = await readdir9(profilesDir, { withFileTypes: true }).catch(
|
|
20454
20645
|
(error) => {
|
|
@@ -20473,6 +20664,36 @@ async function listHermesProfiles(paths = resolveRuntimePaths()) {
|
|
|
20473
20664
|
return left.name.localeCompare(right.name);
|
|
20474
20665
|
});
|
|
20475
20666
|
}
|
|
20667
|
+
async function prepareHermesProfilesForUse(paths = resolveRuntimePaths()) {
|
|
20668
|
+
const profiles = await listHermesProfiles(paths);
|
|
20669
|
+
const prepared = [];
|
|
20670
|
+
for (const profile of profiles) {
|
|
20671
|
+
try {
|
|
20672
|
+
const result = await ensureHermesApiServerKey(
|
|
20673
|
+
profile.name,
|
|
20674
|
+
profile.configPath
|
|
20675
|
+
);
|
|
20676
|
+
prepared.push({
|
|
20677
|
+
profile,
|
|
20678
|
+
apiServer: result.apiServer,
|
|
20679
|
+
changed: result.changed,
|
|
20680
|
+
backupPath: result.backupPath,
|
|
20681
|
+
notice: result.notice,
|
|
20682
|
+
error: null
|
|
20683
|
+
});
|
|
20684
|
+
} catch (error) {
|
|
20685
|
+
prepared.push({
|
|
20686
|
+
profile,
|
|
20687
|
+
apiServer: null,
|
|
20688
|
+
changed: false,
|
|
20689
|
+
backupPath: null,
|
|
20690
|
+
notice: null,
|
|
20691
|
+
error: errorMessage(error)
|
|
20692
|
+
});
|
|
20693
|
+
}
|
|
20694
|
+
}
|
|
20695
|
+
return prepared;
|
|
20696
|
+
}
|
|
20476
20697
|
async function getHermesProfileStatus(name, paths = resolveRuntimePaths()) {
|
|
20477
20698
|
assertProfileName(name);
|
|
20478
20699
|
const profile = await profileInfo(name, paths);
|
|
@@ -20596,6 +20817,19 @@ async function pathExists(targetPath) {
|
|
|
20596
20817
|
throw error;
|
|
20597
20818
|
});
|
|
20598
20819
|
}
|
|
20820
|
+
async function hasDefaultProfileConfigSource() {
|
|
20821
|
+
const profilePath = resolveHermesProfileDir(DEFAULT_PROFILE);
|
|
20822
|
+
const profileStat = await stat13(profilePath).catch((error) => {
|
|
20823
|
+
if (isNodeError15(error, "ENOENT")) {
|
|
20824
|
+
return null;
|
|
20825
|
+
}
|
|
20826
|
+
throw error;
|
|
20827
|
+
});
|
|
20828
|
+
if (!profileStat?.isDirectory()) {
|
|
20829
|
+
return false;
|
|
20830
|
+
}
|
|
20831
|
+
return await pathExists(resolveHermesConfigPath(DEFAULT_PROFILE)) || await pathExists(path20.join(profilePath, ".env"));
|
|
20832
|
+
}
|
|
20599
20833
|
async function deleteHermesProfileWithCli(name) {
|
|
20600
20834
|
try {
|
|
20601
20835
|
await execFileAsync4(resolveHermesBin(), ["profile", "delete", name, "--yes"], {
|
|
@@ -20747,6 +20981,9 @@ function readExecErrorOutput2(error) {
|
|
|
20747
20981
|
function isNodeError15(error, code) {
|
|
20748
20982
|
return typeof error === "object" && error !== null && "code" in error && error.code === code;
|
|
20749
20983
|
}
|
|
20984
|
+
function errorMessage(error) {
|
|
20985
|
+
return error instanceof Error ? error.message : String(error);
|
|
20986
|
+
}
|
|
20750
20987
|
function escapeRegExp2(value) {
|
|
20751
20988
|
return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
20752
20989
|
}
|
|
@@ -21473,7 +21710,11 @@ function registerProfileCatalogRoutes(router, options) {
|
|
|
21473
21710
|
});
|
|
21474
21711
|
}
|
|
21475
21712
|
async function readProfileCatalogItem(profile) {
|
|
21476
|
-
const [capabilities, permissions, modelConfigs] = await Promise.all([
|
|
21713
|
+
const [apiServer, capabilities, permissions, modelConfigs] = await Promise.all([
|
|
21714
|
+
readCatalogField(
|
|
21715
|
+
"apiServer",
|
|
21716
|
+
() => readHermesApiServerConfig(profile.name, profile.configPath)
|
|
21717
|
+
),
|
|
21477
21718
|
readCatalogField(
|
|
21478
21719
|
"capabilities",
|
|
21479
21720
|
() => readHermesProfileCapabilities(profile.name)
|
|
@@ -21486,10 +21727,17 @@ async function readProfileCatalogItem(profile) {
|
|
|
21486
21727
|
]);
|
|
21487
21728
|
return {
|
|
21488
21729
|
profile,
|
|
21730
|
+
apiServer: apiServer.value ? {
|
|
21731
|
+
host: apiServer.value.host ?? null,
|
|
21732
|
+
port: apiServer.value.port ?? null,
|
|
21733
|
+
configured: Boolean(apiServer.value.key),
|
|
21734
|
+
changed: false
|
|
21735
|
+
} : null,
|
|
21489
21736
|
capabilities: capabilities.value,
|
|
21490
21737
|
permissions: permissions.value,
|
|
21491
21738
|
modelConfigs: modelConfigs.value,
|
|
21492
21739
|
errors: [
|
|
21740
|
+
...apiServer.errors,
|
|
21493
21741
|
...capabilities.errors,
|
|
21494
21742
|
...permissions.errors,
|
|
21495
21743
|
...modelConfigs.errors
|
|
@@ -21502,11 +21750,11 @@ async function readCatalogField(field, load) {
|
|
|
21502
21750
|
} catch (error) {
|
|
21503
21751
|
return {
|
|
21504
21752
|
value: null,
|
|
21505
|
-
errors: [{ field, message:
|
|
21753
|
+
errors: [{ field, message: errorMessage2(error) }]
|
|
21506
21754
|
};
|
|
21507
21755
|
}
|
|
21508
21756
|
}
|
|
21509
|
-
function
|
|
21757
|
+
function errorMessage2(error) {
|
|
21510
21758
|
return error instanceof Error ? error.message : String(error);
|
|
21511
21759
|
}
|
|
21512
21760
|
|
|
@@ -21619,7 +21867,7 @@ async function ensureHermesLinkSkillInstalledForProfiles(options = {}) {
|
|
|
21619
21867
|
const skillChanged = await writeHermesLinkSkill(skillPath);
|
|
21620
21868
|
const profiles = await listHermesProfiles(paths);
|
|
21621
21869
|
const results = [];
|
|
21622
|
-
for (const profile of profiles) {
|
|
21870
|
+
for (const profile of withDefaultProfilePlaceholder(profiles)) {
|
|
21623
21871
|
try {
|
|
21624
21872
|
results.push(await ensureProfileUsesExternalSkillDir(profile, externalDir));
|
|
21625
21873
|
} catch (error) {
|
|
@@ -21673,6 +21921,25 @@ async function ensureHermesLinkSkillInstalledBestEffort(options = {}) {
|
|
|
21673
21921
|
});
|
|
21674
21922
|
}
|
|
21675
21923
|
}
|
|
21924
|
+
function withDefaultProfilePlaceholder(profiles) {
|
|
21925
|
+
if (profiles.some((profile) => profile.name === "default")) {
|
|
21926
|
+
return profiles;
|
|
21927
|
+
}
|
|
21928
|
+
return [
|
|
21929
|
+
{
|
|
21930
|
+
uid: "default",
|
|
21931
|
+
name: "default",
|
|
21932
|
+
active: false,
|
|
21933
|
+
path: resolveHermesProfileDir("default"),
|
|
21934
|
+
configPath: resolveHermesConfigPath("default"),
|
|
21935
|
+
displayName: null,
|
|
21936
|
+
description: null,
|
|
21937
|
+
avatarType: "default",
|
|
21938
|
+
avatarUrl: null
|
|
21939
|
+
},
|
|
21940
|
+
...profiles
|
|
21941
|
+
];
|
|
21942
|
+
}
|
|
21676
21943
|
function resolveHermesLinkSkillExternalDir(paths = resolveRuntimePaths()) {
|
|
21677
21944
|
return path21.join(paths.homeDir, HERMES_LINK_SKILL_ROOT_DIR);
|
|
21678
21945
|
}
|
|
@@ -25153,6 +25420,16 @@ function registerProfileRoutes(router, options) {
|
|
|
25153
25420
|
profiles: await listHermesProfiles(paths)
|
|
25154
25421
|
};
|
|
25155
25422
|
});
|
|
25423
|
+
router.post("/api/v1/profiles/prepare", async (ctx) => {
|
|
25424
|
+
await authenticateRequest(ctx, paths);
|
|
25425
|
+
ctx.set("cache-control", "no-store");
|
|
25426
|
+
ctx.body = {
|
|
25427
|
+
ok: true,
|
|
25428
|
+
profiles: (await prepareHermesProfilesForUse(paths)).map(
|
|
25429
|
+
formatProfilePreparation
|
|
25430
|
+
)
|
|
25431
|
+
};
|
|
25432
|
+
});
|
|
25156
25433
|
router.get("/api/v1/profile-creation/status", async (ctx) => {
|
|
25157
25434
|
await authenticateRequest(ctx, paths);
|
|
25158
25435
|
ctx.set("cache-control", "no-store");
|
|
@@ -25252,6 +25529,20 @@ function registerProfileRoutes(router, options) {
|
|
|
25252
25529
|
ctx.status = 204;
|
|
25253
25530
|
});
|
|
25254
25531
|
}
|
|
25532
|
+
function formatProfilePreparation(item) {
|
|
25533
|
+
return {
|
|
25534
|
+
profile: item.profile,
|
|
25535
|
+
apiServer: item.apiServer ? {
|
|
25536
|
+
host: item.apiServer.host ?? null,
|
|
25537
|
+
port: item.apiServer.port ?? null,
|
|
25538
|
+
configured: Boolean(item.apiServer.key),
|
|
25539
|
+
changed: item.changed
|
|
25540
|
+
} : null,
|
|
25541
|
+
changed: item.changed,
|
|
25542
|
+
notice: item.notice,
|
|
25543
|
+
error: item.error
|
|
25544
|
+
};
|
|
25545
|
+
}
|
|
25255
25546
|
function readProfileName(body) {
|
|
25256
25547
|
if (typeof body.name !== "string") {
|
|
25257
25548
|
throw new Error("invalid profile name");
|
|
@@ -26055,12 +26346,10 @@ function computeRelayBackoffMs(attempt, options = {}) {
|
|
|
26055
26346
|
return exponential + Math.floor(exponential * ratio);
|
|
26056
26347
|
}
|
|
26057
26348
|
async function updateRelayReconnectState(paths, update) {
|
|
26058
|
-
|
|
26059
|
-
const next = {
|
|
26349
|
+
await updateJsonFile(paths.stateFile, (state) => ({
|
|
26060
26350
|
...state,
|
|
26061
|
-
relayReconnect: update(normalizeRelayReconnectState(state
|
|
26062
|
-
};
|
|
26063
|
-
await writeJsonFile(paths.stateFile, next);
|
|
26351
|
+
relayReconnect: update(normalizeRelayReconnectState(state?.relayReconnect))
|
|
26352
|
+
}));
|
|
26064
26353
|
}
|
|
26065
26354
|
async function readLinkState(paths) {
|
|
26066
26355
|
const state = await readJsonFile(paths.stateFile);
|
|
@@ -26167,6 +26456,9 @@ function readInteger4(value) {
|
|
|
26167
26456
|
}
|
|
26168
26457
|
|
|
26169
26458
|
// src/relay/control-client.ts
|
|
26459
|
+
var DEFAULT_RELAY_HANDSHAKE_TIMEOUT_MS = 2e4;
|
|
26460
|
+
var DEFAULT_RELAY_PING_INTERVAL_MS = 3 * 6e4;
|
|
26461
|
+
var DEFAULT_RELAY_PONG_TIMEOUT_MS = 3e4;
|
|
26170
26462
|
function connectRelayControl(options) {
|
|
26171
26463
|
const wsUrl = new URL(`${options.relayBaseUrl.replace(/\/+$/u, "")}/api/v1/relay/link/connect`);
|
|
26172
26464
|
wsUrl.protocol = wsUrl.protocol === "https:" ? "wss:" : "ws:";
|
|
@@ -26175,10 +26467,15 @@ function connectRelayControl(options) {
|
|
|
26175
26467
|
const maxReconnectAttempts = options.maxReconnectAttempts ?? Number.POSITIVE_INFINITY;
|
|
26176
26468
|
const backoffBaseMs = options.backoffBaseMs ?? DEFAULT_RELAY_RECONNECT_BASE_MS;
|
|
26177
26469
|
const backoffMaxMs = options.backoffMaxMs ?? DEFAULT_RELAY_RECONNECT_MAX_MS;
|
|
26470
|
+
const handshakeTimeoutMs = positiveInteger2(options.handshakeTimeoutMs, DEFAULT_RELAY_HANDSHAKE_TIMEOUT_MS);
|
|
26471
|
+
const pingIntervalMs = positiveInteger2(options.pingIntervalMs, DEFAULT_RELAY_PING_INTERVAL_MS);
|
|
26472
|
+
const pongTimeoutMs = positiveInteger2(options.pongTimeoutMs, DEFAULT_RELAY_PONG_TIMEOUT_MS);
|
|
26178
26473
|
let reconnectAttempts = 0;
|
|
26179
26474
|
let closedByUser = false;
|
|
26180
26475
|
let socket = null;
|
|
26181
26476
|
let retryTimer = null;
|
|
26477
|
+
let pingTimer = null;
|
|
26478
|
+
let pongTimer = null;
|
|
26182
26479
|
let abortControllers = /* @__PURE__ */ new Map();
|
|
26183
26480
|
let fatalRelayRejection = null;
|
|
26184
26481
|
let relayRetryAfterMs = null;
|
|
@@ -26206,15 +26503,19 @@ function connectRelayControl(options) {
|
|
|
26206
26503
|
});
|
|
26207
26504
|
};
|
|
26208
26505
|
const connect = () => {
|
|
26506
|
+
clearRetryTimer();
|
|
26507
|
+
clearHeartbeatTimers();
|
|
26209
26508
|
options.onStatus?.({ state: "connecting", attempt: reconnectAttempts });
|
|
26210
26509
|
fatalRelayRejection = null;
|
|
26211
26510
|
relayRetryAfterMs = null;
|
|
26212
26511
|
let closeHandled = false;
|
|
26512
|
+
let localCloseReason;
|
|
26213
26513
|
const handleConnectionClosed = (reason) => {
|
|
26214
26514
|
if (closeHandled) {
|
|
26215
26515
|
return;
|
|
26216
26516
|
}
|
|
26217
26517
|
closeHandled = true;
|
|
26518
|
+
clearHeartbeatTimers();
|
|
26218
26519
|
abortAll(abortControllers);
|
|
26219
26520
|
abortControllers = /* @__PURE__ */ new Map();
|
|
26220
26521
|
if (fatalRelayRejection) {
|
|
@@ -26241,30 +26542,48 @@ function connectRelayControl(options) {
|
|
|
26241
26542
|
scheduleTimer(backoffMaxMs, "retrying", `Relay reconnect scheduling failed: ${message}`);
|
|
26242
26543
|
});
|
|
26243
26544
|
};
|
|
26244
|
-
|
|
26545
|
+
const currentSocket = new WebSocket(wsUrl, {
|
|
26546
|
+
handshakeTimeout: handshakeTimeoutMs,
|
|
26245
26547
|
headers: {
|
|
26246
26548
|
"x-hermes-link-version": LINK_VERSION
|
|
26247
26549
|
}
|
|
26248
26550
|
});
|
|
26249
|
-
socket
|
|
26551
|
+
socket = currentSocket;
|
|
26552
|
+
currentSocket.on("open", () => {
|
|
26553
|
+
if (socket !== currentSocket) {
|
|
26554
|
+
return;
|
|
26555
|
+
}
|
|
26250
26556
|
reconnectAttempts = 0;
|
|
26251
26557
|
void clearRelayReconnectState(paths).catch(() => void 0);
|
|
26252
26558
|
options.onStatus?.({ state: "connected", attempt: reconnectAttempts });
|
|
26253
|
-
|
|
26254
|
-
|
|
26559
|
+
startHeartbeat(currentSocket, (message) => {
|
|
26560
|
+
localCloseReason = message;
|
|
26561
|
+
options.onStatus?.({ state: "disconnected", attempt: reconnectAttempts, message });
|
|
26562
|
+
currentSocket.terminate();
|
|
26563
|
+
});
|
|
26564
|
+
if (latestNetworkRoutes) {
|
|
26255
26565
|
sendNetworkRoutes(currentSocket, options.linkId, latestNetworkRoutes);
|
|
26256
26566
|
}
|
|
26257
26567
|
});
|
|
26258
|
-
|
|
26259
|
-
if (
|
|
26568
|
+
currentSocket.on("pong", () => {
|
|
26569
|
+
if (socket !== currentSocket) {
|
|
26260
26570
|
return;
|
|
26261
26571
|
}
|
|
26262
|
-
|
|
26572
|
+
clearPongTimer();
|
|
26573
|
+
});
|
|
26574
|
+
currentSocket.on("message", (raw) => {
|
|
26575
|
+
if (socket !== currentSocket || typeof raw !== "string" && !Buffer.isBuffer(raw)) {
|
|
26576
|
+
return;
|
|
26577
|
+
}
|
|
26578
|
+
void handleFrame(currentSocket, String(raw), options.localPort, abortControllers, streamBatchPolicy).catch((error) => {
|
|
26263
26579
|
const message = error instanceof Error ? error.message : "Relay request failed";
|
|
26264
|
-
|
|
26580
|
+
currentSocket.send(JSON.stringify({ type: "http.error", id: "unknown", status: 502, message }));
|
|
26265
26581
|
});
|
|
26266
26582
|
});
|
|
26267
|
-
|
|
26583
|
+
currentSocket.on("unexpected-response", (request, response) => {
|
|
26584
|
+
if (socket !== currentSocket) {
|
|
26585
|
+
return;
|
|
26586
|
+
}
|
|
26268
26587
|
const statusCode = response.statusCode ?? 0;
|
|
26269
26588
|
fatalRelayRejection = resolveFatalRelayRejectionFromStatus(statusCode);
|
|
26270
26589
|
relayRetryAfterMs = readRetryAfterMs(response);
|
|
@@ -26278,7 +26597,10 @@ function connectRelayControl(options) {
|
|
|
26278
26597
|
handleConnectionClosed(message);
|
|
26279
26598
|
request.destroy();
|
|
26280
26599
|
});
|
|
26281
|
-
|
|
26600
|
+
currentSocket.on("error", (error) => {
|
|
26601
|
+
if (socket !== currentSocket) {
|
|
26602
|
+
return;
|
|
26603
|
+
}
|
|
26282
26604
|
const message = error instanceof Error ? error.message : "Relay websocket error";
|
|
26283
26605
|
fatalRelayRejection = resolveFatalRelayRejection(message);
|
|
26284
26606
|
options.onStatus?.({
|
|
@@ -26287,8 +26609,12 @@ function connectRelayControl(options) {
|
|
|
26287
26609
|
message: fatalRelayRejection ?? message
|
|
26288
26610
|
});
|
|
26289
26611
|
});
|
|
26290
|
-
|
|
26291
|
-
|
|
26612
|
+
currentSocket.on("close", (code, reason) => {
|
|
26613
|
+
if (socket !== currentSocket) {
|
|
26614
|
+
return;
|
|
26615
|
+
}
|
|
26616
|
+
socket = null;
|
|
26617
|
+
handleConnectionClosed(localCloseReason ?? formatCloseReason(code, reason));
|
|
26292
26618
|
});
|
|
26293
26619
|
};
|
|
26294
26620
|
startConnect();
|
|
@@ -26318,10 +26644,59 @@ function connectRelayControl(options) {
|
|
|
26318
26644
|
return await readRelayCooldownDelayMs(paths).catch(() => 0);
|
|
26319
26645
|
}
|
|
26320
26646
|
function scheduleTimer(delay4, state, message) {
|
|
26647
|
+
clearRetryTimer();
|
|
26321
26648
|
options.onStatus?.({ state, attempt: reconnectAttempts, message });
|
|
26322
26649
|
retryTimer = setTimeout(connect, delay4);
|
|
26323
26650
|
retryTimer.unref?.();
|
|
26324
26651
|
}
|
|
26652
|
+
function clearRetryTimer() {
|
|
26653
|
+
if (retryTimer == null) {
|
|
26654
|
+
return;
|
|
26655
|
+
}
|
|
26656
|
+
clearTimeout(retryTimer);
|
|
26657
|
+
retryTimer = null;
|
|
26658
|
+
}
|
|
26659
|
+
function startHeartbeat(currentSocket, onTimeout) {
|
|
26660
|
+
clearHeartbeatTimers();
|
|
26661
|
+
pingTimer = setInterval(() => {
|
|
26662
|
+
if (socket !== currentSocket || currentSocket.readyState !== WebSocket.OPEN) {
|
|
26663
|
+
clearHeartbeatTimers();
|
|
26664
|
+
return;
|
|
26665
|
+
}
|
|
26666
|
+
if (pongTimer != null) {
|
|
26667
|
+
return;
|
|
26668
|
+
}
|
|
26669
|
+
try {
|
|
26670
|
+
currentSocket.ping();
|
|
26671
|
+
} catch {
|
|
26672
|
+
onTimeout("Relay websocket ping failed");
|
|
26673
|
+
return;
|
|
26674
|
+
}
|
|
26675
|
+
pongTimer = setTimeout(() => {
|
|
26676
|
+
if (socket !== currentSocket || currentSocket.readyState !== WebSocket.OPEN) {
|
|
26677
|
+
clearPongTimer();
|
|
26678
|
+
return;
|
|
26679
|
+
}
|
|
26680
|
+
onTimeout(`Relay websocket pong timed out after ${pongTimeoutMs}ms`);
|
|
26681
|
+
}, pongTimeoutMs);
|
|
26682
|
+
pongTimer.unref?.();
|
|
26683
|
+
}, pingIntervalMs);
|
|
26684
|
+
pingTimer.unref?.();
|
|
26685
|
+
}
|
|
26686
|
+
function clearHeartbeatTimers() {
|
|
26687
|
+
if (pingTimer != null) {
|
|
26688
|
+
clearInterval(pingTimer);
|
|
26689
|
+
pingTimer = null;
|
|
26690
|
+
}
|
|
26691
|
+
clearPongTimer();
|
|
26692
|
+
}
|
|
26693
|
+
function clearPongTimer() {
|
|
26694
|
+
if (pongTimer == null) {
|
|
26695
|
+
return;
|
|
26696
|
+
}
|
|
26697
|
+
clearTimeout(pongTimer);
|
|
26698
|
+
pongTimer = null;
|
|
26699
|
+
}
|
|
26325
26700
|
return {
|
|
26326
26701
|
publishNetworkRoutes(routes) {
|
|
26327
26702
|
latestNetworkRoutes = routes;
|
|
@@ -26335,10 +26710,8 @@ function connectRelayControl(options) {
|
|
|
26335
26710
|
},
|
|
26336
26711
|
close() {
|
|
26337
26712
|
closedByUser = true;
|
|
26338
|
-
|
|
26339
|
-
|
|
26340
|
-
retryTimer = null;
|
|
26341
|
-
}
|
|
26713
|
+
clearRetryTimer();
|
|
26714
|
+
clearHeartbeatTimers();
|
|
26342
26715
|
abortAll(abortControllers);
|
|
26343
26716
|
socket?.terminate();
|
|
26344
26717
|
}
|
|
@@ -26390,6 +26763,16 @@ function readRetryAfterMs(response) {
|
|
|
26390
26763
|
}
|
|
26391
26764
|
return Math.max(0, dateMs - Date.now());
|
|
26392
26765
|
}
|
|
26766
|
+
function formatCloseReason(code, reason) {
|
|
26767
|
+
const text = reason.toString("utf8").trim();
|
|
26768
|
+
if (code === 1e3 && !text) {
|
|
26769
|
+
return void 0;
|
|
26770
|
+
}
|
|
26771
|
+
return text ? `Relay websocket closed (${code}): ${text}` : `Relay websocket closed (${code})`;
|
|
26772
|
+
}
|
|
26773
|
+
function positiveInteger2(value, fallback) {
|
|
26774
|
+
return Number.isFinite(value) ? Math.max(1, Math.floor(value)) : fallback;
|
|
26775
|
+
}
|
|
26393
26776
|
async function handleFrame(socket, raw, localPort, abortControllers, streamBatchPolicy) {
|
|
26394
26777
|
const frame = JSON.parse(raw);
|
|
26395
26778
|
if (frame.type === "relay.config.update") {
|
|
@@ -26515,8 +26898,7 @@ async function readRelayStatusSnapshot(paths) {
|
|
|
26515
26898
|
return normalizeRelayStatusSnapshot(state.relayStatus);
|
|
26516
26899
|
}
|
|
26517
26900
|
async function writeRelayStatusSnapshot(paths, status, now = /* @__PURE__ */ new Date()) {
|
|
26518
|
-
|
|
26519
|
-
await writeJsonFile(paths.stateFile, {
|
|
26901
|
+
await updateLinkState(paths, (current) => ({
|
|
26520
26902
|
...current,
|
|
26521
26903
|
relayStatus: {
|
|
26522
26904
|
state: status.state,
|
|
@@ -26524,7 +26906,10 @@ async function writeRelayStatusSnapshot(paths, status, now = /* @__PURE__ */ new
|
|
|
26524
26906
|
message: normalizeMessage(status.message),
|
|
26525
26907
|
updatedAt: now.toISOString()
|
|
26526
26908
|
}
|
|
26527
|
-
});
|
|
26909
|
+
}));
|
|
26910
|
+
}
|
|
26911
|
+
async function updateLinkState(paths, update) {
|
|
26912
|
+
await updateJsonFile(paths.stateFile, (state) => update(state && typeof state === "object" ? state : {}));
|
|
26528
26913
|
}
|
|
26529
26914
|
async function readLinkState2(paths) {
|
|
26530
26915
|
const state = await readJsonFile(paths.stateFile);
|
|
@@ -26943,12 +27328,10 @@ async function mergeLastReportedPublicRoutes(paths, snapshotInput) {
|
|
|
26943
27328
|
};
|
|
26944
27329
|
}
|
|
26945
27330
|
async function updateNetworkReportState(paths, update) {
|
|
26946
|
-
|
|
26947
|
-
const next = {
|
|
27331
|
+
await updateJsonFile(paths.stateFile, (state) => ({
|
|
26948
27332
|
...state,
|
|
26949
|
-
networkReport: update(normalizeNetworkReportState(state
|
|
26950
|
-
};
|
|
26951
|
-
await writeJsonFile(paths.stateFile, next);
|
|
27333
|
+
networkReport: update(normalizeNetworkReportState(state?.networkReport))
|
|
27334
|
+
}));
|
|
26952
27335
|
}
|
|
26953
27336
|
async function readLinkState3(paths) {
|
|
26954
27337
|
const state = await readJsonFile(paths.stateFile);
|
|
@@ -27413,6 +27796,15 @@ async function startLinkService(options = {}) {
|
|
|
27413
27796
|
});
|
|
27414
27797
|
return streamBatchPolicy;
|
|
27415
27798
|
};
|
|
27799
|
+
let profilePreparation = Promise.resolve();
|
|
27800
|
+
const triggerProfilePreparation = (source) => {
|
|
27801
|
+
profilePreparation = profilePreparation.catch(() => void 0).then(() => prepareHermesProfilesForUse(paths)).then((profiles) => logProfilePreparationResult(logger, source, profiles)).catch((error) => {
|
|
27802
|
+
void logger.warn("profile_preparation_failed", {
|
|
27803
|
+
source,
|
|
27804
|
+
error: error instanceof Error ? error.message : String(error)
|
|
27805
|
+
});
|
|
27806
|
+
});
|
|
27807
|
+
};
|
|
27416
27808
|
let hermesSessionSync = Promise.resolve();
|
|
27417
27809
|
const triggerHermesSessionSync = () => {
|
|
27418
27810
|
hermesSessionSync = Promise.resolve().then(() => conversations.syncHermesSessions()).then(() => void 0).catch((error) => {
|
|
@@ -27432,6 +27824,7 @@ async function startLinkService(options = {}) {
|
|
|
27432
27824
|
logger,
|
|
27433
27825
|
source: "pairing_claimed"
|
|
27434
27826
|
});
|
|
27827
|
+
triggerProfilePreparation("pairing_claimed");
|
|
27435
27828
|
triggerHermesSessionSync();
|
|
27436
27829
|
void loadRelayStreamBatchPolicy("pairing_claimed");
|
|
27437
27830
|
await options.onPairingClaimed?.();
|
|
@@ -27492,6 +27885,9 @@ async function startLinkService(options = {}) {
|
|
|
27492
27885
|
port: config.port,
|
|
27493
27886
|
link_id: identity?.link_id ?? null
|
|
27494
27887
|
});
|
|
27888
|
+
if (identity?.link_id) {
|
|
27889
|
+
triggerProfilePreparation("service_startup");
|
|
27890
|
+
}
|
|
27495
27891
|
triggerHermesSessionSync();
|
|
27496
27892
|
const scheduler = startCronDeliveryScheduler({
|
|
27497
27893
|
paths,
|
|
@@ -27576,6 +27972,7 @@ async function startLinkService(options = {}) {
|
|
|
27576
27972
|
scheduler.close(),
|
|
27577
27973
|
hermesSessionSyncScheduler.close(),
|
|
27578
27974
|
lanIpMonitor?.close(),
|
|
27975
|
+
profilePreparation.catch(() => void 0),
|
|
27579
27976
|
hermesSessionSync.catch(() => void 0)
|
|
27580
27977
|
]);
|
|
27581
27978
|
await logger.info("service_stopped");
|
|
@@ -27601,6 +27998,32 @@ function waitForRelayReadyTimeout(timeoutMs) {
|
|
|
27601
27998
|
timer.unref?.();
|
|
27602
27999
|
});
|
|
27603
28000
|
}
|
|
28001
|
+
async function logProfilePreparationResult(logger, source, profiles) {
|
|
28002
|
+
const changed = profiles.filter((profile) => profile.changed);
|
|
28003
|
+
const failed = profiles.filter((profile) => profile.error);
|
|
28004
|
+
const summary = {
|
|
28005
|
+
source,
|
|
28006
|
+
total: profiles.length,
|
|
28007
|
+
prepared: profiles.length - failed.length,
|
|
28008
|
+
changed: changed.length,
|
|
28009
|
+
failed: failed.length,
|
|
28010
|
+
changed_profiles: changed.map((profile) => profile.profile.name)
|
|
28011
|
+
};
|
|
28012
|
+
if (changed.length > 0 || failed.length > 0) {
|
|
28013
|
+
await logger.warn("profiles_prepared", summary);
|
|
28014
|
+
} else {
|
|
28015
|
+
await logger.info("profiles_prepared", summary);
|
|
28016
|
+
}
|
|
28017
|
+
if (failed.length > 0) {
|
|
28018
|
+
await logger.warn("profile_preparation_partial_failure", {
|
|
28019
|
+
source,
|
|
28020
|
+
failed_profiles: failed.map((profile) => ({
|
|
28021
|
+
profile: profile.profile.name,
|
|
28022
|
+
error: profile.error
|
|
28023
|
+
}))
|
|
28024
|
+
});
|
|
28025
|
+
}
|
|
28026
|
+
}
|
|
27604
28027
|
function pidFilePath(paths = resolveRuntimePaths()) {
|
|
27605
28028
|
return `${paths.runDir}/hermeslink.pid`;
|
|
27606
28029
|
}
|
|
@@ -30569,7 +30992,7 @@ async function createApp(options = {}) {
|
|
|
30569
30992
|
conversations,
|
|
30570
30993
|
syncCronDeliveries
|
|
30571
30994
|
});
|
|
30572
|
-
registerConversationRoutes(router, { paths, conversations });
|
|
30995
|
+
registerConversationRoutes(router, { paths, logger, conversations });
|
|
30573
30996
|
registerRunRoutes(router, { paths, logger, conversations });
|
|
30574
30997
|
registerProfileRoutes(router, { paths, logger, conversations });
|
|
30575
30998
|
app.use(router.routes());
|
|
@@ -30585,6 +31008,7 @@ export {
|
|
|
30585
31008
|
resolveLanguage,
|
|
30586
31009
|
translate,
|
|
30587
31010
|
localizeErrorMessage,
|
|
31011
|
+
DEFAULT_HERMES_API_SERVER_PORT,
|
|
30588
31012
|
resolveHermesProfileDir,
|
|
30589
31013
|
resolveHermesConfigPath,
|
|
30590
31014
|
ensureHermesApiServerConfig,
|
|
@@ -30599,6 +31023,7 @@ export {
|
|
|
30599
31023
|
HermesApiServerUnavailableError,
|
|
30600
31024
|
ensureHermesApiServerAvailable,
|
|
30601
31025
|
readHermesVersion,
|
|
31026
|
+
readHermesApiServerHealth,
|
|
30602
31027
|
defaultLinkConfig,
|
|
30603
31028
|
loadConfig,
|
|
30604
31029
|
saveConfig,
|
|
@@ -30609,6 +31034,7 @@ export {
|
|
|
30609
31034
|
getIdentityStatus,
|
|
30610
31035
|
ConversationService,
|
|
30611
31036
|
hasActiveDevices,
|
|
31037
|
+
prepareHermesProfilesForUse,
|
|
30612
31038
|
ensureHermesLinkSkillInstalledBestEffort,
|
|
30613
31039
|
detectRuntimeEnvironment,
|
|
30614
31040
|
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-57ZJLOQA.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