@yoooclaw/phone-notifications 1.10.4 → 1.10.6-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/index.cjs CHANGED
@@ -610,9 +610,9 @@ async function probeUrl(url, logger) {
610
610
  } finally {
611
611
  clearTimeout(timer);
612
612
  }
613
- } catch (err) {
613
+ } catch (err2) {
614
614
  logger.info(
615
- `[whisper-local] \u8FDE\u901A\u6027\u63A2\u6D4B\u5931\u8D25: ${url} (${err?.name ?? err?.message ?? "unknown"})`
615
+ `[whisper-local] \u8FDE\u901A\u6027\u63A2\u6D4B\u5931\u8D25: ${url} (${err2?.name ?? err2?.message ?? "unknown"})`
616
616
  );
617
617
  return false;
618
618
  }
@@ -663,12 +663,12 @@ async function downloadFromUrl(url, modelPath, logger) {
663
663
  `[whisper-local] \u6A21\u578B\u4E0B\u8F7D\u5B8C\u6210: ${(0, import_node_path22.basename)(modelPath)} (${formatBytes2(fileSize)})`
664
664
  );
665
665
  return { ok: true, modelPath };
666
- } catch (err) {
666
+ } catch (err2) {
667
667
  try {
668
668
  if ((0, import_node_fs26.existsSync)(tmpPath)) (0, import_node_fs26.unlinkSync)(tmpPath);
669
669
  } catch {
670
670
  }
671
- const msg = err?.name === "AbortError" ? "\u6A21\u578B\u4E0B\u8F7D\u8D85\u65F6\uFF0830 \u5206\u949F\uFF09" : err?.message ?? String(err);
671
+ const msg = err2?.name === "AbortError" ? "\u6A21\u578B\u4E0B\u8F7D\u8D85\u65F6\uFF0830 \u5206\u949F\uFF09" : err2?.message ?? String(err2);
672
672
  logger.error(`[whisper-local] \u6A21\u578B\u4E0B\u8F7D\u5931\u8D25: ${msg}`);
673
673
  return { ok: false, modelPath, error: msg };
674
674
  }
@@ -765,9 +765,9 @@ async function transcribeWithWhisperLocal(audioFilePath, localConfig, dataDir, l
765
765
  // 100MB stdout buffer
766
766
  stdio: ["pipe", "pipe", "pipe"]
767
767
  });
768
- } catch (err) {
768
+ } catch (err2) {
769
769
  cleanupTmpWav(tmpWavPath);
770
- return { ok: false, error: `whisper.cpp \u6267\u884C\u5931\u8D25: ${err?.message ?? err}` };
770
+ return { ok: false, error: `whisper.cpp \u6267\u884C\u5931\u8D25: ${err2?.message ?? err2}` };
771
771
  }
772
772
  if (result.status !== 0) {
773
773
  cleanupTmpWav(tmpWavPath);
@@ -792,9 +792,9 @@ async function transcribeWithWhisperLocal(audioFilePath, localConfig, dataDir, l
792
792
  }
793
793
  cleanupTmpWav(tmpWavPath);
794
794
  return parseWhisperOutput(jsonContent, logger);
795
- } catch (err) {
795
+ } catch (err2) {
796
796
  cleanupTmpWav(tmpWavPath);
797
- return { ok: false, error: `whisper.cpp \u8F6C\u5199\u5F02\u5E38: ${err?.message ?? err}` };
797
+ return { ok: false, error: `whisper.cpp \u8F6C\u5199\u5F02\u5E38: ${err2?.message ?? err2}` };
798
798
  }
799
799
  }
800
800
  function getWhisperLocalStatus(dataDir, logger) {
@@ -963,14 +963,14 @@ function convertToWav(inputPath, outputPath, actualFmt, logger) {
963
963
  }
964
964
  const stderr = afResult.stderr?.slice(0, 200) ?? "";
965
965
  return { ok: false, error: `afconvert \u8F6C\u6362\u5931\u8D25 (exit ${afResult.status}): ${stderr}` };
966
- } catch (err) {
966
+ } catch (err2) {
967
967
  if (tmpCopy && (0, import_node_fs26.existsSync)(tmpCopy)) {
968
968
  try {
969
969
  (0, import_node_fs26.unlinkSync)(tmpCopy);
970
970
  } catch {
971
971
  }
972
972
  }
973
- return { ok: false, error: `afconvert \u4E0D\u53EF\u7528: ${err?.message}` };
973
+ return { ok: false, error: `afconvert \u4E0D\u53EF\u7528: ${err2?.message}` };
974
974
  }
975
975
  }
976
976
  const fmtHint = actualFmt === ".ogg" ? "OGG/Opus \u683C\u5F0F\u9700\u8981 ffmpeg \u6216 opus-tools\uFF08brew install opus-tools\uFF09" : "\u8BF7\u5B89\u88C5 ffmpeg\uFF08brew install ffmpeg\uFF09\u6216\u786E\u4FDD\u97F3\u9891\u6587\u4EF6\u4E3A WAV \u683C\u5F0F";
@@ -1043,8 +1043,8 @@ function parseWhisperOutput(stdout, logger) {
1043
1043
  text: fullText,
1044
1044
  segments
1045
1045
  };
1046
- } catch (err) {
1047
- logger.warn(`[whisper-local] JSON \u89E3\u6790\u5931\u8D25\uFF0C\u5C1D\u8BD5\u7EAF\u6587\u672C: ${err?.message}`);
1046
+ } catch (err2) {
1047
+ logger.warn(`[whisper-local] JSON \u89E3\u6790\u5931\u8D25\uFF0C\u5C1D\u8BD5\u7EAF\u6587\u672C: ${err2?.message}`);
1048
1048
  return {
1049
1049
  ok: true,
1050
1050
  text: stdout.trim(),
@@ -1250,8 +1250,8 @@ async function transcribeAudio(audioFilePath, config, logger, options = {}) {
1250
1250
  error: `\u672A\u77E5\u7684 ASR mode: ${config.mode}`
1251
1251
  };
1252
1252
  }
1253
- } catch (err) {
1254
- const msg = err?.message ?? String(err);
1253
+ } catch (err2) {
1254
+ const msg = err2?.message ?? String(err2);
1255
1255
  logger.error(`[asr] \u8F6C\u5199\u5F02\u5E38: ${msg}`);
1256
1256
  return { ok: false, error: msg };
1257
1257
  }
@@ -2175,9 +2175,9 @@ var require_permessage_deflate = __commonJS({
2175
2175
  */
2176
2176
  decompress(data, fin, callback) {
2177
2177
  zlibLimiter.add((done) => {
2178
- this._decompress(data, fin, (err, result) => {
2178
+ this._decompress(data, fin, (err2, result) => {
2179
2179
  done();
2180
- callback(err, result);
2180
+ callback(err2, result);
2181
2181
  });
2182
2182
  });
2183
2183
  }
@@ -2191,9 +2191,9 @@ var require_permessage_deflate = __commonJS({
2191
2191
  */
2192
2192
  compress(data, fin, callback) {
2193
2193
  zlibLimiter.add((done) => {
2194
- this._compress(data, fin, (err, result) => {
2194
+ this._compress(data, fin, (err2, result) => {
2195
2195
  done();
2196
- callback(err, result);
2196
+ callback(err2, result);
2197
2197
  });
2198
2198
  });
2199
2199
  }
@@ -2224,11 +2224,11 @@ var require_permessage_deflate = __commonJS({
2224
2224
  this._inflate.write(data);
2225
2225
  if (fin) this._inflate.write(TRAILER);
2226
2226
  this._inflate.flush(() => {
2227
- const err = this._inflate[kError];
2228
- if (err) {
2227
+ const err2 = this._inflate[kError];
2228
+ if (err2) {
2229
2229
  this._inflate.close();
2230
2230
  this._inflate = null;
2231
- callback(err);
2231
+ callback(err2);
2232
2232
  return;
2233
2233
  }
2234
2234
  const data2 = bufferUtil.concat(
@@ -2309,14 +2309,14 @@ var require_permessage_deflate = __commonJS({
2309
2309
  this.removeListener("data", inflateOnData);
2310
2310
  this.reset();
2311
2311
  }
2312
- function inflateOnError(err) {
2312
+ function inflateOnError(err2) {
2313
2313
  this[kPerMessageDeflate]._inflate = null;
2314
2314
  if (this[kError]) {
2315
2315
  this[kCallback](this[kError]);
2316
2316
  return;
2317
2317
  }
2318
- err[kStatusCode] = 1007;
2319
- this[kCallback](err);
2318
+ err2[kStatusCode] = 1007;
2319
+ this[kCallback](err2);
2320
2320
  }
2321
2321
  }
2322
2322
  });
@@ -2939,8 +2939,8 @@ var require_receiver = __commonJS({
2939
2939
  */
2940
2940
  decompress(data, cb) {
2941
2941
  const perMessageDeflate = this._extensions[PerMessageDeflate.extensionName];
2942
- perMessageDeflate.decompress(data, this._fin, (err, buf) => {
2943
- if (err) return cb(err);
2942
+ perMessageDeflate.decompress(data, this._fin, (err2, buf) => {
2943
+ if (err2) return cb(err2);
2944
2944
  if (buf.length) {
2945
2945
  this._messageLength += buf.length;
2946
2946
  if (this._messageLength > this._maxPayload && this._maxPayload > 0) {
@@ -3101,13 +3101,13 @@ var require_receiver = __commonJS({
3101
3101
  createError(ErrorCtor, message, prefix, statusCode, errorCode) {
3102
3102
  this._loop = false;
3103
3103
  this._errored = true;
3104
- const err = new ErrorCtor(
3104
+ const err2 = new ErrorCtor(
3105
3105
  prefix ? `Invalid WebSocket frame: ${message}` : message
3106
3106
  );
3107
- Error.captureStackTrace(err, this.createError);
3108
- err.code = errorCode;
3109
- err[kStatusCode] = statusCode;
3110
- return err;
3107
+ Error.captureStackTrace(err2, this.createError);
3108
+ err2.code = errorCode;
3109
+ err2[kStatusCode] = statusCode;
3110
+ return err2;
3111
3111
  }
3112
3112
  };
3113
3113
  module2.exports = Receiver2;
@@ -3481,10 +3481,10 @@ var require_sender = __commonJS({
3481
3481
  this._state = GET_BLOB_DATA;
3482
3482
  blob.arrayBuffer().then((arrayBuffer) => {
3483
3483
  if (this._socket.destroyed) {
3484
- const err = new Error(
3484
+ const err2 = new Error(
3485
3485
  "The socket was closed while the blob was being read"
3486
3486
  );
3487
- process.nextTick(callCallbacks, this, err, cb);
3487
+ process.nextTick(callCallbacks, this, err2, cb);
3488
3488
  return;
3489
3489
  }
3490
3490
  this._bufferedBytes -= options[kByteLength];
@@ -3496,8 +3496,8 @@ var require_sender = __commonJS({
3496
3496
  } else {
3497
3497
  this.dispatch(data, compress, options, cb);
3498
3498
  }
3499
- }).catch((err) => {
3500
- process.nextTick(onError, this, err, cb);
3499
+ }).catch((err2) => {
3500
+ process.nextTick(onError, this, err2, cb);
3501
3501
  });
3502
3502
  }
3503
3503
  /**
@@ -3533,10 +3533,10 @@ var require_sender = __commonJS({
3533
3533
  this._state = DEFLATING;
3534
3534
  perMessageDeflate.compress(data, options.fin, (_, buf) => {
3535
3535
  if (this._socket.destroyed) {
3536
- const err = new Error(
3536
+ const err2 = new Error(
3537
3537
  "The socket was closed while data was being compressed"
3538
3538
  );
3539
- callCallbacks(this, err, cb);
3539
+ callCallbacks(this, err2, cb);
3540
3540
  return;
3541
3541
  }
3542
3542
  this._bufferedBytes -= options[kByteLength];
@@ -3587,17 +3587,17 @@ var require_sender = __commonJS({
3587
3587
  }
3588
3588
  };
3589
3589
  module2.exports = Sender2;
3590
- function callCallbacks(sender, err, cb) {
3591
- if (typeof cb === "function") cb(err);
3590
+ function callCallbacks(sender, err2, cb) {
3591
+ if (typeof cb === "function") cb(err2);
3592
3592
  for (let i = 0; i < sender._queue.length; i++) {
3593
3593
  const params = sender._queue[i];
3594
3594
  const callback = params[params.length - 1];
3595
- if (typeof callback === "function") callback(err);
3595
+ if (typeof callback === "function") callback(err2);
3596
3596
  }
3597
3597
  }
3598
- function onError(sender, err, cb) {
3599
- callCallbacks(sender, err, cb);
3600
- sender.onerror(err);
3598
+ function onError(sender, err2, cb) {
3599
+ callCallbacks(sender, err2, cb);
3600
+ sender.onerror(err2);
3601
3601
  }
3602
3602
  }
3603
3603
  });
@@ -4245,8 +4245,8 @@ var require_websocket = __commonJS({
4245
4245
  return;
4246
4246
  }
4247
4247
  this._readyState = _WebSocket.CLOSING;
4248
- this._sender.close(code, data, !this._isServer, (err) => {
4249
- if (err) return;
4248
+ this._sender.close(code, data, !this._isServer, (err2) => {
4249
+ if (err2) return;
4250
4250
  this._closeFrameSent = true;
4251
4251
  if (this._closeFrameReceived || this._receiver._writableState.errorEmitted) {
4252
4252
  this._socket.end();
@@ -4514,11 +4514,11 @@ var require_websocket = __commonJS({
4514
4514
  invalidUrlMessage = "The URL contains a fragment identifier";
4515
4515
  }
4516
4516
  if (invalidUrlMessage) {
4517
- const err = new SyntaxError(invalidUrlMessage);
4517
+ const err2 = new SyntaxError(invalidUrlMessage);
4518
4518
  if (websocket._redirects === 0) {
4519
- throw err;
4519
+ throw err2;
4520
4520
  } else {
4521
- emitErrorAndClose(websocket, err);
4521
+ emitErrorAndClose(websocket, err2);
4522
4522
  return;
4523
4523
  }
4524
4524
  }
@@ -4613,10 +4613,10 @@ var require_websocket = __commonJS({
4613
4613
  abortHandshake(websocket, req, "Opening handshake has timed out");
4614
4614
  });
4615
4615
  }
4616
- req.on("error", (err) => {
4616
+ req.on("error", (err2) => {
4617
4617
  if (req === null || req[kAborted]) return;
4618
4618
  req = websocket._req = null;
4619
- emitErrorAndClose(websocket, err);
4619
+ emitErrorAndClose(websocket, err2);
4620
4620
  });
4621
4621
  req.on("response", (res) => {
4622
4622
  const location = res.headers.location;
@@ -4631,8 +4631,8 @@ var require_websocket = __commonJS({
4631
4631
  try {
4632
4632
  addr = new URL2(location, address);
4633
4633
  } catch (e) {
4634
- const err = new SyntaxError(`Invalid URL: ${location}`);
4635
- emitErrorAndClose(websocket, err);
4634
+ const err2 = new SyntaxError(`Invalid URL: ${location}`);
4635
+ emitErrorAndClose(websocket, err2);
4636
4636
  return;
4637
4637
  }
4638
4638
  initAsClient(websocket, addr, protocols, options);
@@ -4684,7 +4684,7 @@ var require_websocket = __commonJS({
4684
4684
  let extensions;
4685
4685
  try {
4686
4686
  extensions = parse(secWebSocketExtensions);
4687
- } catch (err) {
4687
+ } catch (err2) {
4688
4688
  const message = "Invalid Sec-WebSocket-Extensions header";
4689
4689
  abortHandshake(websocket, socket, message);
4690
4690
  return;
@@ -4697,7 +4697,7 @@ var require_websocket = __commonJS({
4697
4697
  }
4698
4698
  try {
4699
4699
  perMessageDeflate.accept(extensions[PerMessageDeflate.extensionName]);
4700
- } catch (err) {
4700
+ } catch (err2) {
4701
4701
  const message = "Invalid Sec-WebSocket-Extensions header";
4702
4702
  abortHandshake(websocket, socket, message);
4703
4703
  return;
@@ -4717,10 +4717,10 @@ var require_websocket = __commonJS({
4717
4717
  req.end();
4718
4718
  }
4719
4719
  }
4720
- function emitErrorAndClose(websocket, err) {
4720
+ function emitErrorAndClose(websocket, err2) {
4721
4721
  websocket._readyState = WebSocket2.CLOSING;
4722
4722
  websocket._errorEmitted = true;
4723
- websocket.emit("error", err);
4723
+ websocket.emit("error", err2);
4724
4724
  websocket.emitClose();
4725
4725
  }
4726
4726
  function netConnect(options) {
@@ -4736,17 +4736,17 @@ var require_websocket = __commonJS({
4736
4736
  }
4737
4737
  function abortHandshake(websocket, stream, message) {
4738
4738
  websocket._readyState = WebSocket2.CLOSING;
4739
- const err = new Error(message);
4740
- Error.captureStackTrace(err, abortHandshake);
4739
+ const err2 = new Error(message);
4740
+ Error.captureStackTrace(err2, abortHandshake);
4741
4741
  if (stream.setHeader) {
4742
4742
  stream[kAborted] = true;
4743
4743
  stream.abort();
4744
4744
  if (stream.socket && !stream.socket.destroyed) {
4745
4745
  stream.socket.destroy();
4746
4746
  }
4747
- process.nextTick(emitErrorAndClose, websocket, err);
4747
+ process.nextTick(emitErrorAndClose, websocket, err2);
4748
4748
  } else {
4749
- stream.destroy(err);
4749
+ stream.destroy(err2);
4750
4750
  stream.once("error", websocket.emit.bind(websocket, "error"));
4751
4751
  stream.once("close", websocket.emitClose.bind(websocket));
4752
4752
  }
@@ -4758,10 +4758,10 @@ var require_websocket = __commonJS({
4758
4758
  else websocket._bufferedAmount += length;
4759
4759
  }
4760
4760
  if (cb) {
4761
- const err = new Error(
4761
+ const err2 = new Error(
4762
4762
  `WebSocket is not open: readyState ${websocket.readyState} (${readyStates[websocket.readyState]})`
4763
4763
  );
4764
- process.nextTick(cb, err);
4764
+ process.nextTick(cb, err2);
4765
4765
  }
4766
4766
  }
4767
4767
  function receiverOnConclude(code, reason) {
@@ -4779,16 +4779,16 @@ var require_websocket = __commonJS({
4779
4779
  const websocket = this[kWebSocket];
4780
4780
  if (!websocket.isPaused) websocket._socket.resume();
4781
4781
  }
4782
- function receiverOnError(err) {
4782
+ function receiverOnError(err2) {
4783
4783
  const websocket = this[kWebSocket];
4784
4784
  if (websocket._socket[kWebSocket] !== void 0) {
4785
4785
  websocket._socket.removeListener("data", socketOnData);
4786
4786
  process.nextTick(resume, websocket._socket);
4787
- websocket.close(err[kStatusCode]);
4787
+ websocket.close(err2[kStatusCode]);
4788
4788
  }
4789
4789
  if (!websocket._errorEmitted) {
4790
4790
  websocket._errorEmitted = true;
4791
- websocket.emit("error", err);
4791
+ websocket.emit("error", err2);
4792
4792
  }
4793
4793
  }
4794
4794
  function receiverOnFinish() {
@@ -4808,7 +4808,7 @@ var require_websocket = __commonJS({
4808
4808
  function resume(stream) {
4809
4809
  stream.resume();
4810
4810
  }
4811
- function senderOnError(err) {
4811
+ function senderOnError(err2) {
4812
4812
  const websocket = this[kWebSocket];
4813
4813
  if (websocket.readyState === WebSocket2.CLOSED) return;
4814
4814
  if (websocket.readyState === WebSocket2.OPEN) {
@@ -4818,7 +4818,7 @@ var require_websocket = __commonJS({
4818
4818
  this._socket.end();
4819
4819
  if (!websocket._errorEmitted) {
4820
4820
  websocket._errorEmitted = true;
4821
- websocket.emit("error", err);
4821
+ websocket.emit("error", err2);
4822
4822
  }
4823
4823
  }
4824
4824
  function setCloseTimer(websocket) {
@@ -4884,11 +4884,11 @@ var require_stream = __commonJS({
4884
4884
  this.destroy();
4885
4885
  }
4886
4886
  }
4887
- function duplexOnError(err) {
4887
+ function duplexOnError(err2) {
4888
4888
  this.removeListener("error", duplexOnError);
4889
4889
  this.destroy();
4890
4890
  if (this.listenerCount("error") === 0) {
4891
- this.emit("error", err);
4891
+ this.emit("error", err2);
4892
4892
  }
4893
4893
  }
4894
4894
  function createWebSocketStream2(ws, options) {
@@ -4904,28 +4904,28 @@ var require_stream = __commonJS({
4904
4904
  const data = !isBinary && duplex._readableState.objectMode ? msg.toString() : msg;
4905
4905
  if (!duplex.push(data)) ws.pause();
4906
4906
  });
4907
- ws.once("error", function error(err) {
4907
+ ws.once("error", function error(err2) {
4908
4908
  if (duplex.destroyed) return;
4909
4909
  terminateOnDestroy = false;
4910
- duplex.destroy(err);
4910
+ duplex.destroy(err2);
4911
4911
  });
4912
4912
  ws.once("close", function close() {
4913
4913
  if (duplex.destroyed) return;
4914
4914
  duplex.push(null);
4915
4915
  });
4916
- duplex._destroy = function(err, callback) {
4916
+ duplex._destroy = function(err2, callback) {
4917
4917
  if (ws.readyState === ws.CLOSED) {
4918
- callback(err);
4918
+ callback(err2);
4919
4919
  process.nextTick(emitClose, duplex);
4920
4920
  return;
4921
4921
  }
4922
4922
  let called = false;
4923
- ws.once("error", function error(err2) {
4923
+ ws.once("error", function error(err3) {
4924
4924
  called = true;
4925
- callback(err2);
4925
+ callback(err3);
4926
4926
  });
4927
4927
  ws.once("close", function close() {
4928
- if (!called) callback(err);
4928
+ if (!called) callback(err2);
4929
4929
  process.nextTick(emitClose, duplex);
4930
4930
  });
4931
4931
  if (terminateOnDestroy) ws.terminate();
@@ -5247,7 +5247,7 @@ var require_websocket_server = __commonJS({
5247
5247
  if (secWebSocketProtocol !== void 0) {
5248
5248
  try {
5249
5249
  protocols = subprotocol.parse(secWebSocketProtocol);
5250
- } catch (err) {
5250
+ } catch (err2) {
5251
5251
  const message = "Invalid Sec-WebSocket-Protocol header";
5252
5252
  abortHandshakeOrEmitwsClientError(this, req, socket, 400, message);
5253
5253
  return;
@@ -5267,7 +5267,7 @@ var require_websocket_server = __commonJS({
5267
5267
  perMessageDeflate.accept(offers[PerMessageDeflate.extensionName]);
5268
5268
  extensions[PerMessageDeflate.extensionName] = perMessageDeflate;
5269
5269
  }
5270
- } catch (err) {
5270
+ } catch (err2) {
5271
5271
  const message = "Invalid or unacceptable Sec-WebSocket-Extensions header";
5272
5272
  abortHandshakeOrEmitwsClientError(this, req, socket, 400, message);
5273
5273
  return;
@@ -5396,9 +5396,9 @@ var require_websocket_server = __commonJS({
5396
5396
  }
5397
5397
  function abortHandshakeOrEmitwsClientError(server, req, socket, code, message, headers) {
5398
5398
  if (server.listenerCount("wsClientError")) {
5399
- const err = new Error(message);
5400
- Error.captureStackTrace(err, abortHandshakeOrEmitwsClientError);
5401
- server.emit("wsClientError", err, socket, req);
5399
+ const err2 = new Error(message);
5400
+ Error.captureStackTrace(err2, abortHandshakeOrEmitwsClientError);
5401
+ server.emit("wsClientError", err2, socket, req);
5402
5402
  } else {
5403
5403
  abortHandshake(socket, code, message, headers);
5404
5404
  }
@@ -5424,7 +5424,7 @@ function readBuildInjectedVersion() {
5424
5424
  if (false) {
5425
5425
  return void 0;
5426
5426
  }
5427
- const version = "1.10.4".trim();
5427
+ const version = "1.10.6-beta.0".trim();
5428
5428
  return version || void 0;
5429
5429
  }
5430
5430
  function readPluginVersionFromPackageJson() {
@@ -6681,11 +6681,11 @@ function registerLightRulesGateway(api, registry, logger, rememberBroadcast) {
6681
6681
  id: rule.name
6682
6682
  }));
6683
6683
  respond(true, { ok: true, rules });
6684
- } catch (err) {
6685
- logger.warn(`lightrules.list failed: ${err?.message}`);
6684
+ } catch (err2) {
6685
+ logger.warn(`lightrules.list failed: ${err2?.message}`);
6686
6686
  respond(false, null, {
6687
6687
  code: "INTERNAL_ERROR",
6688
- message: err?.message ?? "Unknown error"
6688
+ message: err2?.message ?? "Unknown error"
6689
6689
  });
6690
6690
  }
6691
6691
  });
@@ -6715,8 +6715,8 @@ function registerLightRulesGateway(api, registry, logger, rememberBroadcast) {
6715
6715
  try {
6716
6716
  repeatTimes = normalizeRepeatTimes({ repeat, repeat_times });
6717
6717
  assertAncsRepeatTimes(repeatTimes);
6718
- } catch (err) {
6719
- respond(false, null, { code: "VALIDATION_FAILED", message: err?.message ?? "Unknown error" });
6718
+ } catch (err2) {
6719
+ respond(false, null, { code: "VALIDATION_FAILED", message: err2?.message ?? "Unknown error" });
6720
6720
  return;
6721
6721
  }
6722
6722
  try {
@@ -6730,12 +6730,12 @@ function registerLightRulesGateway(api, registry, logger, rememberBroadcast) {
6730
6730
  });
6731
6731
  logger.info(`Light rule created: ${name}`);
6732
6732
  respond(true, { ok: true, name, cronHint: result.cronHint });
6733
- } catch (err) {
6734
- if (err instanceof LightRuleError) {
6735
- respond(false, null, { code: err.code, message: err.message });
6733
+ } catch (err2) {
6734
+ if (err2 instanceof LightRuleError) {
6735
+ respond(false, null, { code: err2.code, message: err2.message });
6736
6736
  } else {
6737
- logger.warn(`lightrules.create failed: ${err?.message}`);
6738
- respond(false, null, { code: "INTERNAL_ERROR", message: err?.message ?? "Unknown error" });
6737
+ logger.warn(`lightrules.create failed: ${err2?.message}`);
6738
+ respond(false, null, { code: "INTERNAL_ERROR", message: err2?.message ?? "Unknown error" });
6739
6739
  }
6740
6740
  }
6741
6741
  });
@@ -6774,8 +6774,8 @@ function registerLightRulesGateway(api, registry, logger, rememberBroadcast) {
6774
6774
  try {
6775
6775
  repeatTimes = normalizeRepeatTimes({ repeat, repeat_times });
6776
6776
  assertAncsRepeatTimes(repeatTimes);
6777
- } catch (err) {
6778
- respond(false, null, { code: "VALIDATION_FAILED", message: err?.message ?? "Unknown error" });
6777
+ } catch (err2) {
6778
+ respond(false, null, { code: "VALIDATION_FAILED", message: err2?.message ?? "Unknown error" });
6779
6779
  return;
6780
6780
  }
6781
6781
  }
@@ -6798,12 +6798,12 @@ function registerLightRulesGateway(api, registry, logger, rememberBroadcast) {
6798
6798
  rule: result.meta,
6799
6799
  cronHint: result.cronHint
6800
6800
  });
6801
- } catch (err) {
6802
- if (err instanceof LightRuleError) {
6803
- respond(false, null, { code: err.code, message: err.message });
6801
+ } catch (err2) {
6802
+ if (err2 instanceof LightRuleError) {
6803
+ respond(false, null, { code: err2.code, message: err2.message });
6804
6804
  } else {
6805
- logger.warn(`lightrules.update failed: ${err?.message}`);
6806
- respond(false, null, { code: "INTERNAL_ERROR", message: err?.message ?? "Unknown error" });
6805
+ logger.warn(`lightrules.update failed: ${err2?.message}`);
6806
+ respond(false, null, { code: "INTERNAL_ERROR", message: err2?.message ?? "Unknown error" });
6807
6807
  }
6808
6808
  }
6809
6809
  });
@@ -6826,273 +6826,17 @@ function registerLightRulesGateway(api, registry, logger, rememberBroadcast) {
6826
6826
  deleted: true,
6827
6827
  cronHint: result.cronHint
6828
6828
  });
6829
- } catch (err) {
6830
- if (err instanceof LightRuleError) {
6831
- respond(false, null, { code: err.code, message: err.message });
6829
+ } catch (err2) {
6830
+ if (err2 instanceof LightRuleError) {
6831
+ respond(false, null, { code: err2.code, message: err2.message });
6832
6832
  } else {
6833
- logger.warn(`lightrules.delete failed: ${err?.message}`);
6834
- respond(false, null, { code: "INTERNAL_ERROR", message: err?.message ?? "Unknown error" });
6833
+ logger.warn(`lightrules.delete failed: ${err2?.message}`);
6834
+ respond(false, null, { code: "INTERNAL_ERROR", message: err2?.message ?? "Unknown error" });
6835
6835
  }
6836
6836
  }
6837
6837
  });
6838
6838
  }
6839
6839
 
6840
- // src/light-rules/evaluator-job.ts
6841
- var EVALUATOR_JOB_ID = "light-rules-evaluator";
6842
- var EVALUATOR_SUBAGENT_SESSION_KEY = EVALUATOR_JOB_ID;
6843
- var FALLBACK_CRON_EXPR = "0 0 1 1 *";
6844
- function buildEvaluatorJobMessage(notificationsDir) {
6845
- return `\u706F\u6548\u89C4\u5219\u8BC4\u4F30\u4EFB\u52A1\u3002
6846
-
6847
- \u6267\u884C\u6B65\u9AA4\uFF1A
6848
- 1. \u8BFB\u53D6 tasks/light-rules-evaluator/checkpoint.json\uFF08\u8BB0\u5F55\u4E0A\u6B21\u5904\u7406\u8FDB\u5EA6\uFF09
6849
- 2. \u626B\u63CF ${notificationsDir} \u76EE\u5F55\uFF0C\u83B7\u53D6 checkpoint \u4E4B\u540E\u7684\u65B0\u901A\u77E5
6850
- 3. \u626B\u63CF tasks/ \u76EE\u5F55\uFF0C\u8BFB\u53D6\u6240\u6709 type=light-rule \u4E14 enabled=true \u7684 meta.json
6851
- 4. \u5BF9\u6BCF\u6761\u65B0\u901A\u77E5\uFF0C\u9010\u4E00\u5224\u65AD\u662F\u5426\u547D\u4E2D\u6BCF\u6761\u89C4\u5219\u7684 description\uFF08\u8BED\u4E49\u5339\u914D\uFF09
6852
- 5. \u547D\u4E2D\u65F6\uFF1A\u4EE5\u8BE5\u89C4\u5219\u7684 segments \u548C repeat_times \u8C03\u7528 light_control \u5DE5\u5177
6853
- 6. \u66F4\u65B0 checkpoint.json\uFF0C\u8BB0\u5F55\u5DF2\u5904\u7406\u5230\u7684\u6700\u65B0\u901A\u77E5\u4F4D\u7F6E
6854
- 7. \u82E5\u65E0\u65B0\u901A\u77E5\u6216\u65E0 enabled \u89C4\u5219\uFF1A\u8F93\u51FA NO_CHANGE\uFF0C\u76F4\u63A5\u7ED3\u675F`;
6855
- }
6856
- var LightRulesEvaluatorJob = class {
6857
- logger;
6858
- registry;
6859
- subagentRunner;
6860
- getNotificationsDir;
6861
- /**
6862
- * 记录本进程生命周期内 job 是否已确认存在。
6863
- * 仅在 `ensureJobExists` 成功后置 true,避免每次 push 都做检查。
6864
- */
6865
- jobEnsured = false;
6866
- /**
6867
- * 首次创建 job 时的并发保护。
6868
- * 避免冷启动瞬间多条通知并发到达时重复调用 `cron.add`。
6869
- */
6870
- ensureJobPromise = null;
6871
- /**
6872
- * subagent fallback 路径的并发保护。
6873
- * 若评估 session 已在运行中,跳过本次触发(checkpoint 保证下次补处理)。
6874
- */
6875
- subagentInFlight = false;
6876
- constructor(deps) {
6877
- this.logger = deps.logger;
6878
- this.registry = deps.registry;
6879
- this.subagentRunner = deps.subagentRunner;
6880
- this.getNotificationsDir = deps.getNotificationsDir ?? (() => void 0);
6881
- }
6882
- /**
6883
- * 通知落盘后调用。若有新增通知且存在 enabled 规则,则触发评估。
6884
- *
6885
- * 两条路径:
6886
- * - cron 不为 null:enqueueRun("force") 入队(gateway context 路径,正常路径)
6887
- * - cron 为 null:通过 subagentRunner 直接运行(HTTP Relay 路径,fallback)
6888
- *
6889
- * @param cron 来自 gateway context 的 CronService;HTTP 路径下为 null
6890
- * @param insertedCount 本次 ingest 新落盘的通知条数(StoredNotification 去重后)
6891
- */
6892
- async triggerIfNeeded(cron, insertedCount) {
6893
- if (insertedCount === 0) return;
6894
- if (this.registry.getEnabled().length === 0) return;
6895
- if (!cron) {
6896
- await this.triggerViaSubagent();
6897
- return;
6898
- }
6899
- try {
6900
- await this.ensureJobExists(cron);
6901
- } catch (err) {
6902
- this.logger.warn(`light-rules-evaluator: job ensure failed: ${err?.message ?? err}`);
6903
- return;
6904
- }
6905
- try {
6906
- const result = await cron.enqueueRun(EVALUATOR_JOB_ID, "force");
6907
- if (!result.ok) {
6908
- this.logger.warn("light-rules-evaluator: enqueueRun returned ok=false");
6909
- return;
6910
- }
6911
- if ("enqueued" in result && result.enqueued) {
6912
- this.logger.info(`light-rules-evaluator: enqueued runId=${result.runId}`);
6913
- } else if ("reason" in result) {
6914
- this.logger.info(`light-rules-evaluator: enqueueRun skipped (${result.reason})`);
6915
- }
6916
- } catch (err) {
6917
- this.logger.warn(`light-rules-evaluator: enqueueRun failed: ${err?.message ?? err}`);
6918
- }
6919
- }
6920
- /**
6921
- * cron service 不可用时的 fallback:直接通过 subagentRunner 运行评估 session。
6922
- *
6923
- * 并发保护:若上一次 subagent 运行尚未完成,本次跳过。
6924
- * checkpoint 保证即使本次跳过,下次触发时会处理所有积压通知。
6925
- */
6926
- async triggerViaSubagent() {
6927
- if (!this.subagentRunner) {
6928
- this.logger.warn(
6929
- "light-rules-evaluator: cron service unavailable and no subagent fallback configured; notifications ingested via HTTP will not trigger light rules until an agent session is active"
6930
- );
6931
- return;
6932
- }
6933
- if (this.subagentInFlight) {
6934
- this.logger.info("light-rules-evaluator: subagent run in-flight, skipping this trigger");
6935
- return;
6936
- }
6937
- const notificationsDir = this.getNotificationsDir();
6938
- if (!notificationsDir) {
6939
- this.logger.warn("light-rules-evaluator: notifications dir not ready, skipping subagent trigger");
6940
- return;
6941
- }
6942
- this.subagentInFlight = true;
6943
- try {
6944
- const result = await this.subagentRunner.run({
6945
- sessionKey: EVALUATOR_SUBAGENT_SESSION_KEY,
6946
- message: buildEvaluatorJobMessage(notificationsDir),
6947
- deliver: false,
6948
- idempotencyKey: `${EVALUATOR_SUBAGENT_SESSION_KEY}-${Date.now()}`
6949
- });
6950
- this.logger.info(`light-rules-evaluator: subagent triggered runId=${result.runId}`);
6951
- } catch (err) {
6952
- this.logger.warn(`light-rules-evaluator: subagent trigger failed: ${err?.message ?? err}`);
6953
- } finally {
6954
- this.subagentInFlight = false;
6955
- }
6956
- }
6957
- /**
6958
- * 按需创建 `light-rules-evaluator` job。
6959
- * 若 job 已存在(内存缓存或 cron store),直接返回;否则调用 `cron.add`。
6960
- */
6961
- async ensureJobExists(cron) {
6962
- if (this.jobEnsured) return;
6963
- if (!this.ensureJobPromise) {
6964
- this.ensureJobPromise = this.createJobIfNeeded(cron).finally(() => {
6965
- this.ensureJobPromise = null;
6966
- });
6967
- }
6968
- await this.ensureJobPromise;
6969
- }
6970
- async createJobIfNeeded(cron) {
6971
- if (cron.getJob(EVALUATOR_JOB_ID)) {
6972
- this.jobEnsured = true;
6973
- return;
6974
- }
6975
- try {
6976
- await cron.add({
6977
- id: EVALUATOR_JOB_ID,
6978
- name: "\u706F\u6548\u89C4\u5219\u8BC4\u4F30",
6979
- description: "\u4E8B\u4EF6\u9A71\u52A8\uFF1A\u901A\u77E5\u5230\u8FBE\u65F6\u8BC4\u4F30\u6240\u6709 enabled \u706F\u6548\u89C4\u5219\uFF0C\u547D\u4E2D\u5219\u8C03\u7528 light_control \u89E6\u53D1\u706F\u6548",
6980
- enabled: true,
6981
- schedule: { kind: "cron", expr: FALLBACK_CRON_EXPR },
6982
- sessionTarget: "isolated",
6983
- wakeMode: "now",
6984
- payload: {
6985
- kind: "agentTurn",
6986
- message: buildEvaluatorJobMessage(this.getNotificationsDir() ?? "notifications")
6987
- }
6988
- });
6989
- this.logger.info("light-rules-evaluator: job created");
6990
- } catch (err) {
6991
- if (!cron.getJob(EVALUATOR_JOB_ID)) {
6992
- throw err;
6993
- }
6994
- }
6995
- this.jobEnsured = true;
6996
- }
6997
- };
6998
-
6999
- // src/light-rules/migration.ts
7000
- var import_node_fs6 = require("fs");
7001
- var import_node_path5 = require("path");
7002
- var NO_MATCH_FETCH_PY = `#!/usr/bin/env python3
7003
- # \u6B64\u6587\u4EF6\u7531\u8FC1\u79FB\u5DE5\u5177\u751F\u6210\u3002
7004
- # \u706F\u6548\u89C4\u5219\u5DF2\u8FC1\u79FB\u81F3\u4E8B\u4EF6\u9A71\u52A8\u67B6\u6784\uFF0C\u6B64 cron job \u4E0D\u518D\u6267\u884C\u5B9E\u9645\u5DE5\u4F5C\u3002
7005
- print("NO_MATCH")
7006
- `;
7007
- function normalizeScriptText(text) {
7008
- return text.replace(/\r\n/g, "\n").trim();
7009
- }
7010
- function resolveTasksDir(ctx) {
7011
- if (ctx.workspaceDir) return (0, import_node_path5.join)(ctx.workspaceDir, "tasks");
7012
- if (ctx.stateDir) {
7013
- const inferredWorkspaceDir = (0, import_node_path5.join)(ctx.stateDir, "workspace");
7014
- if ((0, import_node_fs6.existsSync)(inferredWorkspaceDir)) return (0, import_node_path5.join)(inferredWorkspaceDir, "tasks");
7015
- return (0, import_node_path5.join)(ctx.stateDir, "tasks");
7016
- }
7017
- return null;
7018
- }
7019
- function migrateLegacyLightRuleTasks(ctx, logger) {
7020
- const tasksDir3 = resolveTasksDir(ctx);
7021
- if (!tasksDir3 || !(0, import_node_fs6.existsSync)(tasksDir3)) return;
7022
- try {
7023
- for (const entry of (0, import_node_fs6.readdirSync)(tasksDir3, { withFileTypes: true })) {
7024
- if (!entry.isDirectory()) continue;
7025
- migrateTaskDir((0, import_node_path5.join)(tasksDir3, String(entry.name)), logger);
7026
- }
7027
- } catch (err) {
7028
- logger.warn(`migration: failed to read tasks dir: ${err?.message}`);
7029
- }
7030
- }
7031
- function migrateTaskDir(taskDir, logger) {
7032
- const metaPath = (0, import_node_path5.join)(taskDir, "meta.json");
7033
- if (!(0, import_node_fs6.existsSync)(metaPath)) return;
7034
- let meta;
7035
- try {
7036
- meta = JSON.parse((0, import_node_fs6.readFileSync)(metaPath, "utf-8"));
7037
- } catch {
7038
- return;
7039
- }
7040
- if (meta.type !== "light-rule") return;
7041
- const name = typeof meta.name === "string" ? meta.name : taskDir;
7042
- mergeMatchRulesIntoDescription(meta, name, metaPath, logger);
7043
- replaceFetchPy(taskDir, name, logger);
7044
- for (const filename of ["README.md", "checkpoint.json"]) {
7045
- removeFile((0, import_node_path5.join)(taskDir, filename), name, filename, logger);
7046
- }
7047
- }
7048
- function mergeMatchRulesIntoDescription(meta, name, metaPath, logger) {
7049
- const matchRules = meta.matchRules;
7050
- if (!matchRules || typeof matchRules !== "object") return;
7051
- const rules = matchRules;
7052
- const parts = [];
7053
- if (rules.appName) parts.push(`app=${rules.appName}`);
7054
- if (rules.senderKeywords?.length) {
7055
- parts.push(`\u53D1\u4EF6\u4EBA\u5173\u952E\u8BCD=${rules.senderKeywords.join("\u3001")}`);
7056
- }
7057
- if (rules.contentKeywords?.length) {
7058
- parts.push(`\u5185\u5BB9\u5173\u952E\u8BCD=${rules.contentKeywords.join("\u3001")}`);
7059
- }
7060
- if (parts.length > 0) {
7061
- const existing = typeof meta.description === "string" ? meta.description.trim() : "";
7062
- meta.description = existing ? `${existing}\u3002\u5339\u914D\u89C4\u5219\uFF1A${parts.join("\uFF0C")}` : `\u5339\u914D\u89C4\u5219\uFF1A${parts.join("\uFF0C")}`;
7063
- }
7064
- delete meta.matchRules;
7065
- try {
7066
- (0, import_node_fs6.writeFileSync)(metaPath, JSON.stringify(meta, null, 2), "utf-8");
7067
- logger.info(`migration: merged matchRules into description for light rule: ${name}`);
7068
- } catch (err) {
7069
- logger.warn(`migration: failed to update meta.json for ${name}: ${err?.message}`);
7070
- }
7071
- }
7072
- function replaceFetchPy(taskDir, name, logger) {
7073
- const fetchPyPath = (0, import_node_path5.join)(taskDir, "fetch.py");
7074
- if (!(0, import_node_fs6.existsSync)(fetchPyPath)) return;
7075
- try {
7076
- const existing = (0, import_node_fs6.readFileSync)(fetchPyPath, "utf-8");
7077
- if (normalizeScriptText(existing) === normalizeScriptText(NO_MATCH_FETCH_PY)) {
7078
- return;
7079
- }
7080
- (0, import_node_fs6.writeFileSync)(fetchPyPath, NO_MATCH_FETCH_PY, "utf-8");
7081
- logger.info(`migration: replaced fetch.py with NO_MATCH placeholder for ${name}`);
7082
- } catch (err) {
7083
- logger.warn(`migration: failed to replace fetch.py for ${name}: ${err?.message}`);
7084
- }
7085
- }
7086
- function removeFile(filePath, ruleName, filename, logger) {
7087
- if (!(0, import_node_fs6.existsSync)(filePath)) return;
7088
- try {
7089
- (0, import_node_fs6.rmSync)(filePath);
7090
- logger.info(`migration: removed ${filename} for light rule: ${ruleName}`);
7091
- } catch (err) {
7092
- logger.warn(`migration: failed to remove ${filename} for ${ruleName}: ${err?.message}`);
7093
- }
7094
- }
7095
-
7096
6840
  // src/light-rules/registry.ts
7097
6841
  var LightRuleRegistry = class {
7098
6842
  ctx;
@@ -7213,20 +6957,705 @@ var LightRuleRegistry = class {
7213
6957
  }
7214
6958
  };
7215
6959
 
7216
- // src/plugin/auto-update.ts
7217
- init_env();
7218
-
7219
- // src/update/channel.ts
7220
- function resolveUpdateChannel(params) {
7221
- if (params.configuredChannel) {
7222
- return params.configuredChannel;
7223
- }
7224
- if (params.currentVersion.includes("-")) {
7225
- return "beta";
7226
- }
7227
- return params.envName === "development" ? "beta" : "latest";
7228
- }
7229
-
6960
+ // src/light/protocol.ts
6961
+ var MAX_LIGHT_SEGMENTS = 12;
6962
+ var PROTOCOL_DIGITS = [
6963
+ "\x80",
6964
+ "\x81",
6965
+ "\x82",
6966
+ "\x83",
6967
+ "\x84",
6968
+ "\x91",
6969
+ "\x92",
6970
+ "\x93",
6971
+ "\x94",
6972
+ "\x95",
6973
+ "\x96",
6974
+ "\x97"
6975
+ ];
6976
+ var LED_SEPARATOR_ONCE = "\x9A";
6977
+ var LED_SEPARATOR_LOOP = "\x9B";
6978
+ var DURATION_STEPS_S = [0.5, 1, 2, 3, 5, 6, 8, 16, 24, 32, 48];
6979
+ var INTERVAL_STEPS_MS = [50, 100, 200, 300, 500, 600, 800, 1600, 2400, 3200, 4800];
6980
+ var BREATH_STEPS_MS = [1040, 1560, 2080, 2600, 3100, 4160];
6981
+ var BRIGHTNESS_STEPS = [32, 64, 96, 128, 192, 255];
6982
+ var COLOR_STEPS = [0, 32, 64, 128, 192, 255];
6983
+ var BACKGROUND_BRIGHTNESS_STEPS = [0, 32, 64, 96, 128, 192, 255];
6984
+ var MULTI_CHANNEL_COLOR_COEFFICIENTS = { r: 1, g: 0.25, b: 0.25 };
6985
+ var PURE_WHITE_COLOR_COEFFICIENTS = { r: 1, g: 0.35, b: 0.35 };
6986
+ var MODE_TO_INDEX = {
6987
+ wave: 0,
6988
+ breath: 1,
6989
+ strobe: 2,
6990
+ steady: 3,
6991
+ wave_rainbow: 4,
6992
+ pixel_frame: 5
6993
+ };
6994
+ function buildLightEffectApnsBody(segments, repeatInput) {
6995
+ assertSegmentCount(segments);
6996
+ assertSegmentsValid(segments);
6997
+ const repeatTimes = normalizeRepeatTimes(repeatInput);
6998
+ assertAncsRepeatTimes(repeatTimes);
6999
+ const visibleText = summarizeSegments(segments);
7000
+ const separator = repeatTimes === 0 ? LED_SEPARATOR_LOOP : LED_SEPARATOR_ONCE;
7001
+ const payload = segments.map((segment) => encodeSegment(segment)).join("");
7002
+ return `${visibleText}${separator}${payload}`;
7003
+ }
7004
+ function assertSegmentCount(segments) {
7005
+ if (segments.length < 1 || segments.length > MAX_LIGHT_SEGMENTS) {
7006
+ throw new Error(`light_control supports 1-${MAX_LIGHT_SEGMENTS} segments`);
7007
+ }
7008
+ }
7009
+ function summarizeSegments(segments) {
7010
+ const modeDesc = segments.map((segment) => segment.mode).join("+");
7011
+ return `Effect: ${modeDesc} (${segments.length} segment${segments.length > 1 ? "s" : ""})`;
7012
+ }
7013
+ function assertSegmentsValid(segments) {
7014
+ const validation = validateSegments(segments);
7015
+ if (!validation.valid) {
7016
+ throw new Error(
7017
+ validation.errors.map((error) => `${error.field}: ${error.message}`).join("; ")
7018
+ );
7019
+ }
7020
+ }
7021
+ function encodeSegment(segment) {
7022
+ const common = [
7023
+ MODE_TO_INDEX[segment.mode],
7024
+ quantizeDuration(segment.duration_s)
7025
+ ];
7026
+ let values;
7027
+ switch (segment.mode) {
7028
+ case "wave":
7029
+ case "wave_rainbow":
7030
+ const color = normalizeProtocolColor(segment.color);
7031
+ const background = normalizeProtocolColor(segment.background);
7032
+ values = [
7033
+ ...common,
7034
+ quantize(segment.interval_ms ?? 200, INTERVAL_STEPS_MS),
7035
+ quantizeBrightnessValue(segment.brightness ?? 0),
7036
+ quantize(color.r, COLOR_STEPS),
7037
+ quantize(color.g, COLOR_STEPS),
7038
+ quantize(color.b, COLOR_STEPS),
7039
+ segment.direction === "rtl" ? 1 : 0,
7040
+ quantizeWindow(segment.window ?? 2),
7041
+ quantize(background.r, COLOR_STEPS),
7042
+ quantize(background.g, COLOR_STEPS),
7043
+ quantize(background.b, COLOR_STEPS),
7044
+ quantize(segment.background?.brightness ?? 0, BACKGROUND_BRIGHTNESS_STEPS)
7045
+ ];
7046
+ break;
7047
+ case "breath":
7048
+ const breathColor = normalizeProtocolColor(segment.color);
7049
+ values = [
7050
+ ...common,
7051
+ quantizeBreathRiseFall(segment.breath_timing?.rise_ms),
7052
+ quantizeBreathHoldOff(segment.breath_timing?.hold_ms),
7053
+ quantizeBreathRiseFall(segment.breath_timing?.fall_ms),
7054
+ quantizeBreathHoldOff(segment.breath_timing?.off_ms),
7055
+ quantizeBrightnessValue(segment.brightness ?? 0),
7056
+ quantize(breathColor.r, COLOR_STEPS),
7057
+ quantize(breathColor.g, COLOR_STEPS),
7058
+ quantize(breathColor.b, COLOR_STEPS)
7059
+ ];
7060
+ break;
7061
+ case "strobe":
7062
+ const strobeColor = normalizeProtocolColor(segment.color);
7063
+ values = [
7064
+ ...common,
7065
+ quantize(segment.interval_ms ?? 200, INTERVAL_STEPS_MS),
7066
+ quantizeBrightnessValue(segment.brightness ?? 0),
7067
+ quantize(strobeColor.r, COLOR_STEPS),
7068
+ quantize(strobeColor.g, COLOR_STEPS),
7069
+ quantize(strobeColor.b, COLOR_STEPS)
7070
+ ];
7071
+ break;
7072
+ case "steady":
7073
+ const steadyColor = normalizeProtocolColor(segment.color);
7074
+ values = [
7075
+ ...common,
7076
+ quantizeBrightnessValue(segment.brightness ?? 0),
7077
+ quantize(steadyColor.r, COLOR_STEPS),
7078
+ quantize(steadyColor.g, COLOR_STEPS),
7079
+ quantize(steadyColor.b, COLOR_STEPS)
7080
+ ];
7081
+ break;
7082
+ case "pixel_frame":
7083
+ values = encodePixelFrameValues(common, segment);
7084
+ break;
7085
+ }
7086
+ return values.map((value) => PROTOCOL_DIGITS[value]).join("");
7087
+ }
7088
+ function encodePixelFrameValues(common, segment) {
7089
+ const pixels = segment.pixels ?? [];
7090
+ return [
7091
+ ...common,
7092
+ pixels.length - 1,
7093
+ ...pixels.flatMap((pixel) => {
7094
+ const color = normalizeProtocolColor(pixel.color);
7095
+ return [
7096
+ pixel.index,
7097
+ quantize(color.r, COLOR_STEPS),
7098
+ quantize(color.g, COLOR_STEPS),
7099
+ quantize(color.b, COLOR_STEPS),
7100
+ quantizeBrightnessValue(pixel.brightness)
7101
+ ];
7102
+ })
7103
+ ];
7104
+ }
7105
+ function normalizeProtocolColor(color) {
7106
+ const normalized = {
7107
+ r: color?.r ?? 0,
7108
+ g: color?.g ?? 0,
7109
+ b: color?.b ?? 0
7110
+ };
7111
+ if (isPureWhiteProtocolColor(normalized)) {
7112
+ return applyProtocolColorCoefficients(normalized, PURE_WHITE_COLOR_COEFFICIENTS);
7113
+ }
7114
+ if (countActiveProtocolColorChannels(normalized) <= 1) {
7115
+ return normalized;
7116
+ }
7117
+ return applyProtocolColorCoefficients(normalized, MULTI_CHANNEL_COLOR_COEFFICIENTS);
7118
+ }
7119
+ function countActiveProtocolColorChannels(color) {
7120
+ return Number(color.r > 0) + Number(color.g > 0) + Number(color.b > 0);
7121
+ }
7122
+ function isPureWhiteProtocolColor(color) {
7123
+ return color.r === 255 && color.g === 255 && color.b === 255;
7124
+ }
7125
+ function applyProtocolColorCoefficients(color, coefficients) {
7126
+ return {
7127
+ r: scaleProtocolColorChannel(color.r, coefficients.r),
7128
+ g: scaleProtocolColorChannel(color.g, coefficients.g),
7129
+ b: scaleProtocolColorChannel(color.b, coefficients.b)
7130
+ };
7131
+ }
7132
+ function scaleProtocolColorChannel(value, coefficient) {
7133
+ return Math.max(0, Math.min(255, Math.round(value * coefficient)));
7134
+ }
7135
+ function quantize(value, steps) {
7136
+ let bestIndex = 0;
7137
+ let bestDistance = Number.POSITIVE_INFINITY;
7138
+ for (const [index, step] of steps.entries()) {
7139
+ const distance = Math.abs(value - step);
7140
+ if (distance < bestDistance) {
7141
+ bestIndex = index;
7142
+ bestDistance = distance;
7143
+ }
7144
+ }
7145
+ return bestIndex;
7146
+ }
7147
+ function quantizeDuration(duration_s) {
7148
+ if (duration_s === 0) return 11;
7149
+ return quantize(duration_s, DURATION_STEPS_S);
7150
+ }
7151
+ function quantizeBrightnessValue(brightness) {
7152
+ if (brightness === 0) return 11;
7153
+ return quantize(brightness, BRIGHTNESS_STEPS);
7154
+ }
7155
+ function quantizeBreathRiseFall(value) {
7156
+ return quantize(value ?? 1040, BREATH_STEPS_MS);
7157
+ }
7158
+ function quantizeBreathHoldOff(value) {
7159
+ if (value === 0) {
7160
+ return 5;
7161
+ }
7162
+ return quantize(value ?? 1040, BREATH_STEPS_MS.slice(0, 5));
7163
+ }
7164
+ function quantizeWindow(value) {
7165
+ return value - 1;
7166
+ }
7167
+
7168
+ // src/plugin/light-rules-tools.ts
7169
+ var segmentItemSchema = {
7170
+ type: "object",
7171
+ required: ["mode", "duration_s"],
7172
+ additionalProperties: false,
7173
+ properties: {
7174
+ mode: {
7175
+ type: "string",
7176
+ enum: ["wave", "breath", "strobe", "steady", "wave_rainbow", "pixel_frame"],
7177
+ description: "\u706F\u6548\u6A21\u5F0F\uFF1Awave \u6CE2\u6D6A / breath \u547C\u5438 / strobe \u9891\u95EA / steady \u5E38\u4EAE / wave_rainbow \u6D41\u5149 / pixel_frame \u9010\u7EC4\u50CF\u7D20\u5E27"
7178
+ },
7179
+ duration_s: { type: "number", minimum: 0, description: "\u6301\u7EED\u65F6\u957F\uFF08\u79D2\uFF09\uFF0C0 \u8868\u793A\u65E0\u9650" },
7180
+ brightness: { type: "number", minimum: 0, maximum: 255 },
7181
+ color: {
7182
+ type: "object",
7183
+ required: ["r", "g", "b"],
7184
+ additionalProperties: false,
7185
+ properties: {
7186
+ r: { type: "number", minimum: 0, maximum: 255 },
7187
+ g: { type: "number", minimum: 0, maximum: 255 },
7188
+ b: { type: "number", minimum: 0, maximum: 255 }
7189
+ }
7190
+ },
7191
+ interval_ms: { type: "number", minimum: 0 },
7192
+ direction: { type: "string", enum: ["ltr", "rtl"] },
7193
+ window: { type: "number", enum: [1, 2, 3] },
7194
+ breath_timing: {
7195
+ type: "object",
7196
+ additionalProperties: false,
7197
+ properties: {
7198
+ rise_ms: { type: "number", minimum: 0 },
7199
+ hold_ms: { type: "number", minimum: 0 },
7200
+ fall_ms: { type: "number", minimum: 0 },
7201
+ off_ms: { type: "number", minimum: 0 }
7202
+ }
7203
+ },
7204
+ frames: { type: "array", items: { type: "array", items: { type: "number" } } },
7205
+ frame_duration_ms: { type: "number", minimum: 0 },
7206
+ background: {
7207
+ type: "object",
7208
+ required: ["r", "g", "b"],
7209
+ additionalProperties: false,
7210
+ properties: {
7211
+ r: { type: "number", minimum: 0, maximum: 255 },
7212
+ g: { type: "number", minimum: 0, maximum: 255 },
7213
+ b: { type: "number", minimum: 0, maximum: 255 }
7214
+ }
7215
+ }
7216
+ }
7217
+ };
7218
+ var segmentsSchema = {
7219
+ type: "array",
7220
+ description: "\u706F\u6548\u6BB5\u5E8F\u5217\uFF0C1\u201312 \u6BB5\uFF0C\u6309\u987A\u5E8F\u64AD\u653E",
7221
+ minItems: 1,
7222
+ maxItems: MAX_LIGHT_SEGMENTS,
7223
+ items: segmentItemSchema
7224
+ };
7225
+ function ok(data) {
7226
+ return { content: [{ type: "text", text: JSON.stringify(data) }], details: data };
7227
+ }
7228
+ function err(code, message) {
7229
+ const data = { ok: false, error: { code, message } };
7230
+ return { content: [{ type: "text", text: JSON.stringify(data) }], details: data };
7231
+ }
7232
+ function registerLightRulesTools(api, registry, logger) {
7233
+ api.registerTool({
7234
+ name: "lightrules.list",
7235
+ label: "List Light Rules",
7236
+ description: '\u5217\u51FA\u6240\u6709\u706F\u6548\u89C4\u5219\uFF08\u5305\u542B enabled/disabled \u72B6\u6001\uFF09\u3002\u5F53\u7528\u6237\u8BF4"\u5217\u51FA\u706F\u6548\u89C4\u5219"\u3001"\u6709\u54EA\u4E9B\u706F\u6548\u89C4\u5219"\u3001"\u67E5\u770B\u89C4\u5219"\u7B49\u65F6\u8C03\u7528\u3002\u6CE8\u610F\uFF1A\u706F\u6548\u89C4\u5219\u7684\u6240\u6709 CRUD \u64CD\u4F5C\u5FC5\u987B\u901A\u8FC7 lightrules.* \u5DE5\u5177\u5B8C\u6210\uFF0C\u7981\u6B62\u76F4\u63A5\u7528 write/edit \u4FEE\u6539 tasks/*/meta.json\u3002',
7237
+ parameters: { type: "object", properties: {}, additionalProperties: false },
7238
+ async execute(_toolCallId, _params) {
7239
+ try {
7240
+ const rules = registry.list().map((rule) => ({ ...rule, id: rule.name }));
7241
+ return ok({ ok: true, rules });
7242
+ } catch (e) {
7243
+ logger.warn(`lightrules.list tool failed: ${e?.message}`);
7244
+ return err("INTERNAL_ERROR", e?.message ?? "Unknown error");
7245
+ }
7246
+ }
7247
+ });
7248
+ api.registerTool({
7249
+ name: "lightrules.create",
7250
+ label: "Create Light Rule",
7251
+ description: '\u521B\u5EFA\u4E00\u6761\u706F\u6548\u89C4\u5219\uFF0C\u6307\u5B9A\u540D\u79F0\u3001\u81EA\u7136\u8BED\u8A00\u89E6\u53D1\u63CF\u8FF0\u548C\u706F\u6548\u53C2\u6570\u3002\u5F53\u7528\u6237\u8BF4"\u521B\u5EFA\u706F\u6548\u89C4\u5219"\u3001"\u65B0\u589E\u89C4\u5219"\u7B49\u65F6\u8C03\u7528\u3002',
7252
+ parameters: {
7253
+ type: "object",
7254
+ required: ["name", "description", "segments"],
7255
+ additionalProperties: false,
7256
+ properties: {
7257
+ name: { type: "string", description: "\u89C4\u5219\u7684\u552F\u4E00\u6807\u8BC6\u7B26\uFF08\u82F1\u6587 slug\uFF0C\u5982 red_light_on_wechat\uFF09" },
7258
+ description: {
7259
+ type: "string",
7260
+ description: "\u81EA\u7136\u8BED\u8A00\u89E6\u53D1\u6761\u4EF6\uFF0C\u540C\u65F6\u4F5C\u4E3A\u89C4\u5219\u7528\u9014\u8BF4\u660E\u3002Agent \u6309\u6B64\u5B57\u6BB5\u5224\u65AD\u662F\u5426\u547D\u4E2D\uFF0C\u5FC5\u987B\u6E05\u6670\u63CF\u8FF0\u300C\u4F55\u65F6\u89E6\u53D1\u300D"
7261
+ },
7262
+ segments: segmentsSchema,
7263
+ repeat_times: {
7264
+ type: "number",
7265
+ description: "\u6574\u6761\u706F\u6548\u5E8F\u5217\u91CD\u590D\u6B21\u6570\uFF0C0=\u65E0\u9650\u5FAA\u73AF\uFF0C1=\u64AD\u653E\u4E00\u6B21\uFF08\u9ED8\u8BA4\uFF09"
7266
+ }
7267
+ }
7268
+ },
7269
+ async execute(_toolCallId, params) {
7270
+ const { name, description, segments, repeat_times } = params;
7271
+ if (!name || typeof name !== "string")
7272
+ return err("INVALID_PARAMS", "name is required");
7273
+ if (!description || typeof description !== "string")
7274
+ return err("INVALID_PARAMS", "description is required");
7275
+ const validation = validateSegments(segments);
7276
+ if (!validation.valid) return err("VALIDATION_FAILED", JSON.stringify(validation.errors));
7277
+ let repeatTimes;
7278
+ try {
7279
+ repeatTimes = normalizeRepeatTimes({ repeat_times });
7280
+ assertAncsRepeatTimes(repeatTimes);
7281
+ } catch (e) {
7282
+ return err("VALIDATION_FAILED", e?.message ?? "Unknown error");
7283
+ }
7284
+ try {
7285
+ const result = await registry.create({
7286
+ name,
7287
+ description,
7288
+ matchRules: {},
7289
+ segments: validation.segments,
7290
+ repeat_times: repeatTimes,
7291
+ cronSchedule: "*/5 * * * *"
7292
+ });
7293
+ logger.info(`lightrules.create tool: created ${name}`);
7294
+ return ok({ ok: true, name, cronHint: result.cronHint });
7295
+ } catch (e) {
7296
+ if (e instanceof LightRuleError) return err(e.code, e.message);
7297
+ logger.warn(`lightrules.create tool failed: ${e?.message}`);
7298
+ return err("INTERNAL_ERROR", e?.message ?? "Unknown error");
7299
+ }
7300
+ }
7301
+ });
7302
+ api.registerTool({
7303
+ name: "lightrules.update",
7304
+ label: "Update Light Rule",
7305
+ description: '\u4FEE\u6539\u706F\u6548\u89C4\u5219\uFF08\u542F\u7528/\u7981\u7528\u3001\u6539\u63CF\u8FF0\u3001\u6539\u706F\u6548\u53C2\u6570\uFF09\u3002\u5F53\u7528\u6237\u8BF4"\u7981\u7528\u67D0\u6761\u89C4\u5219"\u3001"\u542F\u7528\u89C4\u5219"\u3001"\u4FEE\u6539\u706F\u6548\u89C4\u5219"\u7B49\u65F6\u8C03\u7528\u3002',
7306
+ parameters: {
7307
+ type: "object",
7308
+ required: ["name"],
7309
+ additionalProperties: false,
7310
+ properties: {
7311
+ name: { type: "string", description: "\u8981\u4FEE\u6539\u7684\u89C4\u5219\u540D\u79F0\uFF08\u552F\u4E00\u6807\u8BC6\u7B26\uFF09" },
7312
+ description: { type: "string", description: "\u65B0\u7684\u89E6\u53D1\u6761\u4EF6\u63CF\u8FF0\uFF08\u53EF\u9009\uFF09" },
7313
+ enabled: { type: "boolean", description: "true=\u542F\u7528\uFF0Cfalse=\u7981\u7528" },
7314
+ segments: { ...segmentsSchema, description: "\u65B0\u7684\u706F\u6548\u6BB5\u5E8F\u5217\uFF08\u53EF\u9009\uFF0C\u4E0D\u586B\u5219\u4FDD\u6301\u4E0D\u53D8\uFF09" },
7315
+ repeat_times: { type: "number", description: "\u65B0\u7684\u91CD\u590D\u6B21\u6570\uFF08\u53EF\u9009\uFF09" }
7316
+ }
7317
+ },
7318
+ async execute(_toolCallId, params) {
7319
+ const { name, description, enabled, segments, repeat_times } = params;
7320
+ if (!name || typeof name !== "string")
7321
+ return err("INVALID_PARAMS", "name is required");
7322
+ let validatedSegments;
7323
+ if (segments !== void 0) {
7324
+ const validation = validateSegments(segments);
7325
+ if (!validation.valid) return err("VALIDATION_FAILED", JSON.stringify(validation.errors));
7326
+ validatedSegments = validation.segments;
7327
+ }
7328
+ let repeatTimes;
7329
+ if (repeat_times !== void 0) {
7330
+ try {
7331
+ repeatTimes = normalizeRepeatTimes({ repeat_times });
7332
+ assertAncsRepeatTimes(repeatTimes);
7333
+ } catch (e) {
7334
+ return err("VALIDATION_FAILED", e?.message ?? "Unknown error");
7335
+ }
7336
+ }
7337
+ try {
7338
+ const result = await registry.update({
7339
+ name,
7340
+ description,
7341
+ segments: validatedSegments,
7342
+ repeat_times: repeatTimes,
7343
+ enabled
7344
+ });
7345
+ logger.info(`lightrules.update tool: updated ${name}`);
7346
+ return ok({
7347
+ ok: true,
7348
+ name: result.meta.name,
7349
+ updated: true,
7350
+ rule: result.meta,
7351
+ cronHint: result.cronHint
7352
+ });
7353
+ } catch (e) {
7354
+ if (e instanceof LightRuleError) return err(e.code, e.message);
7355
+ logger.warn(`lightrules.update tool failed: ${e?.message}`);
7356
+ return err("INTERNAL_ERROR", e?.message ?? "Unknown error");
7357
+ }
7358
+ }
7359
+ });
7360
+ api.registerTool({
7361
+ name: "lightrules.delete",
7362
+ label: "Delete Light Rule",
7363
+ description: '\u5220\u9664\u4E00\u6761\u706F\u6548\u89C4\u5219\uFF08\u4E0D\u53EF\u6062\u590D\uFF09\u3002\u5F53\u7528\u6237\u8BF4"\u5220\u9664\u706F\u6548\u89C4\u5219"\u3001"\u79FB\u9664\u89C4\u5219"\u7B49\u65F6\u8C03\u7528\u3002',
7364
+ parameters: {
7365
+ type: "object",
7366
+ required: ["name"],
7367
+ additionalProperties: false,
7368
+ properties: {
7369
+ name: { type: "string", description: "\u8981\u5220\u9664\u7684\u89C4\u5219\u540D\u79F0" }
7370
+ }
7371
+ },
7372
+ async execute(_toolCallId, params) {
7373
+ const { name } = params;
7374
+ if (!name || typeof name !== "string")
7375
+ return err("INVALID_PARAMS", "name is required");
7376
+ try {
7377
+ const result = await registry.delete(name);
7378
+ logger.info(`lightrules.delete tool: deleted ${name}`);
7379
+ return ok({ ok: true, name: result.name, deleted: true, cronHint: result.cronHint });
7380
+ } catch (e) {
7381
+ if (e instanceof LightRuleError) return err(e.code, e.message);
7382
+ logger.warn(`lightrules.delete tool failed: ${e?.message}`);
7383
+ return err("INTERNAL_ERROR", e?.message ?? "Unknown error");
7384
+ }
7385
+ }
7386
+ });
7387
+ }
7388
+
7389
+ // src/light-rules/evaluator-job.ts
7390
+ var EVALUATOR_JOB_ID = "light-rules-evaluator";
7391
+ var EVALUATOR_SUBAGENT_SESSION_KEY = EVALUATOR_JOB_ID;
7392
+ var FALLBACK_CRON_EXPR = "0 0 1 1 *";
7393
+ function buildEvaluatorJobMessage(notificationsDir) {
7394
+ return `\u706F\u6548\u89C4\u5219\u8BC4\u4F30\u4EFB\u52A1\u3002
7395
+
7396
+ \u6267\u884C\u6B65\u9AA4\uFF1A
7397
+ 1. \u8BFB\u53D6 tasks/light-rules-evaluator/checkpoint.json\uFF08\u8BB0\u5F55\u4E0A\u6B21\u5904\u7406\u8FDB\u5EA6\uFF09
7398
+ 2. \u626B\u63CF ${notificationsDir} \u76EE\u5F55\uFF0C\u83B7\u53D6 checkpoint \u4E4B\u540E\u7684\u65B0\u901A\u77E5
7399
+ 3. \u626B\u63CF tasks/ \u76EE\u5F55\uFF0C\u8BFB\u53D6\u6240\u6709 type=light-rule \u4E14 enabled=true \u7684 meta.json
7400
+ 4. \u5BF9\u6BCF\u6761\u65B0\u901A\u77E5\uFF0C\u9010\u4E00\u5224\u65AD\u662F\u5426\u547D\u4E2D\u6BCF\u6761\u89C4\u5219\u7684 description\uFF08\u8BED\u4E49\u5339\u914D\uFF09
7401
+ 5. \u547D\u4E2D\u65F6\uFF1A\u4EE5\u8BE5\u89C4\u5219\u7684 segments \u548C repeat_times \u8C03\u7528 light_control \u5DE5\u5177
7402
+ 6. \u66F4\u65B0 checkpoint.json\uFF0C\u8BB0\u5F55\u5DF2\u5904\u7406\u5230\u7684\u6700\u65B0\u901A\u77E5\u4F4D\u7F6E
7403
+ 7. \u82E5\u65E0\u65B0\u901A\u77E5\u6216\u65E0 enabled \u89C4\u5219\uFF1A\u8F93\u51FA NO_CHANGE\uFF0C\u76F4\u63A5\u7ED3\u675F`;
7404
+ }
7405
+ var LightRulesEvaluatorJob = class {
7406
+ logger;
7407
+ registry;
7408
+ subagentRunner;
7409
+ getNotificationsDir;
7410
+ /**
7411
+ * 记录本进程生命周期内 job 是否已确认存在。
7412
+ * 仅在 `ensureJobExists` 成功后置 true,避免每次 push 都做检查。
7413
+ */
7414
+ jobEnsured = false;
7415
+ /**
7416
+ * 首次创建 job 时的并发保护。
7417
+ * 避免冷启动瞬间多条通知并发到达时重复调用 `cron.add`。
7418
+ */
7419
+ ensureJobPromise = null;
7420
+ /**
7421
+ * subagent fallback 路径的并发保护。
7422
+ * 若评估 session 已在运行中,跳过本次触发(checkpoint 保证下次补处理)。
7423
+ */
7424
+ subagentInFlight = false;
7425
+ constructor(deps) {
7426
+ this.logger = deps.logger;
7427
+ this.registry = deps.registry;
7428
+ this.subagentRunner = deps.subagentRunner;
7429
+ this.getNotificationsDir = deps.getNotificationsDir ?? (() => void 0);
7430
+ }
7431
+ /**
7432
+ * 通知落盘后调用。若有新增通知且存在 enabled 规则,则触发评估。
7433
+ *
7434
+ * 两条路径:
7435
+ * - cron 不为 null:enqueueRun("force") 入队(gateway context 路径,正常路径)
7436
+ * - cron 为 null:通过 subagentRunner 直接运行(HTTP Relay 路径,fallback)
7437
+ *
7438
+ * @param cron 来自 gateway context 的 CronService;HTTP 路径下为 null
7439
+ * @param insertedCount 本次 ingest 新落盘的通知条数(StoredNotification 去重后)
7440
+ */
7441
+ async triggerIfNeeded(cron, insertedCount) {
7442
+ if (insertedCount === 0) return;
7443
+ if (this.registry.getEnabled().length === 0) return;
7444
+ if (!cron) {
7445
+ await this.triggerViaSubagent();
7446
+ return;
7447
+ }
7448
+ try {
7449
+ await this.ensureJobExists(cron);
7450
+ } catch (err2) {
7451
+ this.logger.warn(`light-rules-evaluator: job ensure failed: ${err2?.message ?? err2}`);
7452
+ return;
7453
+ }
7454
+ try {
7455
+ const result = await cron.enqueueRun(EVALUATOR_JOB_ID, "force");
7456
+ if (!result.ok) {
7457
+ this.logger.warn("light-rules-evaluator: enqueueRun returned ok=false");
7458
+ return;
7459
+ }
7460
+ if ("enqueued" in result && result.enqueued) {
7461
+ this.logger.info(`light-rules-evaluator: enqueued runId=${result.runId}`);
7462
+ } else if ("reason" in result) {
7463
+ this.logger.info(`light-rules-evaluator: enqueueRun skipped (${result.reason})`);
7464
+ }
7465
+ } catch (err2) {
7466
+ this.logger.warn(`light-rules-evaluator: enqueueRun failed: ${err2?.message ?? err2}`);
7467
+ }
7468
+ }
7469
+ /**
7470
+ * cron service 不可用时的 fallback:直接通过 subagentRunner 运行评估 session。
7471
+ *
7472
+ * 并发保护:若上一次 subagent 运行尚未完成,本次跳过。
7473
+ * checkpoint 保证即使本次跳过,下次触发时会处理所有积压通知。
7474
+ */
7475
+ async triggerViaSubagent() {
7476
+ if (!this.subagentRunner) {
7477
+ this.logger.warn(
7478
+ "light-rules-evaluator: cron service unavailable and no subagent fallback configured; notifications ingested via HTTP will not trigger light rules until an agent session is active"
7479
+ );
7480
+ return;
7481
+ }
7482
+ if (this.subagentInFlight) {
7483
+ this.logger.info("light-rules-evaluator: subagent run in-flight, skipping this trigger");
7484
+ return;
7485
+ }
7486
+ const notificationsDir = this.getNotificationsDir();
7487
+ if (!notificationsDir) {
7488
+ this.logger.warn("light-rules-evaluator: notifications dir not ready, skipping subagent trigger");
7489
+ return;
7490
+ }
7491
+ this.subagentInFlight = true;
7492
+ try {
7493
+ const result = await this.subagentRunner.run({
7494
+ sessionKey: EVALUATOR_SUBAGENT_SESSION_KEY,
7495
+ message: buildEvaluatorJobMessage(notificationsDir),
7496
+ deliver: false,
7497
+ idempotencyKey: `${EVALUATOR_SUBAGENT_SESSION_KEY}-${Date.now()}`
7498
+ });
7499
+ this.logger.info(`light-rules-evaluator: subagent triggered runId=${result.runId}`);
7500
+ } catch (err2) {
7501
+ this.logger.warn(`light-rules-evaluator: subagent trigger failed: ${err2?.message ?? err2}`);
7502
+ } finally {
7503
+ this.subagentInFlight = false;
7504
+ }
7505
+ }
7506
+ /**
7507
+ * 按需创建 `light-rules-evaluator` job。
7508
+ * 若 job 已存在(内存缓存或 cron store),直接返回;否则调用 `cron.add`。
7509
+ */
7510
+ async ensureJobExists(cron) {
7511
+ if (this.jobEnsured) return;
7512
+ if (!this.ensureJobPromise) {
7513
+ this.ensureJobPromise = this.createJobIfNeeded(cron).finally(() => {
7514
+ this.ensureJobPromise = null;
7515
+ });
7516
+ }
7517
+ await this.ensureJobPromise;
7518
+ }
7519
+ async createJobIfNeeded(cron) {
7520
+ if (cron.getJob(EVALUATOR_JOB_ID)) {
7521
+ this.jobEnsured = true;
7522
+ return;
7523
+ }
7524
+ try {
7525
+ await cron.add({
7526
+ id: EVALUATOR_JOB_ID,
7527
+ name: "\u706F\u6548\u89C4\u5219\u8BC4\u4F30",
7528
+ description: "\u4E8B\u4EF6\u9A71\u52A8\uFF1A\u901A\u77E5\u5230\u8FBE\u65F6\u8BC4\u4F30\u6240\u6709 enabled \u706F\u6548\u89C4\u5219\uFF0C\u547D\u4E2D\u5219\u8C03\u7528 light_control \u89E6\u53D1\u706F\u6548",
7529
+ enabled: true,
7530
+ schedule: { kind: "cron", expr: FALLBACK_CRON_EXPR },
7531
+ sessionTarget: "isolated",
7532
+ wakeMode: "now",
7533
+ payload: {
7534
+ kind: "agentTurn",
7535
+ message: buildEvaluatorJobMessage(this.getNotificationsDir() ?? "notifications")
7536
+ }
7537
+ });
7538
+ this.logger.info("light-rules-evaluator: job created");
7539
+ } catch (err2) {
7540
+ if (!cron.getJob(EVALUATOR_JOB_ID)) {
7541
+ throw err2;
7542
+ }
7543
+ }
7544
+ this.jobEnsured = true;
7545
+ }
7546
+ };
7547
+
7548
+ // src/light-rules/migration.ts
7549
+ var import_node_fs6 = require("fs");
7550
+ var import_node_path5 = require("path");
7551
+ var NO_MATCH_FETCH_PY = `#!/usr/bin/env python3
7552
+ # \u6B64\u6587\u4EF6\u7531\u8FC1\u79FB\u5DE5\u5177\u751F\u6210\u3002
7553
+ # \u706F\u6548\u89C4\u5219\u5DF2\u8FC1\u79FB\u81F3\u4E8B\u4EF6\u9A71\u52A8\u67B6\u6784\uFF0C\u6B64 cron job \u4E0D\u518D\u6267\u884C\u5B9E\u9645\u5DE5\u4F5C\u3002
7554
+ print("NO_MATCH")
7555
+ `;
7556
+ function normalizeScriptText(text) {
7557
+ return text.replace(/\r\n/g, "\n").trim();
7558
+ }
7559
+ function resolveTasksDir(ctx) {
7560
+ if (ctx.workspaceDir) return (0, import_node_path5.join)(ctx.workspaceDir, "tasks");
7561
+ if (ctx.stateDir) {
7562
+ const inferredWorkspaceDir = (0, import_node_path5.join)(ctx.stateDir, "workspace");
7563
+ if ((0, import_node_fs6.existsSync)(inferredWorkspaceDir)) return (0, import_node_path5.join)(inferredWorkspaceDir, "tasks");
7564
+ return (0, import_node_path5.join)(ctx.stateDir, "tasks");
7565
+ }
7566
+ return null;
7567
+ }
7568
+ function migrateLegacyLightRuleTasks(ctx, logger) {
7569
+ const tasksDir3 = resolveTasksDir(ctx);
7570
+ if (!tasksDir3 || !(0, import_node_fs6.existsSync)(tasksDir3)) return;
7571
+ try {
7572
+ for (const entry of (0, import_node_fs6.readdirSync)(tasksDir3, { withFileTypes: true })) {
7573
+ if (!entry.isDirectory()) continue;
7574
+ migrateTaskDir((0, import_node_path5.join)(tasksDir3, String(entry.name)), logger);
7575
+ }
7576
+ } catch (err2) {
7577
+ logger.warn(`migration: failed to read tasks dir: ${err2?.message}`);
7578
+ }
7579
+ }
7580
+ function migrateTaskDir(taskDir, logger) {
7581
+ const metaPath = (0, import_node_path5.join)(taskDir, "meta.json");
7582
+ if (!(0, import_node_fs6.existsSync)(metaPath)) return;
7583
+ let meta;
7584
+ try {
7585
+ meta = JSON.parse((0, import_node_fs6.readFileSync)(metaPath, "utf-8"));
7586
+ } catch {
7587
+ return;
7588
+ }
7589
+ if (meta.type !== "light-rule") return;
7590
+ const name = typeof meta.name === "string" ? meta.name : taskDir;
7591
+ mergeMatchRulesIntoDescription(meta, name, metaPath, logger);
7592
+ replaceFetchPy(taskDir, name, logger);
7593
+ for (const filename of ["README.md", "checkpoint.json"]) {
7594
+ removeFile((0, import_node_path5.join)(taskDir, filename), name, filename, logger);
7595
+ }
7596
+ }
7597
+ function mergeMatchRulesIntoDescription(meta, name, metaPath, logger) {
7598
+ const matchRules = meta.matchRules;
7599
+ if (!matchRules || typeof matchRules !== "object") return;
7600
+ const rules = matchRules;
7601
+ const parts = [];
7602
+ if (rules.appName) parts.push(`app=${rules.appName}`);
7603
+ if (rules.senderKeywords?.length) {
7604
+ parts.push(`\u53D1\u4EF6\u4EBA\u5173\u952E\u8BCD=${rules.senderKeywords.join("\u3001")}`);
7605
+ }
7606
+ if (rules.contentKeywords?.length) {
7607
+ parts.push(`\u5185\u5BB9\u5173\u952E\u8BCD=${rules.contentKeywords.join("\u3001")}`);
7608
+ }
7609
+ if (parts.length > 0) {
7610
+ const existing = typeof meta.description === "string" ? meta.description.trim() : "";
7611
+ meta.description = existing ? `${existing}\u3002\u5339\u914D\u89C4\u5219\uFF1A${parts.join("\uFF0C")}` : `\u5339\u914D\u89C4\u5219\uFF1A${parts.join("\uFF0C")}`;
7612
+ }
7613
+ delete meta.matchRules;
7614
+ try {
7615
+ (0, import_node_fs6.writeFileSync)(metaPath, JSON.stringify(meta, null, 2), "utf-8");
7616
+ logger.info(`migration: merged matchRules into description for light rule: ${name}`);
7617
+ } catch (err2) {
7618
+ logger.warn(`migration: failed to update meta.json for ${name}: ${err2?.message}`);
7619
+ }
7620
+ }
7621
+ function replaceFetchPy(taskDir, name, logger) {
7622
+ const fetchPyPath = (0, import_node_path5.join)(taskDir, "fetch.py");
7623
+ if (!(0, import_node_fs6.existsSync)(fetchPyPath)) return;
7624
+ try {
7625
+ const existing = (0, import_node_fs6.readFileSync)(fetchPyPath, "utf-8");
7626
+ if (normalizeScriptText(existing) === normalizeScriptText(NO_MATCH_FETCH_PY)) {
7627
+ return;
7628
+ }
7629
+ (0, import_node_fs6.writeFileSync)(fetchPyPath, NO_MATCH_FETCH_PY, "utf-8");
7630
+ logger.info(`migration: replaced fetch.py with NO_MATCH placeholder for ${name}`);
7631
+ } catch (err2) {
7632
+ logger.warn(`migration: failed to replace fetch.py for ${name}: ${err2?.message}`);
7633
+ }
7634
+ }
7635
+ function removeFile(filePath, ruleName, filename, logger) {
7636
+ if (!(0, import_node_fs6.existsSync)(filePath)) return;
7637
+ try {
7638
+ (0, import_node_fs6.rmSync)(filePath);
7639
+ logger.info(`migration: removed ${filename} for light rule: ${ruleName}`);
7640
+ } catch (err2) {
7641
+ logger.warn(`migration: failed to remove ${filename} for ${ruleName}: ${err2?.message}`);
7642
+ }
7643
+ }
7644
+
7645
+ // src/plugin/auto-update.ts
7646
+ init_env();
7647
+
7648
+ // src/update/channel.ts
7649
+ function resolveUpdateChannel(params) {
7650
+ if (params.configuredChannel) {
7651
+ return params.configuredChannel;
7652
+ }
7653
+ if (params.currentVersion.includes("-")) {
7654
+ return "beta";
7655
+ }
7656
+ return params.envName === "development" ? "beta" : "latest";
7657
+ }
7658
+
7230
7659
  // src/update/index.ts
7231
7660
  var import_node_path10 = require("path");
7232
7661
 
@@ -7360,8 +7789,8 @@ async function executeUpdate(version, runCommand, logger, targetDir, updateConfi
7360
7789
  { timeoutMs: 3e4 }
7361
7790
  );
7362
7791
  if (tarResult.code !== 0) {
7363
- const err = tarResult.stderr || tarResult.stdout || "unknown error";
7364
- return { success: false, message: `\u89E3\u538B\u5931\u8D25: ${err}` };
7792
+ const err2 = tarResult.stderr || tarResult.stdout || "unknown error";
7793
+ return { success: false, message: `\u89E3\u538B\u5931\u8D25: ${err2}` };
7365
7794
  }
7366
7795
  (0, import_node_fs10.mkdirSync)((0, import_node_path9.dirname)(targetDir), { recursive: true });
7367
7796
  try {
@@ -7373,8 +7802,8 @@ async function executeUpdate(version, runCommand, logger, targetDir, updateConfi
7373
7802
  (0, import_node_fs10.renameSync)(stagingDir, targetDir);
7374
7803
  try {
7375
7804
  await updateConfigRecord2(version, tgzUrl);
7376
- } catch (err) {
7377
- logger.warn(`\u914D\u7F6E\u8BB0\u5F55\u66F4\u65B0\u5931\u8D25\uFF08\u63D2\u4EF6\u6587\u4EF6\u5DF2\u5C31\u4F4D\uFF09: ${String(err)}`);
7805
+ } catch (err2) {
7806
+ logger.warn(`\u914D\u7F6E\u8BB0\u5F55\u66F4\u65B0\u5931\u8D25\uFF08\u63D2\u4EF6\u6587\u4EF6\u5DF2\u5C31\u4F4D\uFF09: ${String(err2)}`);
7378
7807
  }
7379
7808
  if (backupDir) {
7380
7809
  try {
@@ -7385,7 +7814,7 @@ async function executeUpdate(version, runCommand, logger, targetDir, updateConfi
7385
7814
  const msg = `\u5DF2\u66F4\u65B0\u5230 ${version}\uFF0C\u8BF7\u91CD\u542F gateway \u751F\u6548`;
7386
7815
  logger.info(msg);
7387
7816
  return { success: true, message: msg };
7388
- } catch (err) {
7817
+ } catch (err2) {
7389
7818
  if (backupDir) {
7390
7819
  try {
7391
7820
  (0, import_node_fs10.rmSync)(targetDir, { force: true, recursive: true });
@@ -7395,7 +7824,7 @@ async function executeUpdate(version, runCommand, logger, targetDir, updateConfi
7395
7824
  logger.error(`\u56DE\u6EDA\u5931\u8D25: ${String(rollbackErr)}`);
7396
7825
  }
7397
7826
  }
7398
- const errMsg = `\u66F4\u65B0\u6267\u884C\u5F02\u5E38: ${String(err)}`;
7827
+ const errMsg = `\u66F4\u65B0\u6267\u884C\u5F02\u5E38: ${String(err2)}`;
7399
7828
  logger.error(errMsg);
7400
7829
  return { success: false, message: errMsg };
7401
7830
  } finally {
@@ -8064,216 +8493,6 @@ init_credentials();
8064
8493
 
8065
8494
  // src/light/sender.ts
8066
8495
  var import_node_crypto2 = require("crypto");
8067
-
8068
- // src/light/protocol.ts
8069
- var MAX_LIGHT_SEGMENTS = 12;
8070
- var PROTOCOL_DIGITS = [
8071
- "\x80",
8072
- "\x81",
8073
- "\x82",
8074
- "\x83",
8075
- "\x84",
8076
- "\x91",
8077
- "\x92",
8078
- "\x93",
8079
- "\x94",
8080
- "\x95",
8081
- "\x96",
8082
- "\x97"
8083
- ];
8084
- var LED_SEPARATOR_ONCE = "\x9A";
8085
- var LED_SEPARATOR_LOOP = "\x9B";
8086
- var DURATION_STEPS_S = [0.5, 1, 2, 3, 5, 6, 8, 16, 24, 32, 48];
8087
- var INTERVAL_STEPS_MS = [50, 100, 200, 300, 500, 600, 800, 1600, 2400, 3200, 4800];
8088
- var BREATH_STEPS_MS = [1040, 1560, 2080, 2600, 3100, 4160];
8089
- var BRIGHTNESS_STEPS = [32, 64, 96, 128, 192, 255];
8090
- var COLOR_STEPS = [0, 32, 64, 128, 192, 255];
8091
- var BACKGROUND_BRIGHTNESS_STEPS = [0, 32, 64, 96, 128, 192, 255];
8092
- var MULTI_CHANNEL_COLOR_COEFFICIENTS = { r: 1, g: 0.25, b: 0.25 };
8093
- var PURE_WHITE_COLOR_COEFFICIENTS = { r: 1, g: 0.35, b: 0.35 };
8094
- var MODE_TO_INDEX = {
8095
- wave: 0,
8096
- breath: 1,
8097
- strobe: 2,
8098
- steady: 3,
8099
- wave_rainbow: 4,
8100
- pixel_frame: 5
8101
- };
8102
- function buildLightEffectApnsBody(segments, repeatInput) {
8103
- assertSegmentCount(segments);
8104
- assertSegmentsValid(segments);
8105
- const repeatTimes = normalizeRepeatTimes(repeatInput);
8106
- assertAncsRepeatTimes(repeatTimes);
8107
- const visibleText = summarizeSegments(segments);
8108
- const separator = repeatTimes === 0 ? LED_SEPARATOR_LOOP : LED_SEPARATOR_ONCE;
8109
- const payload = segments.map((segment) => encodeSegment(segment)).join("");
8110
- return `${visibleText}${separator}${payload}`;
8111
- }
8112
- function assertSegmentCount(segments) {
8113
- if (segments.length < 1 || segments.length > MAX_LIGHT_SEGMENTS) {
8114
- throw new Error(`light_control supports 1-${MAX_LIGHT_SEGMENTS} segments`);
8115
- }
8116
- }
8117
- function summarizeSegments(segments) {
8118
- const modeDesc = segments.map((segment) => segment.mode).join("+");
8119
- return `Effect: ${modeDesc} (${segments.length} segment${segments.length > 1 ? "s" : ""})`;
8120
- }
8121
- function assertSegmentsValid(segments) {
8122
- const validation = validateSegments(segments);
8123
- if (!validation.valid) {
8124
- throw new Error(
8125
- validation.errors.map((error) => `${error.field}: ${error.message}`).join("; ")
8126
- );
8127
- }
8128
- }
8129
- function encodeSegment(segment) {
8130
- const common = [
8131
- MODE_TO_INDEX[segment.mode],
8132
- quantizeDuration(segment.duration_s)
8133
- ];
8134
- let values;
8135
- switch (segment.mode) {
8136
- case "wave":
8137
- case "wave_rainbow":
8138
- const color = normalizeProtocolColor(segment.color);
8139
- const background = normalizeProtocolColor(segment.background);
8140
- values = [
8141
- ...common,
8142
- quantize(segment.interval_ms ?? 200, INTERVAL_STEPS_MS),
8143
- quantizeBrightnessValue(segment.brightness ?? 0),
8144
- quantize(color.r, COLOR_STEPS),
8145
- quantize(color.g, COLOR_STEPS),
8146
- quantize(color.b, COLOR_STEPS),
8147
- segment.direction === "rtl" ? 1 : 0,
8148
- quantizeWindow(segment.window ?? 2),
8149
- quantize(background.r, COLOR_STEPS),
8150
- quantize(background.g, COLOR_STEPS),
8151
- quantize(background.b, COLOR_STEPS),
8152
- quantize(segment.background?.brightness ?? 0, BACKGROUND_BRIGHTNESS_STEPS)
8153
- ];
8154
- break;
8155
- case "breath":
8156
- const breathColor = normalizeProtocolColor(segment.color);
8157
- values = [
8158
- ...common,
8159
- quantizeBreathRiseFall(segment.breath_timing?.rise_ms),
8160
- quantizeBreathHoldOff(segment.breath_timing?.hold_ms),
8161
- quantizeBreathRiseFall(segment.breath_timing?.fall_ms),
8162
- quantizeBreathHoldOff(segment.breath_timing?.off_ms),
8163
- quantizeBrightnessValue(segment.brightness ?? 0),
8164
- quantize(breathColor.r, COLOR_STEPS),
8165
- quantize(breathColor.g, COLOR_STEPS),
8166
- quantize(breathColor.b, COLOR_STEPS)
8167
- ];
8168
- break;
8169
- case "strobe":
8170
- const strobeColor = normalizeProtocolColor(segment.color);
8171
- values = [
8172
- ...common,
8173
- quantize(segment.interval_ms ?? 200, INTERVAL_STEPS_MS),
8174
- quantizeBrightnessValue(segment.brightness ?? 0),
8175
- quantize(strobeColor.r, COLOR_STEPS),
8176
- quantize(strobeColor.g, COLOR_STEPS),
8177
- quantize(strobeColor.b, COLOR_STEPS)
8178
- ];
8179
- break;
8180
- case "steady":
8181
- const steadyColor = normalizeProtocolColor(segment.color);
8182
- values = [
8183
- ...common,
8184
- quantizeBrightnessValue(segment.brightness ?? 0),
8185
- quantize(steadyColor.r, COLOR_STEPS),
8186
- quantize(steadyColor.g, COLOR_STEPS),
8187
- quantize(steadyColor.b, COLOR_STEPS)
8188
- ];
8189
- break;
8190
- case "pixel_frame":
8191
- values = encodePixelFrameValues(common, segment);
8192
- break;
8193
- }
8194
- return values.map((value) => PROTOCOL_DIGITS[value]).join("");
8195
- }
8196
- function encodePixelFrameValues(common, segment) {
8197
- const pixels = segment.pixels ?? [];
8198
- return [
8199
- ...common,
8200
- pixels.length - 1,
8201
- ...pixels.flatMap((pixel) => {
8202
- const color = normalizeProtocolColor(pixel.color);
8203
- return [
8204
- pixel.index,
8205
- quantize(color.r, COLOR_STEPS),
8206
- quantize(color.g, COLOR_STEPS),
8207
- quantize(color.b, COLOR_STEPS),
8208
- quantizeBrightnessValue(pixel.brightness)
8209
- ];
8210
- })
8211
- ];
8212
- }
8213
- function normalizeProtocolColor(color) {
8214
- const normalized = {
8215
- r: color?.r ?? 0,
8216
- g: color?.g ?? 0,
8217
- b: color?.b ?? 0
8218
- };
8219
- if (isPureWhiteProtocolColor(normalized)) {
8220
- return applyProtocolColorCoefficients(normalized, PURE_WHITE_COLOR_COEFFICIENTS);
8221
- }
8222
- if (countActiveProtocolColorChannels(normalized) <= 1) {
8223
- return normalized;
8224
- }
8225
- return applyProtocolColorCoefficients(normalized, MULTI_CHANNEL_COLOR_COEFFICIENTS);
8226
- }
8227
- function countActiveProtocolColorChannels(color) {
8228
- return Number(color.r > 0) + Number(color.g > 0) + Number(color.b > 0);
8229
- }
8230
- function isPureWhiteProtocolColor(color) {
8231
- return color.r === 255 && color.g === 255 && color.b === 255;
8232
- }
8233
- function applyProtocolColorCoefficients(color, coefficients) {
8234
- return {
8235
- r: scaleProtocolColorChannel(color.r, coefficients.r),
8236
- g: scaleProtocolColorChannel(color.g, coefficients.g),
8237
- b: scaleProtocolColorChannel(color.b, coefficients.b)
8238
- };
8239
- }
8240
- function scaleProtocolColorChannel(value, coefficient) {
8241
- return Math.max(0, Math.min(255, Math.round(value * coefficient)));
8242
- }
8243
- function quantize(value, steps) {
8244
- let bestIndex = 0;
8245
- let bestDistance = Number.POSITIVE_INFINITY;
8246
- for (const [index, step] of steps.entries()) {
8247
- const distance = Math.abs(value - step);
8248
- if (distance < bestDistance) {
8249
- bestIndex = index;
8250
- bestDistance = distance;
8251
- }
8252
- }
8253
- return bestIndex;
8254
- }
8255
- function quantizeDuration(duration_s) {
8256
- if (duration_s === 0) return 11;
8257
- return quantize(duration_s, DURATION_STEPS_S);
8258
- }
8259
- function quantizeBrightnessValue(brightness) {
8260
- if (brightness === 0) return 11;
8261
- return quantize(brightness, BRIGHTNESS_STEPS);
8262
- }
8263
- function quantizeBreathRiseFall(value) {
8264
- return quantize(value ?? 1040, BREATH_STEPS_MS);
8265
- }
8266
- function quantizeBreathHoldOff(value) {
8267
- if (value === 0) {
8268
- return 5;
8269
- }
8270
- return quantize(value ?? 1040, BREATH_STEPS_MS.slice(0, 5));
8271
- }
8272
- function quantizeWindow(value) {
8273
- return value - 1;
8274
- }
8275
-
8276
- // src/light/sender.ts
8277
8496
  init_env();
8278
8497
  async function sendLightEffect(apiKey, segments, logger, repeatInput, reason) {
8279
8498
  const apiUrl = getEnvUrls().lightApiUrl;
@@ -8377,11 +8596,23 @@ function resolveConfigPath2() {
8377
8596
  if (fromEnv) return fromEnv;
8378
8597
  return (0, import_node_path13.join)((0, import_node_os2.homedir)(), ".openclaw", "openclaw.json");
8379
8598
  }
8599
+ var LIGHT_TOOLS = [
8600
+ "light_control",
8601
+ "lightrules.list",
8602
+ "lightrules.create",
8603
+ "lightrules.update",
8604
+ "lightrules.delete"
8605
+ ];
8380
8606
  function upsertLightControlAlsoAllow(cfg) {
8381
8607
  if (!isObject(cfg.tools)) cfg.tools = {};
8382
8608
  const toolsAlsoAllow = ensureArray(cfg.tools, "alsoAllow");
8383
- const hasGlobal = toolsAlsoAllow.includes("light_control");
8384
- if (!hasGlobal) toolsAlsoAllow.push("light_control");
8609
+ let globalChanged = false;
8610
+ for (const tool of LIGHT_TOOLS) {
8611
+ if (!toolsAlsoAllow.includes(tool)) {
8612
+ toolsAlsoAllow.push(tool);
8613
+ globalChanged = true;
8614
+ }
8615
+ }
8385
8616
  if (!isObject(cfg.agents)) cfg.agents = {};
8386
8617
  const agents = cfg.agents;
8387
8618
  const list = ensureArray(agents, "list");
@@ -8394,12 +8625,14 @@ function upsertLightControlAlsoAllow(cfg) {
8394
8625
  }
8395
8626
  if (!isObject(mainAgent.tools)) mainAgent.tools = {};
8396
8627
  const mainAlsoAllow = ensureArray(mainAgent.tools, "alsoAllow");
8397
- const hasMain = mainAlsoAllow.includes("light_control");
8398
- if (!hasMain) mainAlsoAllow.push("light_control");
8399
- return {
8400
- globalChanged: !hasGlobal,
8401
- mainAgentChanged: !hasMain
8402
- };
8628
+ let mainAgentChanged = false;
8629
+ for (const tool of LIGHT_TOOLS) {
8630
+ if (!mainAlsoAllow.includes(tool)) {
8631
+ mainAlsoAllow.push(tool);
8632
+ mainAgentChanged = true;
8633
+ }
8634
+ }
8635
+ return { globalChanged, mainAgentChanged };
8403
8636
  }
8404
8637
  function registerLightSetupTools(light) {
8405
8638
  light.command("setup").description("\u81EA\u52A8\u653E\u884C light_control\uFF08\u5199\u5165 tools.alsoAllow \u4E0E agents.main.tools.alsoAllow\uFF09").action(() => {
@@ -8412,15 +8645,15 @@ function registerLightSetupTools(light) {
8412
8645
  const raw = (0, import_node_fs14.readFileSync)(configPath, "utf-8");
8413
8646
  const parsed = JSON.parse(raw);
8414
8647
  if (isObject(parsed)) cfg = parsed;
8415
- } catch (err) {
8416
- exitError("CONFIG_INVALID", `\u8BFB\u53D6/\u89E3\u6790\u914D\u7F6E\u5931\u8D25: ${err?.message ?? String(err)}`);
8648
+ } catch (err2) {
8649
+ exitError("CONFIG_INVALID", `\u8BFB\u53D6/\u89E3\u6790\u914D\u7F6E\u5931\u8D25: ${err2?.message ?? String(err2)}`);
8417
8650
  }
8418
8651
  const result = upsertLightControlAlsoAllow(cfg);
8419
8652
  try {
8420
8653
  (0, import_node_fs14.mkdirSync)((0, import_node_path13.dirname)(configPath), { recursive: true });
8421
8654
  (0, import_node_fs14.writeFileSync)(configPath, JSON.stringify(cfg, null, 2) + "\n", "utf-8");
8422
- } catch (err) {
8423
- exitError("WRITE_FAILED", `\u5199\u5165\u914D\u7F6E\u5931\u8D25: ${err?.message ?? String(err)}`);
8655
+ } catch (err2) {
8656
+ exitError("WRITE_FAILED", `\u5199\u5165\u914D\u7F6E\u5931\u8D25: ${err2?.message ?? String(err2)}`);
8424
8657
  }
8425
8658
  output({
8426
8659
  ok: true,
@@ -8488,9 +8721,9 @@ function registerTunnelStatus(ntf, ctx) {
8488
8721
  "\u672A\u627E\u5230\u96A7\u9053\u72B6\u6001\u6587\u4EF6\uFF0C\u96A7\u9053\u670D\u52A1\u53EF\u80FD\u5C1A\u672A\u542F\u52A8\u8FC7\u3002\u8BF7\u786E\u8BA4 openclaw \u4E3B\u8FDB\u7A0B\u6B63\u5728\u8FD0\u884C\u3002"
8489
8722
  );
8490
8723
  }
8491
- const ok = status.state === "connected";
8724
+ const ok2 = status.state === "connected";
8492
8725
  output({
8493
- ok,
8726
+ ok: ok2,
8494
8727
  tunnelUrl,
8495
8728
  connection: {
8496
8729
  state: status.state,
@@ -8500,7 +8733,7 @@ function registerTunnelStatus(ntf, ctx) {
8500
8733
  },
8501
8734
  message: formatMessage(status)
8502
8735
  });
8503
- if (!ok) process.exit(1);
8736
+ if (!ok2) process.exit(1);
8504
8737
  });
8505
8738
  }
8506
8739
 
@@ -8982,9 +9215,9 @@ async function runDoctor(ctx, json, fix) {
8982
9215
  await issue.fix();
8983
9216
  log(`\x1B[32m\u2705\x1B[0m ${issue.title} \u2192 ${issue.fixDescription}`);
8984
9217
  fixed++;
8985
- } catch (err) {
9218
+ } catch (err2) {
8986
9219
  log(
8987
- `\x1B[31m\u274C\x1B[0m ${issue.title} \u4FEE\u590D\u5931\u8D25: ${err?.message ?? String(err)}`
9220
+ `\x1B[31m\u274C\x1B[0m ${issue.title} \u4FEE\u590D\u5931\u8D25: ${err2?.message ?? String(err2)}`
8988
9221
  );
8989
9222
  }
8990
9223
  }
@@ -9214,8 +9447,8 @@ async function runUpdate(ctx, opts) {
9214
9447
  let latest;
9215
9448
  try {
9216
9449
  latest = (await fetchText(`${BASE_URL2}/${channel}`)).trim();
9217
- } catch (err) {
9218
- const msg = `\u65E0\u6CD5\u83B7\u53D6\u6700\u65B0\u7248\u672C: ${err?.message ?? String(err)}`;
9450
+ } catch (err2) {
9451
+ const msg = `\u65E0\u6CD5\u83B7\u53D6\u6700\u65B0\u7248\u672C: ${err2?.message ?? String(err2)}`;
9219
9452
  if (json) {
9220
9453
  output({ ok: false, error: { code: "FETCH_FAILED", message: msg } });
9221
9454
  process.exit(1);
@@ -9239,8 +9472,8 @@ async function runUpdate(ctx, opts) {
9239
9472
  let installScript;
9240
9473
  try {
9241
9474
  installScript = await fetchText(`${BASE_URL2}/install-core.mjs`);
9242
- } catch (err) {
9243
- const msg = `\u4E0B\u8F7D\u5B89\u88C5\u811A\u672C\u5931\u8D25: ${err?.message ?? String(err)}`;
9475
+ } catch (err2) {
9476
+ const msg = `\u4E0B\u8F7D\u5B89\u88C5\u811A\u672C\u5931\u8D25: ${err2?.message ?? String(err2)}`;
9244
9477
  if (json) {
9245
9478
  output({ ok: false, error: { code: "DOWNLOAD_FAILED", message: msg } });
9246
9479
  process.exit(1);
@@ -9252,8 +9485,8 @@ async function runUpdate(ctx, opts) {
9252
9485
  const tmpScript = (0, import_node_path17.join)(import_node_os3.default.tmpdir(), `openclaw-install-${Date.now()}.mjs`);
9253
9486
  try {
9254
9487
  (0, import_node_fs22.writeFileSync)(tmpScript, installScript, "utf-8");
9255
- } catch (err) {
9256
- const msg = `\u5199\u5165\u4E34\u65F6\u6587\u4EF6\u5931\u8D25: ${err?.message ?? String(err)}`;
9488
+ } catch (err2) {
9489
+ const msg = `\u5199\u5165\u4E34\u65F6\u6587\u4EF6\u5931\u8D25: ${err2?.message ?? String(err2)}`;
9257
9490
  if (json) {
9258
9491
  output({ ok: false, error: { code: "WRITE_FAILED", message: msg } });
9259
9492
  process.exit(1);
@@ -10365,13 +10598,13 @@ async function downloadFile(url, destPath, logger, options) {
10365
10598
  } finally {
10366
10599
  clearTimeout(timer);
10367
10600
  }
10368
- } catch (err) {
10369
- lastError = err?.message ?? String(err);
10601
+ } catch (err2) {
10602
+ lastError = err2?.message ?? String(err2);
10370
10603
  try {
10371
10604
  if ((0, import_node_fs25.existsSync)(destPath)) (0, import_node_fs25.unlinkSync)(destPath);
10372
10605
  } catch {
10373
10606
  }
10374
- const isAbort = err?.name === "AbortError";
10607
+ const isAbort = err2?.name === "AbortError";
10375
10608
  logger.warn(
10376
10609
  `[downloader] \u4E0B\u8F7D\u5931\u8D25 (attempt ${attempt}/${maxRetries}): ${isAbort ? "\u8D85\u65F6" : lastError}`
10377
10610
  );
@@ -10433,9 +10666,9 @@ function emitRecordingStatus(recordingId, storage, logger, notifyStatus, error,
10433
10666
  updatedAt: entry.updatedAt,
10434
10667
  error
10435
10668
  });
10436
- } catch (err) {
10669
+ } catch (err2) {
10437
10670
  logger.error(
10438
- `[recording-status] \u72B6\u6001\u4E8B\u4EF6\u53D1\u9001\u5931\u8D25: ${recordingId}, ${err?.message ?? err}`
10671
+ `[recording-status] \u72B6\u6001\u4E8B\u4EF6\u53D1\u9001\u5931\u8D25: ${recordingId}, ${err2?.message ?? err2}`
10439
10672
  );
10440
10673
  }
10441
10674
  }
@@ -10499,8 +10732,8 @@ async function handleRecordingSync(recordingId, metadata, storage, asrConfig, lo
10499
10732
  asrConfig,
10500
10733
  logger,
10501
10734
  options
10502
- ).catch((err) => {
10503
- const error = `\u5F55\u97F3\u540C\u6B65\u5931\u8D25: ${err?.message ?? err}`;
10735
+ ).catch((err2) => {
10736
+ const error = `\u5F55\u97F3\u540C\u6B65\u5931\u8D25: ${err2?.message ?? err2}`;
10504
10737
  logger.error(`[recording-sync] ${error}: ${recordingId}`);
10505
10738
  emitRecordingStatus(
10506
10739
  recordingId,
@@ -10523,9 +10756,9 @@ async function handleRecordingSync(recordingId, metadata, storage, asrConfig, lo
10523
10756
  asrConfig,
10524
10757
  logger,
10525
10758
  options
10526
- ).catch((err) => {
10759
+ ).catch((err2) => {
10527
10760
  logger.error(
10528
- `[asr-trigger] \u8F6C\u5199\u89E6\u53D1\u5931\u8D25: ${recordingId}, ${err?.message ?? err}`
10761
+ `[asr-trigger] \u8F6C\u5199\u89E6\u53D1\u5931\u8D25: ${recordingId}, ${err2?.message ?? err2}`
10529
10762
  );
10530
10763
  });
10531
10764
  }
@@ -10861,9 +11094,9 @@ var RelayClient = class {
10861
11094
  }
10862
11095
  settle();
10863
11096
  });
10864
- ws.on("error", (err) => {
11097
+ ws.on("error", (err2) => {
10865
11098
  this.opts.logger.error(
10866
- `Relay tunnel: WebSocket error: ${err.message} (readyState=${ws.readyState}, reconnectAttempt=${this.reconnectAttempt}, url=${wsUrl.toString()})`
11099
+ `Relay tunnel: WebSocket error: ${err2.message} (readyState=${ws.readyState}, reconnectAttempt=${this.reconnectAttempt}, url=${wsUrl.toString()})`
10867
11100
  );
10868
11101
  settle();
10869
11102
  });
@@ -10871,9 +11104,9 @@ var RelayClient = class {
10871
11104
  }
10872
11105
  emitConnected() {
10873
11106
  for (const handler of this.connectedHandlers) {
10874
- Promise.resolve(handler()).catch((err) => {
11107
+ Promise.resolve(handler()).catch((err2) => {
10875
11108
  this.opts.logger.warn(
10876
- `Relay tunnel: onConnected handler failed: ${err instanceof Error ? err.message : String(err)}`
11109
+ `Relay tunnel: onConnected handler failed: ${err2 instanceof Error ? err2.message : String(err2)}`
10877
11110
  );
10878
11111
  });
10879
11112
  }
@@ -10901,12 +11134,12 @@ var RelayClient = class {
10901
11134
  try {
10902
11135
  const result = handler(frame);
10903
11136
  if (result instanceof Promise) {
10904
- result.catch((err) => {
10905
- this.opts.logger.error(`Relay tunnel: handler error: ${err}`);
11137
+ result.catch((err2) => {
11138
+ this.opts.logger.error(`Relay tunnel: handler error: ${err2}`);
10906
11139
  });
10907
11140
  }
10908
- } catch (err) {
10909
- this.opts.logger.error(`Relay tunnel: handler error: ${err}`);
11141
+ } catch (err2) {
11142
+ this.opts.logger.error(`Relay tunnel: handler error: ${err2}`);
10910
11143
  }
10911
11144
  }
10912
11145
  }
@@ -10995,8 +11228,8 @@ var RelayClient = class {
10995
11228
  this.reconnectTimer = setTimeout(() => {
10996
11229
  this.reconnectTimer = null;
10997
11230
  if (!this.aborted) {
10998
- this.connect().catch((err) => {
10999
- this.opts.logger.error(`Relay tunnel: reconnect failed: ${err}`);
11231
+ this.connect().catch((err2) => {
11232
+ this.opts.logger.error(`Relay tunnel: reconnect failed: ${err2}`);
11000
11233
  });
11001
11234
  }
11002
11235
  }, delayMs);
@@ -11304,8 +11537,8 @@ async function handleHttpRequest(opts, frame) {
11304
11537
  });
11305
11538
  return;
11306
11539
  }
11307
- } catch (err) {
11308
- const message = err instanceof Error ? err.message : String(err);
11540
+ } catch (err2) {
11541
+ const message = err2 instanceof Error ? err2.message : String(err2);
11309
11542
  opts.logger.error(
11310
11543
  `TunnelProxy: HTTP id=${frame.id} ${frame.method} ${mappedPath} failed after ${Date.now() - startedAtMs}ms: ${message}`
11311
11544
  );
@@ -11375,15 +11608,15 @@ async function streamResponse(opts, requestId, res, startedAtMs) {
11375
11608
  state: "end",
11376
11609
  data: ""
11377
11610
  });
11378
- } catch (err) {
11611
+ } catch (err2) {
11379
11612
  opts.logger.error(
11380
- `TunnelProxy: stream error id=${requestId} after ${chunkCount} chunks and ${Date.now() - startedAtMs}ms: ${err instanceof Error ? err.message : String(err)}`
11613
+ `TunnelProxy: stream error id=${requestId} after ${chunkCount} chunks and ${Date.now() - startedAtMs}ms: ${err2 instanceof Error ? err2.message : String(err2)}`
11381
11614
  );
11382
11615
  opts.client.send({
11383
11616
  type: "proxy_error",
11384
11617
  id: requestId,
11385
11618
  status: 502,
11386
- message: `stream error: ${err instanceof Error ? err.message : String(err)}`
11619
+ message: `stream error: ${err2 instanceof Error ? err2.message : String(err2)}`
11387
11620
  });
11388
11621
  }
11389
11622
  }
@@ -11450,27 +11683,27 @@ var WsProxy = class {
11450
11683
  reason: reason.toString()
11451
11684
  });
11452
11685
  });
11453
- ws.on("error", (err) => {
11686
+ ws.on("error", (err2) => {
11454
11687
  this.opts.logger.warn(
11455
- `TunnelProxy: WS id=${frame.id} error: ${err.message}, active=${this.connections.size}`
11688
+ `TunnelProxy: WS id=${frame.id} error: ${err2.message}, active=${this.connections.size}`
11456
11689
  );
11457
11690
  this.connections.delete(frame.id);
11458
11691
  this.opts.client.send({
11459
11692
  type: "ws_close",
11460
11693
  id: frame.id,
11461
11694
  code: 1011,
11462
- reason: err.message
11695
+ reason: err2.message
11463
11696
  });
11464
11697
  });
11465
- } catch (err) {
11698
+ } catch (err2) {
11466
11699
  this.opts.logger.error(
11467
- `TunnelProxy: WS id=${frame.id} failed to connect: ${err instanceof Error ? err.message : String(err)}`
11700
+ `TunnelProxy: WS id=${frame.id} failed to connect: ${err2 instanceof Error ? err2.message : String(err2)}`
11468
11701
  );
11469
11702
  this.opts.client.send({
11470
11703
  type: "ws_close",
11471
11704
  id: frame.id,
11472
11705
  code: 1011,
11473
- reason: `failed to connect: ${err instanceof Error ? err.message : String(err)}`
11706
+ reason: `failed to connect: ${err2 instanceof Error ? err2.message : String(err2)}`
11474
11707
  });
11475
11708
  }
11476
11709
  }
@@ -11510,19 +11743,19 @@ var WsProxy = class {
11510
11743
  var MAX_AUTO_PAIRING_APPROVALS = 3;
11511
11744
  var approveDevicePairingPromise = null;
11512
11745
  var approveDevicePairingWarned = false;
11513
- function formatErrorMessage(err) {
11514
- if (err instanceof Error && err.message) return err.message;
11515
- return String(err);
11746
+ function formatErrorMessage(err2) {
11747
+ if (err2 instanceof Error && err2.message) return err2.message;
11748
+ return String(err2);
11516
11749
  }
11517
11750
  async function loadApproveDevicePairing(logger) {
11518
11751
  if (!approveDevicePairingPromise) {
11519
11752
  approveDevicePairingPromise = import("openclaw/plugin-sdk/device-bootstrap").then(
11520
11753
  (mod) => typeof mod.approveDevicePairing === "function" ? mod.approveDevicePairing : null
11521
- ).catch((err) => {
11754
+ ).catch((err2) => {
11522
11755
  if (!approveDevicePairingWarned) {
11523
11756
  approveDevicePairingWarned = true;
11524
11757
  logger.warn(
11525
- `TunnelProxy: local gateway auto-pairing disabled because current OpenClaw runtime does not expose device bootstrap SDK (${formatErrorMessage(err)})`
11758
+ `TunnelProxy: local gateway auto-pairing disabled because current OpenClaw runtime does not expose device bootstrap SDK (${formatErrorMessage(err2)})`
11526
11759
  );
11527
11760
  }
11528
11761
  return null;
@@ -11747,9 +11980,9 @@ var TunnelProxy = class {
11747
11980
  `TunnelProxy: auto-approved local gateway pairing request ${requestId} (reason=${reason || "not-paired"}, hostStateDir=${this.hostStateDir}, approval=${this.gatewayWsAutoPairingApprovals}/${MAX_AUTO_PAIRING_APPROVALS})`
11748
11981
  );
11749
11982
  return true;
11750
- } catch (err) {
11983
+ } catch (err2) {
11751
11984
  this.opts.logger.warn(
11752
- `TunnelProxy: failed to auto-approve gateway pairing request ${requestId}: ${err?.message ?? String(err)}`
11985
+ `TunnelProxy: failed to auto-approve gateway pairing request ${requestId}: ${err2?.message ?? String(err2)}`
11753
11986
  );
11754
11987
  return false;
11755
11988
  }
@@ -11863,9 +12096,9 @@ var TunnelProxy = class {
11863
12096
  queueMicrotask(() => this.ensureGatewayWs());
11864
12097
  }
11865
12098
  });
11866
- ws.on("error", (err) => {
12099
+ ws.on("error", (err2) => {
11867
12100
  this.opts.logger.warn(
11868
- `TunnelProxy: RPC WS error: ${err.message} (ready=${this.gatewayWsReady}, pending=${this.gatewayWsPending.length}, activeWs=${this.wsProxy.activeCount})`
12101
+ `TunnelProxy: RPC WS error: ${err2.message} (ready=${this.gatewayWsReady}, pending=${this.gatewayWsPending.length}, activeWs=${this.wsProxy.activeCount})`
11869
12102
  );
11870
12103
  this.gatewayWsConnecting = false;
11871
12104
  if (this.gatewayWs === ws) {
@@ -11965,8 +12198,8 @@ function createTunnelService(opts) {
11965
12198
  try {
11966
12199
  process.kill(pid, 0);
11967
12200
  return true;
11968
- } catch (err) {
11969
- return err?.code === "EPERM";
12201
+ } catch (err2) {
12202
+ return err2?.code === "EPERM";
11970
12203
  }
11971
12204
  }
11972
12205
  function readLockOwner(filePath) {
@@ -12011,10 +12244,10 @@ function createTunnelService(opts) {
12011
12244
  lockFilePath = filePath;
12012
12245
  lockFd = fd;
12013
12246
  return true;
12014
- } catch (err) {
12015
- if (err?.code !== "EEXIST") {
12247
+ } catch (err2) {
12248
+ if (err2?.code !== "EEXIST") {
12016
12249
  opts.logger.error(
12017
- `Relay tunnel: failed to acquire local lock ${filePath}: ${String(err)}`
12250
+ `Relay tunnel: failed to acquire local lock ${filePath}: ${String(err2)}`
12018
12251
  );
12019
12252
  return false;
12020
12253
  }
@@ -12102,14 +12335,14 @@ function createTunnelService(opts) {
12102
12335
  emitPendingPluginUpdate("relay connected");
12103
12336
  });
12104
12337
  abortController = new AbortController();
12105
- client.connectWithAutoReconnect(abortController.signal).catch((err) => {
12338
+ client.connectWithAutoReconnect(abortController.signal).catch((err2) => {
12106
12339
  releaseLock();
12107
- logger.error(`Relay tunnel: unexpected error: ${err}`);
12340
+ logger.error(`Relay tunnel: unexpected error: ${err2}`);
12108
12341
  });
12109
12342
  logger.info("Relay tunnel \u670D\u52A1\u5DF2\u542F\u52A8");
12110
- } catch (err) {
12343
+ } catch (err2) {
12111
12344
  releaseLock();
12112
- throw err;
12345
+ throw err2;
12113
12346
  }
12114
12347
  },
12115
12348
  async stop() {
@@ -12215,8 +12448,8 @@ function readHostGatewayConfig(params) {
12215
12448
  if (configPath) {
12216
12449
  try {
12217
12450
  configData = JSON.parse((0, import_node_fs31.readFileSync)(configPath, "utf-8"));
12218
- } catch (err) {
12219
- if (err?.code !== "ENOENT") {
12451
+ } catch (err2) {
12452
+ if (err2?.code !== "ENOENT") {
12220
12453
  params.logger.warn(
12221
12454
  `Relay tunnel: \u65E0\u6CD5\u8BFB\u53D6 gateway \u9274\u6743\u914D\u7F6E (${configPath})`
12222
12455
  );
@@ -12326,7 +12559,7 @@ function registerNotificationInterfaces(deps) {
12326
12559
  } = deps;
12327
12560
  function triggerAfterIngest(insertedCount, cron) {
12328
12561
  if (insertedCount <= 0 || !onAfterIngest) return;
12329
- void Promise.resolve().then(() => onAfterIngest(insertedCount, cron)).catch((err) => logger.warn(`onAfterIngest failed: ${err?.message ?? err}`));
12562
+ void Promise.resolve().then(() => onAfterIngest(insertedCount, cron)).catch((err2) => logger.warn(`onAfterIngest failed: ${err2?.message ?? err2}`));
12330
12563
  }
12331
12564
  registerGatewayMethod(
12332
12565
  "notifications.push",
@@ -12719,9 +12952,9 @@ function registerRecordingInterfaces(deps) {
12719
12952
  }
12720
12953
  triggerTranscription(recordingId, recordingStorage, asr, logger, {
12721
12954
  notifyStatus: notifyRecordingStatus
12722
- }).catch((err) => {
12955
+ }).catch((err2) => {
12723
12956
  logger.error(
12724
- `[recordings.retranscribe] \u91CD\u8BD5\u8F6C\u5199\u5931\u8D25: ${recordingId}, ${err?.message ?? err}`
12957
+ `[recordings.retranscribe] \u91CD\u8BD5\u8F6C\u5199\u5931\u8D25: ${recordingId}, ${err2?.message ?? err2}`
12725
12958
  );
12726
12959
  });
12727
12960
  respond(true, { ok: true, recordingId, message: "\u8F6C\u5199\u5DF2\u91CD\u65B0\u89E6\u53D1" });
@@ -12967,8 +13200,9 @@ var index_default = {
12967
13200
  tunnelService
12968
13201
  });
12969
13202
  registerLightRulesGateway(api, lightRuleRegistry, logger, cacheBroadcast);
13203
+ registerLightRulesTools(api, lightRuleRegistry, logger);
12970
13204
  logger.info(
12971
- "Gateway \u706F\u6548\u89C4\u5219\u65B9\u6CD5\u5DF2\u6CE8\u518C: lightrules.list / lightrules.create / lightrules.update / lightrules.delete"
13205
+ "\u706F\u6548\u89C4\u5219\u65B9\u6CD5\u5DF2\u6CE8\u518C: lightrules.list / lightrules.create / lightrules.update / lightrules.delete"
12972
13206
  );
12973
13207
  autoUpdateLifecycle = registerAutoUpdateLifecycle({
12974
13208
  api,