@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.3";
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: readHeader(ctx, "x-filename") ?? void 0,
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 && osLabel && hostname.toLowerCase() !== osLabel.toLowerCase()) {
20831
- return truncateText(`${hostname} - ${osLabel}`, 128);
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 routes = options.routes ?? await discoverRouteCandidates({
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 routes = await discoverRouteCandidates({
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
- const lanIpMonitor = startLanIpMonitor({
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.close(),
21702
+ lanIpMonitor?.close(),
21642
21703
  hermesSessionSync.catch(() => void 0)
21643
21704
  ]);
21644
21705
  await logger.info("service_stopped");
package/dist/cli/index.js CHANGED
@@ -36,7 +36,7 @@ import {
36
36
  startDaemonProcess,
37
37
  startLinkService,
38
38
  stopDaemonProcess
39
- } from "../chunk-MFRSQUSE.js";
39
+ } from "../chunk-2CHGHWCY.js";
40
40
 
41
41
  // src/cli/index.ts
42
42
  import { Command } from "commander";
package/dist/http/app.js CHANGED
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  createApp
3
- } from "../chunk-MFRSQUSE.js";
3
+ } from "../chunk-2CHGHWCY.js";
4
4
  export {
5
5
  createApp
6
6
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hermespilot/link",
3
- "version": "0.4.3",
3
+ "version": "0.4.4",
4
4
  "private": false,
5
5
  "description": "Hermes Link companion service and CLI for connecting hermes-agent through HermesPilot",
6
6
  "license": "MIT",