@yoooclaw/phone-notifications 1.7.0 → 1.7.2

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/index.js CHANGED
@@ -3648,7 +3648,7 @@ var require_websocket_server = __commonJS({
3648
3648
 
3649
3649
  // src/index.ts
3650
3650
  import { readFileSync as readFileSync14 } from "fs";
3651
- import { basename as basename2, dirname as dirname5 } from "path";
3651
+ import { basename as basename2, dirname as dirname6 } from "path";
3652
3652
 
3653
3653
  // src/light/protocol.ts
3654
3654
  var MAX_LIGHT_SEGMENTS = 12;
@@ -3794,11 +3794,11 @@ import { randomUUID } from "crypto";
3794
3794
 
3795
3795
  // src/env.ts
3796
3796
  import { existsSync as existsSync2, mkdirSync, readFileSync as readFileSync2, writeFileSync } from "fs";
3797
- import { dirname } from "path";
3797
+ import { dirname as dirname2 } from "path";
3798
3798
 
3799
3799
  // src/host.ts
3800
3800
  import { existsSync, readFileSync } from "fs";
3801
- import { join } from "path";
3801
+ import { dirname, join } from "path";
3802
3802
  function trimToUndefined(value) {
3803
3803
  if (typeof value !== "string") {
3804
3804
  return void 0;
@@ -3809,6 +3809,18 @@ function trimToUndefined(value) {
3809
3809
  function homeDir() {
3810
3810
  return process.env.HOME || process.env.USERPROFILE || "/tmp";
3811
3811
  }
3812
+ function expandUserPath(value) {
3813
+ if (!value) {
3814
+ return void 0;
3815
+ }
3816
+ if (value === "~") {
3817
+ return homeDir();
3818
+ }
3819
+ if (value.startsWith("~/")) {
3820
+ return join(homeDir(), value.slice(2));
3821
+ }
3822
+ return value;
3823
+ }
3812
3824
  function candidateMetaPaths() {
3813
3825
  const home = homeDir();
3814
3826
  return [
@@ -3824,8 +3836,8 @@ function loadQClawMeta() {
3824
3836
  try {
3825
3837
  const parsed = JSON.parse(readFileSync(metaPath, "utf-8"));
3826
3838
  return {
3827
- stateDir: trimToUndefined(parsed?.stateDir),
3828
- configPath: trimToUndefined(parsed?.configPath)
3839
+ stateDir: expandUserPath(trimToUndefined(parsed?.stateDir)),
3840
+ configPath: expandUserPath(trimToUndefined(parsed?.configPath))
3829
3841
  };
3830
3842
  } catch {
3831
3843
  }
@@ -3833,21 +3845,42 @@ function loadQClawMeta() {
3833
3845
  return void 0;
3834
3846
  }
3835
3847
  function resolveStateDirFromEnv() {
3836
- return trimToUndefined(process.env.OPENCLAW_STATE_DIR) ?? trimToUndefined(process.env.QCLAW_STATE_DIR) ?? trimToUndefined(process.env.OPENCLAW_ROOT) ?? trimToUndefined(process.env.OPENCLAW_HOME) ?? trimToUndefined(process.env.QCLAW_HOME);
3848
+ return expandUserPath(
3849
+ trimToUndefined(process.env.OPENCLAW_STATE_DIR) ?? trimToUndefined(process.env.QCLAW_STATE_DIR) ?? trimToUndefined(process.env.OPENCLAW_ROOT) ?? trimToUndefined(process.env.OPENCLAW_HOME) ?? trimToUndefined(process.env.QCLAW_HOME)
3850
+ );
3851
+ }
3852
+ function resolveConfigPathFromEnv() {
3853
+ return expandUserPath(
3854
+ trimToUndefined(process.env.OPENCLAW_CONFIG_PATH) ?? trimToUndefined(process.env.QCLAW_CONFIG_PATH)
3855
+ );
3856
+ }
3857
+ function hasOpenClawMarkers() {
3858
+ const baseDir = join(homeDir(), ".openclaw");
3859
+ return [
3860
+ join(baseDir, "openclaw.json"),
3861
+ join(baseDir, "credentials.json"),
3862
+ join(baseDir, "extensions")
3863
+ ].some((candidate) => existsSync(candidate));
3837
3864
  }
3838
3865
  function resolveStateDir() {
3839
3866
  const envDir = resolveStateDirFromEnv();
3840
3867
  if (envDir) {
3841
3868
  return envDir;
3842
3869
  }
3870
+ if (hasOpenClawMarkers()) {
3871
+ return join(homeDir(), ".openclaw");
3872
+ }
3843
3873
  const meta = loadQClawMeta();
3844
3874
  if (meta?.stateDir) {
3845
3875
  return meta.stateDir;
3846
3876
  }
3877
+ if (meta?.configPath) {
3878
+ return dirname(meta.configPath);
3879
+ }
3847
3880
  return join(homeDir(), ".openclaw");
3848
3881
  }
3849
3882
  function resolveConfigPath(stateDir = resolveStateDir()) {
3850
- const envConfigPath = trimToUndefined(process.env.OPENCLAW_CONFIG_PATH) ?? trimToUndefined(process.env.QCLAW_CONFIG_PATH);
3883
+ const envConfigPath = resolveConfigPathFromEnv();
3851
3884
  if (envConfigPath) {
3852
3885
  return envConfigPath;
3853
3886
  }
@@ -3893,7 +3926,7 @@ function saveEnvName(env) {
3893
3926
  throw new Error(`\u65E0\u6548\u7684\u73AF\u5883\u540D\u79F0: ${env}\uFF0C\u53EF\u9009\u503C: ${[...VALID_ENVS].join(", ")}`);
3894
3927
  }
3895
3928
  const filePath = envFilePath();
3896
- mkdirSync(dirname(filePath), { recursive: true, mode: 448 });
3929
+ mkdirSync(dirname2(filePath), { recursive: true, mode: 448 });
3897
3930
  writeFileSync(filePath, JSON.stringify({ env }, null, 2), {
3898
3931
  encoding: "utf-8",
3899
3932
  mode: 384
@@ -3964,7 +3997,7 @@ import {
3964
3997
  writeFileSync as writeFileSync2,
3965
3998
  watch
3966
3999
  } from "fs";
3967
- import { dirname as dirname2, basename } from "path";
4000
+ import { dirname as dirname3, basename } from "path";
3968
4001
  function credentialsPath() {
3969
4002
  return resolveStateFile("credentials.json");
3970
4003
  }
@@ -3979,7 +4012,7 @@ function readCredentials() {
3979
4012
  }
3980
4013
  function writeCredentials(creds) {
3981
4014
  const path2 = credentialsPath();
3982
- mkdirSync2(dirname2(path2), { recursive: true, mode: 448 });
4015
+ mkdirSync2(dirname3(path2), { recursive: true, mode: 448 });
3983
4016
  writeFileSync2(path2, JSON.stringify(creds, null, 2), {
3984
4017
  encoding: "utf-8",
3985
4018
  mode: 384
@@ -4000,7 +4033,7 @@ function requireApiKey() {
4000
4033
  }
4001
4034
  function watchCredentials(onChange) {
4002
4035
  const path2 = credentialsPath();
4003
- const dir = dirname2(path2);
4036
+ const dir = dirname3(path2);
4004
4037
  const filename = basename(path2);
4005
4038
  let debounceTimer = null;
4006
4039
  const delayMs = 200;
@@ -5447,11 +5480,11 @@ import {
5447
5480
  unlinkSync,
5448
5481
  writeFileSync as writeFileSync9
5449
5482
  } from "fs";
5450
- import { dirname as dirname4, join as join10 } from "path";
5483
+ import { dirname as dirname5, join as join10 } from "path";
5451
5484
 
5452
5485
  // src/tunnel/relay-client.ts
5453
5486
  import { writeFileSync as writeFileSync8, mkdirSync as mkdirSync6 } from "fs";
5454
- import { dirname as dirname3 } from "path";
5487
+ import { dirname as dirname4 } from "path";
5455
5488
 
5456
5489
  // node_modules/.pnpm/ws@8.19.0/node_modules/ws/wrapper.mjs
5457
5490
  var import_stream = __toESM(require_stream(), 1);
@@ -5462,6 +5495,16 @@ var import_websocket_server = __toESM(require_websocket_server(), 1);
5462
5495
  var wrapper_default = import_websocket.default;
5463
5496
 
5464
5497
  // src/tunnel/relay-client.ts
5498
+ function previewText(text, max = 500) {
5499
+ return text.length <= max ? text : `${text.substring(0, max)}\u2026`;
5500
+ }
5501
+ function maskSecret(value) {
5502
+ if (!value) return "empty";
5503
+ if (value.length <= 8) {
5504
+ return `${value.slice(0, 2)}\u2026${value.slice(-2)}`;
5505
+ }
5506
+ return `${value.slice(0, 4)}\u2026${value.slice(-4)}`;
5507
+ }
5465
5508
  var RelayClient = class {
5466
5509
  constructor(opts) {
5467
5510
  this.opts = opts;
@@ -5483,7 +5526,7 @@ var RelayClient = class {
5483
5526
  lastDisconnectReason
5484
5527
  };
5485
5528
  try {
5486
- mkdirSync6(dirname3(this.opts.statusFilePath), { recursive: true });
5529
+ mkdirSync6(dirname4(this.opts.statusFilePath), { recursive: true });
5487
5530
  writeFileSync8(this.opts.statusFilePath, JSON.stringify(info, null, 2));
5488
5531
  } catch {
5489
5532
  }
@@ -5542,8 +5585,9 @@ var RelayClient = class {
5542
5585
  async connect() {
5543
5586
  if (this.aborted) return;
5544
5587
  this.cleanup(true);
5588
+ const rawApiKey = this.opts.apiKey.startsWith("Bearer ") ? this.opts.apiKey.slice("Bearer ".length) : this.opts.apiKey;
5545
5589
  this.opts.logger.info(
5546
- `Relay tunnel: connecting to ${this.opts.tunnelUrl} (attempt=${this.reconnectAttempt}, heartbeat=${this.opts.heartbeatSec}s)`
5590
+ `Relay tunnel: connecting to ${this.opts.tunnelUrl} (attempt=${this.reconnectAttempt}, heartbeat=${this.opts.heartbeatSec}s, apiKey=${maskSecret(rawApiKey)})`
5547
5591
  );
5548
5592
  this.writeStatus("connecting");
5549
5593
  return new Promise((resolve) => {
@@ -5554,7 +5598,6 @@ var RelayClient = class {
5554
5598
  resolve();
5555
5599
  }
5556
5600
  };
5557
- const rawApiKey = this.opts.apiKey.startsWith("Bearer ") ? this.opts.apiKey.slice("Bearer ".length) : this.opts.apiKey;
5558
5601
  const wsUrl = new URL(this.opts.tunnelUrl);
5559
5602
  if (!wsUrl.searchParams.get("apiKey")) {
5560
5603
  wsUrl.searchParams.set("apiKey", rawApiKey);
@@ -5598,8 +5641,9 @@ var RelayClient = class {
5598
5641
  });
5599
5642
  ws.on("close", (code, reason) => {
5600
5643
  const reasonStr = reason.toString();
5644
+ const lastInboundAgoMs = this.lastInboundAt ? Date.now() - this.lastInboundAt : null;
5601
5645
  this.opts.logger.warn(
5602
- `Relay tunnel: disconnected (code=${code}, reason=${reasonStr})`
5646
+ `Relay tunnel: disconnected (code=${code}, reason=${previewText(reasonStr, 200)}, lastInboundAgoMs=${lastInboundAgoMs ?? "N/A"}, reconnectAttempt=${this.reconnectAttempt})`
5603
5647
  );
5604
5648
  if (this.ws === ws) {
5605
5649
  this.stopHeartbeat();
@@ -5610,7 +5654,9 @@ var RelayClient = class {
5610
5654
  settle();
5611
5655
  });
5612
5656
  ws.on("error", (err) => {
5613
- this.opts.logger.error(`Relay tunnel: WebSocket error: ${err.message}`);
5657
+ this.opts.logger.error(
5658
+ `Relay tunnel: WebSocket error: ${err.message} (readyState=${ws.readyState}, reconnectAttempt=${this.reconnectAttempt}, url=${wsUrl.toString()})`
5659
+ );
5614
5660
  settle();
5615
5661
  });
5616
5662
  });
@@ -5621,13 +5667,16 @@ var RelayClient = class {
5621
5667
  if (text === "pong") {
5622
5668
  return;
5623
5669
  }
5624
- const preview = text.length <= 500 ? text : text.substring(0, 500) + "\u2026";
5625
- this.opts.logger.info(`Relay tunnel: \u2605 received message (${text.length} chars): ${preview}`);
5670
+ this.opts.logger.info(
5671
+ `Relay tunnel: \u2605 received message (${text.length} chars): ${previewText(text)}`
5672
+ );
5626
5673
  let frame;
5627
5674
  try {
5628
5675
  frame = JSON.parse(text);
5629
5676
  } catch {
5630
- this.opts.logger.warn("Relay tunnel: received invalid frame, ignoring");
5677
+ this.opts.logger.warn(
5678
+ `Relay tunnel: received invalid frame, ignoring (preview=${previewText(text, 200)})`
5679
+ );
5631
5680
  return;
5632
5681
  }
5633
5682
  this.opts.logger.info(`Relay tunnel: parsed frame type=${frame.type}, id=${"id" in frame ? frame.id : "N/A"}`);
@@ -5742,6 +5791,32 @@ import { randomUUID as randomUUID2 } from "crypto";
5742
5791
  import crypto from "crypto";
5743
5792
  import fs from "fs";
5744
5793
  import path from "path";
5794
+ function previewText2(text, max = 200) {
5795
+ if (!text) return "";
5796
+ return text.length <= max ? text : `${text.substring(0, max)}\u2026`;
5797
+ }
5798
+ function findHeaderValue(headers, key) {
5799
+ if (!headers) return void 0;
5800
+ const lowerKey = key.toLowerCase();
5801
+ for (const [headerKey, headerValue] of Object.entries(headers)) {
5802
+ if (headerKey.toLowerCase() === lowerKey) {
5803
+ return headerValue;
5804
+ }
5805
+ }
5806
+ return void 0;
5807
+ }
5808
+ function summarizeRequestHeaders(headers) {
5809
+ const contentType = findHeaderValue(headers, "content-type");
5810
+ const requestId = findHeaderValue(headers, "x-request-id");
5811
+ const parts = [];
5812
+ if (contentType) {
5813
+ parts.push(`contentType=${contentType}`);
5814
+ }
5815
+ if (requestId) {
5816
+ parts.push(`xRequestId=${previewText2(requestId, 120)}`);
5817
+ }
5818
+ return parts.length ? `, ${parts.join(", ")}` : "";
5819
+ }
5745
5820
  var ED25519_SPKI_PREFIX = Buffer.from("302a300506032b6570032100", "hex");
5746
5821
  function base64UrlEncode(buf) {
5747
5822
  return buf.toString("base64").replaceAll("+", "-").replaceAll("/", "_").replace(/=+$/g, "");
@@ -5840,6 +5915,12 @@ var TunnelProxy = class _TunnelProxy {
5840
5915
  gatewayWsPending = [];
5841
5916
  /** 设备身份,用于 Gateway connect 握手 */
5842
5917
  deviceIdentity;
5918
+ pushGatewayPending(payload, reason) {
5919
+ this.gatewayWsPending.push(payload);
5920
+ this.opts.logger.info(
5921
+ `TunnelProxy: gateway WS pending queue size=${this.gatewayWsPending.length} (${reason})`
5922
+ );
5923
+ }
5843
5924
  resolveGatewayConnectAuth() {
5844
5925
  const token = this.opts.gatewayToken?.trim() || void 0;
5845
5926
  const password = this.opts.gatewayPassword?.trim() || void 0;
@@ -5888,19 +5969,21 @@ var TunnelProxy = class _TunnelProxy {
5888
5969
  }
5889
5970
  return attempts;
5890
5971
  }
5891
- async sendHttpResponse(frameId, res) {
5972
+ async sendHttpResponse(params) {
5973
+ const { frameId, method, path: path2, authLabel, startedAtMs, res } = params;
5892
5974
  const contentType = res.headers.get("content-type") ?? "";
5893
5975
  const isStreaming = contentType.includes("text/event-stream");
5976
+ const elapsedMs = Date.now() - startedAtMs;
5894
5977
  this.opts.logger.info(
5895
- `TunnelProxy: response status=${res.status}, content-type=${contentType}, streaming=${isStreaming}`
5978
+ `TunnelProxy: HTTP id=${frameId} ${method} ${path2} <= ${res.status} (${elapsedMs}ms, auth=${authLabel}, content-type=${contentType}, streaming=${isStreaming})`
5896
5979
  );
5897
5980
  if (isStreaming && res.body) {
5898
- await this.streamResponse(frameId, res);
5981
+ await this.streamResponse(frameId, res, startedAtMs);
5899
5982
  return;
5900
5983
  }
5901
5984
  const body = await res.text();
5902
5985
  this.opts.logger.info(
5903
- `TunnelProxy: response status=${res.status}, body=${body.substring(0, 200)}`
5986
+ `TunnelProxy: HTTP id=${frameId} response body=${previewText2(body)}`
5904
5987
  );
5905
5988
  const headers = {};
5906
5989
  res.headers.forEach((value, key) => {
@@ -5949,7 +6032,7 @@ var TunnelProxy = class _TunnelProxy {
5949
6032
  if (this.gatewayWsReady && this.gatewayWs?.readyState === wrapper_default.OPEN) {
5950
6033
  this.gatewayWs.send(payload);
5951
6034
  } else {
5952
- this.gatewayWsPending.push(payload);
6035
+ this.pushGatewayPending(payload, "raw frame queued before gateway WS ready");
5953
6036
  }
5954
6037
  }
5955
6038
  /** 清理所有代理的 WebSocket 连接 */
@@ -5983,12 +6066,14 @@ var TunnelProxy = class _TunnelProxy {
5983
6066
  this.gatewayWsConnecting = true;
5984
6067
  this.gatewayWsReady = false;
5985
6068
  const wsUrl = this.opts.gatewayBaseUrl.replace(/^http/, "ws");
5986
- this.opts.logger.info(`TunnelProxy: RPC WS connecting to gateway ${wsUrl}`);
6069
+ this.opts.logger.info(
6070
+ `TunnelProxy: RPC WS connecting to gateway ${wsUrl} (pending=${this.gatewayWsPending.length})`
6071
+ );
5987
6072
  const ws = new wrapper_default(wsUrl);
5988
6073
  ws.on("open", () => {
5989
6074
  this.gatewayWs = ws;
5990
6075
  this.opts.logger.info(
5991
- "TunnelProxy: RPC WS tcp connected, waiting for connect.challenge"
6076
+ `TunnelProxy: RPC WS tcp connected, waiting for connect.challenge (pending=${this.gatewayWsPending.length})`
5992
6077
  );
5993
6078
  });
5994
6079
  ws.on("message", (data) => {
@@ -6015,15 +6100,16 @@ var TunnelProxy = class _TunnelProxy {
6015
6100
  }
6016
6101
  if (frame.type === "event" && frame.event === "connect.challenge") {
6017
6102
  const challengeNonce = frame.payload?.nonce ?? "";
6103
+ const connectRequestId = `tunnel-connect-${randomUUID2()}`;
6104
+ const gatewayAuth = this.resolveGatewayConnectAuth();
6018
6105
  this.opts.logger.info(
6019
- `TunnelProxy: received connect.challenge (nonce=${challengeNonce}), sending connect request with device identity`
6106
+ `TunnelProxy: received connect.challenge (nonce=${challengeNonce}, connectReqId=${connectRequestId}, hasToken=${!!gatewayAuth?.token}, hasPassword=${!!gatewayAuth?.password}), sending connect request with device identity`
6020
6107
  );
6021
6108
  const role = "operator";
6022
6109
  const scopes = ["operator.admin"];
6023
6110
  const signedAtMs = Date.now();
6024
6111
  const clientId = "gateway-client";
6025
6112
  const clientMode = "backend";
6026
- const gatewayAuth = this.resolveGatewayConnectAuth();
6027
6113
  const authPayload = buildDeviceAuthPayload({
6028
6114
  deviceId: this.deviceIdentity.deviceId,
6029
6115
  clientId,
@@ -6040,7 +6126,7 @@ var TunnelProxy = class _TunnelProxy {
6040
6126
  );
6041
6127
  const connectReq = {
6042
6128
  type: "req",
6043
- id: `tunnel-connect-${randomUUID2()}`,
6129
+ id: connectRequestId,
6044
6130
  method: "connect",
6045
6131
  params: {
6046
6132
  minProtocol: 3,
@@ -6082,7 +6168,7 @@ var TunnelProxy = class _TunnelProxy {
6082
6168
  }
6083
6169
  if (frame.type === "res" && frame.ok === false && !this.gatewayWsReady) {
6084
6170
  this.opts.logger.error(
6085
- `TunnelProxy: RPC WS handshake failed: ${JSON.stringify(frame.error)}`
6171
+ `TunnelProxy: RPC WS handshake failed (pending=${this.gatewayWsPending.length}): ${previewText2(JSON.stringify(frame.error), 500)}`
6086
6172
  );
6087
6173
  ws.close();
6088
6174
  return;
@@ -6090,8 +6176,10 @@ var TunnelProxy = class _TunnelProxy {
6090
6176
  this.opts.client.sendRaw(text);
6091
6177
  });
6092
6178
  ws.on("close", (code, reason) => {
6179
+ const wasReady = this.gatewayWsReady;
6180
+ const pendingCount = this.gatewayWsPending.length;
6093
6181
  this.opts.logger.info(
6094
- `TunnelProxy: RPC WS closed by gateway (code=${code}, reason=${reason.toString()})`
6182
+ `TunnelProxy: RPC WS closed by gateway (code=${code}, reason=${reason.toString()}, ready=${wasReady}, pending=${pendingCount}, activeWs=${this.wsConnections.size})`
6095
6183
  );
6096
6184
  if (this.gatewayWs === ws) {
6097
6185
  this.gatewayWs = null;
@@ -6100,7 +6188,9 @@ var TunnelProxy = class _TunnelProxy {
6100
6188
  this.gatewayWsConnecting = false;
6101
6189
  });
6102
6190
  ws.on("error", (err) => {
6103
- this.opts.logger.warn(`TunnelProxy: RPC WS error: ${err.message}`);
6191
+ this.opts.logger.warn(
6192
+ `TunnelProxy: RPC WS error: ${err.message} (ready=${this.gatewayWsReady}, pending=${this.gatewayWsPending.length}, activeWs=${this.wsConnections.size})`
6193
+ );
6104
6194
  this.gatewayWsConnecting = false;
6105
6195
  if (this.gatewayWs === ws) {
6106
6196
  this.gatewayWs = null;
@@ -6121,7 +6211,10 @@ var TunnelProxy = class _TunnelProxy {
6121
6211
  this.opts.logger.info(
6122
6212
  `TunnelProxy: req id=${frame.id} queued, gateway WS not ready yet`
6123
6213
  );
6124
- this.gatewayWsPending.push(payload);
6214
+ this.pushGatewayPending(
6215
+ payload,
6216
+ `req id=${frame.id} queued before gateway WS handshake`
6217
+ );
6125
6218
  }
6126
6219
  }
6127
6220
  // ─── 路径映射 ───
@@ -6133,7 +6226,9 @@ var TunnelProxy = class _TunnelProxy {
6133
6226
  }
6134
6227
  // ─── HTTP 请求代理 ───
6135
6228
  async handleHttpRequest(frame) {
6136
- const url = new URL(this.mapPath(frame.path), this.opts.gatewayBaseUrl);
6229
+ const mappedPath = this.mapPath(frame.path);
6230
+ const url = new URL(mappedPath, this.opts.gatewayBaseUrl);
6231
+ const startedAtMs = Date.now();
6137
6232
  const localHeaders = {};
6138
6233
  for (const [k, v] of Object.entries(frame.headers ?? {})) {
6139
6234
  const lower = k.toLowerCase();
@@ -6143,11 +6238,14 @@ var TunnelProxy = class _TunnelProxy {
6143
6238
  }
6144
6239
  const authAttempts = this.buildLocalGatewayAuthAttempts(localHeaders);
6145
6240
  this.opts.logger.info(
6146
- `TunnelProxy: HTTP ${frame.method} ${frame.path} \u2192 ${url.toString()}, body=${frame.body?.substring(0, 200)}`
6241
+ `TunnelProxy: HTTP id=${frame.id} ${frame.method} ${frame.path} \u2192 ${url.toString()}${summarizeRequestHeaders(frame.headers)}, authAttempts=${authAttempts.map((attempt) => attempt.label).join(" -> ")}, body=${previewText2(frame.body)}`
6147
6242
  );
6148
6243
  try {
6149
6244
  for (let attemptIndex = 0; attemptIndex < authAttempts.length; attemptIndex++) {
6150
6245
  const attempt = authAttempts[attemptIndex];
6246
+ this.opts.logger.info(
6247
+ `TunnelProxy: HTTP id=${frame.id} attempt ${attemptIndex + 1}/${authAttempts.length} auth=${attempt.label}`
6248
+ );
6151
6249
  const res = await fetch(url.toString(), {
6152
6250
  method: frame.method,
6153
6251
  headers: attempt.headers,
@@ -6157,23 +6255,34 @@ var TunnelProxy = class _TunnelProxy {
6157
6255
  if (res.status === 401 && hasFallback) {
6158
6256
  const body = await res.text();
6159
6257
  this.opts.logger.warn(
6160
- `TunnelProxy: local gateway auth via ${attempt.label} returned 401, retrying next credential${body ? `, body=${body.substring(0, 200)}` : ""}`
6258
+ `TunnelProxy: HTTP id=${frame.id} local gateway auth via ${attempt.label} returned 401 after ${Date.now() - startedAtMs}ms, retrying next credential${body ? `, body=${previewText2(body)}` : ""}`
6161
6259
  );
6162
6260
  continue;
6163
6261
  }
6164
- await this.sendHttpResponse(frame.id, res);
6262
+ await this.sendHttpResponse({
6263
+ frameId: frame.id,
6264
+ method: frame.method,
6265
+ path: mappedPath,
6266
+ authLabel: attempt.label,
6267
+ startedAtMs,
6268
+ res
6269
+ });
6165
6270
  return;
6166
6271
  }
6167
6272
  } catch (err) {
6273
+ const message = err instanceof Error ? err.message : String(err);
6274
+ this.opts.logger.error(
6275
+ `TunnelProxy: HTTP id=${frame.id} ${frame.method} ${mappedPath} failed after ${Date.now() - startedAtMs}ms: ${message}`
6276
+ );
6168
6277
  this.opts.client.send({
6169
6278
  type: "proxy_error",
6170
6279
  id: frame.id,
6171
6280
  status: 502,
6172
- message: `gateway unreachable: ${err instanceof Error ? err.message : String(err)}`
6281
+ message: `gateway unreachable: ${message}`
6173
6282
  });
6174
6283
  }
6175
6284
  }
6176
- async streamResponse(requestId, res) {
6285
+ async streamResponse(requestId, res, startedAtMs) {
6177
6286
  const reader = res.body.getReader();
6178
6287
  const decoder = new TextDecoder();
6179
6288
  let chunkCount = 0;
@@ -6195,7 +6304,7 @@ var TunnelProxy = class _TunnelProxy {
6195
6304
  });
6196
6305
  }
6197
6306
  this.opts.logger.info(
6198
- `TunnelProxy: stream end id=${requestId}, total chunks=${chunkCount}`
6307
+ `TunnelProxy: stream end id=${requestId}, total chunks=${chunkCount}, totalElapsedMs=${Date.now() - startedAtMs}`
6199
6308
  );
6200
6309
  this.opts.client.send({
6201
6310
  type: "stream",
@@ -6205,7 +6314,7 @@ var TunnelProxy = class _TunnelProxy {
6205
6314
  });
6206
6315
  } catch (err) {
6207
6316
  this.opts.logger.error(
6208
- `TunnelProxy: stream error id=${requestId} after ${chunkCount} chunks: ${err instanceof Error ? err.message : String(err)}`
6317
+ `TunnelProxy: stream error id=${requestId} after ${chunkCount} chunks and ${Date.now() - startedAtMs}ms: ${err instanceof Error ? err.message : String(err)}`
6209
6318
  );
6210
6319
  this.opts.client.send({
6211
6320
  type: "proxy_error",
@@ -6360,7 +6469,7 @@ function createTunnelService(opts) {
6360
6469
  }
6361
6470
  }
6362
6471
  function acquireLock(filePath) {
6363
- mkdirSync7(dirname4(filePath), { recursive: true });
6472
+ mkdirSync7(dirname5(filePath), { recursive: true });
6364
6473
  for (let attempt = 0; attempt < 2; attempt++) {
6365
6474
  try {
6366
6475
  const fd = openSync(filePath, "wx", 384);
@@ -6826,7 +6935,7 @@ var index_default = {
6826
6935
  if (openclawDir) return openclawDir;
6827
6936
  if (!workspaceDir) return void 0;
6828
6937
  if (basename2(workspaceDir) !== "workspace") return void 0;
6829
- return dirname5(workspaceDir);
6938
+ return dirname6(workspaceDir);
6830
6939
  };
6831
6940
  api.registerCli(
6832
6941
  (ctx) => {