@p697/clawket 0.6.0 → 0.6.1

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.
Files changed (2) hide show
  1. package/dist/index.js +150 -7
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -1447,6 +1447,7 @@ var HermesLocalBridge = class {
1447
1447
  hermesChild = null;
1448
1448
  modelStateCache = null;
1449
1449
  contextWindowCache = /* @__PURE__ */ new Map();
1450
+ bridgeRequestSeq = 0;
1450
1451
  constructor(options = {}) {
1451
1452
  this.options = options;
1452
1453
  this.host = normalizeHost(options.host);
@@ -1491,6 +1492,12 @@ var HermesLocalBridge = class {
1491
1492
  if (this.httpServer) {
1492
1493
  return;
1493
1494
  }
1495
+ this.logPerf("bridge_start_begin", {
1496
+ apiBaseUrl: this.apiBaseUrl,
1497
+ host: this.host,
1498
+ port: this.port
1499
+ });
1500
+ const startStartedAt = Date.now();
1494
1501
  const hermesReady = await this.ensureHermesApiReady();
1495
1502
  this.httpServer = createServer((req, res) => {
1496
1503
  void this.handleHttpRequest(req, res);
@@ -1534,6 +1541,11 @@ var HermesLocalBridge = class {
1534
1541
  prewarmComplete: true,
1535
1542
  lastError: hermesReady ? null : this.snapshot.lastError
1536
1543
  });
1544
+ this.logPerf("bridge_start_ready", {
1545
+ elapsedMs: Date.now() - startStartedAt,
1546
+ hermesReady,
1547
+ hermesApiReachable: this.snapshot.hermesApiReachable
1548
+ });
1537
1549
  this.log(hermesReady ? `hermes bridge listening on ${this.snapshot.bridgeUrl}` : `hermes bridge listening on ${this.snapshot.bridgeUrl} (degraded: Hermes API not ready yet)`);
1538
1550
  }
1539
1551
  async stop() {
@@ -1575,21 +1587,39 @@ var HermesLocalBridge = class {
1575
1587
  });
1576
1588
  }
1577
1589
  async ensureHermesApiReady() {
1590
+ const startedAt = Date.now();
1578
1591
  if (await probeHermesApi(this.apiBaseUrl, this.apiKey)) {
1579
1592
  this.updateSnapshot({ hermesApiReachable: true, lastError: null });
1593
+ this.logPerf("hermes_api_probe", {
1594
+ result: "warm",
1595
+ elapsedMs: Date.now() - startedAt
1596
+ });
1580
1597
  this.log(`reusing Hermes API already running at ${this.apiBaseUrl}`);
1581
1598
  return true;
1582
1599
  }
1583
1600
  if (this.options.startHermesIfNeeded === false) {
1584
1601
  const error = `Hermes API is not reachable at ${this.apiBaseUrl}. Start Hermes gateway with API server enabled and retry.`;
1585
1602
  this.updateSnapshot({ hermesApiReachable: false, lastError: error });
1603
+ this.logPerf("hermes_api_probe", {
1604
+ result: "unreachable_no_autostart",
1605
+ elapsedMs: Date.now() - startedAt
1606
+ });
1586
1607
  this.log(error);
1587
1608
  return false;
1588
1609
  }
1610
+ this.logPerf("hermes_api_probe", {
1611
+ result: "cold_start_required",
1612
+ elapsedMs: Date.now() - startedAt
1613
+ });
1589
1614
  return this.startHermesGatewayProcess();
1590
1615
  }
1591
1616
  async startHermesGatewayProcess() {
1592
1617
  const command = this.options.hermesCommand?.trim() || "hermes";
1618
+ const startedAt = Date.now();
1619
+ this.logPerf("hermes_api_cold_start_begin", {
1620
+ command,
1621
+ apiBaseUrl: this.apiBaseUrl
1622
+ });
1593
1623
  this.log(`starting hermes gateway via ${command}`);
1594
1624
  const verboseHermesStdio = process.env.CLAWKET_HERMES_VERBOSE === "1";
1595
1625
  const hermesChildEnv = {
@@ -1624,12 +1654,19 @@ var HermesLocalBridge = class {
1624
1654
  while (Date.now() - startMs < HERMES_BOOT_TIMEOUT_MS) {
1625
1655
  if (await probeHermesApi(this.apiBaseUrl, this.apiKey)) {
1626
1656
  this.updateSnapshot({ hermesApiReachable: true, lastError: null });
1657
+ this.logPerf("hermes_api_cold_start_ready", {
1658
+ elapsedMs: Date.now() - startedAt
1659
+ });
1627
1660
  return true;
1628
1661
  }
1629
1662
  await delay2(500);
1630
1663
  }
1631
1664
  const error = `Hermes API did not become ready within ${HERMES_BOOT_TIMEOUT_MS}ms at ${this.apiBaseUrl}.`;
1632
1665
  this.updateSnapshot({ hermesApiReachable: false, lastError: error });
1666
+ this.logPerf("hermes_api_cold_start_timeout", {
1667
+ elapsedMs: Date.now() - startedAt,
1668
+ timeoutMs: HERMES_BOOT_TIMEOUT_MS
1669
+ });
1633
1670
  this.log(error);
1634
1671
  return false;
1635
1672
  }
@@ -1647,6 +1684,8 @@ var HermesLocalBridge = class {
1647
1684
  });
1648
1685
  }
1649
1686
  async prewarmBridgeState() {
1687
+ const startedAt = Date.now();
1688
+ this.logPerf("bridge_prewarm_begin");
1650
1689
  const tasks = [
1651
1690
  () => {
1652
1691
  this.listHermesSessions(24);
@@ -1665,6 +1704,9 @@ var HermesLocalBridge = class {
1665
1704
  this.log(`bridge prewarm skipped: ${formatError2(error)}`);
1666
1705
  }
1667
1706
  }));
1707
+ this.logPerf("bridge_prewarm_done", {
1708
+ elapsedMs: Date.now() - startedAt
1709
+ });
1668
1710
  }
1669
1711
  async handleHttpRequest(req, res) {
1670
1712
  const pathname = readRequestPathname(req.url);
@@ -1739,23 +1781,34 @@ var HermesLocalBridge = class {
1739
1781
  }
1740
1782
  async dispatchRequest(method, params) {
1741
1783
  const payload = isRecord(params) ? params : {};
1784
+ const shouldTracePerf = method === "health" || method === "last-heartbeat" || method === "sessions.list" || method === "chat.history" || method === "chat.send" || method === "models.list" || method === "model.current" || method === "model.get";
1785
+ const requestStartedAt = shouldTracePerf ? Date.now() : 0;
1786
+ const requestSeq = shouldTracePerf ? ++this.bridgeRequestSeq : 0;
1787
+ if (shouldTracePerf) {
1788
+ this.logPerf("bridge_request_begin", {
1789
+ requestSeq,
1790
+ method,
1791
+ sessionKey: readString(payload.sessionKey) || void 0,
1792
+ limit: readPositiveInt(payload.limit, 0) || void 0
1793
+ });
1794
+ }
1742
1795
  switch (method) {
1743
1796
  case "health":
1744
1797
  case "last-heartbeat":
1745
- return {
1798
+ return this.traceBridgeRequest(method, requestStartedAt, requestSeq, {
1746
1799
  status: this.snapshot.hermesApiReachable ? "ok" : "degraded",
1747
1800
  ts: Date.now(),
1748
1801
  hermesApiReachable: this.snapshot.hermesApiReachable
1749
- };
1802
+ });
1750
1803
  case "sessions.list":
1751
- return {
1804
+ return this.traceBridgeRequest(method, requestStartedAt, requestSeq, {
1752
1805
  defaults: this.getHermesSessionListDefaults(),
1753
1806
  sessions: this.listHermesSessions(readPositiveInt(payload.limit, 100))
1754
- };
1807
+ });
1755
1808
  case "chat.history":
1756
- return this.getHermesSessionHistory(readString(payload.sessionKey) || DEFAULT_SESSION_ID, readPositiveInt(payload.limit, 50));
1809
+ return this.traceBridgeRequest(method, requestStartedAt, requestSeq, this.getHermesSessionHistory(readString(payload.sessionKey) || DEFAULT_SESSION_ID, readPositiveInt(payload.limit, 50)));
1757
1810
  case "chat.send":
1758
- return this.handleChatSend(payload);
1811
+ return this.traceBridgeRequest(method, requestStartedAt, requestSeq, this.handleChatSend(payload));
1759
1812
  case "sessions.reset":
1760
1813
  this.cancelActiveRunsForSession(readString(payload.key) || DEFAULT_SESSION_ID);
1761
1814
  this.sessionStore.resetSession(readString(payload.key) || DEFAULT_SESSION_ID);
@@ -1875,6 +1928,17 @@ var HermesLocalBridge = class {
1875
1928
  throw new Error(`Unsupported Hermes bridge method: ${method}`);
1876
1929
  }
1877
1930
  }
1931
+ async traceBridgeRequest(method, startedAt, requestSeq, value) {
1932
+ const result = await value;
1933
+ if (startedAt > 0) {
1934
+ this.logPerf("bridge_request", {
1935
+ requestSeq,
1936
+ method,
1937
+ elapsedMs: Date.now() - startedAt
1938
+ });
1939
+ }
1940
+ return result;
1941
+ }
1878
1942
  async handleChatSend(payload) {
1879
1943
  const sessionKey = readString(payload.sessionKey) || DEFAULT_SESSION_ID;
1880
1944
  const text = readString(payload.message);
@@ -4659,6 +4723,10 @@ var HermesLocalBridge = class {
4659
4723
  log(line) {
4660
4724
  this.options.onLog?.(line);
4661
4725
  }
4726
+ logPerf(event, fields) {
4727
+ const payload = fields ? Object.entries(fields).filter(([, value]) => value !== void 0).map(([key, value]) => `${key}=${String(value)}`).join(" ") : "";
4728
+ this.log(`[perf] ${event}${payload ? ` ${payload}` : ""}`);
4729
+ }
4662
4730
  async hydrateToolOutputsFromHermesState(params) {
4663
4731
  const toolOutputs = this.readHermesToolOutputsFromLocalState(params.sessionId, params.runStartedAtMs);
4664
4732
  if (toolOutputs.length === 0 || params.completedTools.length === 0) {
@@ -5943,10 +6011,29 @@ var HermesRelayRuntime = class {
5943
6011
  if (text == null)
5944
6012
  return;
5945
6013
  if (text.startsWith(RELAY_CONTROL_PREFIX)) {
6014
+ this.handleRelayControl(text);
5946
6015
  return;
5947
6016
  }
5948
6017
  this.forwardOrQueueBridgeMessage({ text });
5949
6018
  }
6019
+ handleRelayControl(text) {
6020
+ try {
6021
+ const parsed = JSON.parse(text.slice(RELAY_CONTROL_PREFIX.length));
6022
+ if (parsed?.event !== "gateway_ping") {
6023
+ return;
6024
+ }
6025
+ const relay = this.relaySocket;
6026
+ if (!relay || relay.readyState !== WebSocket2.OPEN) {
6027
+ return;
6028
+ }
6029
+ relay.send(`${RELAY_CONTROL_PREFIX}${JSON.stringify({
6030
+ type: "control",
6031
+ event: "gateway_pong",
6032
+ ts: typeof parsed.ts === "number" ? parsed.ts : Date.now()
6033
+ })}`);
6034
+ } catch {
6035
+ }
6036
+ }
5950
6037
  handleBridgeMessage(data, isBinary) {
5951
6038
  const relay = this.relaySocket;
5952
6039
  if (isBinary) {
@@ -9820,6 +9907,8 @@ async function performHermesLocalPairing(args) {
9820
9907
  };
9821
9908
  }
9822
9909
  async function performHermesRelayPairing(args) {
9910
+ const pairingStartedAt = Date.now();
9911
+ logHermesPerf("pair_relay_begin");
9823
9912
  const server = resolvePairServer(args, "hermes");
9824
9913
  const name = readFlag(args, "--name") ?? readFlag(args, "-n") ?? "Hermes";
9825
9914
  const qrFile = readFlag(args, "--qr-file");
@@ -9827,6 +9916,10 @@ async function performHermesRelayPairing(args) {
9827
9916
  serverUrl: server,
9828
9917
  displayName: name
9829
9918
  });
9919
+ logHermesPerf("pair_relay_registered", {
9920
+ elapsedMs: Date.now() - pairingStartedAt,
9921
+ action: paired.action
9922
+ });
9830
9923
  const qrImagePath = await writeRawQrPng(paired.qrPayload, "clawket-hermes-relay-pair", qrFile);
9831
9924
  if (paired.action === "registered") {
9832
9925
  const stalePids = listHermesRelayRuntimePids2();
@@ -9835,6 +9928,9 @@ async function performHermesRelayPairing(args) {
9835
9928
  }
9836
9929
  }
9837
9930
  const runtimeMessage = await ensureHermesRelayBackgroundRuntime(args);
9931
+ logHermesPerf("pair_relay_ready", {
9932
+ elapsedMs: Date.now() - pairingStartedAt
9933
+ });
9838
9934
  return {
9839
9935
  backend: "hermes",
9840
9936
  transport: "relay",
@@ -10265,6 +10361,8 @@ async function keepHermesRelayRuntimeAlive(runtime) {
10265
10361
  });
10266
10362
  }
10267
10363
  async function ensureHermesRelayBackgroundRuntime(args) {
10364
+ const startedAt = Date.now();
10365
+ logHermesPerf("relay_runtime_ensure_begin");
10268
10366
  const relayConfig = readHermesRelayConfig();
10269
10367
  if (!relayConfig) {
10270
10368
  throw new Error("Hermes relay is not paired.");
@@ -10272,6 +10370,10 @@ async function ensureHermesRelayBackgroundRuntime(args) {
10272
10370
  const relayPids = listHermesRelayRuntimePids2();
10273
10371
  if (relayPids.length === 1) {
10274
10372
  await waitForHermesRelayCloudBridgeReady(relayConfig, 2e4);
10373
+ logHermesPerf("relay_runtime_ensure_reused", {
10374
+ elapsedMs: Date.now() - startedAt,
10375
+ pid: relayPids[0]
10376
+ });
10275
10377
  return `Hermes relay runtime already running (pid ${relayPids[0]}) and confirmed by relay.`;
10276
10378
  }
10277
10379
  if (relayPids.length > 1) {
@@ -10290,8 +10392,15 @@ async function ensureHermesRelayBackgroundRuntime(args) {
10290
10392
  await waitForHermesRelayCloudBridgeReady(relayConfig, 2e4);
10291
10393
  const startedPids = listHermesRelayRuntimePids2();
10292
10394
  if (startedPids.length === 0) {
10395
+ logHermesPerf("relay_runtime_ensure_requested_no_pid", {
10396
+ elapsedMs: Date.now() - startedAt
10397
+ });
10293
10398
  return "Hermes relay runtime launch was requested. Run `clawket hermes relay run` manually if it did not stay up.";
10294
10399
  }
10400
+ logHermesPerf("relay_runtime_ensure_started", {
10401
+ elapsedMs: Date.now() - startedAt,
10402
+ pid: startedPids[0]
10403
+ });
10295
10404
  return `Auto-started Hermes relay runtime (pid ${startedPids[0]}) and confirmed cloud bridge attachment.`;
10296
10405
  }
10297
10406
  async function ensureHermesBridgeBackgroundRuntime(input) {
@@ -10506,6 +10615,8 @@ function resolveRequestedPairBackend(args) {
10506
10615
  throw new Error(`Unsupported local pairing backend "${backend}". Use --backend openclaw or --backend hermes.`);
10507
10616
  }
10508
10617
  async function ensureHermesPairingRuntimeReady(args) {
10618
+ const startedAt = Date.now();
10619
+ logHermesPerf("pairing_runtime_ready_begin");
10509
10620
  const saved = readHermesBridgeCliConfig();
10510
10621
  const host = readFlag(args, "--host") ?? saved?.host ?? "0.0.0.0";
10511
10622
  const port = Number(readFlag(args, "--port") ?? saved?.port ?? "4319");
@@ -10513,7 +10624,12 @@ async function ensureHermesPairingRuntimeReady(args) {
10513
10624
  const token = readFlag(args, "--token") ?? process.env.CLAWKET_HERMES_BRIDGE_TOKEN ?? saved?.token ?? randomUUID5();
10514
10625
  const existingBridgePids = listHermesBridgeRuntimePids2();
10515
10626
  if (existingBridgePids.length > 0) {
10516
- return await resolveExistingHermesPairingRuntime(saved);
10627
+ const resolved = await resolveExistingHermesPairingRuntime(saved);
10628
+ logHermesPerf("pairing_runtime_ready_reused", {
10629
+ elapsedMs: Date.now() - startedAt,
10630
+ port: resolved.port
10631
+ });
10632
+ return resolved;
10517
10633
  }
10518
10634
  if (!existsSync7(resolveDefaultHermesSourcePath())) {
10519
10635
  throw new Error(
@@ -10528,6 +10644,10 @@ async function ensureHermesPairingRuntimeReady(args) {
10528
10644
  restartHermes: hasFlag(args, "--restart-hermes")
10529
10645
  });
10530
10646
  await waitForHermesBridgeHealth(port);
10647
+ logHermesPerf("pairing_runtime_ready_started", {
10648
+ elapsedMs: Date.now() - startedAt,
10649
+ port
10650
+ });
10531
10651
  return { host, port, apiBaseUrl, token };
10532
10652
  }
10533
10653
  async function resolveExistingHermesPairingRuntime(saved) {
@@ -10602,17 +10722,27 @@ function startDetachedHermesRelayRuntime(input) {
10602
10722
  child.unref();
10603
10723
  }
10604
10724
  async function waitForHermesBridgeHealth(port, timeoutMs = 15e3) {
10725
+ const startedAt = Date.now();
10605
10726
  const deadline = Date.now() + timeoutMs;
10606
10727
  while (Date.now() < deadline) {
10607
10728
  try {
10608
10729
  const health = await readHermesBridgeHealth2(port);
10609
10730
  if (health.ok && health.running) {
10731
+ logHermesPerf("bridge_health_ready", {
10732
+ elapsedMs: Date.now() - startedAt,
10733
+ port
10734
+ });
10610
10735
  return;
10611
10736
  }
10612
10737
  } catch {
10613
10738
  }
10614
10739
  await new Promise((resolve4) => setTimeout(resolve4, 250));
10615
10740
  }
10741
+ logHermesPerf("bridge_health_timeout", {
10742
+ elapsedMs: Date.now() - startedAt,
10743
+ port,
10744
+ timeoutMs
10745
+ });
10616
10746
  throw new Error(`Hermes bridge did not become ready at http://127.0.0.1:${port}/health within ${timeoutMs}ms.`);
10617
10747
  }
10618
10748
  async function readHermesBridgeHealth2(port) {
@@ -10664,6 +10794,7 @@ async function waitForHermesRelayRuntimeReady(startedAtMs, timeoutMs) {
10664
10794
  }
10665
10795
  }
10666
10796
  async function waitForHermesRelayCloudBridgeReady(relayConfig, timeoutMs) {
10797
+ const startedAt = Date.now();
10667
10798
  const statusUrl = buildHermesRelayBridgeStatusUrl2(relayConfig.relayUrl, relayConfig.bridgeId);
10668
10799
  const deadline = Date.now() + timeoutMs;
10669
10800
  let lastError = null;
@@ -10678,6 +10809,9 @@ async function waitForHermesRelayCloudBridgeReady(relayConfig, timeoutMs) {
10678
10809
  if (response.ok) {
10679
10810
  const payload = await response.json();
10680
10811
  if (payload?.hasBridge) {
10812
+ logHermesPerf("relay_cloud_bridge_ready", {
10813
+ elapsedMs: Date.now() - startedAt
10814
+ });
10681
10815
  return;
10682
10816
  }
10683
10817
  } else {
@@ -10688,6 +10822,10 @@ async function waitForHermesRelayCloudBridgeReady(relayConfig, timeoutMs) {
10688
10822
  }
10689
10823
  await sleep(500);
10690
10824
  }
10825
+ logHermesPerf("relay_cloud_bridge_timeout", {
10826
+ elapsedMs: Date.now() - startedAt,
10827
+ timeoutMs
10828
+ });
10691
10829
  throw new Error(
10692
10830
  `Hermes relay did not observe the local bridge for ${relayConfig.bridgeId} within ${timeoutMs}ms` + (lastError ? ` (${lastError}).` : ".")
10693
10831
  );
@@ -10768,6 +10906,11 @@ function formatLocalTime(iso) {
10768
10906
  function formatError3(error) {
10769
10907
  return error instanceof Error ? error.message : String(error);
10770
10908
  }
10909
+ function logHermesPerf(event, fields) {
10910
+ const payload = fields ? Object.entries(fields).filter(([, value]) => value !== void 0).map(([key, value]) => `${key}=${String(value)}`).join(" ") : "";
10911
+ void event;
10912
+ void payload;
10913
+ }
10771
10914
  async function followCliLogs(input) {
10772
10915
  const sources = getCliLogSourcePaths(input.includeErrorLog);
10773
10916
  const state = /* @__PURE__ */ new Map();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@p697/clawket",
3
- "version": "0.6.0",
3
+ "version": "0.6.1",
4
4
  "private": false,
5
5
  "type": "module",
6
6
  "license": "AGPL-3.0-only",