@yoooclaw/phone-notifications 1.7.2 → 1.7.5

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/README.md CHANGED
@@ -50,13 +50,13 @@ OpenClaw / QClaw 手机通知同步插件:接收手机通知并写入本地 JS
50
50
  ### 通过 OpenClaw CLI 安装(OpenClaw)
51
51
 
52
52
  ```bash
53
- openclaw plugin install @yoooclaw/phone-notifications
53
+ openclaw plugins install @yoooclaw/phone-notifications
54
54
  ```
55
55
 
56
56
  ### 更新
57
57
 
58
58
  ```bash
59
- openclaw plugin update @yoooclaw/phone-notifications
59
+ openclaw plugins update @yoooclaw/phone-notifications
60
60
  ```
61
61
 
62
62
  ### 通过一键脚本安装(推荐给 QClaw,备选给 OpenClaw)
@@ -321,6 +321,10 @@ openclaw ntf monitor delete boss-alert --yes
321
321
  ### 灯效控制
322
322
 
323
323
  ```bash
324
+
325
+ # 启用灯效控制工具
326
+ openclaw ntf light setup
327
+
324
328
  # 直接发送灯效指令到硬件设备(需要先 set-api-key)
325
329
  openclaw ntf light send \
326
330
  --segments '[{"mode":"wave","duration_s":4,"brightness":192,"color":{"r":255,"g":0,"b":0}}]'
package/dist/index.js CHANGED
@@ -3647,8 +3647,8 @@ var require_websocket_server = __commonJS({
3647
3647
  });
3648
3648
 
3649
3649
  // src/index.ts
3650
- import { readFileSync as readFileSync14 } from "fs";
3651
- import { basename as basename2, dirname as dirname6 } from "path";
3650
+ import { readFileSync as readFileSync15 } from "fs";
3651
+ import { basename as basename2, dirname as dirname7 } from "path";
3652
3652
 
3653
3653
  // src/light/protocol.ts
3654
3654
  var MAX_LIGHT_SEGMENTS = 12;
@@ -5271,16 +5271,93 @@ function registerLightSend(light) {
5271
5271
  });
5272
5272
  }
5273
5273
 
5274
+ // src/cli/light-setup-tools.ts
5275
+ import { existsSync as existsSync11, mkdirSync as mkdirSync6, readFileSync as readFileSync10, writeFileSync as writeFileSync8 } from "fs";
5276
+ import { homedir } from "os";
5277
+ import { dirname as dirname4, join as join8 } from "path";
5278
+ function isObject(value) {
5279
+ return !!value && typeof value === "object" && !Array.isArray(value);
5280
+ }
5281
+ function ensureArray(obj, key) {
5282
+ const current = obj[key];
5283
+ if (Array.isArray(current)) return current;
5284
+ const next = [];
5285
+ obj[key] = next;
5286
+ return next;
5287
+ }
5288
+ function resolveConfigPath2() {
5289
+ const fromEnv = process.env.OPENCLAW_CONFIG_PATH?.trim();
5290
+ if (fromEnv) return fromEnv;
5291
+ return join8(homedir(), ".openclaw", "openclaw.json");
5292
+ }
5293
+ function upsertLightControlAlsoAllow(cfg) {
5294
+ if (!isObject(cfg.tools)) cfg.tools = {};
5295
+ const toolsAlsoAllow = ensureArray(cfg.tools, "alsoAllow");
5296
+ const hasGlobal = toolsAlsoAllow.includes("light_control");
5297
+ if (!hasGlobal) toolsAlsoAllow.push("light_control");
5298
+ if (!isObject(cfg.agents)) cfg.agents = {};
5299
+ const agents = cfg.agents;
5300
+ const list = ensureArray(agents, "list");
5301
+ let mainAgent = list.find(
5302
+ (item) => isObject(item) && item.id === "main"
5303
+ );
5304
+ if (!mainAgent) {
5305
+ mainAgent = { id: "main" };
5306
+ list.push(mainAgent);
5307
+ }
5308
+ if (!isObject(mainAgent.tools)) mainAgent.tools = {};
5309
+ const mainAlsoAllow = ensureArray(mainAgent.tools, "alsoAllow");
5310
+ const hasMain = mainAlsoAllow.includes("light_control");
5311
+ if (!hasMain) mainAlsoAllow.push("light_control");
5312
+ return {
5313
+ globalChanged: !hasGlobal,
5314
+ mainAgentChanged: !hasMain
5315
+ };
5316
+ }
5317
+ function registerLightSetupTools(light) {
5318
+ light.command("setup").description("\u81EA\u52A8\u653E\u884C light_control\uFF08\u5199\u5165 tools.alsoAllow \u4E0E agents.main.tools.alsoAllow\uFF09").action(() => {
5319
+ const configPath = resolveConfigPath2();
5320
+ if (!existsSync11(configPath)) {
5321
+ exitError("CONFIG_NOT_FOUND", `\u672A\u627E\u5230\u914D\u7F6E\u6587\u4EF6: ${configPath}`);
5322
+ }
5323
+ let cfg = {};
5324
+ try {
5325
+ const raw = readFileSync10(configPath, "utf-8");
5326
+ const parsed = JSON.parse(raw);
5327
+ if (isObject(parsed)) cfg = parsed;
5328
+ } catch (err) {
5329
+ exitError("CONFIG_INVALID", `\u8BFB\u53D6/\u89E3\u6790\u914D\u7F6E\u5931\u8D25: ${err?.message ?? String(err)}`);
5330
+ }
5331
+ const result = upsertLightControlAlsoAllow(cfg);
5332
+ try {
5333
+ mkdirSync6(dirname4(configPath), { recursive: true });
5334
+ writeFileSync8(configPath, JSON.stringify(cfg, null, 2) + "\n", "utf-8");
5335
+ } catch (err) {
5336
+ exitError("WRITE_FAILED", `\u5199\u5165\u914D\u7F6E\u5931\u8D25: ${err?.message ?? String(err)}`);
5337
+ }
5338
+ output({
5339
+ ok: true,
5340
+ configPath,
5341
+ changed: result.globalChanged || result.mainAgentChanged,
5342
+ updates: {
5343
+ toolsAlsoAllow: result.globalChanged ? "added" : "already_exists",
5344
+ mainAgentAlsoAllow: result.mainAgentChanged ? "added" : "already_exists"
5345
+ },
5346
+ next: "\u8BF7\u6267\u884C: openclaw gateway restart"
5347
+ });
5348
+ });
5349
+ }
5350
+
5274
5351
  // src/cli/tunnel-status.ts
5275
- import { existsSync as existsSync11, readFileSync as readFileSync10 } from "fs";
5276
- import { join as join8 } from "path";
5277
- var STATUS_REL_PATH = join8("plugins", "phone-notifications", "tunnel-status.json");
5352
+ import { existsSync as existsSync12, readFileSync as readFileSync11 } from "fs";
5353
+ import { join as join9 } from "path";
5354
+ var STATUS_REL_PATH = join9("plugins", "phone-notifications", "tunnel-status.json");
5278
5355
  function readTunnelStatus(ctx) {
5279
5356
  if (!ctx.stateDir) return null;
5280
- const filePath = join8(ctx.stateDir, STATUS_REL_PATH);
5281
- if (!existsSync11(filePath)) return null;
5357
+ const filePath = join9(ctx.stateDir, STATUS_REL_PATH);
5358
+ if (!existsSync12(filePath)) return null;
5282
5359
  try {
5283
- return JSON.parse(readFileSync10(filePath, "utf-8"));
5360
+ return JSON.parse(readFileSync11(filePath, "utf-8"));
5284
5361
  } catch {
5285
5362
  return null;
5286
5363
  }
@@ -5348,17 +5425,17 @@ function registerNtfStoragePath(ntf, ctx) {
5348
5425
  }
5349
5426
 
5350
5427
  // src/cli/log-search.ts
5351
- import { existsSync as existsSync12, readFileSync as readFileSync11, readdirSync as readdirSync4 } from "fs";
5352
- import { join as join9 } from "path";
5428
+ import { existsSync as existsSync13, readFileSync as readFileSync12, readdirSync as readdirSync4 } from "fs";
5429
+ import { join as join10 } from "path";
5353
5430
  function resolveLogsDir(ctx) {
5354
5431
  if (ctx.stateDir) {
5355
- const dir = join9(
5432
+ const dir = join10(
5356
5433
  ctx.stateDir,
5357
5434
  "plugins",
5358
5435
  "phone-notifications",
5359
5436
  "logs"
5360
5437
  );
5361
- if (existsSync12(dir)) return dir;
5438
+ if (existsSync13(dir)) return dir;
5362
5439
  }
5363
5440
  return null;
5364
5441
  }
@@ -5373,9 +5450,9 @@ function listLogDateKeys(dir) {
5373
5450
  return keys.sort().reverse();
5374
5451
  }
5375
5452
  function collectLogLines(dir, dateKey, keyword, limit, collected) {
5376
- const filePath = join9(dir, `${dateKey}.log`);
5377
- if (!existsSync12(filePath)) return;
5378
- const content = readFileSync11(filePath, "utf-8");
5453
+ const filePath = join10(dir, `${dateKey}.log`);
5454
+ if (!existsSync13(filePath)) return;
5455
+ const content = readFileSync12(filePath, "utf-8");
5379
5456
  const lowerKeyword = keyword?.toLowerCase();
5380
5457
  for (const line of content.split("\n")) {
5381
5458
  if (collected.length >= limit) return;
@@ -5441,12 +5518,12 @@ function registerEnvCli(ntf) {
5441
5518
  }
5442
5519
 
5443
5520
  // src/version.ts
5444
- import { readFileSync as readFileSync12 } from "fs";
5521
+ import { readFileSync as readFileSync13 } from "fs";
5445
5522
  function readPluginVersion() {
5446
5523
  try {
5447
5524
  const packageJsonUrl = new URL("../package.json", import.meta.url);
5448
5525
  const packageJson = JSON.parse(
5449
- readFileSync12(packageJsonUrl, "utf-8")
5526
+ readFileSync13(packageJsonUrl, "utf-8")
5450
5527
  );
5451
5528
  return packageJson.version ?? "unknown";
5452
5529
  } catch {
@@ -5465,6 +5542,7 @@ function registerAllCli(program, ctx, rootCommandName = "ntf") {
5465
5542
  registerNtfMonitor(ntf, ctx);
5466
5543
  const light = registerLightRules(ntf, ctx);
5467
5544
  registerLightSend(light);
5545
+ registerLightSetupTools(light);
5468
5546
  registerNtfStoragePath(ntf, ctx);
5469
5547
  registerLogSearch(ntf, ctx);
5470
5548
  registerTunnelStatus(ntf, ctx);
@@ -5474,17 +5552,17 @@ function registerAllCli(program, ctx, rootCommandName = "ntf") {
5474
5552
  // src/tunnel/service.ts
5475
5553
  import {
5476
5554
  closeSync,
5477
- mkdirSync as mkdirSync7,
5555
+ mkdirSync as mkdirSync8,
5478
5556
  openSync,
5479
- readFileSync as readFileSync13,
5557
+ readFileSync as readFileSync14,
5480
5558
  unlinkSync,
5481
- writeFileSync as writeFileSync9
5559
+ writeFileSync as writeFileSync10
5482
5560
  } from "fs";
5483
- import { dirname as dirname5, join as join10 } from "path";
5561
+ import { dirname as dirname6, join as join11 } from "path";
5484
5562
 
5485
5563
  // src/tunnel/relay-client.ts
5486
- import { writeFileSync as writeFileSync8, mkdirSync as mkdirSync6 } from "fs";
5487
- import { dirname as dirname4 } from "path";
5564
+ import { writeFileSync as writeFileSync9, mkdirSync as mkdirSync7 } from "fs";
5565
+ import { dirname as dirname5 } from "path";
5488
5566
 
5489
5567
  // node_modules/.pnpm/ws@8.19.0/node_modules/ws/wrapper.mjs
5490
5568
  var import_stream = __toESM(require_stream(), 1);
@@ -5526,8 +5604,8 @@ var RelayClient = class {
5526
5604
  lastDisconnectReason
5527
5605
  };
5528
5606
  try {
5529
- mkdirSync6(dirname4(this.opts.statusFilePath), { recursive: true });
5530
- writeFileSync8(this.opts.statusFilePath, JSON.stringify(info, null, 2));
5607
+ mkdirSync7(dirname5(this.opts.statusFilePath), { recursive: true });
5608
+ writeFileSync9(this.opts.statusFilePath, JSON.stringify(info, null, 2));
5531
5609
  } catch {
5532
5610
  }
5533
5611
  }
@@ -5856,11 +5934,96 @@ function buildDeviceAuthPayload(params) {
5856
5934
  params.nonce
5857
5935
  ].join("|");
5858
5936
  }
5859
- function resolveStateDir2() {
5860
- return resolveStateDir();
5937
+ function resolveClientStateDir(stateDir) {
5938
+ return stateDir ?? resolveStateDir();
5939
+ }
5940
+ function ensureDir(filePath) {
5941
+ fs.mkdirSync(path.dirname(filePath), { recursive: true });
5942
+ }
5943
+ function resolveIdentityPath(stateDir) {
5944
+ return path.join(stateDir, "identity", "device.json");
5861
5945
  }
5862
- function loadOrCreateDeviceIdentity() {
5863
- const filePath = path.join(resolveStateDir2(), "identity", "device.json");
5946
+ function normalizeDeviceAuthRole(role) {
5947
+ return role.trim();
5948
+ }
5949
+ function normalizeDeviceAuthScopes(scopes) {
5950
+ const out = /* @__PURE__ */ new Set();
5951
+ for (const scope of scopes) {
5952
+ const trimmed = scope.trim();
5953
+ if (trimmed) {
5954
+ out.add(trimmed);
5955
+ }
5956
+ }
5957
+ return [...out].sort();
5958
+ }
5959
+ function resolveDeviceAuthPath(stateDir) {
5960
+ return path.join(stateDir, "identity", "device-auth.json");
5961
+ }
5962
+ function readDeviceAuthStore(filePath) {
5963
+ try {
5964
+ if (!fs.existsSync(filePath)) return null;
5965
+ const raw = fs.readFileSync(filePath, "utf8");
5966
+ const parsed = JSON.parse(raw);
5967
+ if (parsed?.version !== 1 || typeof parsed.deviceId !== "string") return null;
5968
+ if (!parsed.tokens || typeof parsed.tokens !== "object") return null;
5969
+ return parsed;
5970
+ } catch {
5971
+ return null;
5972
+ }
5973
+ }
5974
+ function writeDeviceAuthStore(filePath, store) {
5975
+ ensureDir(filePath);
5976
+ fs.writeFileSync(filePath, `${JSON.stringify(store, null, 2)}
5977
+ `, {
5978
+ mode: 384
5979
+ });
5980
+ try {
5981
+ fs.chmodSync(filePath, 384);
5982
+ } catch {
5983
+ }
5984
+ }
5985
+ function loadDeviceAuthToken(params) {
5986
+ const store = readDeviceAuthStore(resolveDeviceAuthPath(params.stateDir));
5987
+ if (!store || store.deviceId !== params.deviceId) return null;
5988
+ const entry = store.tokens[normalizeDeviceAuthRole(params.role)];
5989
+ if (!entry || typeof entry.token !== "string") return null;
5990
+ return entry;
5991
+ }
5992
+ function storeDeviceAuthToken(params) {
5993
+ const filePath = resolveDeviceAuthPath(params.stateDir);
5994
+ const existing = readDeviceAuthStore(filePath);
5995
+ const role = normalizeDeviceAuthRole(params.role);
5996
+ const next = {
5997
+ version: 1,
5998
+ deviceId: params.deviceId,
5999
+ tokens: existing && existing.deviceId === params.deviceId && existing.tokens ? { ...existing.tokens } : {}
6000
+ };
6001
+ const entry = {
6002
+ token: params.token,
6003
+ role,
6004
+ scopes: normalizeDeviceAuthScopes(params.scopes),
6005
+ updatedAtMs: Date.now()
6006
+ };
6007
+ next.tokens[role] = entry;
6008
+ writeDeviceAuthStore(filePath, next);
6009
+ return entry;
6010
+ }
6011
+ function clearDeviceAuthToken(params) {
6012
+ const filePath = resolveDeviceAuthPath(params.stateDir);
6013
+ const store = readDeviceAuthStore(filePath);
6014
+ if (!store || store.deviceId !== params.deviceId) return;
6015
+ const role = normalizeDeviceAuthRole(params.role);
6016
+ if (!store.tokens[role]) return;
6017
+ const next = {
6018
+ version: 1,
6019
+ deviceId: store.deviceId,
6020
+ tokens: { ...store.tokens }
6021
+ };
6022
+ delete next.tokens[role];
6023
+ writeDeviceAuthStore(filePath, next);
6024
+ }
6025
+ function loadOrCreateDeviceIdentity(stateDir) {
6026
+ const filePath = resolveIdentityPath(stateDir);
5864
6027
  try {
5865
6028
  if (fs.existsSync(filePath)) {
5866
6029
  const raw = fs.readFileSync(filePath, "utf8");
@@ -5890,6 +6053,7 @@ function loadOrCreateDeviceIdentity() {
5890
6053
  ...identity,
5891
6054
  createdAtMs: Date.now()
5892
6055
  };
6056
+ ensureDir(filePath);
5893
6057
  fs.writeFileSync(filePath, `${JSON.stringify(stored, null, 2)}
5894
6058
  `, {
5895
6059
  mode: 384
@@ -5899,7 +6063,8 @@ function loadOrCreateDeviceIdentity() {
5899
6063
  var TunnelProxy = class _TunnelProxy {
5900
6064
  constructor(opts) {
5901
6065
  this.opts = opts;
5902
- this.deviceIdentity = loadOrCreateDeviceIdentity();
6066
+ this.stateDir = resolveClientStateDir(opts.stateDir);
6067
+ this.deviceIdentity = loadOrCreateDeviceIdentity(this.stateDir);
5903
6068
  opts.logger.info(
5904
6069
  `TunnelProxy: loaded device identity (deviceId=${this.deviceIdentity.deviceId})`
5905
6070
  );
@@ -5915,6 +6080,7 @@ var TunnelProxy = class _TunnelProxy {
5915
6080
  gatewayWsPending = [];
5916
6081
  /** 设备身份,用于 Gateway connect 握手 */
5917
6082
  deviceIdentity;
6083
+ stateDir;
5918
6084
  pushGatewayPending(payload, reason) {
5919
6085
  this.gatewayWsPending.push(payload);
5920
6086
  this.opts.logger.info(
@@ -5932,6 +6098,65 @@ var TunnelProxy = class _TunnelProxy {
5932
6098
  password
5933
6099
  };
5934
6100
  }
6101
+ loadStoredDeviceToken(role) {
6102
+ return loadDeviceAuthToken({
6103
+ stateDir: this.stateDir,
6104
+ deviceId: this.deviceIdentity.deviceId,
6105
+ role
6106
+ })?.token ?? void 0;
6107
+ }
6108
+ buildGatewayConnectAuth(role) {
6109
+ const explicitAuth = this.resolveGatewayConnectAuth();
6110
+ const authPassword = explicitAuth?.password?.trim() || void 0;
6111
+ const explicitGatewayToken = explicitAuth?.token?.trim() || void 0;
6112
+ const deviceToken = explicitGatewayToken ? void 0 : this.loadStoredDeviceToken(role);
6113
+ const authToken = explicitGatewayToken ?? deviceToken;
6114
+ const auth = authToken || authPassword || deviceToken ? {
6115
+ token: authToken,
6116
+ deviceToken,
6117
+ password: authPassword
6118
+ } : void 0;
6119
+ return {
6120
+ auth,
6121
+ authToken,
6122
+ authPassword,
6123
+ deviceToken
6124
+ };
6125
+ }
6126
+ storeIssuedDeviceToken(params) {
6127
+ const token = params.authInfo?.deviceToken;
6128
+ if (typeof token !== "string" || !token.trim()) {
6129
+ return;
6130
+ }
6131
+ const role = typeof params.authInfo?.role === "string" && params.authInfo.role.trim() ? params.authInfo.role.trim() : params.fallbackRole;
6132
+ const scopes = Array.isArray(params.authInfo?.scopes) ? params.authInfo.scopes.filter(
6133
+ (scope) => typeof scope === "string" && !!scope.trim()
6134
+ ) : params.fallbackScopes;
6135
+ storeDeviceAuthToken({
6136
+ stateDir: this.stateDir,
6137
+ deviceId: this.deviceIdentity.deviceId,
6138
+ role,
6139
+ token: token.trim(),
6140
+ scopes
6141
+ });
6142
+ }
6143
+ maybeClearStoredDeviceTokenOnMismatch(code, reason) {
6144
+ const explicitAuth = this.resolveGatewayConnectAuth();
6145
+ if (explicitAuth?.token || explicitAuth?.password) {
6146
+ return;
6147
+ }
6148
+ if (code !== 1008 || !reason.toLowerCase().includes("device token mismatch")) {
6149
+ return;
6150
+ }
6151
+ clearDeviceAuthToken({
6152
+ stateDir: this.stateDir,
6153
+ deviceId: this.deviceIdentity.deviceId,
6154
+ role: "operator"
6155
+ });
6156
+ this.opts.logger.warn(
6157
+ `TunnelProxy: cleared stale stored device token after gateway mismatch (deviceId=${this.deviceIdentity.deviceId})`
6158
+ );
6159
+ }
5935
6160
  buildLocalGatewayAuthAttempts(baseHeaders) {
5936
6161
  const auth = this.resolveGatewayConnectAuth();
5937
6162
  const attempts = [];
@@ -6101,12 +6326,12 @@ var TunnelProxy = class _TunnelProxy {
6101
6326
  if (frame.type === "event" && frame.event === "connect.challenge") {
6102
6327
  const challengeNonce = frame.payload?.nonce ?? "";
6103
6328
  const connectRequestId = `tunnel-connect-${randomUUID2()}`;
6104
- const gatewayAuth = this.resolveGatewayConnectAuth();
6105
- this.opts.logger.info(
6106
- `TunnelProxy: received connect.challenge (nonce=${challengeNonce}, connectReqId=${connectRequestId}, hasToken=${!!gatewayAuth?.token}, hasPassword=${!!gatewayAuth?.password}), sending connect request with device identity`
6107
- );
6108
6329
  const role = "operator";
6109
6330
  const scopes = ["operator.admin"];
6331
+ const gatewayConnectAuth = this.buildGatewayConnectAuth(role);
6332
+ this.opts.logger.info(
6333
+ `TunnelProxy: received connect.challenge (nonce=${challengeNonce}, connectReqId=${connectRequestId}, hasToken=${!!gatewayConnectAuth.authToken}, hasPassword=${!!gatewayConnectAuth.authPassword}, hasDeviceToken=${!!gatewayConnectAuth.deviceToken}), sending connect request with device identity`
6334
+ );
6110
6335
  const signedAtMs = Date.now();
6111
6336
  const clientId = "gateway-client";
6112
6337
  const clientMode = "backend";
@@ -6117,7 +6342,7 @@ var TunnelProxy = class _TunnelProxy {
6117
6342
  role,
6118
6343
  scopes,
6119
6344
  signedAtMs,
6120
- token: gatewayAuth?.token ?? null,
6345
+ token: gatewayConnectAuth.authToken ?? null,
6121
6346
  nonce: challengeNonce
6122
6347
  });
6123
6348
  const signature = signDevicePayload(
@@ -6139,7 +6364,7 @@ var TunnelProxy = class _TunnelProxy {
6139
6364
  },
6140
6365
  role,
6141
6366
  scopes,
6142
- ...gatewayAuth ? { auth: gatewayAuth } : {},
6367
+ ...gatewayConnectAuth.auth ? { auth: gatewayConnectAuth.auth } : {},
6143
6368
  device: {
6144
6369
  id: this.deviceIdentity.deviceId,
6145
6370
  publicKey: publicKeyRawBase64UrlFromPem(
@@ -6155,6 +6380,11 @@ var TunnelProxy = class _TunnelProxy {
6155
6380
  return;
6156
6381
  }
6157
6382
  if (frame.type === "res" && frame.ok === true && frame.payload?.type === "hello-ok") {
6383
+ this.storeIssuedDeviceToken({
6384
+ fallbackRole: "operator",
6385
+ fallbackScopes: ["operator.admin"],
6386
+ authInfo: frame.payload?.auth
6387
+ });
6158
6388
  this.gatewayWsReady = true;
6159
6389
  this.gatewayWsConnecting = false;
6160
6390
  this.opts.logger.info(
@@ -6178,14 +6408,16 @@ var TunnelProxy = class _TunnelProxy {
6178
6408
  ws.on("close", (code, reason) => {
6179
6409
  const wasReady = this.gatewayWsReady;
6180
6410
  const pendingCount = this.gatewayWsPending.length;
6411
+ const reasonText = reason.toString();
6181
6412
  this.opts.logger.info(
6182
- `TunnelProxy: RPC WS closed by gateway (code=${code}, reason=${reason.toString()}, ready=${wasReady}, pending=${pendingCount}, activeWs=${this.wsConnections.size})`
6413
+ `TunnelProxy: RPC WS closed by gateway (code=${code}, reason=${reasonText}, ready=${wasReady}, pending=${pendingCount}, activeWs=${this.wsConnections.size})`
6183
6414
  );
6184
6415
  if (this.gatewayWs === ws) {
6185
6416
  this.gatewayWs = null;
6186
6417
  this.gatewayWsReady = false;
6187
6418
  }
6188
6419
  this.gatewayWsConnecting = false;
6420
+ this.maybeClearStoredDeviceTokenOnMismatch(code, reasonText);
6189
6421
  });
6190
6422
  ws.on("error", (err) => {
6191
6423
  this.opts.logger.warn(
@@ -6444,7 +6676,7 @@ function createTunnelService(opts) {
6444
6676
  }
6445
6677
  function readLockOwner(filePath) {
6446
6678
  try {
6447
- const parsed = JSON.parse(readFileSync13(filePath, "utf-8"));
6679
+ const parsed = JSON.parse(readFileSync14(filePath, "utf-8"));
6448
6680
  return typeof parsed.pid === "number" ? parsed.pid : null;
6449
6681
  } catch {
6450
6682
  return null;
@@ -6469,11 +6701,11 @@ function createTunnelService(opts) {
6469
6701
  }
6470
6702
  }
6471
6703
  function acquireLock(filePath) {
6472
- mkdirSync7(dirname5(filePath), { recursive: true });
6704
+ mkdirSync8(dirname6(filePath), { recursive: true });
6473
6705
  for (let attempt = 0; attempt < 2; attempt++) {
6474
6706
  try {
6475
6707
  const fd = openSync(filePath, "wx", 384);
6476
- writeFileSync9(
6708
+ writeFileSync10(
6477
6709
  fd,
6478
6710
  JSON.stringify({
6479
6711
  pid: process.pid,
@@ -6530,12 +6762,12 @@ function createTunnelService(opts) {
6530
6762
  return;
6531
6763
  }
6532
6764
  const { logger } = opts;
6533
- const baseStateDir = join10(ctx.stateDir, "plugins", "phone-notifications");
6765
+ const baseStateDir = join11(ctx.stateDir, "plugins", "phone-notifications");
6534
6766
  logger.info(
6535
6767
  `Relay tunnel: starting (pid=${process.pid}, url=${opts.tunnelUrl}, heartbeat=${opts.heartbeatSec ?? DEFAULT_HEARTBEAT_SEC}s, backoff=${opts.reconnectBackoffMs ?? DEFAULT_RECONNECT_BACKOFF_MS}ms, gateway=${opts.gatewayBaseUrl}, hasGatewayToken=${!!opts.gatewayToken}, hasGatewayPwd=${!!opts.gatewayPassword})`
6536
6768
  );
6537
- const statusFilePath = join10(baseStateDir, "tunnel-status.json");
6538
- const lockPath = join10(baseStateDir, "relay-tunnel.lock");
6769
+ const statusFilePath = join11(baseStateDir, "tunnel-status.json");
6770
+ const lockPath = join11(baseStateDir, "relay-tunnel.lock");
6539
6771
  if (!acquireLock(lockPath)) {
6540
6772
  return;
6541
6773
  }
@@ -6549,6 +6781,7 @@ function createTunnelService(opts) {
6549
6781
  logger
6550
6782
  });
6551
6783
  proxy = new TunnelProxy({
6784
+ stateDir: baseStateDir,
6552
6785
  gatewayBaseUrl: opts.gatewayBaseUrl,
6553
6786
  gatewayAuthMode: opts.gatewayAuthMode,
6554
6787
  gatewayToken: opts.gatewayToken,
@@ -6583,13 +6816,13 @@ function createTunnelService(opts) {
6583
6816
  }
6584
6817
 
6585
6818
  // src/logger.ts
6586
- import { appendFileSync as appendFileSync2, mkdirSync as mkdirSync8 } from "fs";
6587
- import { join as join11 } from "path";
6819
+ import { appendFileSync as appendFileSync2, mkdirSync as mkdirSync9 } from "fs";
6820
+ import { join as join12 } from "path";
6588
6821
  var PluginFileLogger = class {
6589
6822
  constructor(upstream, stateDir) {
6590
6823
  this.upstream = upstream;
6591
- this.logsDir = join11(stateDir, "plugins", "phone-notifications", "logs");
6592
- mkdirSync8(this.logsDir, { recursive: true });
6824
+ this.logsDir = join12(stateDir, "plugins", "phone-notifications", "logs");
6825
+ mkdirSync9(this.logsDir, { recursive: true });
6593
6826
  }
6594
6827
  logsDir;
6595
6828
  info(msg) {
@@ -6611,7 +6844,7 @@ var PluginFileLogger = class {
6611
6844
  const line = `${time} [${level}] ${msg}
6612
6845
  `;
6613
6846
  try {
6614
- appendFileSync2(join11(this.logsDir, `${dateKey}.log`), line);
6847
+ appendFileSync2(join12(this.logsDir, `${dateKey}.log`), line);
6615
6848
  } catch {
6616
6849
  }
6617
6850
  }
@@ -6657,7 +6890,7 @@ function resolveLocalGatewayAuth(params) {
6657
6890
  const configPath = params.stateDir ? resolveConfigPath(params.stateDir) : void 0;
6658
6891
  if (configPath) {
6659
6892
  try {
6660
- const configData = JSON.parse(readFileSync14(configPath, "utf-8"));
6893
+ const configData = JSON.parse(readFileSync15(configPath, "utf-8"));
6661
6894
  const rawGatewayAuthMode = trimToUndefined2(configData?.gateway?.auth?.mode);
6662
6895
  if (rawGatewayAuthMode === "token" || rawGatewayAuthMode === "password") {
6663
6896
  configGatewayAuthMode = rawGatewayAuthMode;
@@ -6935,7 +7168,7 @@ var index_default = {
6935
7168
  if (openclawDir) return openclawDir;
6936
7169
  if (!workspaceDir) return void 0;
6937
7170
  if (basename2(workspaceDir) !== "workspace") return void 0;
6938
- return dirname6(workspaceDir);
7171
+ return dirname7(workspaceDir);
6939
7172
  };
6940
7173
  api.registerCli(
6941
7174
  (ctx) => {