@hermespilot/link 0.7.5 → 0.7.7-beta.0

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/cli/index.js CHANGED
@@ -7,6 +7,7 @@ import {
7
7
  LINK_VERSION,
8
8
  LinkHttpError,
9
9
  clearPairingClaim,
10
+ closeTuiGatewayBackends,
10
11
  connectRelayControl,
11
12
  createFileLogger,
12
13
  currentCliScriptPath,
@@ -38,10 +39,10 @@ import {
38
39
  readHermesVersion,
39
40
  readPairingClaim,
40
41
  readRecentGatewayLogEntries,
41
- readRecentLogEntries,
42
- readRecentTextLogEntries,
43
42
  readRelayStatusSnapshot,
43
+ readTuiGatewayStatus,
44
44
  reportLinkStatusToServer,
45
+ resolveConversationRunBackend,
45
46
  resolveHermesConfigPath,
46
47
  resolveHermesProfileDir,
47
48
  resolveLanguage,
@@ -53,15 +54,17 @@ import {
53
54
  stopDaemonProcess,
54
55
  summarizeUsageProbeEnsure,
55
56
  translate
56
- } from "../chunk-MVKOUAVA.js";
57
+ } from "../chunk-RFZ4SQV4.js";
57
58
 
58
59
  // src/cli/index.ts
59
60
  import { Command } from "commander";
61
+ import { execFile as execFile2 } from "child_process";
60
62
  import { realpathSync as realpathSync2, unwatchFile, watchFile } from "fs";
61
63
  import { open, readFile as readFile2, stat as stat2 } from "fs/promises";
62
64
  import path4 from "path";
63
65
  import { createInterface } from "readline/promises";
64
66
  import { pathToFileURL } from "url";
67
+ import { promisify as promisify2 } from "util";
65
68
  import qrcode from "qrcode-terminal";
66
69
 
67
70
  // src/autostart/autostart.ts
@@ -503,6 +506,26 @@ async function assertPairingPreflightReady(options = {}) {
503
506
  options.language
504
507
  );
505
508
  }
509
+ if (options.requireApiServer === false) {
510
+ return {
511
+ profileName,
512
+ hermesHome,
513
+ configPath,
514
+ envPath,
515
+ apiServer: {
516
+ available: true,
517
+ started: false,
518
+ host: null,
519
+ port: null
520
+ },
521
+ notice: null,
522
+ backupPath: null,
523
+ hermesVersion: {
524
+ raw: hermesVersion.raw,
525
+ version: hermesVersion.version
526
+ }
527
+ };
528
+ }
506
529
  options.onProgress?.("hermes_api_server");
507
530
  try {
508
531
  const ensureAvailable = options.ensureApiServerAvailable ?? ensureHermesApiServerAvailable;
@@ -781,6 +804,8 @@ function quoteShellToken(value, platform) {
781
804
  }
782
805
 
783
806
  // src/cli/index.ts
807
+ var execFileAsync2 = promisify2(execFile2);
808
+ var MIN_TUI_GATEWAY_RPC_VERSION = "0.11.0";
784
809
  var program = new Command();
785
810
  var helpLanguage = detectSystemLanguage();
786
811
  var helpText = translate.bind(null, helpLanguage);
@@ -790,11 +815,25 @@ program.command("status").option("--json", helpText("status.json")).description(
790
815
  const [identity, config] = await Promise.all([loadIdentity(paths), loadConfig(paths)]);
791
816
  const language = resolveLanguage(config.language);
792
817
  const t = translate.bind(null, language);
793
- const [daemonStatus, relayStatus, recentError] = await Promise.all([
818
+ const [daemonStatus, relayStatus] = await Promise.all([
794
819
  getDaemonStatus(paths),
795
- readRelayStatusSnapshot(paths).catch(() => null),
796
- readMostRecentError(paths).catch(() => null)
820
+ readRelayStatusSnapshot(paths).catch(() => null)
797
821
  ]);
822
+ const conversationBackend = resolveConversationRunBackend();
823
+ const tuiGatewayCompatibility = conversationBackend === "tui_gateway" ? await readTuiGatewayCompatibility() : null;
824
+ const tuiGatewayRead = await readDaemonTuiGatewayStatus(config.port).then((status) => ({ status, source: "daemon" })).catch(async () => ({
825
+ status: await readTuiGatewayStatus({
826
+ paths,
827
+ connect: false
828
+ }).catch((error) => ({
829
+ available: false,
830
+ profile: "default",
831
+ state: "error",
832
+ error: error instanceof Error ? error.message : String(error)
833
+ })),
834
+ source: "cli"
835
+ }));
836
+ const tuiGatewayDiagnostics = options.json ? await readTuiGatewayDiagnostics(paths).catch(() => null) : null;
798
837
  const serviceProbe = daemonStatus.running ? await probeLocalLinkService({
799
838
  port: config.port,
800
839
  linkId: identity?.link_id ?? void 0,
@@ -825,7 +864,11 @@ program.command("status").option("--json", helpText("status.json")).description(
825
864
  environment: detectRuntimeEnvironment(),
826
865
  identity: identity ? getIdentityStatus(identity) : null,
827
866
  relay,
828
- recentError
867
+ conversationBackend,
868
+ tuiGateway: tuiGatewayRead.status,
869
+ tuiGatewayStatusSource: tuiGatewayRead.source,
870
+ tuiGatewayCompatibility,
871
+ tuiGatewayDiagnostics
829
872
  };
830
873
  if (options.json) {
831
874
  console.log(JSON.stringify(payload, null, 2));
@@ -844,8 +887,28 @@ program.command("status").option("--json", helpText("status.json")).description(
844
887
  console.log(t("status.environmentWarning", { message: environmentWarning }));
845
888
  }
846
889
  console.log(t("status.linkId", { value: payload.identity?.linkId ?? t("status.notPaired") }));
890
+ console.log(
891
+ t("status.conversationBackend", {
892
+ value: payload.conversationBackend
893
+ })
894
+ );
895
+ console.log(
896
+ t("status.tuiGateway", {
897
+ value: formatTuiGatewayStatus(
898
+ payload.tuiGateway,
899
+ payload.tuiGatewayStatusSource,
900
+ t
901
+ )
902
+ })
903
+ );
904
+ const tuiGatewayUpgradeRequired = formatTuiGatewayUpgradeRequired(
905
+ payload.tuiGatewayCompatibility,
906
+ t
907
+ );
908
+ if (tuiGatewayUpgradeRequired) {
909
+ console.log(tuiGatewayUpgradeRequired);
910
+ }
847
911
  console.log(t("status.relay", { value: formatRelayStatus(payload.relay, t, language) }));
848
- console.log(t("status.recentError", { value: formatRecentError(payload.recentError, t, language) }));
849
912
  });
850
913
  var configCommand = program.command("config").description(helpText("config.description"));
851
914
  configCommand.command("set").argument("<key>").argument("<value>").description(helpText("config.set.description")).action(async (key, value) => {
@@ -993,9 +1056,11 @@ program.command("pair").description(helpText("pair.description")).action(async (
993
1056
  if (environmentWarning) {
994
1057
  console.log(t("doctor.networkWarning", { message: environmentWarning }));
995
1058
  }
1059
+ const conversationBackend = resolveConversationRunBackend();
996
1060
  const preflight = await assertPairingPreflightReady({
997
1061
  paths,
998
1062
  language,
1063
+ requireApiServer: conversationBackend !== "tui_gateway",
999
1064
  onProgress: (stage) => {
1000
1065
  console.log(t(pairingPreflightProgressKey(stage)));
1001
1066
  }
@@ -1006,6 +1071,45 @@ program.command("pair").description(helpText("pair.description")).action(async (
1006
1071
  console.log(t("config.backup", { path: preflight.backupPath }));
1007
1072
  }
1008
1073
  }
1074
+ if (conversationBackend === "tui_gateway") {
1075
+ console.log(t("pair.preflight.tuiGateway"));
1076
+ const tuiGatewayCompatibility = tuiGatewayCompatibilityFromVersion(
1077
+ preflight.hermesVersion
1078
+ );
1079
+ const upgradeRequired = formatTuiGatewayUpgradeRequired(
1080
+ tuiGatewayCompatibility,
1081
+ t
1082
+ );
1083
+ if (upgradeRequired) {
1084
+ throw new Error(upgradeRequired);
1085
+ }
1086
+ try {
1087
+ const rpcStatus = await readTuiGatewayStatus({
1088
+ paths,
1089
+ connect: true
1090
+ });
1091
+ if (!rpcStatus.available) {
1092
+ throw new Error(formatTuiGatewayStatus(rpcStatus, "cli", t));
1093
+ }
1094
+ console.log(
1095
+ t("pair.tuiGatewayReady", {
1096
+ value: formatTuiGatewayStatus(rpcStatus, "cli", t)
1097
+ })
1098
+ );
1099
+ } finally {
1100
+ closeTuiGatewayBackends();
1101
+ }
1102
+ try {
1103
+ const availability = await ensureHermesApiServerAvailable({
1104
+ timeoutMs: 3e3,
1105
+ language,
1106
+ autoStart: false
1107
+ });
1108
+ console.log(t(availability.started ? "doctor.apiStarted" : "doctor.apiReady"));
1109
+ } catch (error) {
1110
+ console.log(formatHermesApiServerUnavailable(error, language));
1111
+ }
1112
+ }
1009
1113
  console.log(t("pair.preparing"));
1010
1114
  await ensureIdentity(paths);
1011
1115
  const hadActiveDevices = await hasActiveDevices(paths);
@@ -1217,6 +1321,31 @@ program.command("doctor").option("--install", helpText("doctor.installOnly")).op
1217
1321
  } catch (error) {
1218
1322
  console.log(formatHermesApiServerUnavailable(error, language));
1219
1323
  }
1324
+ if (resolveConversationRunBackend() === "tui_gateway") {
1325
+ console.log(t("doctor.tuiGatewayChecking"));
1326
+ const tuiGatewayCompatibility = await readTuiGatewayCompatibility();
1327
+ const upgradeRequired = formatTuiGatewayUpgradeRequired(
1328
+ tuiGatewayCompatibility,
1329
+ t
1330
+ );
1331
+ if (upgradeRequired) {
1332
+ console.log(upgradeRequired);
1333
+ } else {
1334
+ try {
1335
+ const rpcStatus = await readTuiGatewayStatus({
1336
+ paths,
1337
+ connect: true
1338
+ });
1339
+ console.log(
1340
+ t("doctor.tuiGateway", {
1341
+ value: formatTuiGatewayStatus(rpcStatus, "cli", t)
1342
+ })
1343
+ );
1344
+ } finally {
1345
+ closeTuiGatewayBackends();
1346
+ }
1347
+ }
1348
+ }
1220
1349
  const usageProbe = await ensureUsageProbeFromCli(paths, t, language, true, "cli_doctor");
1221
1350
  if (options.profiles !== false) {
1222
1351
  await printProfileDiagnostics(t, usageProbe.profiles);
@@ -1409,6 +1538,29 @@ function formatDaemonStatus(daemon, t) {
1409
1538
  { pid }
1410
1539
  );
1411
1540
  }
1541
+ async function readTuiGatewayCompatibility() {
1542
+ const version = await readHermesVersion().catch(() => null);
1543
+ return tuiGatewayCompatibilityFromVersion(version);
1544
+ }
1545
+ function tuiGatewayCompatibilityFromVersion(version) {
1546
+ if (!version) {
1547
+ return null;
1548
+ }
1549
+ return {
1550
+ version: version.version,
1551
+ raw: version.raw,
1552
+ upgradeRequired: version.version ? compareSemver(version.version, MIN_TUI_GATEWAY_RPC_VERSION) < 0 : false
1553
+ };
1554
+ }
1555
+ function formatTuiGatewayUpgradeRequired(compatibility, t) {
1556
+ if (!compatibility?.upgradeRequired) {
1557
+ return null;
1558
+ }
1559
+ return t("tuiGateway.upgradeRequired", {
1560
+ version: compatibility.version ?? compatibility.raw ?? t("status.unknown"),
1561
+ minimum: MIN_TUI_GATEWAY_RPC_VERSION
1562
+ });
1563
+ }
1412
1564
  function formatRelayStatus(relay, t, language) {
1413
1565
  if (relay.state === "not_paired") {
1414
1566
  return t("status.relay.notPaired");
@@ -1472,65 +1624,120 @@ function formatRelayState(state, t) {
1472
1624
  return t("status.relay.state.failed");
1473
1625
  }
1474
1626
  }
1475
- async function readMostRecentError(paths) {
1476
- const [service, daemon, gateway] = await Promise.all([
1477
- readRecentLogEntries({ paths, limit: 120 }).catch(() => []),
1478
- readRecentTextLogEntries({
1479
- paths,
1480
- filePaths: [daemonLogFile(paths)],
1481
- limit: 120
1482
- }).catch(() => []),
1483
- readRecentGatewayLogEntries({ paths, limit: 120 }).catch(() => [])
1484
- ]);
1485
- const errors = [
1486
- ...service.map((entry) => toStatusRecentError("service", entry)),
1487
- ...daemon.map((entry) => toStatusRecentError("daemon", entry)),
1488
- ...gateway.map((entry) => toStatusRecentError("gateway", entry))
1489
- ].filter((item) => item !== null);
1490
- errors.sort((left, right) => readTimestampMs(right.ts) - readTimestampMs(left.ts));
1491
- return errors[0] ?? null;
1492
- }
1493
- function toStatusRecentError(source, entry) {
1494
- if (!isActionableStatusError(source, entry)) {
1495
- return null;
1627
+ function formatTuiGatewayStatus(status, source, t) {
1628
+ const sourceLabel = t(
1629
+ source === "daemon" ? "status.tuiGateway.source.daemon" : "status.tuiGateway.source.cli"
1630
+ );
1631
+ if (status.available) {
1632
+ const endpoint = status.baseUrl ?? t("status.unknown");
1633
+ const pid = status.pid ?? t("status.unknown");
1634
+ return t("status.tuiGateway.available", {
1635
+ profile: status.profile,
1636
+ endpoint,
1637
+ pid,
1638
+ source: sourceLabel
1639
+ });
1640
+ }
1641
+ if (status.error) {
1642
+ return t("status.tuiGateway.error", {
1643
+ profile: status.profile,
1644
+ state: status.state,
1645
+ message: status.error,
1646
+ source: sourceLabel
1647
+ });
1648
+ }
1649
+ if (status.state === "closed" && status.backend_state === "stopped") {
1650
+ return t("status.tuiGateway.idle", {
1651
+ profile: status.profile,
1652
+ source: sourceLabel
1653
+ });
1654
+ }
1655
+ return t("status.tuiGateway.unavailable", {
1656
+ profile: status.profile,
1657
+ state: status.state,
1658
+ source: sourceLabel
1659
+ });
1660
+ }
1661
+ async function readDaemonTuiGatewayStatus(port) {
1662
+ const response = await fetch(`http://127.0.0.1:${port}/internal/status`, {
1663
+ headers: { accept: "application/json" },
1664
+ signal: AbortSignal.timeout(500)
1665
+ });
1666
+ if (!response.ok) {
1667
+ throw new Error(`Hermes Link internal status returned HTTP ${response.status}`);
1668
+ }
1669
+ const body = await response.json();
1670
+ const status = toRecord(body?.tui_gateway);
1671
+ if (!status) {
1672
+ throw new Error("Hermes Link internal status did not include tui_gateway");
1496
1673
  }
1497
- const errorDetail = typeof entry.fields?.error === "string" ? entry.fields.error : null;
1498
- const message = errorDetail && errorDetail !== entry.message ? `${entry.message}: ${errorDetail}` : entry.message;
1499
1674
  return {
1500
- source,
1501
- ts: entry.ts,
1502
- message
1675
+ available: readBooleanValue(status.available) ?? false,
1676
+ profile: readStringValue(status.profile) ?? "default",
1677
+ state: readTuiGatewayState(status.state),
1678
+ ...readStringValue(status.baseUrl) ? { baseUrl: readStringValue(status.baseUrl) } : {},
1679
+ ...readNumberValue(status.pid) !== void 0 ? { pid: readNumberValue(status.pid) } : status.pid === null ? { pid: null } : {},
1680
+ ...readTuiGatewayBackendState(status.backend_state) ? { backend_state: readTuiGatewayBackendState(status.backend_state) } : {},
1681
+ ...readStringValue(status.last_active_at) ? { last_active_at: readStringValue(status.last_active_at) } : {},
1682
+ ...readStringValue(status.log_path) ? { log_path: readStringValue(status.log_path) } : {},
1683
+ ...readStringValue(status.error) ? { error: readStringValue(status.error) } : {}
1503
1684
  };
1504
1685
  }
1505
- function isActionableStatusError(source, entry) {
1506
- if (source === "service") {
1507
- return entry.level === "error";
1508
- }
1509
- if (entry.level !== "error") {
1510
- return false;
1686
+ function readTuiGatewayState(value) {
1687
+ if (value === "open" || value === "closed" || value === "connecting" || value === "error") {
1688
+ return value;
1511
1689
  }
1512
- return /(^|[\s[])(error|fatal)([\]\s:]|$)|\b(traceback|exception|failed|failure|unhandledRejection|uncaughtException)\b/iu.test(
1513
- entry.message
1514
- );
1690
+ return "error";
1515
1691
  }
1516
- function formatRecentError(error, t, language) {
1517
- if (!error) {
1518
- return t("status.recentError.none");
1692
+ function readTuiGatewayBackendState(value) {
1693
+ if (value === "running" || value === "stopped") {
1694
+ return value;
1519
1695
  }
1520
- return t("status.recentError.format", {
1521
- time: error.ts ? formatTimestamp(error.ts, language) : t("status.timeUnknown"),
1522
- source: formatLogSourceLabel(error.source, t),
1523
- message: error.message
1524
- });
1696
+ return void 0;
1697
+ }
1698
+ function toRecord(value) {
1699
+ return value && typeof value === "object" ? value : null;
1700
+ }
1701
+ function readStringValue(value) {
1702
+ return typeof value === "string" && value.trim() ? value.trim() : void 0;
1703
+ }
1704
+ function readNumberValue(value) {
1705
+ return typeof value === "number" && Number.isFinite(value) ? value : void 0;
1525
1706
  }
1526
- function formatLogSourceLabel(source, t) {
1527
- switch (source) {
1528
- case "service":
1529
- return t("status.logSource.service");
1530
- case "daemon":
1531
- return t("status.logSource.daemon");
1532
- case "gateway":
1533
- return t("status.logSource.gateway");
1707
+ function readBooleanValue(value) {
1708
+ return typeof value === "boolean" ? value : void 0;
1709
+ }
1710
+ async function readTuiGatewayDiagnostics(paths) {
1711
+ const [dashboardProcesses, logEntries] = await Promise.all([
1712
+ listHermesDashboardProcesses(),
1713
+ readRecentGatewayLogEntries({ paths, limit: 40 }).catch(() => [])
1714
+ ]);
1715
+ const recentLogLines = logEntries.map((entry) => entry.message).filter(
1716
+ (message) => /HERMES_DASHBOARD_READY|Building web UI|Web UI built|dashboard|tui_gateway|Traceback|Error|failed/iu.test(
1717
+ message
1718
+ )
1719
+ ).slice(-6);
1720
+ return {
1721
+ dashboardProcesses,
1722
+ recentLogLines,
1723
+ logFiles: getGatewayLogFiles(paths)
1724
+ };
1725
+ }
1726
+ async function listHermesDashboardProcesses() {
1727
+ try {
1728
+ const { stdout } = await execFileAsync2("ps", ["axo", "pid=,command="], {
1729
+ timeout: 2e3,
1730
+ windowsHide: true
1731
+ });
1732
+ return stdout.split(/\r?\n/u).map((line) => line.trim()).filter((line) => /\bhermes\b.*\bdashboard\b/u.test(line)).map((line) => {
1733
+ const match = line.match(/^(\d+)\s+(.*)$/u);
1734
+ return {
1735
+ pid: match ? Number(match[1]) : null,
1736
+ command: match?.[2] ?? line
1737
+ };
1738
+ });
1739
+ } catch {
1740
+ return [];
1534
1741
  }
1535
1742
  }
1536
1743
  function formatRuntimeEnvironmentWarning(environment, t) {
@@ -1556,16 +1763,20 @@ function formatTimestamp(value, language) {
1556
1763
  hour12: false
1557
1764
  }).format(new Date(timestamp));
1558
1765
  }
1559
- function readTimestampMs(value) {
1560
- if (!value) {
1561
- return 0;
1562
- }
1563
- const timestamp = Date.parse(value);
1564
- return Number.isFinite(timestamp) ? timestamp : 0;
1565
- }
1566
1766
  function formatHermesVersion(version) {
1567
1767
  return version.version ?? version.raw;
1568
1768
  }
1769
+ function compareSemver(left, right) {
1770
+ const leftParts = left.split(".").map((part) => Number.parseInt(part, 10));
1771
+ const rightParts = right.split(".").map((part) => Number.parseInt(part, 10));
1772
+ for (let index = 0; index < 3; index += 1) {
1773
+ const diff = (leftParts[index] || 0) - (rightParts[index] || 0);
1774
+ if (diff !== 0) {
1775
+ return diff;
1776
+ }
1777
+ }
1778
+ return 0;
1779
+ }
1569
1780
  function printInstallDiagnostics(info, t, verbose) {
1570
1781
  if (!verbose && !hasInstallPathIssue(info)) {
1571
1782
  return;
@@ -15,6 +15,7 @@ interface ConversationSummary {
15
15
  created_at: string;
16
16
  updated_at: string;
17
17
  last_event_seq: number;
18
+ event_stream?: ConversationEventStreamState;
18
19
  usage: {
19
20
  input_tokens: number;
20
21
  output_tokens: number;
@@ -254,8 +255,10 @@ interface LinkRun {
254
255
  queue_mode?: 'guided_interrupt';
255
256
  queue_promoted_at?: string;
256
257
  guided_after_run_id?: string;
258
+ hermes_backend?: 'tui_gateway' | 'responses' | 'runs';
257
259
  hermes_run_id?: string;
258
260
  hermes_response_id?: string;
261
+ hermes_rpc_session_id?: string;
259
262
  previous_response_id?: string;
260
263
  conversation_id: string;
261
264
  trigger_message_id: string;
package/dist/http/app.js CHANGED
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  createApp
3
- } from "../chunk-MVKOUAVA.js";
3
+ } from "../chunk-RFZ4SQV4.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.7.5",
3
+ "version": "0.7.7-beta.0",
4
4
  "private": false,
5
5
  "description": "Hermes Link companion service and CLI for connecting hermes-agent through HermesPilot",
6
6
  "license": "MIT",