@yoooclaw/phone-notifications 1.11.5 → 1.11.7

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
@@ -251,6 +251,18 @@ function writeDotEnv(key, value) {
251
251
  }
252
252
  (0, import_node_fs7.writeFileSync)(path2, lines.join("\n"), "utf-8");
253
253
  }
254
+ function buildEnvUrls(host) {
255
+ const https = `https://${host}`;
256
+ const wss = `wss://${host}`;
257
+ return {
258
+ lightApiUrl: `${https}/api/message/tob/sendMessage`,
259
+ relayTunnelUrl: `${wss}/message/messages/ws/plugin`,
260
+ appNameMapUrl: `${https}/api/application-config/app-package/config-all`,
261
+ modelProxyLongRecordingSubmitTaskUrl: `${https}/api/model-proxy/long-recording/submit-task`,
262
+ modelProxyLongRecordingQueryTaskResultBaseUrl: `${https}/api/model-proxy/long-recording/query-task-result`,
263
+ accountFileDeleteUrl: `${https}/api/account/file/delete`
264
+ };
265
+ }
254
266
  function readDotEnv() {
255
267
  const path2 = resolveStateFile(".env");
256
268
  if (!(0, import_node_fs7.existsSync)(path2)) return {};
@@ -290,7 +302,7 @@ function getEnvUrls(env) {
290
302
  function getAvailableEnvs() {
291
303
  return Object.keys(ENV_CONFIG);
292
304
  }
293
- var import_node_fs7, import_node_path6, ENV_CONFIG, VALID_ENVS;
305
+ var import_node_fs7, import_node_path6, ENV_HOSTS, ENV_CONFIG, VALID_ENVS;
294
306
  var init_env = __esm({
295
307
  "src/env.ts"() {
296
308
  "use strict";
@@ -298,31 +310,15 @@ var init_env = __esm({
298
310
  import_node_path6 = require("path");
299
311
  init_credentials();
300
312
  init_host();
313
+ ENV_HOSTS = {
314
+ development: "openclaw-service-dev.yoooclaw.com",
315
+ test: "openclaw-service-test.yoooclaw.com",
316
+ production: "openclaw-service.yoooclaw.com"
317
+ };
301
318
  ENV_CONFIG = {
302
- development: {
303
- lightApiUrl: "https://openclaw-service-dev.yoooclaw.com/api/message/tob/sendMessage",
304
- relayTunnelUrl: "wss://openclaw-service-dev.yoooclaw.com/message/messages/ws/plugin",
305
- appNameMapUrl: "https://openclaw-service-dev.yoooclaw.com/api/application-config/app-package/config-all",
306
- modelProxyLongRecordingSubmitTaskUrl: "https://openclaw-service-dev.yoooclaw.com/api/model-proxy/long-recording/submit-task",
307
- modelProxyLongRecordingQueryTaskResultBaseUrl: "https://openclaw-service-dev.yoooclaw.com/api/model-proxy/long-recording/query-task-result",
308
- accountFileDeleteUrl: "https://openclaw-service-dev.yoooclaw.com/api/account/file/delete"
309
- },
310
- test: {
311
- lightApiUrl: "https://openclaw-service-test.yoooclaw.com/api/message/tob/sendMessage",
312
- relayTunnelUrl: "wss://openclaw-service-test.yoooclaw.com/message/messages/ws/plugin",
313
- appNameMapUrl: "https://openclaw-service-test.yoooclaw.com/api/application-config/app-package/config-all",
314
- modelProxyLongRecordingSubmitTaskUrl: "https://openclaw-service-test.yoooclaw.com/api/model-proxy/long-recording/submit-task",
315
- modelProxyLongRecordingQueryTaskResultBaseUrl: "https://openclaw-service-test.yoooclaw.com/api/model-proxy/long-recording/query-task-result",
316
- accountFileDeleteUrl: "https://openclaw-service-test.yoooclaw.com/api/account/file/delete"
317
- },
318
- production: {
319
- lightApiUrl: "https://openclaw-service.yoooclaw.com/api/message/tob/sendMessage",
320
- relayTunnelUrl: "wss://openclaw-service.yoooclaw.com/message/messages/ws/plugin",
321
- appNameMapUrl: "https://openclaw-service.yoooclaw.com/api/application-config/app-package/config-all",
322
- modelProxyLongRecordingSubmitTaskUrl: "https://openclaw-service.yoooclaw.com/api/model-proxy/long-recording/submit-task",
323
- modelProxyLongRecordingQueryTaskResultBaseUrl: "https://openclaw-service.yoooclaw.com/api/model-proxy/long-recording/query-task-result",
324
- accountFileDeleteUrl: "https://openclaw-service.yoooclaw.com/api/account/file/delete"
325
- }
319
+ development: buildEnvUrls(ENV_HOSTS.development),
320
+ test: buildEnvUrls(ENV_HOSTS.test),
321
+ production: buildEnvUrls(ENV_HOSTS.production)
326
322
  };
327
323
  VALID_ENVS = new Set(Object.keys(ENV_CONFIG));
328
324
  }
@@ -5578,7 +5574,7 @@ function readBuildInjectedVersion() {
5578
5574
  if (false) {
5579
5575
  return void 0;
5580
5576
  }
5581
- const version = "1.11.5".trim();
5577
+ const version = "1.11.7".trim();
5582
5578
  return version || void 0;
5583
5579
  }
5584
5580
  function readPluginVersionFromPackageJson() {
@@ -5598,6 +5594,78 @@ var PLUGIN_VERSION = readBuildInjectedVersion() ?? readPluginVersionFromPackageJ
5598
5594
  // src/logger.ts
5599
5595
  var DEFAULT_LOG_RETENTION_DAYS = 30;
5600
5596
  var DAY_MS = 24 * 60 * 60 * 1e3;
5597
+ var REDACTED = "[redacted]";
5598
+ var REDACTED_SECRET = "[redacted-secret]";
5599
+ var REDACTED_URL = "[redacted-url]";
5600
+ var SECRET_KEYS = [
5601
+ "apiKey",
5602
+ "api_key",
5603
+ "appKey",
5604
+ "authorization",
5605
+ "password",
5606
+ "secret",
5607
+ "token",
5608
+ "accessToken",
5609
+ "refreshToken",
5610
+ "gatewayToken",
5611
+ "gatewayPassword",
5612
+ "apnsToken",
5613
+ "deviceToken",
5614
+ "X-Api-Key-Id",
5615
+ "x-api-key-id",
5616
+ "x-openclaw-password"
5617
+ ];
5618
+ var USER_TEXT_KEYS = [
5619
+ "accountId",
5620
+ "body",
5621
+ "content",
5622
+ "conversationName",
5623
+ "deviceId",
5624
+ "loginAccount",
5625
+ "metadata",
5626
+ "name",
5627
+ "raw",
5628
+ "rawResponse",
5629
+ "reason",
5630
+ "recordResult",
5631
+ "recordingName",
5632
+ "resBody",
5633
+ "response",
5634
+ "senderName",
5635
+ "sourceText",
5636
+ "sourceTextList",
5637
+ "summary",
5638
+ "summaryResult",
5639
+ "summaryText",
5640
+ "text",
5641
+ "title",
5642
+ "transcript",
5643
+ "transcriptData",
5644
+ "userId"
5645
+ ];
5646
+ var USER_URL_KEYS = [
5647
+ "audio",
5648
+ "audioOssUrl",
5649
+ "fileUrl",
5650
+ "oss_audio_url",
5651
+ "oss_srt_url",
5652
+ "srt",
5653
+ "url"
5654
+ ];
5655
+ var SENSITIVE_QUERY_KEYS = [
5656
+ "access_token",
5657
+ "api_key",
5658
+ "apikey",
5659
+ "authorization",
5660
+ "fileUrl",
5661
+ "password",
5662
+ "refresh_token",
5663
+ "secret",
5664
+ "signature",
5665
+ "token",
5666
+ "x-api-key-id",
5667
+ "x-oss-security-token"
5668
+ ];
5601
5669
  function isBetaPluginVersion(version = PLUGIN_VERSION) {
5602
5670
  return /\bbeta\b/i.test(version);
5603
5671
  }
@@ -5606,13 +5674,14 @@ function createVersionAwareLogger(upstream, options = {}) {
5606
5674
  return upstream;
5607
5675
  }
5608
5676
  return {
5609
- info() {
5677
+ info(msg) {
5678
+ upstream.info(redactLogMessage(msg));
5610
5679
  },
5611
5680
  warn(msg) {
5612
- upstream.warn(msg);
5681
+ upstream.warn(redactLogMessage(msg));
5613
5682
  },
5614
5683
  error(msg) {
5615
- upstream.error(msg);
5684
+ upstream.error(redactLogMessage(msg));
5616
5685
  }
5617
5686
  };
5618
5687
  }
@@ -5620,7 +5689,7 @@ var PluginFileLogger = class {
5620
5689
  constructor(upstream, stateDir, options = {}) {
5621
5690
  this.upstream = upstream;
5622
5691
  this.logsDir = (0, import_node_path.join)(stateDir, "plugins", "phone-notifications", "logs");
5623
- this.infoEnabled = isBetaPluginVersion(options.version ?? PLUGIN_VERSION);
5692
+ this.redactLogs = !isBetaPluginVersion(options.version ?? PLUGIN_VERSION);
5624
5693
  this.retentionDays = Number.isFinite(options.retentionDays) && (options.retentionDays ?? 0) > 0 ? options.retentionDays : DEFAULT_LOG_RETENTION_DAYS;
5625
5694
  (0, import_node_fs2.mkdirSync)(this.logsDir, { recursive: true });
5626
5695
  const now = /* @__PURE__ */ new Date();
@@ -5628,24 +5697,27 @@ var PluginFileLogger = class {
5628
5697
  this.lastPruneDateKey = formatDate(now);
5629
5698
  }
5630
5699
  logsDir;
5631
- infoEnabled;
5700
+ redactLogs;
5632
5701
  retentionDays;
5633
5702
  lastPruneDateKey = null;
5634
5703
  info(msg) {
5635
5704
  this.prepareLogDate(/* @__PURE__ */ new Date());
5636
- if (!this.infoEnabled) {
5637
- return;
5638
- }
5639
- this.upstream.info(msg);
5640
- this.append("INFO", msg);
5705
+ const safeMsg = this.formatLogMessage(msg);
5706
+ this.upstream.info(safeMsg);
5707
+ this.append("INFO", safeMsg);
5641
5708
  }
5642
5709
  warn(msg) {
5643
- this.upstream.warn(msg);
5644
- this.append("WARN", msg);
5710
+ const safeMsg = this.formatLogMessage(msg);
5711
+ this.upstream.warn(safeMsg);
5712
+ this.append("WARN", safeMsg);
5645
5713
  }
5646
5714
  error(msg) {
5647
- this.upstream.error(msg);
5648
- this.append("ERROR", msg);
5715
+ const safeMsg = this.formatLogMessage(msg);
5716
+ this.upstream.error(safeMsg);
5717
+ this.append("ERROR", safeMsg);
5718
+ }
5719
+ formatLogMessage(msg) {
5720
+ return this.redactLogs ? redactLogMessage(msg) : msg;
5649
5721
  }
5650
5722
  append(level, msg) {
5651
5723
  const now = /* @__PURE__ */ new Date();
@@ -5681,6 +5753,188 @@ var PluginFileLogger = class {
5681
5753
  }
5682
5754
  }
5683
5755
  };
5756
+ function redactLogMessage(msg) {
5757
+ let redacted = String(msg);
5758
+ redacted = redactQuotedObjectFields(redacted, SECRET_KEYS, REDACTED_SECRET);
5759
+ redacted = redactQuotedObjectFields(redacted, USER_TEXT_KEYS, REDACTED);
5760
+ redacted = redactQuotedObjectFields(redacted, USER_URL_KEYS, REDACTED_URL);
5761
+ redacted = redactStructuredKeyValueFields(
5762
+ redacted,
5763
+ SECRET_KEYS,
5764
+ REDACTED_SECRET
5765
+ );
5766
+ redacted = redactStructuredKeyValueFields(redacted, USER_TEXT_KEYS, REDACTED);
5767
+ redacted = redactStructuredKeyValueFields(redacted, USER_URL_KEYS, REDACTED_URL);
5768
+ redacted = redactKeyValueFields(redacted, SECRET_KEYS, REDACTED_SECRET);
5769
+ redacted = redactKeyValueFields(redacted, USER_TEXT_KEYS, REDACTED);
5770
+ redacted = redactKeyValueFields(redacted, USER_URL_KEYS, REDACTED_URL);
5771
+ redacted = redactColonTextFields(redacted, SECRET_KEYS, REDACTED_SECRET);
5772
+ redacted = redactColonTextFields(redacted, USER_TEXT_KEYS, REDACTED);
5773
+ redacted = redactQueryParams(redacted, SENSITIVE_QUERY_KEYS);
5774
+ redacted = redactBearerTokens(redacted);
5775
+ redacted = redactLikelyUserUrls(redacted);
5776
+ redacted = redactEmails(redacted);
5777
+ redacted = redactPhoneNumbers(redacted);
5778
+ redacted = redactJwtTokens(redacted);
5779
+ redacted = redactLongHexTokens(redacted);
5780
+ return redacted;
5781
+ }
5782
+ function redactQuotedObjectFields(input, keys, placeholder) {
5783
+ const keyPattern = buildKeyPattern(keys);
5784
+ return input.replace(
5785
+ new RegExp(
5786
+ `(["'])(${keyPattern})\\1\\s*:\\s*(?:"(?:\\\\.|[^"\\\\])*"|'(?:\\\\.|[^'\\\\])*'|[^,}\\]]+)`,
5787
+ "gi"
5788
+ ),
5789
+ (_match, quote, key) => `${quote}${key}${quote}: "${placeholder}"`
5790
+ );
5791
+ }
5792
+ function redactKeyValueFields(input, keys, placeholder) {
5793
+ const keyPattern = buildKeyPattern(keys);
5794
+ return input.replace(
5795
+ new RegExp(
5796
+ `(?<![?&])\\b(${keyPattern})\\s*=\\s*(?:Bearer\\s+[^,\\s)]+|"(?:\\\\.|[^"\\\\])*"|'(?:\\\\.|[^'\\\\])*'|[^,\\s)]+)`,
5797
+ "gi"
5798
+ ),
5799
+ (_match, key) => `${key}=${placeholder}`
5800
+ );
5801
+ }
5802
+ function redactStructuredKeyValueFields(input, keys, placeholder) {
5803
+ const keyPattern = buildKeyPattern(keys);
5804
+ const regex = new RegExp(`\\b(${keyPattern})\\s*=\\s*([\\[{])`, "gi");
5805
+ let result = "";
5806
+ let lastIndex = 0;
5807
+ let match;
5808
+ while (match = regex.exec(input)) {
5809
+ const valueStart = regex.lastIndex - 1;
5810
+ const valueEnd = findStructuredValueEnd(input, valueStart);
5811
+ if (valueEnd === null) {
5812
+ continue;
5813
+ }
5814
+ result += input.slice(lastIndex, match.index);
5815
+ result += `${match[1]}=${placeholder}`;
5816
+ lastIndex = valueEnd;
5817
+ regex.lastIndex = valueEnd;
5818
+ }
5819
+ if (lastIndex === 0) {
5820
+ return input;
5821
+ }
5822
+ return result + input.slice(lastIndex);
5823
+ }
5824
+ function findStructuredValueEnd(input, start) {
5825
+ const open = input[start];
5826
+ const close = open === "{" ? "}" : "]";
5827
+ const stack = [close];
5828
+ let quote = null;
5829
+ let escaped = false;
5830
+ for (let i = start + 1; i < input.length; i++) {
5831
+ const ch = input[i];
5832
+ if (quote) {
5833
+ if (escaped) {
5834
+ escaped = false;
5835
+ } else if (ch === "\\") {
5836
+ escaped = true;
5837
+ } else if (ch === quote) {
5838
+ quote = null;
5839
+ }
5840
+ continue;
5841
+ }
5842
+ if (ch === '"' || ch === "'") {
5843
+ quote = ch;
5844
+ continue;
5845
+ }
5846
+ if (ch === "{" || ch === "[") {
5847
+ stack.push(ch === "{" ? "}" : "]");
5848
+ continue;
5849
+ }
5850
+ if (ch === stack[stack.length - 1]) {
5851
+ stack.pop();
5852
+ if (stack.length === 0) {
5853
+ return i + 1;
5854
+ }
5855
+ }
5856
+ }
5857
+ return null;
5858
+ }
5859
+ function redactColonTextFields(input, keys, placeholder) {
5860
+ const keyPattern = buildKeyPattern(keys);
5861
+ return input.replace(
5862
+ new RegExp(`\\b(${keyPattern})\\s*:\\s*([^,\\n]+)`, "gi"),
5863
+ (_match, key) => `${key}: ${placeholder}`
5864
+ );
5865
+ }
5866
+ function redactQueryParams(input, keys) {
5867
+ const keyPattern = buildKeyPattern(keys);
5868
+ return input.replace(
5869
+ new RegExp(`([?&](${keyPattern})=)([^&#\\s]+)`, "gi"),
5870
+ (_match, prefix) => `${prefix}${encodeURIComponent(REDACTED)}`
5871
+ );
5872
+ }
5873
+ function redactBearerTokens(input) {
5874
+ return input.replace(
5875
+ /\b(Bearer\s+)[A-Za-z0-9._~+/=-]+/gi,
5876
+ `$1${REDACTED_SECRET}`
5877
+ );
5878
+ }
5879
+ function redactJwtTokens(input) {
5880
+ return input.replace(
5881
+ /\beyJ[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\b/g,
5882
+ REDACTED_SECRET
5883
+ );
5884
+ }
5885
+ function redactLongHexTokens(input) {
5886
+ return input.replace(/\b[a-f0-9]{32,}\b/gi, REDACTED_SECRET);
5887
+ }
5888
+ function redactEmails(input) {
5889
+ return input.replace(
5890
+ /\b[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,}\b/gi,
5891
+ "[redacted-email]"
5892
+ );
5893
+ }
5894
+ function redactPhoneNumbers(input) {
5895
+ return input.replace(
5896
+ /\b1[3-9]\d{9}\b/g,
5897
+ (phone) => `${phone.slice(0, 3)}****${phone.slice(-4)}`
5898
+ );
5899
+ }
5900
+ function redactLikelyUserUrls(input) {
5901
+ return input.replace(
5902
+ /https?:\/\/[^\s"',)]+/gi,
5903
+ (rawUrl) => redactUrl(rawUrl)
5904
+ );
5905
+ }
5906
+ function redactUrl(rawUrl) {
5907
+ try {
5908
+ const url = new URL(rawUrl);
5909
+ for (const key of Array.from(url.searchParams.keys())) {
5910
+ if (SENSITIVE_QUERY_KEYS.some(
5911
+ (sensitiveKey) => sensitiveKey.toLowerCase() === key.toLowerCase()
5912
+ )) {
5913
+ url.searchParams.set(key, REDACTED);
5914
+ }
5915
+ }
5916
+ if (shouldRedactUrlPath(url)) {
5917
+ return `${url.origin}/${REDACTED_URL}`;
5918
+ }
5919
+ return url.toString();
5920
+ } catch {
5921
+ return rawUrl;
5922
+ }
5923
+ }
5924
+ function shouldRedactUrlPath(url) {
5925
+ const host = url.hostname.toLowerCase();
5926
+ const path2 = decodeURIComponent(url.pathname).toLowerCase();
5927
+ if (/(^|\.)((oss|cos|s3|storage|cdn)[.-])/.test(host) || /(aliyuncs|myqcloud|amazonaws|oss|storage|cdn)/.test(host)) {
5928
+ return true;
5929
+ }
5930
+ return /\/(audio|avatar|feedback|log|logs|recording|recordings)\//.test(path2) || /\.(aac|flac|json|m4a|md|mp3|ogg|opus|srt|wav|zip)$/i.test(path2);
5931
+ }
5932
+ function buildKeyPattern(keys) {
5933
+ return keys.map(escapeRegExp).join("|");
5934
+ }
5935
+ function escapeRegExp(value) {
5936
+ return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
5937
+ }
5684
5938
  function formatDate(d) {
5685
5939
  const y = d.getFullYear();
5686
5940
  const m = String(d.getMonth() + 1).padStart(2, "0");
@@ -7215,10 +7469,21 @@ var InlineLightRuleEvaluator = class {
7215
7469
  */
7216
7470
  async evaluate(notifications) {
7217
7471
  if (notifications.length === 0) return true;
7472
+ this.reloadRegistry("before evaluation");
7218
7473
  const rules = this.registry.getEnabled();
7219
- if (rules.length === 0) return true;
7474
+ if (rules.length === 0) {
7475
+ this.logger.info(
7476
+ `lightrules: no enabled rules (notifications=${notifications.length})`
7477
+ );
7478
+ return true;
7479
+ }
7220
7480
  const matches = await this.invoker.matchNotifications(notifications, rules);
7221
- if (matches === null) return false;
7481
+ if (matches === null) {
7482
+ this.logger.warn(
7483
+ `lightrules: match failed (notifications=${notifications.length}, rules=${rules.length})`
7484
+ );
7485
+ return false;
7486
+ }
7222
7487
  if (matches.length === 0) {
7223
7488
  this.logger.info(
7224
7489
  `lightrules: 0 matches (notifications=${notifications.length}, rules=${rules.length})`
@@ -7226,12 +7491,19 @@ var InlineLightRuleEvaluator = class {
7226
7491
  return true;
7227
7492
  }
7228
7493
  const firedRules = /* @__PURE__ */ new Set();
7494
+ this.reloadRegistry("before triggering");
7229
7495
  for (const match of matches) {
7230
7496
  if (firedRules.has(match.ruleName)) continue;
7231
7497
  const rule = this.registry.get(match.ruleName);
7232
7498
  if (!rule) {
7233
7499
  this.logger.warn(
7234
- `lightrules: matched rule '${match.ruleName}' not found in registry`
7500
+ `lightrules: matched rule '${match.ruleName}' skipped - not found after reload`
7501
+ );
7502
+ continue;
7503
+ }
7504
+ if (!rule.enabled) {
7505
+ this.logger.info(
7506
+ `lightrules: matched rule '${match.ruleName}' skipped - disabled after reload`
7235
7507
  );
7236
7508
  continue;
7237
7509
  }
@@ -7244,6 +7516,15 @@ var InlineLightRuleEvaluator = class {
7244
7516
  );
7245
7517
  return true;
7246
7518
  }
7519
+ reloadRegistry(stage) {
7520
+ try {
7521
+ this.registry.reload();
7522
+ } catch (err2) {
7523
+ this.logger.warn(
7524
+ `lightrules: registry reload failed ${stage}: ${err2?.message ?? err2}`
7525
+ );
7526
+ }
7527
+ }
7247
7528
  async triggerLight(rule, notification) {
7248
7529
  let apiKey;
7249
7530
  try {
@@ -7302,6 +7583,74 @@ function summarizeContent(content) {
7302
7583
  return `${trimmed.slice(0, REASON_CONTENT_MAX)}\u2026`;
7303
7584
  }
7304
7585
 
7586
+ // src/light-rules/evaluation-scheduler.ts
7587
+ var DEFAULT_LIGHT_RULE_EVALUATION_DEBOUNCE_MS = 1e3;
7588
+ var LightRuleEvaluationScheduler = class {
7589
+ evaluator;
7590
+ logger;
7591
+ debounceMs;
7592
+ pending = [];
7593
+ timer = null;
7594
+ running = false;
7595
+ flushRequestedWhileRunning = false;
7596
+ constructor(deps) {
7597
+ this.evaluator = deps.evaluator;
7598
+ this.logger = deps.logger;
7599
+ const debounceMs = deps.options?.debounceMs ?? DEFAULT_LIGHT_RULE_EVALUATION_DEBOUNCE_MS;
7600
+ this.debounceMs = Number.isFinite(debounceMs) && debounceMs >= 0 ? debounceMs : DEFAULT_LIGHT_RULE_EVALUATION_DEBOUNCE_MS;
7601
+ }
7602
+ enqueue(notifications) {
7603
+ if (notifications.length === 0) return;
7604
+ this.pending.push(...notifications);
7605
+ this.scheduleFlush();
7606
+ }
7607
+ scheduleFlush() {
7608
+ if (this.timer !== null) return;
7609
+ this.timer = setTimeout(() => {
7610
+ this.timer = null;
7611
+ void this.drain().catch((err2) => {
7612
+ this.logger.warn(
7613
+ `lightrules: scheduled evaluation failed: ${err2?.message ?? err2}`
7614
+ );
7615
+ });
7616
+ }, this.debounceMs);
7617
+ const maybeNodeTimer = this.timer;
7618
+ maybeNodeTimer.unref?.();
7619
+ }
7620
+ async drain() {
7621
+ if (this.running) {
7622
+ this.flushRequestedWhileRunning = true;
7623
+ return;
7624
+ }
7625
+ const batch = this.pending.splice(0);
7626
+ if (batch.length === 0) return;
7627
+ this.running = true;
7628
+ try {
7629
+ await this.evaluator.evaluate(batch);
7630
+ } catch (err2) {
7631
+ this.logger.warn(
7632
+ `lightrules: evaluation failed (notifications=${batch.length}): ${err2?.message ?? err2}`
7633
+ );
7634
+ } finally {
7635
+ this.running = false;
7636
+ if (this.pending.length > 0) {
7637
+ if (this.flushRequestedWhileRunning) {
7638
+ this.flushRequestedWhileRunning = false;
7639
+ void this.drain().catch((err2) => {
7640
+ this.logger.warn(
7641
+ `lightrules: queued evaluation failed: ${err2?.message ?? err2}`
7642
+ );
7643
+ });
7644
+ } else {
7645
+ this.scheduleFlush();
7646
+ }
7647
+ } else {
7648
+ this.flushRequestedWhileRunning = false;
7649
+ }
7650
+ }
7651
+ }
7652
+ };
7653
+
7305
7654
  // src/light-rules/pi-invoker.ts
7306
7655
  var import_agent_runtime = require("openclaw/plugin-sdk/agent-runtime");
7307
7656
  var DEFAULT_PROVIDER = "anthropic";
@@ -7325,7 +7674,16 @@ var PiAiInvoker = class {
7325
7674
  */
7326
7675
  async matchNotifications(notifications, rules) {
7327
7676
  if (notifications.length === 0 || rules.length === 0) return [];
7328
- const { label, prepared } = await this.prepareCompletionModel();
7677
+ let label;
7678
+ let prepared;
7679
+ try {
7680
+ ({ label, prepared } = await this.prepareCompletionModel());
7681
+ } catch (err2) {
7682
+ this.logger.warn(
7683
+ `PiAiInvoker: prepare failed: ${err2?.message ?? String(err2)}`
7684
+ );
7685
+ return null;
7686
+ }
7329
7687
  if ("error" in prepared) {
7330
7688
  this.logger.warn(
7331
7689
  `PiAiInvoker: prepare ${label} failed: ${prepared.error}`
@@ -11203,6 +11561,12 @@ var RecordingStorage = class {
11203
11561
  if (typeof entry.lastError === "string" && entry.lastError.trim()) {
11204
11562
  compacted.lastError = entry.lastError.trim();
11205
11563
  }
11564
+ if (compacted.status === "transcribing") {
11565
+ compacted.status = "transcribe_failed";
11566
+ compacted.lastError = compacted.lastError ?? "\u8F6C\u5199\u4EFB\u52A1\u5DF2\u4E2D\u65AD\uFF0C\u8BF7\u91CD\u65B0\u53D1\u8D77\u8F6C\u5199";
11567
+ compacted.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
11568
+ needsRewrite = true;
11569
+ }
11206
11570
  return compacted;
11207
11571
  });
11208
11572
  const hadLargeFields = raw.recordings.some(
@@ -11545,20 +11909,27 @@ async function triggerTranscription(recordingId, storage, asrConfig, logger, opt
11545
11909
  storage.setLastError(recordingId, void 0);
11546
11910
  emitRecordingStatus(recordingId, storage, logger, options.notifyStatus);
11547
11911
  const audioFilePath = storage.getAudioFilePath(recordingId);
11548
- const result = await runTranscriptionWorkflow({
11549
- audioFilePath,
11550
- audioOssUrl: entry.metadata.oss_audio_url,
11551
- config: asrConfig,
11552
- markers: entry.metadata.markers ?? [],
11553
- recordingName: entry.metadata.name,
11554
- durationSec: entry.metadata.duration_sec,
11555
- createdAt: entry.metadata.created_at,
11556
- transcriptDataDir: storage.getTranscriptDataDir(),
11557
- transcriptsDir: storage.getTranscriptsDir(),
11558
- summariesDir: storage.getSummariesDir(),
11559
- recordingId,
11560
- logger
11561
- });
11912
+ let result;
11913
+ try {
11914
+ result = await runTranscriptionWorkflow({
11915
+ audioFilePath,
11916
+ audioOssUrl: entry.metadata.oss_audio_url,
11917
+ config: asrConfig,
11918
+ markers: entry.metadata.markers ?? [],
11919
+ recordingName: entry.metadata.name,
11920
+ durationSec: entry.metadata.duration_sec,
11921
+ createdAt: entry.metadata.created_at,
11922
+ transcriptDataDir: storage.getTranscriptDataDir(),
11923
+ transcriptsDir: storage.getTranscriptsDir(),
11924
+ summariesDir: storage.getSummariesDir(),
11925
+ recordingId,
11926
+ logger
11927
+ });
11928
+ } catch (err2) {
11929
+ const error = `\u8F6C\u5199\u4EFB\u52A1\u5F02\u5E38: ${err2?.message ?? err2}`;
11930
+ logger.error(`[asr-trigger] ${error}: ${recordingId}`);
11931
+ result = { ok: false, error };
11932
+ }
11562
11933
  if (result.ok && result.transcriptFilename) {
11563
11934
  if (result.transcriptDataFilename) {
11564
11935
  storage.setTranscriptDataFile(recordingId, result.transcriptDataFilename);
@@ -11593,17 +11964,18 @@ async function triggerTranscription(recordingId, storage, asrConfig, logger, opt
11593
11964
  });
11594
11965
  }
11595
11966
  } else {
11967
+ const error = result.error ?? "ASR \u8F6C\u5199\u5931\u8D25";
11596
11968
  storage.updateStatus(recordingId, "transcribe_failed");
11597
- storage.setLastError(recordingId, result.error);
11969
+ storage.setLastError(recordingId, error);
11598
11970
  emitRecordingStatus(
11599
11971
  recordingId,
11600
11972
  storage,
11601
11973
  logger,
11602
11974
  options.notifyStatus,
11603
- result.error
11975
+ error
11604
11976
  );
11605
11977
  logger.error(
11606
- `[asr-trigger] \u8F6C\u5199\u5931\u8D25: ${recordingId}, error=${result.error}`
11978
+ `[asr-trigger] \u8F6C\u5199\u5931\u8D25: ${recordingId}, error=${error}`
11607
11979
  );
11608
11980
  }
11609
11981
  }
@@ -14077,6 +14449,10 @@ var index_default = {
14077
14449
  registry: lightRuleRegistry,
14078
14450
  invoker: lightRuleInvoker
14079
14451
  });
14452
+ const lightRuleEvaluationScheduler = new LightRuleEvaluationScheduler({
14453
+ evaluator: inlineLightRuleEvaluator,
14454
+ logger
14455
+ });
14080
14456
  registerStorageLifecycle({
14081
14457
  api,
14082
14458
  config,
@@ -14105,8 +14481,8 @@ var index_default = {
14105
14481
  filterNotifications,
14106
14482
  registerGatewayMethod: registerGatewayMethodWithBroadcastCapture,
14107
14483
  tunnelService,
14108
- onAfterIngest: (inserted) => {
14109
- void inlineLightRuleEvaluator.evaluate(inserted);
14484
+ onAfterIngest(inserted) {
14485
+ lightRuleEvaluationScheduler.enqueue(inserted);
14110
14486
  }
14111
14487
  });
14112
14488
  registerLightControlTool(api, logger);