@yoooclaw/phone-notifications 1.11.4-beta.2 → 1.11.4-beta.4

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
@@ -579,20 +579,20 @@ function selectModelByMemory(availableGB, isAppleSilicon = false) {
579
579
  return "tiny";
580
580
  }
581
581
  function resolveModelsDir(dataDir) {
582
- const dir = (0, import_node_path21.join)(dataDir, WHISPER_MODELS_DIR);
583
- (0, import_node_fs25.mkdirSync)(dir, { recursive: true });
582
+ const dir = (0, import_node_path20.join)(dataDir, WHISPER_MODELS_DIR);
583
+ (0, import_node_fs24.mkdirSync)(dir, { recursive: true });
584
584
  return dir;
585
585
  }
586
586
  function resolveBinDir(dataDir) {
587
- const dir = (0, import_node_path21.join)(dataDir, WHISPER_BIN_DIR);
588
- (0, import_node_fs25.mkdirSync)(dir, { recursive: true });
587
+ const dir = (0, import_node_path20.join)(dataDir, WHISPER_BIN_DIR);
588
+ (0, import_node_fs24.mkdirSync)(dir, { recursive: true });
589
589
  return dir;
590
590
  }
591
591
  function isModelDownloaded(modelsDir, modelSize) {
592
- const modelPath = (0, import_node_path21.join)(modelsDir, MODEL_FILENAMES[modelSize]);
593
- if (!(0, import_node_fs25.existsSync)(modelPath)) return false;
592
+ const modelPath = (0, import_node_path20.join)(modelsDir, MODEL_FILENAMES[modelSize]);
593
+ if (!(0, import_node_fs24.existsSync)(modelPath)) return false;
594
594
  try {
595
- const stat = (0, import_node_fs25.statSync)(modelPath);
595
+ const stat = (0, import_node_fs24.statSync)(modelPath);
596
596
  const expectedSize = MODEL_DISK_SIZES[modelSize];
597
597
  return stat.size >= expectedSize * 0.8;
598
598
  } catch {
@@ -601,7 +601,7 @@ function isModelDownloaded(modelsDir, modelSize) {
601
601
  }
602
602
  async function downloadModel(modelsDir, modelSize, logger, modelSource, mirrorUrl) {
603
603
  const filename = MODEL_FILENAMES[modelSize];
604
- const modelPath = (0, import_node_path21.join)(modelsDir, filename);
604
+ const modelPath = (0, import_node_path20.join)(modelsDir, filename);
605
605
  if (isModelDownloaded(modelsDir, modelSize)) {
606
606
  logger.info(`[whisper-local] \u6A21\u578B\u5DF2\u5B58\u5728\uFF0C\u8DF3\u8FC7\u4E0B\u8F7D: ${filename}`);
607
607
  return { ok: true, modelPath };
@@ -667,7 +667,7 @@ async function probeUrl(url, logger) {
667
667
  async function downloadFromUrl(url, modelPath, logger) {
668
668
  const tmpPath = `${modelPath}.downloading`;
669
669
  try {
670
- (0, import_node_fs25.mkdirSync)((0, import_node_path21.dirname)(modelPath), { recursive: true });
670
+ (0, import_node_fs24.mkdirSync)((0, import_node_path20.dirname)(modelPath), { recursive: true });
671
671
  const controller = new AbortController();
672
672
  const timer = setTimeout(() => controller.abort(), 30 * 60 * 1e3);
673
673
  try {
@@ -683,7 +683,7 @@ async function downloadFromUrl(url, modelPath, logger) {
683
683
  return { ok: false, modelPath, error: "\u6A21\u578B\u4E0B\u8F7D\u5931\u8D25: \u54CD\u5E94\u4F53\u4E3A\u7A7A" };
684
684
  }
685
685
  const contentLength = Number(res.headers.get("content-length") ?? 0);
686
- const writeStream = (0, import_node_fs25.createWriteStream)(tmpPath);
686
+ const writeStream = (0, import_node_fs24.createWriteStream)(tmpPath);
687
687
  const readable = import_node_stream2.Readable.fromWeb(res.body);
688
688
  let downloaded = 0;
689
689
  let lastLogPercent = 0;
@@ -705,14 +705,14 @@ async function downloadFromUrl(url, modelPath, logger) {
705
705
  }
706
706
  const { renameSync: renameSync2 } = await import("fs");
707
707
  renameSync2(tmpPath, modelPath);
708
- const fileSize = (0, import_node_fs25.statSync)(modelPath).size;
708
+ const fileSize = (0, import_node_fs24.statSync)(modelPath).size;
709
709
  logger.info(
710
- `[whisper-local] \u6A21\u578B\u4E0B\u8F7D\u5B8C\u6210: ${(0, import_node_path21.basename)(modelPath)} (${formatBytes2(fileSize)})`
710
+ `[whisper-local] \u6A21\u578B\u4E0B\u8F7D\u5B8C\u6210: ${(0, import_node_path20.basename)(modelPath)} (${formatBytes2(fileSize)})`
711
711
  );
712
712
  return { ok: true, modelPath };
713
713
  } catch (err2) {
714
714
  try {
715
- if ((0, import_node_fs25.existsSync)(tmpPath)) (0, import_node_fs25.unlinkSync)(tmpPath);
715
+ if ((0, import_node_fs24.existsSync)(tmpPath)) (0, import_node_fs24.unlinkSync)(tmpPath);
716
716
  } catch {
717
717
  }
718
718
  const msg = err2?.name === "AbortError" ? "\u6A21\u578B\u4E0B\u8F7D\u8D85\u65F6\uFF0830 \u5206\u949F\uFF09" : err2?.message ?? String(err2);
@@ -724,8 +724,8 @@ function findWhisperBinary(dataDir, logger) {
724
724
  const binDir = resolveBinDir(dataDir);
725
725
  const binNames = (0, import_node_os4.platform)() === "win32" ? ["whisper-cli.exe", "whisper.exe", "main.exe"] : ["whisper-cli", "whisper", "main"];
726
726
  for (const name of binNames) {
727
- const binPath = (0, import_node_path21.join)(binDir, name);
728
- if ((0, import_node_fs25.existsSync)(binPath)) {
727
+ const binPath = (0, import_node_path20.join)(binDir, name);
728
+ if ((0, import_node_fs24.existsSync)(binPath)) {
729
729
  logger.info(`[whisper-local] \u627E\u5230\u672C\u5730\u4E8C\u8FDB\u5236: ${binPath}`);
730
730
  return binPath;
731
731
  }
@@ -776,7 +776,7 @@ async function transcribeWithWhisperLocal(audioFilePath, localConfig, dataDir, l
776
776
  return { ok: false, error: `\u6A21\u578B\u4E0B\u8F7D\u5931\u8D25: ${downloadResult.error}` };
777
777
  }
778
778
  }
779
- const modelPath = (0, import_node_path21.join)(modelsDir, MODEL_FILENAMES[modelSize]);
779
+ const modelPath = (0, import_node_path20.join)(modelsDir, MODEL_FILENAMES[modelSize]);
780
780
  let inputPath = audioFilePath;
781
781
  let tmpWavPath = null;
782
782
  const actualFmt = detectAudioFormat(audioFilePath);
@@ -828,10 +828,10 @@ async function transcribeWithWhisperLocal(audioFilePath, localConfig, dataDir, l
828
828
  logger.info(`[whisper-local] \u8F6C\u5199\u8017\u65F6: ${Math.round(elapsed / 1e3)}s`);
829
829
  const jsonPath = inputPath + ".json";
830
830
  let jsonContent;
831
- if ((0, import_node_fs25.existsSync)(jsonPath)) {
832
- jsonContent = (0, import_node_fs25.readFileSync)(jsonPath, "utf-8");
831
+ if ((0, import_node_fs24.existsSync)(jsonPath)) {
832
+ jsonContent = (0, import_node_fs24.readFileSync)(jsonPath, "utf-8");
833
833
  try {
834
- (0, import_node_fs25.unlinkSync)(jsonPath);
834
+ (0, import_node_fs24.unlinkSync)(jsonPath);
835
835
  } catch {
836
836
  }
837
837
  } else {
@@ -909,10 +909,10 @@ function getPhysicalCoreCount() {
909
909
  }
910
910
  function detectAudioFormat(filePath) {
911
911
  try {
912
- const fd = (0, import_node_fs25.openSync)(filePath, "r");
912
+ const fd = (0, import_node_fs24.openSync)(filePath, "r");
913
913
  const buf = Buffer.alloc(12);
914
- (0, import_node_fs25.readSync)(fd, buf, 0, 12, 0);
915
- (0, import_node_fs25.closeSync)(fd);
914
+ (0, import_node_fs24.readSync)(fd, buf, 0, 12, 0);
915
+ (0, import_node_fs24.closeSync)(fd);
916
916
  const header = buf.toString("ascii", 0, 4);
917
917
  const header8 = buf.toString("ascii", 0, 8);
918
918
  if (header === "RIFF" && buf.toString("ascii", 8, 12) === "WAVE") return ".wav";
@@ -947,7 +947,7 @@ function convertToWav(inputPath, outputPath, actualFmt, logger) {
947
947
  timeout: 12e4,
948
948
  stdio: ["pipe", "pipe", "pipe"]
949
949
  });
950
- if (ffmpegResult.status === 0 && (0, import_node_fs25.existsSync)(outputPath)) {
950
+ if (ffmpegResult.status === 0 && (0, import_node_fs24.existsSync)(outputPath)) {
951
951
  logger.info(`[whisper-local] ffmpeg \u8F6C\u6362\u5B8C\u6210: ${outputPath}`);
952
952
  return { ok: true };
953
953
  }
@@ -960,7 +960,7 @@ function convertToWav(inputPath, outputPath, actualFmt, logger) {
960
960
  ["--rate", "16000", "--mono", inputPath, outputPath],
961
961
  { encoding: "utf-8", timeout: 12e4, stdio: ["pipe", "pipe", "pipe"] }
962
962
  );
963
- if (opusResult.status === 0 && (0, import_node_fs25.existsSync)(outputPath)) {
963
+ if (opusResult.status === 0 && (0, import_node_fs24.existsSync)(outputPath)) {
964
964
  logger.info(`[whisper-local] opusdec \u8F6C\u6362\u5B8C\u6210: ${outputPath}`);
965
965
  return { ok: true };
966
966
  }
@@ -974,7 +974,7 @@ function convertToWav(inputPath, outputPath, actualFmt, logger) {
974
974
  if (detectedExt && !inputPath.endsWith(detectedExt)) {
975
975
  tmpCopy = inputPath + ".detected" + detectedExt;
976
976
  try {
977
- (0, import_node_fs25.copyFileSync)(inputPath, tmpCopy);
977
+ (0, import_node_fs24.copyFileSync)(inputPath, tmpCopy);
978
978
  actualInputPath = tmpCopy;
979
979
  logger.info(
980
980
  `[whisper-local] \u68C0\u6D4B\u5230\u5B9E\u9645\u683C\u5F0F ${detectedExt}\uFF0C\u4E34\u65F6\u91CD\u547D\u540D`
@@ -998,22 +998,22 @@ function convertToWav(inputPath, outputPath, actualFmt, logger) {
998
998
  timeout: 12e4,
999
999
  stdio: ["pipe", "pipe", "pipe"]
1000
1000
  });
1001
- if (tmpCopy && (0, import_node_fs25.existsSync)(tmpCopy)) {
1001
+ if (tmpCopy && (0, import_node_fs24.existsSync)(tmpCopy)) {
1002
1002
  try {
1003
- (0, import_node_fs25.unlinkSync)(tmpCopy);
1003
+ (0, import_node_fs24.unlinkSync)(tmpCopy);
1004
1004
  } catch {
1005
1005
  }
1006
1006
  }
1007
- if (afResult.status === 0 && (0, import_node_fs25.existsSync)(outputPath)) {
1007
+ if (afResult.status === 0 && (0, import_node_fs24.existsSync)(outputPath)) {
1008
1008
  logger.info(`[whisper-local] afconvert \u8F6C\u6362\u5B8C\u6210: ${outputPath}`);
1009
1009
  return { ok: true };
1010
1010
  }
1011
1011
  const stderr = afResult.stderr?.slice(0, 200) ?? "";
1012
1012
  return { ok: false, error: `afconvert \u8F6C\u6362\u5931\u8D25 (exit ${afResult.status}): ${stderr}` };
1013
1013
  } catch (err2) {
1014
- if (tmpCopy && (0, import_node_fs25.existsSync)(tmpCopy)) {
1014
+ if (tmpCopy && (0, import_node_fs24.existsSync)(tmpCopy)) {
1015
1015
  try {
1016
- (0, import_node_fs25.unlinkSync)(tmpCopy);
1016
+ (0, import_node_fs24.unlinkSync)(tmpCopy);
1017
1017
  } catch {
1018
1018
  }
1019
1019
  }
@@ -1024,9 +1024,9 @@ function convertToWav(inputPath, outputPath, actualFmt, logger) {
1024
1024
  return { ok: false, error: `\u65E0\u6CD5\u5C06\u97F3\u9891\u8F6C\u6362\u4E3A WAV \u683C\u5F0F\u3002${fmtHint}` };
1025
1025
  }
1026
1026
  function cleanupTmpWav(path2) {
1027
- if (path2 && (0, import_node_fs25.existsSync)(path2)) {
1027
+ if (path2 && (0, import_node_fs24.existsSync)(path2)) {
1028
1028
  try {
1029
- (0, import_node_fs25.unlinkSync)(path2);
1029
+ (0, import_node_fs24.unlinkSync)(path2);
1030
1030
  } catch {
1031
1031
  }
1032
1032
  }
@@ -1112,13 +1112,13 @@ function formatBytes2(bytes) {
1112
1112
  if (bytes < 1024 * 1024 * 1024) return `${(bytes / (1024 * 1024)).toFixed(1)}MB`;
1113
1113
  return `${(bytes / (1024 * 1024 * 1024)).toFixed(2)}GB`;
1114
1114
  }
1115
- var import_node_child_process3, import_node_fs25, import_node_path21, import_promises3, import_node_stream2, import_node_os4, WHISPER_MODELS_DIR, WHISPER_BIN_DIR, HF_MODEL_URL_TEMPLATE, MODELSCOPE_MODEL_URL_TEMPLATE, PROBE_TIMEOUT_MS, MODEL_FILENAMES, MODEL_DISK_SIZES;
1115
+ var import_node_child_process3, import_node_fs24, import_node_path20, import_promises3, import_node_stream2, import_node_os4, WHISPER_MODELS_DIR, WHISPER_BIN_DIR, HF_MODEL_URL_TEMPLATE, MODELSCOPE_MODEL_URL_TEMPLATE, PROBE_TIMEOUT_MS, MODEL_FILENAMES, MODEL_DISK_SIZES;
1116
1116
  var init_whisper_local = __esm({
1117
1117
  "src/recording/whisper-local.ts"() {
1118
1118
  "use strict";
1119
1119
  import_node_child_process3 = require("child_process");
1120
- import_node_fs25 = require("fs");
1121
- import_node_path21 = require("path");
1120
+ import_node_fs24 = require("fs");
1121
+ import_node_path20 = require("path");
1122
1122
  import_promises3 = require("stream/promises");
1123
1123
  import_node_stream2 = require("stream");
1124
1124
  import_node_os4 = require("os");
@@ -1273,7 +1273,7 @@ async function initializeAsr(config, dataDir, logger) {
1273
1273
  }
1274
1274
  }
1275
1275
  async function transcribeAudio(audioFilePath, config, logger, options = {}) {
1276
- if (!(0, import_node_fs26.existsSync)(audioFilePath)) {
1276
+ if (!(0, import_node_fs25.existsSync)(audioFilePath)) {
1277
1277
  return { ok: false, error: `\u97F3\u9891\u6587\u4EF6\u4E0D\u5B58\u5728: ${audioFilePath}` };
1278
1278
  }
1279
1279
  logger.info(
@@ -1419,8 +1419,8 @@ async function runTranscriptionWorkflow(params) {
1419
1419
  createdAt
1420
1420
  );
1421
1421
  const transcriptDataFilename = buildTranscriptDataFilename(recordingId);
1422
- const transcriptDataPath = (0, import_node_path22.join)(transcriptDataDir, transcriptDataFilename);
1423
- (0, import_node_fs26.writeFileSync)(
1422
+ const transcriptDataPath = (0, import_node_path21.join)(transcriptDataDir, transcriptDataFilename);
1423
+ (0, import_node_fs25.writeFileSync)(
1424
1424
  transcriptDataPath,
1425
1425
  JSON.stringify(transcriptData, null, 2),
1426
1426
  "utf-8"
@@ -1428,14 +1428,14 @@ async function runTranscriptionWorkflow(params) {
1428
1428
  logger.info(`[asr] \u8F6C\u5199 JSON \u5DF2\u5199\u5165: ${transcriptDataPath}`);
1429
1429
  const safeSummary = title.replace(/[/\\:*?"<>|]/g, "").trim().slice(0, 20);
1430
1430
  const filename = safeSummary ? `${recordingId}_${safeSummary}.md` : `${recordingId}.md`;
1431
- const filePath = (0, import_node_path22.join)(transcriptsDir, filename);
1432
- (0, import_node_fs26.writeFileSync)(filePath, markdown, "utf-8");
1431
+ const filePath = (0, import_node_path21.join)(transcriptsDir, filename);
1432
+ (0, import_node_fs25.writeFileSync)(filePath, markdown, "utf-8");
1433
1433
  logger.info(`[asr] \u8F6C\u5199\u6587\u672C\u5DF2\u5199\u5165: ${filePath}`);
1434
1434
  let summaryFilename;
1435
1435
  if (summary) {
1436
1436
  summaryFilename = `${recordingId}.md`;
1437
- const summaryFilePath = (0, import_node_path22.join)(summariesDir, summaryFilename);
1438
- (0, import_node_fs26.writeFileSync)(summaryFilePath, summary, "utf-8");
1437
+ const summaryFilePath = (0, import_node_path21.join)(summariesDir, summaryFilename);
1438
+ (0, import_node_fs25.writeFileSync)(summaryFilePath, summary, "utf-8");
1439
1439
  logger.info(`[asr] \u6458\u8981\u6587\u672C\u5DF2\u5199\u5165: ${summaryFilePath}`);
1440
1440
  }
1441
1441
  return {
@@ -1521,7 +1521,7 @@ async function transcribeWithModelProxy(audioOssUrl, audioDurationMs, apiConfig,
1521
1521
  }
1522
1522
  async function transcribeWithWhisperLocal2(audioFilePath, config, logger) {
1523
1523
  const { transcribeWithWhisperLocal: runLocal } = await Promise.resolve().then(() => (init_whisper_local(), whisper_local_exports));
1524
- const dataDir = process.env.OPENCLAW_STATE_DIR ?? process.env.QCLAW_STATE_DIR ?? (0, import_node_path22.join)(audioFilePath, "..", "..", "..");
1524
+ const dataDir = process.env.OPENCLAW_STATE_DIR ?? process.env.QCLAW_STATE_DIR ?? (0, import_node_path21.join)(audioFilePath, "..", "..", "..");
1525
1525
  const localConfig = config.local ?? {};
1526
1526
  const result = await runLocal(
1527
1527
  audioFilePath,
@@ -1830,12 +1830,12 @@ function formatTranscriptSegmentText(segment) {
1830
1830
  }
1831
1831
  return text;
1832
1832
  }
1833
- var import_node_fs26, import_node_path22, DEFAULT_LONG_RECORDING_POLL_INTERVAL_MS, DEFAULT_LONG_RECORDING_MAX_POLL_ATTEMPTS, LONG_RECORDING_RUNNING_STATUSES, LONG_RECORDING_TERMINAL_FAILURE_STATUSES;
1833
+ var import_node_fs25, import_node_path21, DEFAULT_LONG_RECORDING_POLL_INTERVAL_MS, DEFAULT_LONG_RECORDING_MAX_POLL_ATTEMPTS, LONG_RECORDING_RUNNING_STATUSES, LONG_RECORDING_TERMINAL_FAILURE_STATUSES;
1834
1834
  var init_asr = __esm({
1835
1835
  "src/recording/asr.ts"() {
1836
1836
  "use strict";
1837
- import_node_fs26 = require("fs");
1838
- import_node_path22 = require("path");
1837
+ import_node_fs25 = require("fs");
1838
+ import_node_path21 = require("path");
1839
1839
  init_credentials();
1840
1840
  init_env();
1841
1841
  init_transcript_document();
@@ -5480,7 +5480,7 @@ function readBuildInjectedVersion() {
5480
5480
  if (false) {
5481
5481
  return void 0;
5482
5482
  }
5483
- const version = "1.11.4-beta.2".trim();
5483
+ const version = "1.11.4-beta.4".trim();
5484
5484
  return version || void 0;
5485
5485
  }
5486
5486
  function readPluginVersionFromPackageJson() {
@@ -7097,109 +7097,6 @@ function resolveLightTitle(title, reason, segments) {
7097
7097
  return `Effect: ${modeDesc || "custom"}`;
7098
7098
  }
7099
7099
 
7100
- // src/light-rules/local-matcher.ts
7101
- var CONTACT_PREFIXES = ["\u91CD\u8981\u8054\u7CFB\u4EBA", "\u8054\u7CFB\u4EBA"];
7102
- var CONTACT_ACTION_RE = /(给我)?发(?:来)?消息|(给我)?发信息|来消息|回复(?:我)?|回应(?:我)?|私聊(?:我)?/u;
7103
- var LEADING_SENDER_RE = /^(.{1,40}?)(?:\s+回应)?\s*[::]/u;
7104
- function matchNotificationsLocally(notifications, rules) {
7105
- const parsedRules = rules.map((rule) => parseContactRule(rule)).filter((item) => item !== null);
7106
- if (parsedRules.length === 0) {
7107
- return [];
7108
- }
7109
- const results = [];
7110
- notifications.forEach((notification, notificationIndex) => {
7111
- const senders = collectSenderCandidates(notification);
7112
- if (senders.length === 0) {
7113
- return;
7114
- }
7115
- parsedRules.forEach((rule) => {
7116
- if (senders.some((sender) => senderMatchesContact(sender, rule.contactName))) {
7117
- results.push({ notificationIndex, ruleName: rule.ruleName });
7118
- }
7119
- });
7120
- });
7121
- return results;
7122
- }
7123
- function parseContactRule(rule) {
7124
- const contactName = extractImportantContact(rule.description);
7125
- if (!contactName) {
7126
- return null;
7127
- }
7128
- return {
7129
- ruleName: rule.name,
7130
- contactName
7131
- };
7132
- }
7133
- function extractImportantContact(description) {
7134
- const text = description.trim();
7135
- if (!text) {
7136
- return null;
7137
- }
7138
- for (const prefix of CONTACT_PREFIXES) {
7139
- const start = text.indexOf(prefix);
7140
- if (start < 0) {
7141
- continue;
7142
- }
7143
- const tail = text.slice(start + prefix.length);
7144
- const actionMatch = CONTACT_ACTION_RE.exec(tail);
7145
- if (!actionMatch || actionMatch.index === 0) {
7146
- continue;
7147
- }
7148
- const rawName = tail.slice(0, actionMatch.index).trim();
7149
- const cleaned = trimContactName(rawName);
7150
- if (cleaned) {
7151
- return cleaned;
7152
- }
7153
- }
7154
- return null;
7155
- }
7156
- function trimContactName(value) {
7157
- const cleaned = value.replace(/^[是为叫做名为\s]+/u, "").replace(/[,。,.;;].*$/u, "").trim();
7158
- return cleaned || null;
7159
- }
7160
- function collectSenderCandidates(notification) {
7161
- const candidates = [
7162
- notification.senderName,
7163
- notification.title,
7164
- notification.conversationName,
7165
- extractLeadingSender(notification.content)
7166
- ];
7167
- const deduped = /* @__PURE__ */ new Map();
7168
- for (const candidate of candidates) {
7169
- if (!candidate) {
7170
- continue;
7171
- }
7172
- const normalized = normalizeComparableName(candidate);
7173
- if (normalized.length < 2) {
7174
- continue;
7175
- }
7176
- deduped.set(normalized, candidate);
7177
- }
7178
- return Array.from(deduped.values());
7179
- }
7180
- function extractLeadingSender(content) {
7181
- const text = content.trim();
7182
- if (!text) {
7183
- return null;
7184
- }
7185
- const match = LEADING_SENDER_RE.exec(text);
7186
- if (!match) {
7187
- return null;
7188
- }
7189
- return match[1]?.trim() || null;
7190
- }
7191
- function senderMatchesContact(sender, contactName) {
7192
- const normalizedSender = normalizeComparableName(sender);
7193
- const normalizedContact = normalizeComparableName(contactName);
7194
- if (!normalizedSender || !normalizedContact) {
7195
- return false;
7196
- }
7197
- return normalizedSender.includes(normalizedContact) || normalizedContact.includes(normalizedSender);
7198
- }
7199
- function normalizeComparableName(value) {
7200
- return value.normalize("NFKC").replace(new RegExp("\\p{Extended_Pictographic}", "gu"), "").replace(/\s+/gu, "").replace(/[^\p{L}\p{N}]/gu, "");
7201
- }
7202
-
7203
7100
  // src/light-rules/inline-evaluator.ts
7204
7101
  var InlineLightRuleEvaluator = class {
7205
7102
  logger;
@@ -7219,40 +7116,8 @@ var InlineLightRuleEvaluator = class {
7219
7116
  if (notifications.length === 0) return true;
7220
7117
  const rules = this.registry.getEnabled();
7221
7118
  if (rules.length === 0) return true;
7222
- const localMatches = matchNotificationsLocally(notifications, rules);
7223
- const localMatchedRuleNames = new Set(localMatches.map((match) => match.ruleName));
7224
- const llmCandidateRules = rules.filter((rule) => !localMatchedRuleNames.has(rule.name));
7225
- const combinedMatches = /* @__PURE__ */ new Map();
7226
- for (const match of localMatches) {
7227
- combinedMatches.set(
7228
- `${match.notificationIndex}:${match.ruleName}`,
7229
- match
7230
- );
7231
- }
7232
- if (localMatches.length > 0) {
7233
- this.logger.info(
7234
- `lightrules: local matched ${localMatchedRuleNames.size} rule(s) (notifications=${notifications.length}, remainingRules=${llmCandidateRules.length})`
7235
- );
7236
- }
7237
- if (llmCandidateRules.length > 0) {
7238
- const llmMatches = await this.invoker.matchNotifications(notifications, llmCandidateRules);
7239
- if (llmMatches === null) {
7240
- if (combinedMatches.size === 0) {
7241
- return false;
7242
- }
7243
- this.logger.warn(
7244
- `lightrules: invoker failed; using ${combinedMatches.size} local fallback match(es)`
7245
- );
7246
- } else {
7247
- for (const match of llmMatches) {
7248
- combinedMatches.set(
7249
- `${match.notificationIndex}:${match.ruleName}`,
7250
- match
7251
- );
7252
- }
7253
- }
7254
- }
7255
- const matches = Array.from(combinedMatches.values());
7119
+ const matches = await this.invoker.matchNotifications(notifications, rules);
7120
+ if (matches === null) return false;
7256
7121
  if (matches.length === 0) {
7257
7122
  this.logger.info(
7258
7123
  `lightrules: 0 matches (notifications=${notifications.length}, rules=${rules.length})`
@@ -7465,15 +7330,35 @@ function buildSystemPrompt(rules) {
7465
7330
  lines.push("");
7466
7331
  });
7467
7332
  lines.push(
7468
- "\u4EFB\u52A1\uFF1A\u5BF9\u7528\u6237\u63A5\u4E0B\u6765\u53D1\u6765\u7684\u6BCF\u6761\u901A\u77E5\uFF0C\u5224\u65AD\u5B83\u547D\u4E2D\u4E86\u54EA\u4E9B\u89C4\u5219\u3002",
7333
+ "\u5DE5\u4F5C\u6B65\u9AA4\uFF08\u5FC5\u987B\u6309\u987A\u5E8F\u6267\u884C\uFF09\uFF1A",
7334
+ "",
7335
+ "1. \u5148\u628A\u6BCF\u6761\u89C4\u5219\u6309 description \u5F52\u7C7B\uFF1A",
7336
+ ' - \u53D1\u4EF6\u4EBA\u578B\uFF1A\u63CF\u8FF0\u91CC\u51FA\u73B0\u5177\u4F53\u4EBA\u540D/\u79F0\u8C13\uFF08\u542B"\u91CD\u8981\u8054\u7CFB\u4EBA X""\u8001\u5A46""\u5F20\u67D0\u67D0"\u7B49\uFF09+ "\u53D1\u6D88\u606F/\u6765\u6D88\u606F/\u56DE\u590D/\u79C1\u804A"\u7B49\u52A8\u4F5C',
7337
+ " - \u5173\u952E\u8BCD\u578B\uFF1A\u63CF\u8FF0\u8981\u6C42\u6D88\u606F\u4E2D\u5305\u542B\u7279\u5B9A\u5B57\u773C/\u8BDD\u9898",
7338
+ " - \u8BED\u4E49\u578B\uFF1A\u9700\u8981\u7406\u89E3\u6D88\u606F\u542B\u4E49\u624D\u80FD\u5224\u65AD\uFF08\u60C5\u7EEA\u3001\u610F\u56FE\u3001\u573A\u666F\u7B49\uFF09",
7469
7339
  "",
7470
- "\u8F93\u51FA\u5FC5\u987B\u662F\u7EAF JSON\uFF08\u4E0D\u8981 Markdown \u4EE3\u7801\u5757\u3001\u4E0D\u8981\u4EFB\u4F55\u89E3\u91CA\u6587\u5B57\uFF09\uFF1A",
7340
+ "2. \u6309\u7C7B\u578B\u5224\u65AD\u6BCF\u6761\u901A\u77E5\u662F\u5426\u547D\u4E2D\uFF1A",
7341
+ ' - \u53D1\u4EF6\u4EBA\u578B\uFF1A\u4F9D\u6B21\u6BD4\u5BF9 sender \u2192 conversation_name \u2192 title \u2192 content \u5F00\u5934\u7684 "X:"/"X\uFF1A" \u524D\u7F00\u3002',
7342
+ " \u6BD4\u8F83\u524D\u5148\u5F52\u4E00\u5316\uFF08NFKC\u3001\u53BB\u7A7A\u767D\u3001\u53BB emoji\u3001\u5FFD\u7565\u4E2D\u82F1\u6587\u6807\u70B9\u5DEE\u5F02\uFF09\u3002",
7343
+ " \u4EFB\u4E00\u5B57\u6BB5\u4E0E\u63CF\u8FF0\u4E2D\u7684\u4EBA\u540D\u4E92\u76F8\u5305\u542B\uFF08A \u662F B \u7684\u5B50\u4E32\u6216\u53CD\u4E4B\uFF09\u5373\u89C6\u4E3A\u547D\u4E2D\u3002",
7344
+ ' \u4E0D\u8981\u6C42\u901A\u77E5\u91CC\u51FA\u73B0"\u91CD\u8981\u8054\u7CFB\u4EBA"\u7B49\u4FEE\u9970\u8BCD\uFF0C\u53EA\u8981\u540D\u5B57\u5BF9\u5F97\u4E0A\u5C31\u89E6\u53D1\u3002',
7345
+ " - \u5173\u952E\u8BCD\u578B\uFF1A\u5728 title + content \u4E2D\u67E5\u627E\u5173\u952E\u8BCD\uFF08\u540C\u6837\u5F52\u4E00\u5316\u540E\u6BD4\u8F83\uFF0C\u652F\u6301\u540C\u4E49/\u8FD1\u4E49\u5339\u914D\uFF09",
7346
+ " - \u8BED\u4E49\u578B\uFF1A\u7EFC\u5408\u7406\u89E3 title + content \u540E\u5224\u65AD\uFF1B\u8FD9\u4E00\u7C7B\u62FF\u4E0D\u51C6\u65F6\u503E\u5411\u4E8E\u4E0D\u89E6\u53D1",
7347
+ "",
7348
+ "3. \u8F93\u51FA JSON\u3002",
7349
+ "",
7350
+ "\u8F93\u51FA\u683C\u5F0F\uFF08\u7EAF JSON\uFF0C\u4E0D\u8981 Markdown \u4EE3\u7801\u5757\u3001\u4E0D\u8981\u4EFB\u4F55\u89E3\u91CA\u6587\u5B57\uFF09\uFF1A",
7471
7351
  '{"matches":[{"i":<\u901A\u77E5\u4E0B\u6807>,"rule":"<\u89C4\u5219name>"}]}',
7472
7352
  "",
7473
7353
  '- \u547D\u4E2D 0 \u6761 \u2192 \u8F93\u51FA {"matches":[]}',
7474
- "- \u4E00\u6761\u901A\u77E5\u53EF\u80FD\u547D\u4E2D\u591A\u6761\u89C4\u5219 \u2192 \u6BCF\u4E2A\u7EC4\u5408\u4E00\u4E2A\u6761\u76EE",
7475
- '- \u4E25\u683C\u6309\u89C4\u5219 description \u5224\u65AD\uFF0C\u62FF\u4E0D\u51C6\u65F6\u503E\u5411\u4E8E"\u4E0D\u89E6\u53D1"',
7476
- "- rule \u5FC5\u987B\u7CBE\u786E\u7B49\u4E8E\u4E0A\u9762\u5217\u51FA\u7684 name \u5B57\u7B26\u4E32"
7354
+ "- \u4E00\u6761\u901A\u77E5\u53EF\u547D\u4E2D\u591A\u6761\u89C4\u5219 \u2192 \u6BCF\u4E2A\u7EC4\u5408\u4E00\u4E2A\u6761\u76EE",
7355
+ "- rule \u5FC5\u987B\u7CBE\u786E\u7B49\u4E8E\u4E0A\u9762\u5217\u51FA\u7684 name \u5B57\u7B26\u4E32",
7356
+ "",
7357
+ "\u793A\u4F8B\uFF08\u4EC5\u4F9B\u53C2\u8003\u5339\u914D\u7B56\u7565\uFF0C\u4E0D\u4EE3\u8868\u5F53\u524D\u89C4\u5219\uFF09\uFF1A",
7358
+ ' \u89C4\u5219 description: "\u91CD\u8981\u8054\u7CFB\u4EBA \u7814\u53D1\u5C0F\u52A9\u7406 \u53D1\u6D88\u606F\u65F6\u63D0\u9192"',
7359
+ ' \u901A\u77E5 sender="\u7814\u53D1\u5C0F\u52A9\u7406\u{1F990}" \u2192 \u547D\u4E2D\uFF08\u53BB emoji \u540E\u540D\u5B57\u76F8\u540C\uFF09',
7360
+ ' \u901A\u77E5 sender="\u674E\u56DB", content="\u7814\u53D1\u5C0F\u52A9\u7406 \u56DE\u5E94: \u6536\u5230" \u2192 \u547D\u4E2D\uFF08content \u5934\u90E8\u524D\u7F00\u5339\u914D\uFF09',
7361
+ ' \u901A\u77E5 sender="\u738B\u4E94", content="\u7814\u53D1\u5C0F\u52A9\u7406\u8FD9\u4E2A\u4EA7\u54C1\u633A\u597D" \u2192 \u4E0D\u547D\u4E2D\uFF08\u540D\u5B57\u51FA\u73B0\u5728\u6B63\u6587\u4F46\u4E0D\u662F\u53D1\u4EF6\u4EBA\uFF09'
7477
7362
  );
7478
7363
  return lines.join("\n");
7479
7364
  }
@@ -7542,6 +7427,43 @@ function resolveUpdateChannel(params) {
7542
7427
  // src/update/index.ts
7543
7428
  var import_node_path8 = require("path");
7544
7429
 
7430
+ // src/plugin/runtime-state.ts
7431
+ init_host();
7432
+
7433
+ // src/plugin/shared.ts
7434
+ function readBody(req) {
7435
+ return new Promise((resolve, reject) => {
7436
+ const chunks = [];
7437
+ req.on("data", (chunk) => chunks.push(chunk));
7438
+ req.on("end", () => resolve(Buffer.concat(chunks).toString()));
7439
+ req.on("error", reject);
7440
+ });
7441
+ }
7442
+ function trimToUndefined2(value) {
7443
+ if (typeof value !== "string") {
7444
+ return void 0;
7445
+ }
7446
+ const trimmed = value.trim();
7447
+ return trimmed || void 0;
7448
+ }
7449
+
7450
+ // src/plugin/runtime-state.ts
7451
+ function tryResolveRuntimeStateDir(api) {
7452
+ const runtimeState = api?.runtime?.state;
7453
+ const resolveStateDir2 = runtimeState?.resolveStateDir;
7454
+ if (typeof resolveStateDir2 !== "function") {
7455
+ return void 0;
7456
+ }
7457
+ try {
7458
+ return trimToUndefined2(resolveStateDir2.call(runtimeState));
7459
+ } catch {
7460
+ return void 0;
7461
+ }
7462
+ }
7463
+ function resolvePluginStateDir(api) {
7464
+ return tryResolveRuntimeStateDir(api) ?? resolveStateDir();
7465
+ }
7466
+
7545
7467
  // src/update/checker.ts
7546
7468
  function parseSemver(v) {
7547
7469
  const [main, pre = null] = v.split("-", 2);
@@ -7762,7 +7684,7 @@ function resolveTargetDir(api) {
7762
7684
  if (installPath) return installPath;
7763
7685
  } catch {
7764
7686
  }
7765
- return (0, import_node_path8.join)(api.runtime.state.resolveStateDir(), "extensions", PLUGIN_ID);
7687
+ return (0, import_node_path8.join)(resolvePluginStateDir(api), "extensions", PLUGIN_ID);
7766
7688
  }
7767
7689
  async function updateConfigRecord(api, version, targetDir, tgzUrl) {
7768
7690
  const configApi = api.runtime.config;
@@ -7972,7 +7894,7 @@ function registerAutoUpdateLifecycle(deps) {
7972
7894
  }
7973
7895
 
7974
7896
  // src/plugin/cli.ts
7975
- var import_node_path16 = require("path");
7897
+ var import_node_path15 = require("path");
7976
7898
 
7977
7899
  // src/cli/auth.ts
7978
7900
  var import_node_fs9 = require("fs");
@@ -8647,21 +8569,155 @@ function registerLightSetupTools(light) {
8647
8569
  }
8648
8570
 
8649
8571
  // src/cli/tunnel-status.ts
8650
- var import_node_fs13 = require("fs");
8651
- var import_node_path12 = require("path");
8652
8572
  init_credentials();
8653
8573
  init_env();
8654
- var STATUS_REL_PATH = (0, import_node_path12.join)("plugins", "phone-notifications", "tunnel-status.json");
8655
- function readTunnelStatus(ctx) {
8656
- if (!ctx.stateDir) return null;
8657
- const filePath = (0, import_node_path12.join)(ctx.stateDir, STATUS_REL_PATH);
8658
- if (!(0, import_node_fs13.existsSync)(filePath)) return null;
8574
+
8575
+ // src/tunnel/status.ts
8576
+ var import_node_fs13 = require("fs");
8577
+ var import_node_path12 = require("path");
8578
+ var TUNNEL_STATUS_REL_PATH = (0, import_node_path12.join)(
8579
+ "plugins",
8580
+ "phone-notifications",
8581
+ "tunnel-status.json"
8582
+ );
8583
+ var TUNNEL_LOCK_REL_PATH = (0, import_node_path12.join)(
8584
+ "plugins",
8585
+ "phone-notifications",
8586
+ "relay-tunnel.lock"
8587
+ );
8588
+ function isTunnelState(value) {
8589
+ return value === "connected" || value === "connecting" || value === "disconnected" || value === "stopped";
8590
+ }
8591
+ function isTunnelStatusInfo(value) {
8592
+ if (!value || typeof value !== "object" || Array.isArray(value)) return false;
8593
+ const obj = value;
8594
+ return isTunnelState(obj.state) && typeof obj.since === "string" && typeof obj.reconnectAttempt === "number" && (obj.lastDisconnectReason === void 0 || typeof obj.lastDisconnectReason === "string");
8595
+ }
8596
+ function parseTunnelLockInfo(value) {
8597
+ if (!value || typeof value !== "object" || Array.isArray(value)) return null;
8598
+ const obj = value;
8599
+ return typeof obj.pid === "number" && typeof obj.startedAt === "string" ? { pid: obj.pid, startedAt: obj.startedAt } : null;
8600
+ }
8601
+ function isProcessAlive(pid) {
8602
+ if (!Number.isInteger(pid) || pid <= 0) return false;
8603
+ if (pid === process.pid) return true;
8604
+ try {
8605
+ process.kill(pid, 0);
8606
+ return true;
8607
+ } catch (err2) {
8608
+ return err2?.code === "EPERM";
8609
+ }
8610
+ }
8611
+ function isStatusOlderThanLock(statusSince, lockStartedAt) {
8612
+ const statusTs = Date.parse(statusSince);
8613
+ const lockTs = Date.parse(lockStartedAt);
8614
+ if (Number.isNaN(statusTs) || Number.isNaN(lockTs)) return false;
8615
+ return statusTs + 1e3 < lockTs;
8616
+ }
8617
+ function staleStatusMessage(status, lock) {
8618
+ const prefix = `\u96A7\u9053\u72B6\u6001\u6587\u4EF6\u663E\u793A\u5DF2\u8FDE\u63A5\uFF08\u81EA ${status.since}\uFF09`;
8619
+ if (!lock.exists) {
8620
+ return `${prefix}\uFF0C\u4F46\u672A\u627E\u5230\u672C\u5730\u8FD0\u884C\u9501\uFF0C\u72B6\u6001\u53EF\u80FD\u5DF2\u8FC7\u671F\u3002\u8BF7\u91CD\u542F openclaw \u4E3B\u8FDB\u7A0B\u3002`;
8621
+ }
8622
+ if (lock.pid === null || lock.startedAt === null) {
8623
+ return `${prefix}\uFF0C\u4F46\u672C\u5730\u8FD0\u884C\u9501\u5185\u5BB9\u4E0D\u5B8C\u6574\uFF0C\u65E0\u6CD5\u786E\u8BA4\u5F53\u524D\u8FDE\u63A5\u662F\u5426\u4ECD\u7136\u6709\u6548\u3002\u8BF7\u91CD\u542F openclaw \u4E3B\u8FDB\u7A0B\u3002`;
8624
+ }
8625
+ if (lock.active === false) {
8626
+ return `${prefix}\uFF0C\u4F46\u672C\u5730\u8FD0\u884C\u9501\u4ECD\u6307\u5411\u5DF2\u9000\u51FA\u7684\u8FDB\u7A0B pid=${lock.pid}\uFF0C\u72B6\u6001\u53EF\u80FD\u5DF2\u8FC7\u671F\u3002\u8BF7\u91CD\u542F openclaw \u4E3B\u8FDB\u7A0B\u3002`;
8627
+ }
8628
+ return `${prefix}\uFF0C\u4F46\u5F53\u524D\u8FD0\u884C\u9501\u542F\u52A8\u4E8E ${lock.startedAt}\uFF0C\u665A\u4E8E\u72B6\u6001\u65F6\u95F4\uFF0C\u8BF4\u660E\u72B6\u6001\u672A\u968F\u65B0\u8FDB\u7A0B\u5237\u65B0\u3002\u8BF7\u91CD\u542F openclaw \u4E3B\u8FDB\u7A0B\u3002`;
8629
+ }
8630
+ function assessTunnelStatus(stateDir) {
8631
+ const statusFilePath = (0, import_node_path12.join)(stateDir, TUNNEL_STATUS_REL_PATH);
8632
+ const lockFilePath = (0, import_node_path12.join)(stateDir, TUNNEL_LOCK_REL_PATH);
8633
+ if (!(0, import_node_fs13.existsSync)(statusFilePath)) {
8634
+ return {
8635
+ status: null,
8636
+ issueCode: "STATUS_NOT_FOUND",
8637
+ issueMessage: "\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",
8638
+ statusFilePath,
8639
+ lockFilePath,
8640
+ lock: {
8641
+ exists: (0, import_node_fs13.existsSync)(lockFilePath),
8642
+ pid: null,
8643
+ startedAt: null,
8644
+ active: null
8645
+ }
8646
+ };
8647
+ }
8648
+ let rawStatus;
8659
8649
  try {
8660
- return JSON.parse((0, import_node_fs13.readFileSync)(filePath, "utf-8"));
8650
+ rawStatus = JSON.parse((0, import_node_fs13.readFileSync)(statusFilePath, "utf-8"));
8661
8651
  } catch {
8662
- return null;
8652
+ return {
8653
+ status: null,
8654
+ issueCode: "STATUS_INVALID",
8655
+ issueMessage: "\u96A7\u9053\u72B6\u6001\u6587\u4EF6\u89E3\u6790\u5931\u8D25\uFF0C\u6587\u4EF6\u5185\u5BB9\u635F\u574F\u6216\u683C\u5F0F\u4E0D\u6B63\u786E\u3002\u8BF7\u91CD\u542F openclaw \u4E3B\u8FDB\u7A0B\u3002",
8656
+ statusFilePath,
8657
+ lockFilePath,
8658
+ lock: {
8659
+ exists: (0, import_node_fs13.existsSync)(lockFilePath),
8660
+ pid: null,
8661
+ startedAt: null,
8662
+ active: null
8663
+ }
8664
+ };
8665
+ }
8666
+ if (!isTunnelStatusInfo(rawStatus)) {
8667
+ return {
8668
+ status: null,
8669
+ issueCode: "STATUS_INVALID",
8670
+ issueMessage: "\u96A7\u9053\u72B6\u6001\u6587\u4EF6\u7F3A\u5C11\u5FC5\u8981\u5B57\u6BB5\u6216\u5B57\u6BB5\u7C7B\u578B\u4E0D\u6B63\u786E\u3002\u8BF7\u91CD\u542F openclaw \u4E3B\u8FDB\u7A0B\u3002",
8671
+ statusFilePath,
8672
+ lockFilePath,
8673
+ lock: {
8674
+ exists: (0, import_node_fs13.existsSync)(lockFilePath),
8675
+ pid: null,
8676
+ startedAt: null,
8677
+ active: null
8678
+ }
8679
+ };
8663
8680
  }
8681
+ const status = rawStatus;
8682
+ const lockExists = (0, import_node_fs13.existsSync)(lockFilePath);
8683
+ let lockInfo = null;
8684
+ if (lockExists) {
8685
+ try {
8686
+ lockInfo = parseTunnelLockInfo(JSON.parse((0, import_node_fs13.readFileSync)(lockFilePath, "utf-8")));
8687
+ } catch {
8688
+ lockInfo = null;
8689
+ }
8690
+ }
8691
+ const lock = {
8692
+ exists: lockExists,
8693
+ pid: lockInfo?.pid ?? null,
8694
+ startedAt: lockInfo?.startedAt ?? null,
8695
+ active: lockInfo ? isProcessAlive(lockInfo.pid) : null
8696
+ };
8697
+ if (status.state === "connected") {
8698
+ const isStale = !lock.exists || lock.pid === null || lock.startedAt === null || lock.active === false || isStatusOlderThanLock(status.since, lock.startedAt);
8699
+ if (isStale) {
8700
+ return {
8701
+ status,
8702
+ issueCode: "STATUS_STALE",
8703
+ issueMessage: staleStatusMessage(status, lock),
8704
+ statusFilePath,
8705
+ lockFilePath,
8706
+ lock
8707
+ };
8708
+ }
8709
+ }
8710
+ return {
8711
+ status,
8712
+ issueCode: null,
8713
+ issueMessage: null,
8714
+ statusFilePath,
8715
+ lockFilePath,
8716
+ lock
8717
+ };
8664
8718
  }
8719
+
8720
+ // src/cli/tunnel-status.ts
8665
8721
  function formatMessage(status) {
8666
8722
  switch (status.state) {
8667
8723
  case "connected":
@@ -8692,13 +8748,23 @@ function registerTunnelStatus(ntf, ctx) {
8692
8748
  "API Key \u672A\u8BBE\u7F6E\uFF0C\u96A7\u9053\u65E0\u6CD5\u8FDE\u63A5\u3002\u8BF7\u6267\u884C openclaw ntf auth set-api-key <apiKey>"
8693
8749
  );
8694
8750
  }
8695
- const status = readTunnelStatus(ctx);
8751
+ if (!ctx.stateDir) {
8752
+ exitError(
8753
+ "STATE_DIR_UNAVAILABLE",
8754
+ "\u65E0\u6CD5\u786E\u5B9A\u72B6\u6001\u76EE\u5F55\uFF0C\u65E0\u6CD5\u68C0\u67E5\u96A7\u9053\u72B6\u6001\u3002\u8BF7\u5728 openclaw \u4E3B\u73AF\u5883\u4E2D\u6267\u884C\u8BE5\u547D\u4EE4\u3002"
8755
+ );
8756
+ }
8757
+ const assessment = assessTunnelStatus(ctx.stateDir);
8758
+ const status = assessment.status;
8696
8759
  if (!status) {
8697
8760
  exitError(
8698
- "STATUS_NOT_FOUND",
8699
- "\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"
8761
+ assessment.issueCode ?? "STATUS_NOT_FOUND",
8762
+ assessment.issueMessage ?? "\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"
8700
8763
  );
8701
8764
  }
8765
+ if (assessment.issueCode) {
8766
+ exitError(assessment.issueCode, assessment.issueMessage ?? formatMessage(status));
8767
+ }
8702
8768
  const ok2 = status.state === "connected";
8703
8769
  output({
8704
8770
  ok: ok2,
@@ -8709,6 +8775,11 @@ function registerTunnelStatus(ntf, ctx) {
8709
8775
  reconnectAttempt: status.reconnectAttempt,
8710
8776
  lastDisconnectReason: status.lastDisconnectReason
8711
8777
  },
8778
+ runtime: {
8779
+ lockOwnerPid: assessment.lock.pid,
8780
+ lockOwnerStartedAt: assessment.lock.startedAt,
8781
+ lockOwnerActive: assessment.lock.active
8782
+ },
8712
8783
  message: formatMessage(status)
8713
8784
  });
8714
8785
  if (!ok2) process.exit(1);
@@ -8888,7 +8959,7 @@ function registerEnvCli(ntf, deps = {}) {
8888
8959
  }
8889
8960
 
8890
8961
  // src/cli/doctor.ts
8891
- var import_node_fs18 = require("fs");
8962
+ var import_node_fs17 = require("fs");
8892
8963
  var import_node_readline = require("readline");
8893
8964
  init_host();
8894
8965
 
@@ -9037,39 +9108,40 @@ var checkCredentials = () => {
9037
9108
  };
9038
9109
 
9039
9110
  // src/cli/doctor/check-tunnel.ts
9040
- var import_node_fs17 = require("fs");
9041
- var import_node_path14 = require("path");
9042
- var STATUS_REL_PATH2 = (0, import_node_path14.join)(
9043
- "plugins",
9044
- "phone-notifications",
9045
- "tunnel-status.json"
9046
- );
9047
9111
  var checkTunnel = ({ stateDir }) => {
9048
- const filePath = (0, import_node_path14.join)(stateDir, STATUS_REL_PATH2);
9049
- if (!(0, import_node_fs17.existsSync)(filePath)) {
9112
+ const assessment = assessTunnelStatus(stateDir);
9113
+ const status = assessment.status;
9114
+ if (assessment.issueCode === "STATUS_NOT_FOUND") {
9050
9115
  return {
9051
9116
  id: "tunnel",
9052
9117
  severity: "warn",
9053
9118
  title: "\u96A7\u9053\u72B6\u6001\u6587\u4EF6\u4E0D\u5B58\u5728",
9054
- detail: "\u96A7\u9053\u670D\u52A1\u53EF\u80FD\u5C1A\u672A\u542F\u52A8\u8FC7\uFF0C\u6216\u72B6\u6001\u6587\u4EF6\u5DF2\u88AB\u5220\u9664\u3002",
9119
+ detail: assessment.issueMessage ?? "\u96A7\u9053\u670D\u52A1\u53EF\u80FD\u5C1A\u672A\u542F\u52A8\u8FC7\uFF0C\u6216\u72B6\u6001\u6587\u4EF6\u5DF2\u88AB\u5220\u9664\u3002",
9055
9120
  fixDescription: "\u8BF7\u786E\u8BA4 openclaw \u4E3B\u8FDB\u7A0B\u6B63\u5728\u8FD0\u884C",
9056
9121
  fix: null
9057
9122
  };
9058
9123
  }
9059
- let status;
9060
- try {
9061
- status = JSON.parse((0, import_node_fs17.readFileSync)(filePath, "utf-8"));
9062
- } catch {
9124
+ if (assessment.issueCode === "STATUS_INVALID") {
9063
9125
  return {
9064
9126
  id: "tunnel",
9065
9127
  severity: "warn",
9066
9128
  title: "\u96A7\u9053\u72B6\u6001\u6587\u4EF6\u89E3\u6790\u5931\u8D25",
9067
- detail: "\u6587\u4EF6\u5185\u5BB9\u635F\u574F\u6216\u683C\u5F0F\u4E0D\u6B63\u786E\u3002",
9129
+ detail: assessment.issueMessage ?? "\u6587\u4EF6\u5185\u5BB9\u635F\u574F\u6216\u683C\u5F0F\u4E0D\u6B63\u786E\u3002",
9068
9130
  fixDescription: "\u8BF7\u91CD\u542F openclaw \u4E3B\u8FDB\u7A0B",
9069
9131
  fix: null
9070
9132
  };
9071
9133
  }
9072
- if (status.state === "connected") return null;
9134
+ if (assessment.issueCode === "STATUS_STALE") {
9135
+ return {
9136
+ id: "tunnel",
9137
+ severity: "warn",
9138
+ title: "\u96A7\u9053\u72B6\u6001\u5DF2\u8FC7\u671F",
9139
+ detail: assessment.issueMessage ?? "\u96A7\u9053\u72B6\u6001\u6587\u4EF6\u4ECD\u663E\u793A\u5DF2\u8FDE\u63A5\uFF0C\u4F46\u8FD0\u884C\u65F6\u72B6\u6001\u5DF2\u5931\u6548\u3002",
9140
+ fixDescription: "\u8BF7\u91CD\u542F openclaw \u4E3B\u8FDB\u7A0B",
9141
+ fix: null
9142
+ };
9143
+ }
9144
+ if (!status || status.state === "connected") return null;
9073
9145
  const reasonSuffix = status.lastDisconnectReason ? `\uFF0C\u539F\u56E0: ${status.lastDisconnectReason}` : "";
9074
9146
  return {
9075
9147
  id: "tunnel",
@@ -9151,9 +9223,9 @@ function isObject5(v) {
9151
9223
  return !!v && typeof v === "object" && !Array.isArray(v);
9152
9224
  }
9153
9225
  function readConfig(configPath) {
9154
- if (!(0, import_node_fs18.existsSync)(configPath)) return {};
9226
+ if (!(0, import_node_fs17.existsSync)(configPath)) return {};
9155
9227
  try {
9156
- const parsed = JSON.parse((0, import_node_fs18.readFileSync)(configPath, "utf-8"));
9228
+ const parsed = JSON.parse((0, import_node_fs17.readFileSync)(configPath, "utf-8"));
9157
9229
  return isObject5(parsed) ? parsed : {};
9158
9230
  } catch {
9159
9231
  return {};
@@ -9356,7 +9428,7 @@ function registerRecStoragePath(rec, ctx) {
9356
9428
 
9357
9429
  // src/cli/rec-setup.ts
9358
9430
  var import_node_readline2 = require("readline");
9359
- var import_node_fs19 = require("fs");
9431
+ var import_node_fs18 = require("fs");
9360
9432
  function ask(rl, question) {
9361
9433
  return new Promise((resolve) => rl.question(question, resolve));
9362
9434
  }
@@ -9442,9 +9514,9 @@ async function setupLocal(rl) {
9442
9514
  function registerRecSetup(rec, ctx) {
9443
9515
  rec.command("setup").description("\u4EA4\u4E92\u5F0F\u914D\u7F6E ASR \u8F6C\u5199\u53C2\u6570\uFF0C\u4FDD\u5B58\u5230\u672C\u5730\u914D\u7F6E\u6587\u4EF6").action(async () => {
9444
9516
  const configPath = resolveAsrConfigPath(ctx);
9445
- if ((0, import_node_fs19.existsSync)(configPath)) {
9517
+ if ((0, import_node_fs18.existsSync)(configPath)) {
9446
9518
  try {
9447
- const existing = JSON.parse((0, import_node_fs19.readFileSync)(configPath, "utf-8"));
9519
+ const existing = JSON.parse((0, import_node_fs18.readFileSync)(configPath, "utf-8"));
9448
9520
  process.stderr.write(`\u5F53\u524D\u5DF2\u6709\u914D\u7F6E\uFF1Amode = ${existing.mode}`);
9449
9521
  if (existing.updatedAt) process.stderr.write(`\uFF0C\u66F4\u65B0\u4E8E ${existing.updatedAt}`);
9450
9522
  process.stderr.write("\n");
@@ -9457,7 +9529,7 @@ function registerRecSetup(rec, ctx) {
9457
9529
  const modeIdx = await askChoice(rl, "\u9009\u62E9\u6A21\u5F0F", ["api\uFF08\u4E91\u7AEF model-proxy \u957F\u5F55\u97F3\uFF09", "local\uFF08\u672C\u5730 Whisper\uFF09"]);
9458
9530
  const config = modeIdx === 0 ? await setupApi(rl) : await setupLocal(rl);
9459
9531
  const stored = { ...config, updatedAt: (/* @__PURE__ */ new Date()).toISOString() };
9460
- (0, import_node_fs19.writeFileSync)(configPath, JSON.stringify(stored, null, 2), "utf-8");
9532
+ (0, import_node_fs18.writeFileSync)(configPath, JSON.stringify(stored, null, 2), "utf-8");
9461
9533
  process.stderr.write(`
9462
9534
  \u2713 \u914D\u7F6E\u5DF2\u4FDD\u5B58\u5230 ${configPath}
9463
9535
 
@@ -9471,8 +9543,8 @@ function registerRecSetup(rec, ctx) {
9471
9543
 
9472
9544
  // src/cli/update.ts
9473
9545
  var import_node_child_process2 = require("child_process");
9474
- var import_node_fs20 = require("fs");
9475
- var import_node_path15 = require("path");
9546
+ var import_node_fs19 = require("fs");
9547
+ var import_node_path14 = require("path");
9476
9548
  var import_node_os3 = __toESM(require("os"), 1);
9477
9549
  init_host();
9478
9550
  var BASE_URL2 = "https://artifact.yoooclaw.com/plugin";
@@ -9531,9 +9603,9 @@ async function runUpdate(ctx, opts) {
9531
9603
  `);
9532
9604
  process.exit(1);
9533
9605
  }
9534
- const tmpScript = (0, import_node_path15.join)(import_node_os3.default.tmpdir(), `openclaw-install-${Date.now()}.mjs`);
9606
+ const tmpScript = (0, import_node_path14.join)(import_node_os3.default.tmpdir(), `openclaw-install-${Date.now()}.mjs`);
9535
9607
  try {
9536
- (0, import_node_fs20.writeFileSync)(tmpScript, installScript, "utf-8");
9608
+ (0, import_node_fs19.writeFileSync)(tmpScript, installScript, "utf-8");
9537
9609
  } catch (err2) {
9538
9610
  const msg = `\u5199\u5165\u4E34\u65F6\u6587\u4EF6\u5931\u8D25: ${err2?.message ?? String(err2)}`;
9539
9611
  if (json) {
@@ -9551,7 +9623,7 @@ async function runUpdate(ctx, opts) {
9551
9623
  { stdio: "inherit" }
9552
9624
  );
9553
9625
  try {
9554
- (0, import_node_fs20.unlinkSync)(tmpScript);
9626
+ (0, import_node_fs19.unlinkSync)(tmpScript);
9555
9627
  } catch {
9556
9628
  }
9557
9629
  if (result.error) {
@@ -9615,17 +9687,17 @@ function inferOpenClawRootDir(workspaceDir) {
9615
9687
  if (!workspaceDir) {
9616
9688
  return void 0;
9617
9689
  }
9618
- if ((0, import_node_path16.basename)(workspaceDir) !== "workspace") {
9690
+ if ((0, import_node_path15.basename)(workspaceDir) !== "workspace") {
9619
9691
  return void 0;
9620
9692
  }
9621
- return (0, import_node_path16.dirname)(workspaceDir);
9693
+ return (0, import_node_path15.dirname)(workspaceDir);
9622
9694
  }
9623
9695
  function registerPluginCli(api, params) {
9624
9696
  const { logger, openclawDir } = params;
9625
9697
  const registerAlias = (command) => {
9626
9698
  api.registerCli(
9627
9699
  (ctx) => {
9628
- const effectiveStateDir = openclawDir ?? inferOpenClawRootDir(ctx.workspaceDir);
9700
+ const effectiveStateDir = openclawDir ?? ctx.stateDir ?? inferOpenClawRootDir(ctx.workspaceDir);
9629
9701
  registerAllCli(
9630
9702
  ctx.program,
9631
9703
  {
@@ -9869,12 +9941,12 @@ function registerLightControlTool(api, logger) {
9869
9941
  }
9870
9942
 
9871
9943
  // src/plugin/lifecycle.ts
9872
- var import_node_fs30 = require("fs");
9944
+ var import_node_fs29 = require("fs");
9873
9945
  init_host();
9874
9946
 
9875
9947
  // src/notification/app-name-map.ts
9876
- var import_node_fs21 = require("fs");
9877
- var import_node_path17 = require("path");
9948
+ var import_node_fs20 = require("fs");
9949
+ var import_node_path16 = require("path");
9878
9950
  init_credentials();
9879
9951
  init_env();
9880
9952
  var PLUGIN_STATE_DIR = "phone-notifications";
@@ -9895,7 +9967,7 @@ function isAppNameMapApiResponse(v) {
9895
9967
  );
9896
9968
  }
9897
9969
  function getCachePath(stateDir) {
9898
- return (0, import_node_path17.join)(stateDir, "plugins", PLUGIN_STATE_DIR, CACHE_FILE);
9970
+ return (0, import_node_path16.join)(stateDir, "plugins", PLUGIN_STATE_DIR, CACHE_FILE);
9899
9971
  }
9900
9972
  function createAppNameMapProvider(opts) {
9901
9973
  const { stateDir, logger } = opts;
@@ -9907,9 +9979,9 @@ function createAppNameMapProvider(opts) {
9907
9979
  let inFlightFetch = null;
9908
9980
  function loadFromDisk() {
9909
9981
  const path2 = getCachePath(stateDir);
9910
- if (!(0, import_node_fs21.existsSync)(path2)) return;
9982
+ if (!(0, import_node_fs20.existsSync)(path2)) return;
9911
9983
  try {
9912
- const raw = JSON.parse((0, import_node_fs21.readFileSync)(path2, "utf-8"));
9984
+ const raw = JSON.parse((0, import_node_fs20.readFileSync)(path2, "utf-8"));
9913
9985
  if (!isRecordOfStrings(raw)) return;
9914
9986
  map.clear();
9915
9987
  for (const [k, v] of Object.entries(raw)) map.set(k, v);
@@ -9953,10 +10025,10 @@ function createAppNameMapProvider(opts) {
9953
10025
  logger.warn("[app-name-map] refresh succeeded but got 0 entries");
9954
10026
  return;
9955
10027
  }
9956
- const dir = (0, import_node_path17.join)(stateDir, "plugins", PLUGIN_STATE_DIR);
9957
- (0, import_node_fs21.mkdirSync)(dir, { recursive: true });
10028
+ const dir = (0, import_node_path16.join)(stateDir, "plugins", PLUGIN_STATE_DIR);
10029
+ (0, import_node_fs20.mkdirSync)(dir, { recursive: true });
9958
10030
  const cachePath = getCachePath(stateDir);
9959
- (0, import_node_fs21.writeFileSync)(cachePath, JSON.stringify(Object.fromEntries(map), null, 2), "utf-8");
10031
+ (0, import_node_fs20.writeFileSync)(cachePath, JSON.stringify(Object.fromEntries(map), null, 2), "utf-8");
9960
10032
  logger.info(`[app-name-map] refreshed ${map.size} entries from server and saved: ${cachePath}`);
9961
10033
  } catch (e) {
9962
10034
  const message = e instanceof Error ? e.message : String(e);
@@ -10000,9 +10072,9 @@ function createAppNameMapProvider(opts) {
10000
10072
  }
10001
10073
 
10002
10074
  // src/notification/storage.ts
10003
- var import_node_fs22 = require("fs");
10075
+ var import_node_fs21 = require("fs");
10004
10076
  var import_node_crypto2 = require("crypto");
10005
- var import_node_path18 = require("path");
10077
+ var import_node_path17 = require("path");
10006
10078
 
10007
10079
  // src/notification/feishu-normalize.ts
10008
10080
  function normalizeOptionalText(value) {
@@ -10130,7 +10202,7 @@ var NOTIFICATION_DIR_NAME = "notifications";
10130
10202
  var ID_INDEX_DIR_NAME = ".ids";
10131
10203
  var CONTENT_KEY_INDEX_DIR_NAME = ".keys";
10132
10204
  function getStateFallbackNotificationDir(stateDir) {
10133
- return (0, import_node_path18.join)(stateDir, "plugins", "phone-notifications", NOTIFICATION_DIR_NAME);
10205
+ return (0, import_node_path17.join)(stateDir, "plugins", "phone-notifications", NOTIFICATION_DIR_NAME);
10134
10206
  }
10135
10207
  function buildFallbackContent(n) {
10136
10208
  const body = n.body?.trim();
@@ -10148,8 +10220,8 @@ function buildFallbackContent(n) {
10148
10220
  }
10149
10221
  function ensureWritableDirectory(dir) {
10150
10222
  try {
10151
- (0, import_node_fs22.mkdirSync)(dir, { recursive: true });
10152
- (0, import_node_fs22.accessSync)(dir, import_node_fs22.constants.R_OK | import_node_fs22.constants.W_OK);
10223
+ (0, import_node_fs21.mkdirSync)(dir, { recursive: true });
10224
+ (0, import_node_fs21.accessSync)(dir, import_node_fs21.constants.R_OK | import_node_fs21.constants.W_OK);
10153
10225
  return true;
10154
10226
  } catch {
10155
10227
  return false;
@@ -10162,7 +10234,7 @@ function resolveNotificationStorageDir(ctx, logger) {
10162
10234
  return stateNotifDir;
10163
10235
  }
10164
10236
  if (ctx.workspaceDir) {
10165
- const workspaceDir = (0, import_node_path18.join)(ctx.workspaceDir, NOTIFICATION_DIR_NAME);
10237
+ const workspaceDir = (0, import_node_path17.join)(ctx.workspaceDir, NOTIFICATION_DIR_NAME);
10166
10238
  if (ensureWritableDirectory(workspaceDir)) {
10167
10239
  logger.warn(
10168
10240
  `stateDir \u4E0D\u53EF\u7528\uFF0C\u901A\u77E5\u5DF2\u56DE\u9000\u5230 workspace \u8DEF\u5F84: ${workspaceDir}`
@@ -10177,8 +10249,8 @@ var NotificationStorage = class {
10177
10249
  this.config = config;
10178
10250
  this.logger = logger;
10179
10251
  this.dir = dir;
10180
- this.idIndexDir = (0, import_node_path18.join)(dir, ID_INDEX_DIR_NAME);
10181
- this.contentKeyIndexDir = (0, import_node_path18.join)(dir, CONTENT_KEY_INDEX_DIR_NAME);
10252
+ this.idIndexDir = (0, import_node_path17.join)(dir, ID_INDEX_DIR_NAME);
10253
+ this.contentKeyIndexDir = (0, import_node_path17.join)(dir, CONTENT_KEY_INDEX_DIR_NAME);
10182
10254
  this.resolveDisplayName = resolveDisplayName;
10183
10255
  }
10184
10256
  dir;
@@ -10189,10 +10261,10 @@ var NotificationStorage = class {
10189
10261
  dateWriteChains = /* @__PURE__ */ new Map();
10190
10262
  resolveDisplayName;
10191
10263
  async init() {
10192
- (0, import_node_fs22.mkdirSync)(this.dir, { recursive: true });
10193
- (0, import_node_fs22.mkdirSync)(this.idIndexDir, { recursive: true });
10194
- (0, import_node_fs22.rmSync)(this.contentKeyIndexDir, { recursive: true, force: true });
10195
- (0, import_node_fs22.mkdirSync)(this.contentKeyIndexDir, { recursive: true });
10264
+ (0, import_node_fs21.mkdirSync)(this.dir, { recursive: true });
10265
+ (0, import_node_fs21.mkdirSync)(this.idIndexDir, { recursive: true });
10266
+ (0, import_node_fs21.rmSync)(this.contentKeyIndexDir, { recursive: true, force: true });
10267
+ (0, import_node_fs21.mkdirSync)(this.contentKeyIndexDir, { recursive: true });
10196
10268
  }
10197
10269
  async ingest(items) {
10198
10270
  const result = {
@@ -10231,7 +10303,7 @@ var NotificationStorage = class {
10231
10303
  return { kind: "invalid" };
10232
10304
  }
10233
10305
  const dateKey = this.formatDate(ts);
10234
- const filePath = (0, import_node_path18.join)(this.dir, `${dateKey}.json`);
10306
+ const filePath = (0, import_node_path17.join)(this.dir, `${dateKey}.json`);
10235
10307
  const normalizedId = typeof n.id === "string" ? n.id.trim() : "";
10236
10308
  const entry = this.buildStoredNotification(n);
10237
10309
  return this.withDateWriteLock(dateKey, async () => {
@@ -10248,7 +10320,7 @@ var NotificationStorage = class {
10248
10320
  };
10249
10321
  const arr = this.readStoredNotifications(filePath);
10250
10322
  arr.push(storedEntry);
10251
- (0, import_node_fs22.writeFileSync)(filePath, JSON.stringify(arr, null, 2), "utf-8");
10323
+ (0, import_node_fs21.writeFileSync)(filePath, JSON.stringify(arr, null, 2), "utf-8");
10252
10324
  if (normalizedId) {
10253
10325
  this.recordNotificationId(dateKey, normalizedId);
10254
10326
  }
@@ -10282,7 +10354,7 @@ var NotificationStorage = class {
10282
10354
  return `${year}-${month}-${day}`;
10283
10355
  }
10284
10356
  getIdIndexPath(dateKey) {
10285
- return (0, import_node_path18.join)(this.idIndexDir, `${dateKey}.ids`);
10357
+ return (0, import_node_path17.join)(this.idIndexDir, `${dateKey}.ids`);
10286
10358
  }
10287
10359
  getIdSet(dateKey) {
10288
10360
  const cached = this.idCache.get(dateKey);
@@ -10291,8 +10363,8 @@ var NotificationStorage = class {
10291
10363
  }
10292
10364
  const idPath = this.getIdIndexPath(dateKey);
10293
10365
  const ids = /* @__PURE__ */ new Set();
10294
- if ((0, import_node_fs22.existsSync)(idPath)) {
10295
- const lines = (0, import_node_fs22.readFileSync)(idPath, "utf-8").split(/\r?\n/);
10366
+ if ((0, import_node_fs21.existsSync)(idPath)) {
10367
+ const lines = (0, import_node_fs21.readFileSync)(idPath, "utf-8").split(/\r?\n/);
10296
10368
  for (const line of lines) {
10297
10369
  const id = line.trim();
10298
10370
  if (id) {
@@ -10307,7 +10379,7 @@ var NotificationStorage = class {
10307
10379
  return this.getIdSet(dateKey).has(id);
10308
10380
  }
10309
10381
  getContentKeyIndexPath(dateKey) {
10310
- return (0, import_node_path18.join)(this.contentKeyIndexDir, `${dateKey}.keys`);
10382
+ return (0, import_node_path17.join)(this.contentKeyIndexDir, `${dateKey}.keys`);
10311
10383
  }
10312
10384
  getContentKeySet(dateKey, filePath) {
10313
10385
  const cached = this.contentKeyCache.get(dateKey);
@@ -10316,16 +10388,16 @@ var NotificationStorage = class {
10316
10388
  }
10317
10389
  const keyPath = this.getContentKeyIndexPath(dateKey);
10318
10390
  const keys = /* @__PURE__ */ new Set();
10319
- if ((0, import_node_fs22.existsSync)(filePath)) {
10391
+ if ((0, import_node_fs21.existsSync)(filePath)) {
10320
10392
  for (const item of this.readStoredNotifications(filePath)) {
10321
10393
  keys.add(this.buildNotificationContentKey(item));
10322
10394
  }
10323
10395
  }
10324
10396
  if (keys.size > 0) {
10325
- (0, import_node_fs22.writeFileSync)(keyPath, `${Array.from(keys).join("\n")}
10397
+ (0, import_node_fs21.writeFileSync)(keyPath, `${Array.from(keys).join("\n")}
10326
10398
  `, "utf-8");
10327
- } else if ((0, import_node_fs22.existsSync)(keyPath)) {
10328
- (0, import_node_fs22.rmSync)(keyPath, { force: true });
10399
+ } else if ((0, import_node_fs21.existsSync)(keyPath)) {
10400
+ (0, import_node_fs21.rmSync)(keyPath, { force: true });
10329
10401
  }
10330
10402
  this.contentKeyCache.set(dateKey, keys);
10331
10403
  return keys;
@@ -10340,7 +10412,7 @@ var NotificationStorage = class {
10340
10412
  if (ids.has(id)) {
10341
10413
  return;
10342
10414
  }
10343
- (0, import_node_fs22.appendFileSync)(this.getIdIndexPath(dateKey), `${id}
10415
+ (0, import_node_fs21.appendFileSync)(this.getIdIndexPath(dateKey), `${id}
10344
10416
  `, "utf-8");
10345
10417
  ids.add(id);
10346
10418
  }
@@ -10350,7 +10422,7 @@ var NotificationStorage = class {
10350
10422
  if (keys.has(key)) {
10351
10423
  return;
10352
10424
  }
10353
- (0, import_node_fs22.appendFileSync)(this.getContentKeyIndexPath(dateKey), `${key}
10425
+ (0, import_node_fs21.appendFileSync)(this.getContentKeyIndexPath(dateKey), `${key}
10354
10426
  `, "utf-8");
10355
10427
  keys.add(key);
10356
10428
  }
@@ -10358,11 +10430,11 @@ var NotificationStorage = class {
10358
10430
  return (0, import_node_crypto2.createHash)("sha256").update(entry.appName).update("").update(entry.title).update("").update(entry.content).update("").update(entry.timestamp).digest("hex");
10359
10431
  }
10360
10432
  readStoredNotifications(filePath) {
10361
- if (!(0, import_node_fs22.existsSync)(filePath)) {
10433
+ if (!(0, import_node_fs21.existsSync)(filePath)) {
10362
10434
  return [];
10363
10435
  }
10364
10436
  try {
10365
- const parsed = JSON.parse((0, import_node_fs22.readFileSync)(filePath, "utf-8"));
10437
+ const parsed = JSON.parse((0, import_node_fs21.readFileSync)(filePath, "utf-8"));
10366
10438
  return Array.isArray(parsed) ? parsed : [];
10367
10439
  } catch {
10368
10440
  return [];
@@ -10402,14 +10474,14 @@ var NotificationStorage = class {
10402
10474
  const dateFilePattern = /^(\d{4}-\d{2}-\d{2})\.(json|md)$/;
10403
10475
  const dateDirPattern = /^\d{4}-\d{2}-\d{2}$/;
10404
10476
  try {
10405
- for (const entry of (0, import_node_fs22.readdirSync)(this.dir, { withFileTypes: true })) {
10477
+ for (const entry of (0, import_node_fs21.readdirSync)(this.dir, { withFileTypes: true })) {
10406
10478
  if (entry.isFile()) {
10407
10479
  const match = dateFilePattern.exec(entry.name);
10408
10480
  if (match && match[1] < cutoffDate) {
10409
- (0, import_node_fs22.rmSync)((0, import_node_path18.join)(this.dir, entry.name), { force: true });
10481
+ (0, import_node_fs21.rmSync)((0, import_node_path17.join)(this.dir, entry.name), { force: true });
10410
10482
  }
10411
10483
  } else if (entry.isDirectory() && dateDirPattern.test(entry.name) && entry.name < cutoffDate) {
10412
- (0, import_node_fs22.rmSync)((0, import_node_path18.join)(this.dir, entry.name), { recursive: true, force: true });
10484
+ (0, import_node_fs21.rmSync)((0, import_node_path17.join)(this.dir, entry.name), { recursive: true, force: true });
10413
10485
  }
10414
10486
  }
10415
10487
  } catch {
@@ -10418,11 +10490,11 @@ var NotificationStorage = class {
10418
10490
  /** Remove expired .ids index files */
10419
10491
  pruneIdIndex(cutoffDate) {
10420
10492
  try {
10421
- for (const entry of (0, import_node_fs22.readdirSync)(this.idIndexDir, { withFileTypes: true })) {
10493
+ for (const entry of (0, import_node_fs21.readdirSync)(this.idIndexDir, { withFileTypes: true })) {
10422
10494
  if (!entry.isFile()) continue;
10423
10495
  const match = /^(\d{4}-\d{2}-\d{2})\.ids$/.exec(entry.name);
10424
10496
  if (match && match[1] < cutoffDate) {
10425
- (0, import_node_fs22.rmSync)((0, import_node_path18.join)(this.idIndexDir, entry.name), { force: true });
10497
+ (0, import_node_fs21.rmSync)((0, import_node_path17.join)(this.idIndexDir, entry.name), { force: true });
10426
10498
  this.idCache.delete(match[1]);
10427
10499
  }
10428
10500
  }
@@ -10431,11 +10503,11 @@ var NotificationStorage = class {
10431
10503
  }
10432
10504
  pruneContentKeyIndex(cutoffDate) {
10433
10505
  try {
10434
- for (const entry of (0, import_node_fs22.readdirSync)(this.contentKeyIndexDir, { withFileTypes: true })) {
10506
+ for (const entry of (0, import_node_fs21.readdirSync)(this.contentKeyIndexDir, { withFileTypes: true })) {
10435
10507
  if (!entry.isFile()) continue;
10436
10508
  const match = /^(\d{4}-\d{2}-\d{2})\.keys$/.exec(entry.name);
10437
10509
  if (match && match[1] < cutoffDate) {
10438
- (0, import_node_fs22.rmSync)((0, import_node_path18.join)(this.contentKeyIndexDir, entry.name), { force: true });
10510
+ (0, import_node_fs21.rmSync)((0, import_node_path17.join)(this.contentKeyIndexDir, entry.name), { force: true });
10439
10511
  this.contentKeyCache.delete(match[1]);
10440
10512
  }
10441
10513
  }
@@ -10450,8 +10522,8 @@ var NotificationStorage = class {
10450
10522
  };
10451
10523
 
10452
10524
  // src/recording/storage.ts
10453
- var import_node_fs23 = require("fs");
10454
- var import_node_path19 = require("path");
10525
+ var import_node_fs22 = require("fs");
10526
+ var import_node_path18 = require("path");
10455
10527
 
10456
10528
  // src/recording/state-machine.ts
10457
10529
  var VALID_TRANSITIONS = /* @__PURE__ */ new Map([
@@ -10513,7 +10585,7 @@ function stripMarkdownFence(markdown) {
10513
10585
  }
10514
10586
  function deriveTitleFromTranscriptPath(transcriptFile, recordingId) {
10515
10587
  if (!transcriptFile) return void 0;
10516
- const name = (0, import_node_path19.basename)(transcriptFile, ".md");
10588
+ const name = (0, import_node_path18.basename)(transcriptFile, ".md");
10517
10589
  const prefix = `${recordingId}_`;
10518
10590
  if (name.startsWith(prefix)) {
10519
10591
  const derived = name.slice(prefix.length).trim();
@@ -10541,22 +10613,22 @@ function extractTranscriptContent(markdown) {
10541
10613
  return lines.join("\n").replace(/\n{3,}/g, "\n\n").trim();
10542
10614
  }
10543
10615
  function resolveRecordingStorageDir(ctx, logger) {
10544
- const stateRecDir = (0, import_node_path19.join)(
10616
+ const stateRecDir = (0, import_node_path18.join)(
10545
10617
  ctx.stateDir,
10546
10618
  "plugins",
10547
10619
  "phone-notifications",
10548
10620
  RECORDINGS_DIR
10549
10621
  );
10550
10622
  try {
10551
- (0, import_node_fs23.mkdirSync)(stateRecDir, { recursive: true });
10623
+ (0, import_node_fs22.mkdirSync)(stateRecDir, { recursive: true });
10552
10624
  logger.info(`\u5F55\u97F3\u5C06\u5199\u5165 stateDir \u8DEF\u5F84: ${stateRecDir}`);
10553
10625
  return stateRecDir;
10554
10626
  } catch {
10555
10627
  }
10556
10628
  if (ctx.workspaceDir) {
10557
- const wsRecDir = (0, import_node_path19.join)(ctx.workspaceDir, RECORDINGS_DIR);
10629
+ const wsRecDir = (0, import_node_path18.join)(ctx.workspaceDir, RECORDINGS_DIR);
10558
10630
  try {
10559
- (0, import_node_fs23.mkdirSync)(wsRecDir, { recursive: true });
10631
+ (0, import_node_fs22.mkdirSync)(wsRecDir, { recursive: true });
10560
10632
  logger.warn(`stateDir \u4E0D\u53EF\u7528\uFF0C\u5F55\u97F3\u5DF2\u56DE\u9000\u5230 workspace \u8DEF\u5F84: ${wsRecDir}`);
10561
10633
  return wsRecDir;
10562
10634
  } catch {
@@ -10568,11 +10640,11 @@ var RecordingStorage = class {
10568
10640
  constructor(dir, logger) {
10569
10641
  this.logger = logger;
10570
10642
  this.dir = dir;
10571
- this.audioDir = (0, import_node_path19.join)(dir, AUDIO_DIR);
10572
- this.transcriptDataDir = (0, import_node_path19.join)(dir, TRANSCRIPT_DATA_DIR);
10573
- this.transcriptsDir = (0, import_node_path19.join)(dir, TRANSCRIPTS_DIR);
10574
- this.summariesDir = (0, import_node_path19.join)(dir, SUMMARIES_DIR);
10575
- this.indexPath = (0, import_node_path19.join)(dir, INDEX_FILE);
10643
+ this.audioDir = (0, import_node_path18.join)(dir, AUDIO_DIR);
10644
+ this.transcriptDataDir = (0, import_node_path18.join)(dir, TRANSCRIPT_DATA_DIR);
10645
+ this.transcriptsDir = (0, import_node_path18.join)(dir, TRANSCRIPTS_DIR);
10646
+ this.summariesDir = (0, import_node_path18.join)(dir, SUMMARIES_DIR);
10647
+ this.indexPath = (0, import_node_path18.join)(dir, INDEX_FILE);
10576
10648
  }
10577
10649
  dir;
10578
10650
  audioDir;
@@ -10583,10 +10655,10 @@ var RecordingStorage = class {
10583
10655
  index = { recordings: [] };
10584
10656
  /** 初始化目录结构并加载索引 */
10585
10657
  async init() {
10586
- (0, import_node_fs23.mkdirSync)(this.audioDir, { recursive: true });
10587
- (0, import_node_fs23.mkdirSync)(this.transcriptDataDir, { recursive: true });
10588
- (0, import_node_fs23.mkdirSync)(this.transcriptsDir, { recursive: true });
10589
- (0, import_node_fs23.mkdirSync)(this.summariesDir, { recursive: true });
10658
+ (0, import_node_fs22.mkdirSync)(this.audioDir, { recursive: true });
10659
+ (0, import_node_fs22.mkdirSync)(this.transcriptDataDir, { recursive: true });
10660
+ (0, import_node_fs22.mkdirSync)(this.transcriptsDir, { recursive: true });
10661
+ (0, import_node_fs22.mkdirSync)(this.summariesDir, { recursive: true });
10590
10662
  this.loadIndex();
10591
10663
  this.logger.info(
10592
10664
  `\u5F55\u97F3\u5B58\u50A8\u5DF2\u521D\u59CB\u5316: ${this.dir}\uFF08\u5171 ${this.index.recordings.length} \u6761\u8BB0\u5F55\uFF09`
@@ -10630,13 +10702,13 @@ var RecordingStorage = class {
10630
10702
  return id;
10631
10703
  }
10632
10704
  if (existing.transcriptDataFile) {
10633
- (0, import_node_fs23.rmSync)((0, import_node_path19.join)(this.dir, existing.transcriptDataFile), { force: true });
10705
+ (0, import_node_fs22.rmSync)((0, import_node_path18.join)(this.dir, existing.transcriptDataFile), { force: true });
10634
10706
  }
10635
10707
  if (existing.transcriptFile) {
10636
- (0, import_node_fs23.rmSync)((0, import_node_path19.join)(this.dir, existing.transcriptFile), { force: true });
10708
+ (0, import_node_fs22.rmSync)((0, import_node_path18.join)(this.dir, existing.transcriptFile), { force: true });
10637
10709
  }
10638
10710
  if (existing.summaryFile) {
10639
- (0, import_node_fs23.rmSync)((0, import_node_path19.join)(this.dir, existing.summaryFile), { force: true });
10711
+ (0, import_node_fs22.rmSync)((0, import_node_path18.join)(this.dir, existing.summaryFile), { force: true });
10640
10712
  }
10641
10713
  existing.metadata = metadata;
10642
10714
  existing.status = "syncing_openclaw";
@@ -10705,7 +10777,7 @@ var RecordingStorage = class {
10705
10777
  if (!entry) return;
10706
10778
  const nextTranscriptDataFile = `${TRANSCRIPT_DATA_DIR}/${filename}`;
10707
10779
  if (entry.transcriptDataFile && entry.transcriptDataFile !== nextTranscriptDataFile) {
10708
- (0, import_node_fs23.rmSync)((0, import_node_path19.join)(this.dir, entry.transcriptDataFile), { force: true });
10780
+ (0, import_node_fs22.rmSync)((0, import_node_path18.join)(this.dir, entry.transcriptDataFile), { force: true });
10709
10781
  }
10710
10782
  entry.transcriptDataFile = nextTranscriptDataFile;
10711
10783
  entry.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
@@ -10719,7 +10791,7 @@ var RecordingStorage = class {
10719
10791
  if (!entry) return;
10720
10792
  const nextTranscriptFile = `${TRANSCRIPTS_DIR}/${filename}`;
10721
10793
  if (entry.transcriptFile && entry.transcriptFile !== nextTranscriptFile) {
10722
- (0, import_node_fs23.rmSync)((0, import_node_path19.join)(this.dir, entry.transcriptFile), { force: true });
10794
+ (0, import_node_fs22.rmSync)((0, import_node_path18.join)(this.dir, entry.transcriptFile), { force: true });
10723
10795
  }
10724
10796
  entry.transcriptFile = nextTranscriptFile;
10725
10797
  entry.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
@@ -10733,7 +10805,7 @@ var RecordingStorage = class {
10733
10805
  if (!entry) return;
10734
10806
  const nextSummaryFile = `${SUMMARIES_DIR}/${filename}`;
10735
10807
  if (entry.summaryFile && entry.summaryFile !== nextSummaryFile) {
10736
- (0, import_node_fs23.rmSync)((0, import_node_path19.join)(this.dir, entry.summaryFile), { force: true });
10808
+ (0, import_node_fs22.rmSync)((0, import_node_path18.join)(this.dir, entry.summaryFile), { force: true });
10737
10809
  }
10738
10810
  entry.summaryFile = nextSummaryFile;
10739
10811
  entry.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
@@ -10841,24 +10913,24 @@ var RecordingStorage = class {
10841
10913
  const entry = this.findById(recordingId);
10842
10914
  if (!entry) return false;
10843
10915
  if (entry.audioFile) {
10844
- const audioPath = (0, import_node_path19.join)(this.dir, entry.audioFile);
10845
- (0, import_node_fs23.rmSync)(audioPath, { force: true });
10916
+ const audioPath = (0, import_node_path18.join)(this.dir, entry.audioFile);
10917
+ (0, import_node_fs22.rmSync)(audioPath, { force: true });
10846
10918
  }
10847
10919
  if (entry.srtFile) {
10848
- const srtPath = (0, import_node_path19.join)(this.dir, entry.srtFile);
10849
- (0, import_node_fs23.rmSync)(srtPath, { force: true });
10920
+ const srtPath = (0, import_node_path18.join)(this.dir, entry.srtFile);
10921
+ (0, import_node_fs22.rmSync)(srtPath, { force: true });
10850
10922
  }
10851
10923
  if (entry.transcriptDataFile) {
10852
- const transcriptDataPath = (0, import_node_path19.join)(this.dir, entry.transcriptDataFile);
10853
- (0, import_node_fs23.rmSync)(transcriptDataPath, { force: true });
10924
+ const transcriptDataPath = (0, import_node_path18.join)(this.dir, entry.transcriptDataFile);
10925
+ (0, import_node_fs22.rmSync)(transcriptDataPath, { force: true });
10854
10926
  }
10855
10927
  if (entry.transcriptFile) {
10856
- const transcriptPath = (0, import_node_path19.join)(this.dir, entry.transcriptFile);
10857
- (0, import_node_fs23.rmSync)(transcriptPath, { force: true });
10928
+ const transcriptPath = (0, import_node_path18.join)(this.dir, entry.transcriptFile);
10929
+ (0, import_node_fs22.rmSync)(transcriptPath, { force: true });
10858
10930
  }
10859
10931
  if (entry.summaryFile) {
10860
- const summaryPath = (0, import_node_path19.join)(this.dir, entry.summaryFile);
10861
- (0, import_node_fs23.rmSync)(summaryPath, { force: true });
10932
+ const summaryPath = (0, import_node_path18.join)(this.dir, entry.summaryFile);
10933
+ (0, import_node_fs22.rmSync)(summaryPath, { force: true });
10862
10934
  }
10863
10935
  if (opts?.localOnly) {
10864
10936
  entry.audioFile = void 0;
@@ -10916,34 +10988,34 @@ var RecordingStorage = class {
10916
10988
  * 获取音频文件的绝对路径。ossUrl 用于推断文件扩展名
10917
10989
  */
10918
10990
  getAudioFilePath(recordingId, ossUrl) {
10919
- return (0, import_node_path19.join)(this.audioDir, this.buildAudioFilename(recordingId, ossUrl));
10991
+ return (0, import_node_path18.join)(this.audioDir, this.buildAudioFilename(recordingId, ossUrl));
10920
10992
  }
10921
10993
  /**
10922
10994
  * 获取打点文件的绝对路径
10923
10995
  */
10924
10996
  getSrtFilePath(recordingId) {
10925
- return (0, import_node_path19.join)(this.audioDir, this.buildSrtFilename(recordingId));
10997
+ return (0, import_node_path18.join)(this.audioDir, this.buildSrtFilename(recordingId));
10926
10998
  }
10927
10999
  /**
10928
11000
  * 获取转写 JSON 文件的绝对路径
10929
11001
  */
10930
11002
  getTranscriptDataFilePath(recordingId) {
10931
- return (0, import_node_path19.join)(this.transcriptDataDir, this.buildTranscriptDataFilename(recordingId));
11003
+ return (0, import_node_path18.join)(this.transcriptDataDir, this.buildTranscriptDataFilename(recordingId));
10932
11004
  }
10933
11005
  /**
10934
11006
  * 获取摘要文件的绝对路径
10935
11007
  */
10936
11008
  getSummaryFilePath(recordingId) {
10937
- return (0, import_node_path19.join)(this.summariesDir, this.buildSummaryFilename(recordingId));
11009
+ return (0, import_node_path18.join)(this.summariesDir, this.buildSummaryFilename(recordingId));
10938
11010
  }
10939
11011
  // ─── Persistence ───
10940
11012
  loadIndex() {
10941
- if (!(0, import_node_fs23.existsSync)(this.indexPath)) {
11013
+ if (!(0, import_node_fs22.existsSync)(this.indexPath)) {
10942
11014
  this.index = { recordings: [] };
10943
11015
  return;
10944
11016
  }
10945
11017
  try {
10946
- const raw = JSON.parse((0, import_node_fs23.readFileSync)(this.indexPath, "utf-8"));
11018
+ const raw = JSON.parse((0, import_node_fs22.readFileSync)(this.indexPath, "utf-8"));
10947
11019
  if (raw && Array.isArray(raw.recordings)) {
10948
11020
  let needsRewrite = false;
10949
11021
  const normalized = raw.recordings.filter((entry) => entry && typeof entry === "object").map((entry) => {
@@ -10981,8 +11053,8 @@ var RecordingStorage = class {
10981
11053
  segments: []
10982
11054
  });
10983
11055
  const transcriptDataFilename = this.buildTranscriptDataFilename(compacted.id);
10984
- (0, import_node_fs23.writeFileSync)(
10985
- (0, import_node_path19.join)(this.transcriptDataDir, transcriptDataFilename),
11056
+ (0, import_node_fs22.writeFileSync)(
11057
+ (0, import_node_path18.join)(this.transcriptDataDir, transcriptDataFilename),
10986
11058
  JSON.stringify(transcriptDoc, null, 2),
10987
11059
  "utf-8"
10988
11060
  );
@@ -10994,8 +11066,8 @@ var RecordingStorage = class {
10994
11066
  compacted.summaryFile = entry.summaryFile;
10995
11067
  } else if (typeof entry.summary === "string" && entry.summary.trim()) {
10996
11068
  const summaryFilename = this.buildSummaryFilename(entry.id);
10997
- (0, import_node_fs23.writeFileSync)(
10998
- (0, import_node_path19.join)(this.summariesDir, summaryFilename),
11069
+ (0, import_node_fs22.writeFileSync)(
11070
+ (0, import_node_path18.join)(this.summariesDir, summaryFilename),
10999
11071
  entry.summary.trim(),
11000
11072
  "utf-8"
11001
11073
  );
@@ -11005,8 +11077,8 @@ var RecordingStorage = class {
11005
11077
  const summaryFromDocument = extractTranscriptSummaryFromDocument(transcriptDoc);
11006
11078
  if (summaryFromDocument) {
11007
11079
  const summaryFilename = this.buildSummaryFilename(entry.id);
11008
- (0, import_node_fs23.writeFileSync)(
11009
- (0, import_node_path19.join)(this.summariesDir, summaryFilename),
11080
+ (0, import_node_fs22.writeFileSync)(
11081
+ (0, import_node_path18.join)(this.summariesDir, summaryFilename),
11010
11082
  summaryFromDocument,
11011
11083
  "utf-8"
11012
11084
  );
@@ -11049,7 +11121,7 @@ var RecordingStorage = class {
11049
11121
  }
11050
11122
  readRelativeTextFile(relativePath) {
11051
11123
  try {
11052
- return (0, import_node_fs23.readFileSync)((0, import_node_path19.join)(this.dir, relativePath), "utf-8");
11124
+ return (0, import_node_fs22.readFileSync)((0, import_node_path18.join)(this.dir, relativePath), "utf-8");
11053
11125
  } catch {
11054
11126
  return void 0;
11055
11127
  }
@@ -11062,7 +11134,7 @@ var RecordingStorage = class {
11062
11134
  return parseTranscriptDocument(raw);
11063
11135
  }
11064
11136
  saveIndex() {
11065
- (0, import_node_fs23.writeFileSync)(
11137
+ (0, import_node_fs22.writeFileSync)(
11066
11138
  this.indexPath,
11067
11139
  JSON.stringify(this.index, null, 2),
11068
11140
  "utf-8"
@@ -11076,8 +11148,8 @@ var RecordingStorage = class {
11076
11148
  init_transcript_document();
11077
11149
 
11078
11150
  // src/recording/downloader.ts
11079
- var import_node_fs24 = require("fs");
11080
- var import_node_path20 = require("path");
11151
+ var import_node_fs23 = require("fs");
11152
+ var import_node_path19 = require("path");
11081
11153
  var import_promises2 = require("stream/promises");
11082
11154
  var import_node_stream = require("stream");
11083
11155
  var DEFAULT_TIMEOUT_MS2 = 5 * 60 * 1e3;
@@ -11087,7 +11159,7 @@ async function downloadFile(url, destPath, logger, options) {
11087
11159
  const timeoutMs = options?.timeoutMs ?? DEFAULT_TIMEOUT_MS2;
11088
11160
  const maxRetries = options?.maxRetries ?? DEFAULT_MAX_RETRIES;
11089
11161
  const retryBackoffMs = options?.retryBackoffMs ?? DEFAULT_RETRY_BACKOFF_MS;
11090
- (0, import_node_fs24.mkdirSync)((0, import_node_path20.dirname)(destPath), { recursive: true });
11162
+ (0, import_node_fs23.mkdirSync)((0, import_node_path19.dirname)(destPath), { recursive: true });
11091
11163
  let lastError;
11092
11164
  for (let attempt = 1; attempt <= maxRetries; attempt++) {
11093
11165
  const startMs = Date.now();
@@ -11105,11 +11177,11 @@ async function downloadFile(url, destPath, logger, options) {
11105
11177
  if (!res.body) {
11106
11178
  throw new Error("\u54CD\u5E94\u4F53\u4E3A\u7A7A");
11107
11179
  }
11108
- const writeStream = (0, import_node_fs24.createWriteStream)(destPath);
11180
+ const writeStream = (0, import_node_fs23.createWriteStream)(destPath);
11109
11181
  const readable = import_node_stream.Readable.fromWeb(res.body);
11110
11182
  await (0, import_promises2.pipeline)(readable, writeStream);
11111
11183
  const elapsed = Date.now() - startMs;
11112
- const fileSize = (0, import_node_fs24.existsSync)(destPath) ? (0, import_node_fs24.statSync)(destPath).size : 0;
11184
+ const fileSize = (0, import_node_fs23.existsSync)(destPath) ? (0, import_node_fs23.statSync)(destPath).size : 0;
11113
11185
  logger.info(
11114
11186
  `[downloader] \u4E0B\u8F7D\u5B8C\u6210: ${destPath} (${formatBytes(fileSize)}, ${elapsed}ms)`
11115
11187
  );
@@ -11120,7 +11192,7 @@ async function downloadFile(url, destPath, logger, options) {
11120
11192
  } catch (err2) {
11121
11193
  lastError = err2?.message ?? String(err2);
11122
11194
  try {
11123
- if ((0, import_node_fs24.existsSync)(destPath)) (0, import_node_fs24.unlinkSync)(destPath);
11195
+ if ((0, import_node_fs23.existsSync)(destPath)) (0, import_node_fs23.unlinkSync)(destPath);
11124
11196
  } catch {
11125
11197
  }
11126
11198
  const isAbort = err2?.name === "AbortError";
@@ -11436,13 +11508,13 @@ async function triggerTranscription(recordingId, storage, asrConfig, logger, opt
11436
11508
  }
11437
11509
 
11438
11510
  // src/tunnel/service.ts
11439
- var import_node_fs29 = require("fs");
11440
- var import_node_path25 = require("path");
11511
+ var import_node_fs28 = require("fs");
11512
+ var import_node_path24 = require("path");
11441
11513
  init_credentials();
11442
11514
 
11443
11515
  // src/tunnel/relay-client.ts
11444
- var import_node_fs27 = require("fs");
11445
- var import_node_path23 = require("path");
11516
+ var import_node_fs26 = require("fs");
11517
+ var import_node_path22 = require("path");
11446
11518
 
11447
11519
  // node_modules/.pnpm/ws@8.19.0/node_modules/ws/wrapper.mjs
11448
11520
  var import_stream = __toESM(require_stream(), 1);
@@ -11486,8 +11558,8 @@ var RelayClient = class {
11486
11558
  lastDisconnectReason
11487
11559
  };
11488
11560
  try {
11489
- (0, import_node_fs27.mkdirSync)((0, import_node_path23.dirname)(this.opts.statusFilePath), { recursive: true });
11490
- (0, import_node_fs27.writeFileSync)(this.opts.statusFilePath, JSON.stringify(info, null, 2));
11561
+ (0, import_node_fs26.mkdirSync)((0, import_node_path22.dirname)(this.opts.statusFilePath), { recursive: true });
11562
+ (0, import_node_fs26.writeFileSync)(this.opts.statusFilePath, JSON.stringify(info, null, 2));
11491
11563
  } catch {
11492
11564
  }
11493
11565
  }
@@ -11869,8 +11941,8 @@ init_host();
11869
11941
 
11870
11942
  // src/tunnel/device-identity.ts
11871
11943
  var import_node_crypto3 = __toESM(require("crypto"), 1);
11872
- var import_node_fs28 = __toESM(require("fs"), 1);
11873
- var import_node_path24 = __toESM(require("path"), 1);
11944
+ var import_node_fs27 = __toESM(require("fs"), 1);
11945
+ var import_node_path23 = __toESM(require("path"), 1);
11874
11946
  init_host();
11875
11947
  var ED25519_SPKI_PREFIX = Buffer.from("302a300506032b6570032100", "hex");
11876
11948
  function base64UrlEncode(buf) {
@@ -11915,10 +11987,10 @@ function resolveClientStateDir(stateDir) {
11915
11987
  return stateDir ?? resolveStateDir();
11916
11988
  }
11917
11989
  function ensureDir(filePath) {
11918
- import_node_fs28.default.mkdirSync(import_node_path24.default.dirname(filePath), { recursive: true });
11990
+ import_node_fs27.default.mkdirSync(import_node_path23.default.dirname(filePath), { recursive: true });
11919
11991
  }
11920
11992
  function resolveIdentityPath(stateDir) {
11921
- return import_node_path24.default.join(stateDir, "identity", "device.json");
11993
+ return import_node_path23.default.join(stateDir, "identity", "device.json");
11922
11994
  }
11923
11995
  function normalizeDeviceAuthRole(role) {
11924
11996
  return role.trim();
@@ -11934,12 +12006,12 @@ function normalizeDeviceAuthScopes(scopes) {
11934
12006
  return [...out].sort();
11935
12007
  }
11936
12008
  function resolveDeviceAuthPath(stateDir) {
11937
- return import_node_path24.default.join(stateDir, "identity", "device-auth.json");
12009
+ return import_node_path23.default.join(stateDir, "identity", "device-auth.json");
11938
12010
  }
11939
12011
  function readDeviceAuthStore(filePath) {
11940
12012
  try {
11941
- if (!import_node_fs28.default.existsSync(filePath)) return null;
11942
- const raw = import_node_fs28.default.readFileSync(filePath, "utf8");
12013
+ if (!import_node_fs27.default.existsSync(filePath)) return null;
12014
+ const raw = import_node_fs27.default.readFileSync(filePath, "utf8");
11943
12015
  const parsed = JSON.parse(raw);
11944
12016
  if (parsed?.version !== 1 || typeof parsed.deviceId !== "string") return null;
11945
12017
  if (!parsed.tokens || typeof parsed.tokens !== "object") return null;
@@ -11950,12 +12022,12 @@ function readDeviceAuthStore(filePath) {
11950
12022
  }
11951
12023
  function writeDeviceAuthStore(filePath, store) {
11952
12024
  ensureDir(filePath);
11953
- import_node_fs28.default.writeFileSync(filePath, `${JSON.stringify(store, null, 2)}
12025
+ import_node_fs27.default.writeFileSync(filePath, `${JSON.stringify(store, null, 2)}
11954
12026
  `, {
11955
12027
  mode: 384
11956
12028
  });
11957
12029
  try {
11958
- import_node_fs28.default.chmodSync(filePath, 384);
12030
+ import_node_fs27.default.chmodSync(filePath, 384);
11959
12031
  } catch {
11960
12032
  }
11961
12033
  }
@@ -12002,8 +12074,8 @@ function clearDeviceAuthToken(params) {
12002
12074
  function loadOrCreateDeviceIdentity(stateDir) {
12003
12075
  const filePath = resolveIdentityPath(stateDir);
12004
12076
  try {
12005
- if (import_node_fs28.default.existsSync(filePath)) {
12006
- const raw = import_node_fs28.default.readFileSync(filePath, "utf8");
12077
+ if (import_node_fs27.default.existsSync(filePath)) {
12078
+ const raw = import_node_fs27.default.readFileSync(filePath, "utf8");
12007
12079
  const parsed = JSON.parse(raw);
12008
12080
  if (parsed?.version === 1 && typeof parsed.deviceId === "string" && typeof parsed.publicKeyPem === "string" && typeof parsed.privateKeyPem === "string") {
12009
12081
  const derivedId = fingerprintPublicKey(parsed.publicKeyPem);
@@ -12024,14 +12096,14 @@ function loadOrCreateDeviceIdentity(stateDir) {
12024
12096
  publicKeyPem,
12025
12097
  privateKeyPem
12026
12098
  };
12027
- import_node_fs28.default.mkdirSync(import_node_path24.default.dirname(filePath), { recursive: true });
12099
+ import_node_fs27.default.mkdirSync(import_node_path23.default.dirname(filePath), { recursive: true });
12028
12100
  const stored = {
12029
12101
  version: 1,
12030
12102
  ...identity,
12031
12103
  createdAtMs: Date.now()
12032
12104
  };
12033
12105
  ensureDir(filePath);
12034
- import_node_fs28.default.writeFileSync(filePath, `${JSON.stringify(stored, null, 2)}
12106
+ import_node_fs27.default.writeFileSync(filePath, `${JSON.stringify(stored, null, 2)}
12035
12107
  `, {
12036
12108
  mode: 384
12037
12109
  });
@@ -12915,7 +12987,7 @@ function createTunnelService(opts) {
12915
12987
  );
12916
12988
  client.sendRaw(frame);
12917
12989
  }
12918
- function isProcessAlive(pid) {
12990
+ function isProcessAlive2(pid) {
12919
12991
  if (!Number.isInteger(pid) || pid <= 0) return false;
12920
12992
  if (pid === process.pid) return true;
12921
12993
  try {
@@ -12927,7 +12999,7 @@ function createTunnelService(opts) {
12927
12999
  }
12928
13000
  function readLockOwner(filePath) {
12929
13001
  try {
12930
- const parsed = JSON.parse((0, import_node_fs29.readFileSync)(filePath, "utf-8"));
13002
+ const parsed = JSON.parse((0, import_node_fs28.readFileSync)(filePath, "utf-8"));
12931
13003
  return typeof parsed.pid === "number" ? parsed.pid : null;
12932
13004
  } catch {
12933
13005
  return null;
@@ -12940,23 +13012,23 @@ function createTunnelService(opts) {
12940
13012
  lockFd = null;
12941
13013
  if (fd !== null) {
12942
13014
  try {
12943
- (0, import_node_fs29.closeSync)(fd);
13015
+ (0, import_node_fs28.closeSync)(fd);
12944
13016
  } catch {
12945
13017
  }
12946
13018
  }
12947
13019
  if (filePath) {
12948
13020
  try {
12949
- (0, import_node_fs29.unlinkSync)(filePath);
13021
+ (0, import_node_fs28.unlinkSync)(filePath);
12950
13022
  } catch {
12951
13023
  }
12952
13024
  }
12953
13025
  }
12954
13026
  function acquireLock(filePath) {
12955
- (0, import_node_fs29.mkdirSync)((0, import_node_path25.dirname)(filePath), { recursive: true });
13027
+ (0, import_node_fs28.mkdirSync)((0, import_node_path24.dirname)(filePath), { recursive: true });
12956
13028
  for (let attempt = 0; attempt < 2; attempt++) {
12957
13029
  try {
12958
- const fd = (0, import_node_fs29.openSync)(filePath, "wx", 384);
12959
- (0, import_node_fs29.writeFileSync)(
13030
+ const fd = (0, import_node_fs28.openSync)(filePath, "wx", 384);
13031
+ (0, import_node_fs28.writeFileSync)(
12960
13032
  fd,
12961
13033
  JSON.stringify({
12962
13034
  pid: process.pid,
@@ -12975,12 +13047,12 @@ function createTunnelService(opts) {
12975
13047
  return false;
12976
13048
  }
12977
13049
  const ownerPid = readLockOwner(filePath);
12978
- if (ownerPid && !isProcessAlive(ownerPid)) {
13050
+ if (ownerPid && !isProcessAlive2(ownerPid)) {
12979
13051
  opts.logger.warn(
12980
13052
  `Relay tunnel: removing stale local lock owned by dead pid=${ownerPid}`
12981
13053
  );
12982
13054
  try {
12983
- (0, import_node_fs29.unlinkSync)(filePath);
13055
+ (0, import_node_fs28.unlinkSync)(filePath);
12984
13056
  } catch {
12985
13057
  }
12986
13058
  continue;
@@ -13025,12 +13097,12 @@ function createTunnelService(opts) {
13025
13097
  return;
13026
13098
  }
13027
13099
  const { logger } = opts;
13028
- const baseStateDir = (0, import_node_path25.join)(ctx.stateDir, "plugins", "phone-notifications");
13100
+ const baseStateDir = (0, import_node_path24.join)(ctx.stateDir, "plugins", "phone-notifications");
13029
13101
  logger.info(
13030
13102
  `Relay tunnel: starting (pid=${process.pid}, url=${opts.tunnelUrl}, heartbeat=${opts.heartbeatSec ?? DEFAULT_HEARTBEAT_SEC}s, backoff=${opts.reconnectBackoffMs ?? DEFAULT_RECONNECT_BACKOFF_MS}ms, gateway=${opts.gatewayBaseUrl}, hasGatewayToken=${!!opts.gatewayToken}, hasGatewayPwd=${!!opts.gatewayPassword})`
13031
13103
  );
13032
- const statusFilePath = (0, import_node_path25.join)(baseStateDir, "tunnel-status.json");
13033
- const lockPath = (0, import_node_path25.join)(baseStateDir, "relay-tunnel.lock");
13104
+ const statusFilePath = (0, import_node_path24.join)(baseStateDir, "tunnel-status.json");
13105
+ const lockPath = (0, import_node_path24.join)(baseStateDir, "relay-tunnel.lock");
13034
13106
  if (!acquireLock(lockPath)) {
13035
13107
  return;
13036
13108
  }
@@ -13095,25 +13167,6 @@ function createTunnelService(opts) {
13095
13167
 
13096
13168
  // src/plugin/lifecycle.ts
13097
13169
  init_env();
13098
-
13099
- // src/plugin/shared.ts
13100
- function readBody(req) {
13101
- return new Promise((resolve, reject) => {
13102
- const chunks = [];
13103
- req.on("data", (chunk) => chunks.push(chunk));
13104
- req.on("end", () => resolve(Buffer.concat(chunks).toString()));
13105
- req.on("error", reject);
13106
- });
13107
- }
13108
- function trimToUndefined2(value) {
13109
- if (typeof value !== "string") {
13110
- return void 0;
13111
- }
13112
- const trimmed = value.trim();
13113
- return trimmed || void 0;
13114
- }
13115
-
13116
- // src/plugin/lifecycle.ts
13117
13170
  function registerStorageLifecycle(deps) {
13118
13171
  const {
13119
13172
  api,
@@ -13170,7 +13223,7 @@ function readHostGatewayConfig(params) {
13170
13223
  let configData;
13171
13224
  if (configPath) {
13172
13225
  try {
13173
- configData = JSON.parse((0, import_node_fs30.readFileSync)(configPath, "utf-8"));
13226
+ configData = JSON.parse((0, import_node_fs29.readFileSync)(configPath, "utf-8"));
13174
13227
  } catch (err2) {
13175
13228
  if (err2?.code !== "ENOENT") {
13176
13229
  params.logger.warn(
@@ -13847,7 +13900,7 @@ var index_default = {
13847
13900
  let broadcastFn = null;
13848
13901
  let autoUpdateLifecycle = null;
13849
13902
  let tunnelService = null;
13850
- const openclawDir = api.runtime.state.resolveStateDir();
13903
+ const openclawDir = resolvePluginStateDir(api);
13851
13904
  const logger = openclawDir ? new PluginFileLogger(api.logger, openclawDir) : createVersionAwareLogger(api.logger);
13852
13905
  const lightRuleCtx = {
13853
13906
  stateDir: openclawDir