@hermespilot/link 0.4.3 → 0.4.4
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.
|
@@ -3996,7 +3996,7 @@ import os2 from "os";
|
|
|
3996
3996
|
import path5 from "path";
|
|
3997
3997
|
|
|
3998
3998
|
// src/constants.ts
|
|
3999
|
-
var LINK_VERSION = "0.4.
|
|
3999
|
+
var LINK_VERSION = "0.4.4";
|
|
4000
4000
|
var LINK_COMMAND = "hermeslink";
|
|
4001
4001
|
var LINK_DEFAULT_PORT = 52379;
|
|
4002
4002
|
var LINK_RUNTIME_DIR_NAME = ".hermeslink";
|
|
@@ -15305,6 +15305,24 @@ function readHeader(ctx, name) {
|
|
|
15305
15305
|
const value = ctx.get(name).trim();
|
|
15306
15306
|
return value ? value : null;
|
|
15307
15307
|
}
|
|
15308
|
+
function readUploadFilenameHeader(ctx) {
|
|
15309
|
+
const encoded = readHeader(ctx, "x-filename-base64") ?? readHeader(ctx, "x-filename-b64");
|
|
15310
|
+
if (encoded) {
|
|
15311
|
+
const decoded = decodeBase64Utf8Header(encoded);
|
|
15312
|
+
if (decoded) {
|
|
15313
|
+
return decoded;
|
|
15314
|
+
}
|
|
15315
|
+
}
|
|
15316
|
+
return readHeader(ctx, "x-filename");
|
|
15317
|
+
}
|
|
15318
|
+
function decodeBase64Utf8Header(value) {
|
|
15319
|
+
const trimmed = value.trim();
|
|
15320
|
+
if (!trimmed || !/^[A-Za-z0-9+/]+={0,2}$/u.test(trimmed)) {
|
|
15321
|
+
return null;
|
|
15322
|
+
}
|
|
15323
|
+
const decoded = Buffer.from(trimmed, "base64").toString("utf8").trim();
|
|
15324
|
+
return decoded || null;
|
|
15325
|
+
}
|
|
15308
15326
|
var CONVERSATION_HISTORY_REPLAY_FIELDS = [
|
|
15309
15327
|
"tool_call_id",
|
|
15310
15328
|
"tool_calls",
|
|
@@ -15797,7 +15815,7 @@ function registerConversationRoutes(router, options) {
|
|
|
15797
15815
|
}
|
|
15798
15816
|
const blob = await conversations.writeBlob(ctx.params.conversationId, {
|
|
15799
15817
|
bytes,
|
|
15800
|
-
filename:
|
|
15818
|
+
filename: readUploadFilenameHeader(ctx) ?? void 0,
|
|
15801
15819
|
mime: ctx.get("content-type") || void 0
|
|
15802
15820
|
});
|
|
15803
15821
|
ctx.status = 201;
|
|
@@ -20801,12 +20819,18 @@ async function handleFrame(socket, raw, localPort, abortControllers) {
|
|
|
20801
20819
|
const body = Buffer.from(await response.arrayBuffer()).toString("base64");
|
|
20802
20820
|
socket.send(JSON.stringify({ type: "http.response", id: frame.id, status: response.status, headers, bodyBase64: body }));
|
|
20803
20821
|
} catch (error) {
|
|
20822
|
+
if (abortController.signal.aborted || isAbortError2(error)) {
|
|
20823
|
+
return;
|
|
20824
|
+
}
|
|
20804
20825
|
const message = error instanceof Error ? error.message : "Relay request failed";
|
|
20805
20826
|
socket.send(JSON.stringify({ type: "http.error", id: frame.id, status: 502, message }));
|
|
20806
20827
|
} finally {
|
|
20807
20828
|
abortControllers.delete(frame.id);
|
|
20808
20829
|
}
|
|
20809
20830
|
}
|
|
20831
|
+
function isAbortError2(error) {
|
|
20832
|
+
return typeof error === "object" && error !== null && "name" in error && error.name === "AbortError";
|
|
20833
|
+
}
|
|
20810
20834
|
|
|
20811
20835
|
// src/runtime/system-info.ts
|
|
20812
20836
|
import { execFileSync } from "child_process";
|
|
@@ -20827,8 +20851,8 @@ function readLinkSystemInfo() {
|
|
|
20827
20851
|
function buildDefaultDisplayName(input) {
|
|
20828
20852
|
const hostname = normalizeText(input.hostname);
|
|
20829
20853
|
const osLabel = normalizeText(input.osLabel);
|
|
20830
|
-
if (hostname
|
|
20831
|
-
return truncateText(
|
|
20854
|
+
if (hostname) {
|
|
20855
|
+
return truncateText(hostname, 128);
|
|
20832
20856
|
}
|
|
20833
20857
|
return truncateText(hostname ?? osLabel ?? `Hermes Link ${input.platform}`, 128);
|
|
20834
20858
|
}
|
|
@@ -21126,6 +21150,10 @@ function unique(values) {
|
|
|
21126
21150
|
|
|
21127
21151
|
// src/link/network-report-state.ts
|
|
21128
21152
|
var DEFAULT_AUTO_DAILY_LIMIT = 20;
|
|
21153
|
+
async function readNetworkReportState(paths) {
|
|
21154
|
+
const state = await readLinkState(paths);
|
|
21155
|
+
return normalizeNetworkReportState(state.networkReport);
|
|
21156
|
+
}
|
|
21129
21157
|
async function markNetworkStatusReported(paths, snapshotInput, reportedAt = /* @__PURE__ */ new Date()) {
|
|
21130
21158
|
const snapshot = normalizeNetworkSnapshot(snapshotInput);
|
|
21131
21159
|
await updateNetworkReportState(paths, (current) => ({
|
|
@@ -21175,6 +21203,20 @@ async function reserveAutomaticNetworkReport(paths, snapshotInput, options = {})
|
|
|
21175
21203
|
});
|
|
21176
21204
|
return reservation;
|
|
21177
21205
|
}
|
|
21206
|
+
async function mergeLastReportedPublicRoutes(paths, snapshotInput) {
|
|
21207
|
+
const state = await readNetworkReportState(paths);
|
|
21208
|
+
return {
|
|
21209
|
+
...snapshotInput,
|
|
21210
|
+
publicIpv4s: uniqueStrings([
|
|
21211
|
+
...snapshotInput.publicIpv4s,
|
|
21212
|
+
...state.lastReportedPublicIpv4s
|
|
21213
|
+
]).slice(0, 2),
|
|
21214
|
+
publicIpv6s: uniqueStrings([
|
|
21215
|
+
...snapshotInput.publicIpv6s,
|
|
21216
|
+
...state.lastReportedPublicIpv6s
|
|
21217
|
+
]).slice(0, 2)
|
|
21218
|
+
};
|
|
21219
|
+
}
|
|
21178
21220
|
async function updateNetworkReportState(paths, update) {
|
|
21179
21221
|
const state = await readLinkState(paths);
|
|
21180
21222
|
const next = {
|
|
@@ -21263,6 +21305,9 @@ function sameStringList(left, right) {
|
|
|
21263
21305
|
}
|
|
21264
21306
|
return left.every((value, index) => value === right[index]);
|
|
21265
21307
|
}
|
|
21308
|
+
function uniqueStrings(values) {
|
|
21309
|
+
return [...new Set(values)];
|
|
21310
|
+
}
|
|
21266
21311
|
function formatUtcDay(date) {
|
|
21267
21312
|
return date.toISOString().slice(0, 10);
|
|
21268
21313
|
}
|
|
@@ -21274,7 +21319,7 @@ async function reportLinkStatusToServer(options = {}) {
|
|
|
21274
21319
|
if (!identity?.link_id) {
|
|
21275
21320
|
return null;
|
|
21276
21321
|
}
|
|
21277
|
-
const
|
|
21322
|
+
const discoveredRoutes = options.routes ?? await discoverRouteCandidates({
|
|
21278
21323
|
port: config.port,
|
|
21279
21324
|
relayBaseUrl: config.relayBaseUrl,
|
|
21280
21325
|
linkId: identity.link_id,
|
|
@@ -21284,6 +21329,7 @@ async function reportLinkStatusToServer(options = {}) {
|
|
|
21284
21329
|
configuredLanHost: config.lanHost,
|
|
21285
21330
|
fetchImpl: options.fetchImpl
|
|
21286
21331
|
});
|
|
21332
|
+
const routes = await mergeLastReportedPublicRoutes(paths, discoveredRoutes);
|
|
21287
21333
|
const systemInfo = readLinkSystemInfo();
|
|
21288
21334
|
const payload = {
|
|
21289
21335
|
type: "hermes_link_status_report",
|
|
@@ -21375,12 +21421,16 @@ function startLanIpMonitor(options) {
|
|
|
21375
21421
|
running = false;
|
|
21376
21422
|
}
|
|
21377
21423
|
};
|
|
21378
|
-
current = check({ forceReport: true, publishToRelay: true });
|
|
21424
|
+
current = check({ forceReport: true, publishToRelay: true, observePublicRoute: true });
|
|
21379
21425
|
const timer = setInterval(() => {
|
|
21380
|
-
current = check();
|
|
21426
|
+
current = check({ observePublicRoute: false });
|
|
21381
21427
|
}, options.intervalMs ?? DEFAULT_INTERVAL_MS);
|
|
21382
21428
|
timer.unref?.();
|
|
21383
21429
|
return {
|
|
21430
|
+
async refreshPublicRoutes() {
|
|
21431
|
+
current = check({ forceReport: true, publishToRelay: true, observePublicRoute: true });
|
|
21432
|
+
await current;
|
|
21433
|
+
},
|
|
21384
21434
|
async close() {
|
|
21385
21435
|
closed = true;
|
|
21386
21436
|
clearInterval(timer);
|
|
@@ -21396,16 +21446,17 @@ async function checkLanIpChange(options, context = {}) {
|
|
|
21396
21446
|
if (!identity?.link_id) {
|
|
21397
21447
|
return;
|
|
21398
21448
|
}
|
|
21399
|
-
const
|
|
21449
|
+
const discoveredRoutes = await discoverRouteCandidates({
|
|
21400
21450
|
port: config.port,
|
|
21401
21451
|
relayBaseUrl: config.relayBaseUrl,
|
|
21402
21452
|
linkId: identity.link_id,
|
|
21403
21453
|
installId: identity.install_id,
|
|
21404
21454
|
publicKeyPem: identity.public_key_pem,
|
|
21405
|
-
observePublicRoute: true,
|
|
21455
|
+
observePublicRoute: context.observePublicRoute === true,
|
|
21406
21456
|
configuredLanHost: config.lanHost,
|
|
21407
21457
|
fetchImpl: options.fetchImpl
|
|
21408
21458
|
});
|
|
21459
|
+
const routes = await mergeLastReportedPublicRoutes(options.paths, discoveredRoutes);
|
|
21409
21460
|
if (context.publishToRelay) {
|
|
21410
21461
|
options.onNetworkRoutes?.(routes);
|
|
21411
21462
|
}
|
|
@@ -21515,6 +21566,7 @@ function startHermesSessionSyncScheduler(options) {
|
|
|
21515
21566
|
|
|
21516
21567
|
// src/daemon/service.ts
|
|
21517
21568
|
var DEFAULT_RELAY_READY_TIMEOUT_MS = 2e3;
|
|
21569
|
+
var RELAY_RECONNECT_PUBLIC_ROUTE_REFRESH_MIN_INTERVAL_MS = 15 * 6e4;
|
|
21518
21570
|
async function startLinkService(options = {}) {
|
|
21519
21571
|
const paths = options.paths ?? resolveRuntimePaths();
|
|
21520
21572
|
const [identity, config] = await Promise.all([loadIdentity(paths), loadConfig(paths)]);
|
|
@@ -21585,6 +21637,9 @@ async function startLinkService(options = {}) {
|
|
|
21585
21637
|
logger
|
|
21586
21638
|
});
|
|
21587
21639
|
let relay = null;
|
|
21640
|
+
let lanIpMonitor = null;
|
|
21641
|
+
let hasSeenRelayConnected = false;
|
|
21642
|
+
let lastRelayReconnectPublicRouteRefreshAt = 0;
|
|
21588
21643
|
if (identity?.link_id) {
|
|
21589
21644
|
let resolveRelayReady = null;
|
|
21590
21645
|
const relayReady = new Promise((resolve) => {
|
|
@@ -21600,6 +21655,12 @@ async function startLinkService(options = {}) {
|
|
|
21600
21655
|
onStatus: (status) => {
|
|
21601
21656
|
void logger.info("relay_status", status);
|
|
21602
21657
|
if (status.state === "connected") {
|
|
21658
|
+
const now = Date.now();
|
|
21659
|
+
if (hasSeenRelayConnected && lanIpMonitor && now - lastRelayReconnectPublicRouteRefreshAt >= RELAY_RECONNECT_PUBLIC_ROUTE_REFRESH_MIN_INTERVAL_MS) {
|
|
21660
|
+
lastRelayReconnectPublicRouteRefreshAt = now;
|
|
21661
|
+
void lanIpMonitor.refreshPublicRoutes();
|
|
21662
|
+
}
|
|
21663
|
+
hasSeenRelayConnected = true;
|
|
21603
21664
|
resolveRelayReady?.(true);
|
|
21604
21665
|
resolveRelayReady = null;
|
|
21605
21666
|
} else if (status.state === "failed") {
|
|
@@ -21618,7 +21679,7 @@ async function startLinkService(options = {}) {
|
|
|
21618
21679
|
} else {
|
|
21619
21680
|
void logger.info("relay_skipped", { reason: "link_not_paired" });
|
|
21620
21681
|
}
|
|
21621
|
-
|
|
21682
|
+
lanIpMonitor = startLanIpMonitor({
|
|
21622
21683
|
paths,
|
|
21623
21684
|
logger,
|
|
21624
21685
|
intervalMs: options.lanIpMonitorIntervalMs,
|
|
@@ -21638,7 +21699,7 @@ async function startLinkService(options = {}) {
|
|
|
21638
21699
|
await Promise.all([
|
|
21639
21700
|
scheduler.close(),
|
|
21640
21701
|
hermesSessionSyncScheduler.close(),
|
|
21641
|
-
lanIpMonitor
|
|
21702
|
+
lanIpMonitor?.close(),
|
|
21642
21703
|
hermesSessionSync.catch(() => void 0)
|
|
21643
21704
|
]);
|
|
21644
21705
|
await logger.info("service_stopped");
|
package/dist/cli/index.js
CHANGED
package/dist/http/app.js
CHANGED