@yoooclaw/phone-notifications 1.10.3 → 1.10.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.cjs +1858 -1592
- package/dist/index.cjs.map +1 -1
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -206,9 +206,38 @@ var init_credentials = __esm({
|
|
|
206
206
|
});
|
|
207
207
|
|
|
208
208
|
// src/env.ts
|
|
209
|
+
function writeDotEnv(key, value) {
|
|
210
|
+
const path2 = resolveStateFile(".env");
|
|
211
|
+
(0, import_node_fs9.mkdirSync)((0, import_node_path8.dirname)(path2), { recursive: true });
|
|
212
|
+
const existing = (0, import_node_fs9.existsSync)(path2) ? (0, import_node_fs9.readFileSync)(path2, "utf-8") : "";
|
|
213
|
+
const lines = existing.split("\n");
|
|
214
|
+
const prefix = `${key}=`;
|
|
215
|
+
const idx = lines.findIndex((l) => l.startsWith(prefix));
|
|
216
|
+
const newLine = `${prefix}${value}`;
|
|
217
|
+
if (idx >= 0) {
|
|
218
|
+
lines[idx] = newLine;
|
|
219
|
+
} else {
|
|
220
|
+
if (existing && !existing.endsWith("\n")) lines.push("");
|
|
221
|
+
lines.push(newLine);
|
|
222
|
+
}
|
|
223
|
+
(0, import_node_fs9.writeFileSync)(path2, lines.join("\n"), "utf-8");
|
|
224
|
+
}
|
|
225
|
+
function readDotEnv() {
|
|
226
|
+
const path2 = resolveStateFile(".env");
|
|
227
|
+
if (!(0, import_node_fs9.existsSync)(path2)) return {};
|
|
228
|
+
return Object.fromEntries(
|
|
229
|
+
(0, import_node_fs9.readFileSync)(path2, "utf-8").split("\n").flatMap((line) => {
|
|
230
|
+
const eq = line.indexOf("=");
|
|
231
|
+
if (eq < 1) return [];
|
|
232
|
+
return [[line.slice(0, eq).trim(), line.slice(eq + 1).trim()]];
|
|
233
|
+
})
|
|
234
|
+
);
|
|
235
|
+
}
|
|
209
236
|
function loadEnvName() {
|
|
210
|
-
const fromEnvVar = process.env.
|
|
237
|
+
const fromEnvVar = process.env.PHONE_NOTIFICATIONS_ENV?.trim();
|
|
211
238
|
if (fromEnvVar && VALID_ENVS.has(fromEnvVar)) return fromEnvVar;
|
|
239
|
+
const fromDotEnv = readDotEnv()["PHONE_NOTIFICATIONS_ENV"]?.trim();
|
|
240
|
+
if (fromDotEnv && VALID_ENVS.has(fromDotEnv)) return fromDotEnv;
|
|
212
241
|
const { env } = readCredentials();
|
|
213
242
|
if (env && VALID_ENVS.has(env)) return env;
|
|
214
243
|
return "production";
|
|
@@ -219,7 +248,7 @@ function saveEnvName(env) {
|
|
|
219
248
|
`\u65E0\u6548\u7684\u73AF\u5883\u540D\u79F0: ${env}\uFF0C\u53EF\u9009\u503C: ${[...VALID_ENVS].join(", ")}`
|
|
220
249
|
);
|
|
221
250
|
}
|
|
222
|
-
|
|
251
|
+
writeDotEnv("PHONE_NOTIFICATIONS_ENV", env);
|
|
223
252
|
}
|
|
224
253
|
function getEnvUrls(env) {
|
|
225
254
|
return ENV_CONFIG[env ?? loadEnvName()];
|
|
@@ -227,11 +256,14 @@ function getEnvUrls(env) {
|
|
|
227
256
|
function getAvailableEnvs() {
|
|
228
257
|
return Object.keys(ENV_CONFIG);
|
|
229
258
|
}
|
|
230
|
-
var ENV_CONFIG, VALID_ENVS;
|
|
259
|
+
var import_node_fs9, import_node_path8, ENV_CONFIG, VALID_ENVS;
|
|
231
260
|
var init_env = __esm({
|
|
232
261
|
"src/env.ts"() {
|
|
233
262
|
"use strict";
|
|
263
|
+
import_node_fs9 = require("fs");
|
|
264
|
+
import_node_path8 = require("path");
|
|
234
265
|
init_credentials();
|
|
266
|
+
init_host();
|
|
235
267
|
ENV_CONFIG = {
|
|
236
268
|
development: {
|
|
237
269
|
lightApiUrl: "https://openclaw-service-dev.yoooclaw.com/api/message/tob/sendMessage",
|
|
@@ -500,20 +532,20 @@ function selectModelByMemory(availableGB, isAppleSilicon = false) {
|
|
|
500
532
|
return "tiny";
|
|
501
533
|
}
|
|
502
534
|
function resolveModelsDir(dataDir) {
|
|
503
|
-
const dir = (0,
|
|
504
|
-
(0,
|
|
535
|
+
const dir = (0, import_node_path22.join)(dataDir, WHISPER_MODELS_DIR);
|
|
536
|
+
(0, import_node_fs26.mkdirSync)(dir, { recursive: true });
|
|
505
537
|
return dir;
|
|
506
538
|
}
|
|
507
539
|
function resolveBinDir(dataDir) {
|
|
508
|
-
const dir = (0,
|
|
509
|
-
(0,
|
|
540
|
+
const dir = (0, import_node_path22.join)(dataDir, WHISPER_BIN_DIR);
|
|
541
|
+
(0, import_node_fs26.mkdirSync)(dir, { recursive: true });
|
|
510
542
|
return dir;
|
|
511
543
|
}
|
|
512
544
|
function isModelDownloaded(modelsDir, modelSize) {
|
|
513
|
-
const modelPath = (0,
|
|
514
|
-
if (!(0,
|
|
545
|
+
const modelPath = (0, import_node_path22.join)(modelsDir, MODEL_FILENAMES[modelSize]);
|
|
546
|
+
if (!(0, import_node_fs26.existsSync)(modelPath)) return false;
|
|
515
547
|
try {
|
|
516
|
-
const stat = (0,
|
|
548
|
+
const stat = (0, import_node_fs26.statSync)(modelPath);
|
|
517
549
|
const expectedSize = MODEL_DISK_SIZES[modelSize];
|
|
518
550
|
return stat.size >= expectedSize * 0.8;
|
|
519
551
|
} catch {
|
|
@@ -522,7 +554,7 @@ function isModelDownloaded(modelsDir, modelSize) {
|
|
|
522
554
|
}
|
|
523
555
|
async function downloadModel(modelsDir, modelSize, logger, modelSource, mirrorUrl) {
|
|
524
556
|
const filename = MODEL_FILENAMES[modelSize];
|
|
525
|
-
const modelPath = (0,
|
|
557
|
+
const modelPath = (0, import_node_path22.join)(modelsDir, filename);
|
|
526
558
|
if (isModelDownloaded(modelsDir, modelSize)) {
|
|
527
559
|
logger.info(`[whisper-local] \u6A21\u578B\u5DF2\u5B58\u5728\uFF0C\u8DF3\u8FC7\u4E0B\u8F7D: ${filename}`);
|
|
528
560
|
return { ok: true, modelPath };
|
|
@@ -578,9 +610,9 @@ async function probeUrl(url, logger) {
|
|
|
578
610
|
} finally {
|
|
579
611
|
clearTimeout(timer);
|
|
580
612
|
}
|
|
581
|
-
} catch (
|
|
613
|
+
} catch (err2) {
|
|
582
614
|
logger.info(
|
|
583
|
-
`[whisper-local] \u8FDE\u901A\u6027\u63A2\u6D4B\u5931\u8D25: ${url} (${
|
|
615
|
+
`[whisper-local] \u8FDE\u901A\u6027\u63A2\u6D4B\u5931\u8D25: ${url} (${err2?.name ?? err2?.message ?? "unknown"})`
|
|
584
616
|
);
|
|
585
617
|
return false;
|
|
586
618
|
}
|
|
@@ -588,7 +620,7 @@ async function probeUrl(url, logger) {
|
|
|
588
620
|
async function downloadFromUrl(url, modelPath, logger) {
|
|
589
621
|
const tmpPath = `${modelPath}.downloading`;
|
|
590
622
|
try {
|
|
591
|
-
(0,
|
|
623
|
+
(0, import_node_fs26.mkdirSync)((0, import_node_path22.dirname)(modelPath), { recursive: true });
|
|
592
624
|
const controller = new AbortController();
|
|
593
625
|
const timer = setTimeout(() => controller.abort(), 30 * 60 * 1e3);
|
|
594
626
|
try {
|
|
@@ -604,7 +636,7 @@ async function downloadFromUrl(url, modelPath, logger) {
|
|
|
604
636
|
return { ok: false, modelPath, error: "\u6A21\u578B\u4E0B\u8F7D\u5931\u8D25: \u54CD\u5E94\u4F53\u4E3A\u7A7A" };
|
|
605
637
|
}
|
|
606
638
|
const contentLength = Number(res.headers.get("content-length") ?? 0);
|
|
607
|
-
const writeStream = (0,
|
|
639
|
+
const writeStream = (0, import_node_fs26.createWriteStream)(tmpPath);
|
|
608
640
|
const readable = import_node_stream2.Readable.fromWeb(res.body);
|
|
609
641
|
let downloaded = 0;
|
|
610
642
|
let lastLogPercent = 0;
|
|
@@ -626,17 +658,17 @@ async function downloadFromUrl(url, modelPath, logger) {
|
|
|
626
658
|
}
|
|
627
659
|
const { renameSync: renameSync2 } = await import("fs");
|
|
628
660
|
renameSync2(tmpPath, modelPath);
|
|
629
|
-
const fileSize = (0,
|
|
661
|
+
const fileSize = (0, import_node_fs26.statSync)(modelPath).size;
|
|
630
662
|
logger.info(
|
|
631
|
-
`[whisper-local] \u6A21\u578B\u4E0B\u8F7D\u5B8C\u6210: ${(0,
|
|
663
|
+
`[whisper-local] \u6A21\u578B\u4E0B\u8F7D\u5B8C\u6210: ${(0, import_node_path22.basename)(modelPath)} (${formatBytes2(fileSize)})`
|
|
632
664
|
);
|
|
633
665
|
return { ok: true, modelPath };
|
|
634
|
-
} catch (
|
|
666
|
+
} catch (err2) {
|
|
635
667
|
try {
|
|
636
|
-
if ((0,
|
|
668
|
+
if ((0, import_node_fs26.existsSync)(tmpPath)) (0, import_node_fs26.unlinkSync)(tmpPath);
|
|
637
669
|
} catch {
|
|
638
670
|
}
|
|
639
|
-
const msg =
|
|
671
|
+
const msg = err2?.name === "AbortError" ? "\u6A21\u578B\u4E0B\u8F7D\u8D85\u65F6\uFF0830 \u5206\u949F\uFF09" : err2?.message ?? String(err2);
|
|
640
672
|
logger.error(`[whisper-local] \u6A21\u578B\u4E0B\u8F7D\u5931\u8D25: ${msg}`);
|
|
641
673
|
return { ok: false, modelPath, error: msg };
|
|
642
674
|
}
|
|
@@ -645,8 +677,8 @@ function findWhisperBinary(dataDir, logger) {
|
|
|
645
677
|
const binDir = resolveBinDir(dataDir);
|
|
646
678
|
const binNames = (0, import_node_os4.platform)() === "win32" ? ["whisper-cli.exe", "whisper.exe", "main.exe"] : ["whisper-cli", "whisper", "main"];
|
|
647
679
|
for (const name of binNames) {
|
|
648
|
-
const binPath = (0,
|
|
649
|
-
if ((0,
|
|
680
|
+
const binPath = (0, import_node_path22.join)(binDir, name);
|
|
681
|
+
if ((0, import_node_fs26.existsSync)(binPath)) {
|
|
650
682
|
logger.info(`[whisper-local] \u627E\u5230\u672C\u5730\u4E8C\u8FDB\u5236: ${binPath}`);
|
|
651
683
|
return binPath;
|
|
652
684
|
}
|
|
@@ -697,7 +729,7 @@ async function transcribeWithWhisperLocal(audioFilePath, localConfig, dataDir, l
|
|
|
697
729
|
return { ok: false, error: `\u6A21\u578B\u4E0B\u8F7D\u5931\u8D25: ${downloadResult.error}` };
|
|
698
730
|
}
|
|
699
731
|
}
|
|
700
|
-
const modelPath = (0,
|
|
732
|
+
const modelPath = (0, import_node_path22.join)(modelsDir, MODEL_FILENAMES[modelSize]);
|
|
701
733
|
let inputPath = audioFilePath;
|
|
702
734
|
let tmpWavPath = null;
|
|
703
735
|
const actualFmt = detectAudioFormat(audioFilePath);
|
|
@@ -733,9 +765,9 @@ async function transcribeWithWhisperLocal(audioFilePath, localConfig, dataDir, l
|
|
|
733
765
|
// 100MB stdout buffer
|
|
734
766
|
stdio: ["pipe", "pipe", "pipe"]
|
|
735
767
|
});
|
|
736
|
-
} catch (
|
|
768
|
+
} catch (err2) {
|
|
737
769
|
cleanupTmpWav(tmpWavPath);
|
|
738
|
-
return { ok: false, error: `whisper.cpp \u6267\u884C\u5931\u8D25: ${
|
|
770
|
+
return { ok: false, error: `whisper.cpp \u6267\u884C\u5931\u8D25: ${err2?.message ?? err2}` };
|
|
739
771
|
}
|
|
740
772
|
if (result.status !== 0) {
|
|
741
773
|
cleanupTmpWav(tmpWavPath);
|
|
@@ -749,10 +781,10 @@ async function transcribeWithWhisperLocal(audioFilePath, localConfig, dataDir, l
|
|
|
749
781
|
logger.info(`[whisper-local] \u8F6C\u5199\u8017\u65F6: ${Math.round(elapsed / 1e3)}s`);
|
|
750
782
|
const jsonPath = inputPath + ".json";
|
|
751
783
|
let jsonContent;
|
|
752
|
-
if ((0,
|
|
753
|
-
jsonContent = (0,
|
|
784
|
+
if ((0, import_node_fs26.existsSync)(jsonPath)) {
|
|
785
|
+
jsonContent = (0, import_node_fs26.readFileSync)(jsonPath, "utf-8");
|
|
754
786
|
try {
|
|
755
|
-
(0,
|
|
787
|
+
(0, import_node_fs26.unlinkSync)(jsonPath);
|
|
756
788
|
} catch {
|
|
757
789
|
}
|
|
758
790
|
} else {
|
|
@@ -760,9 +792,9 @@ async function transcribeWithWhisperLocal(audioFilePath, localConfig, dataDir, l
|
|
|
760
792
|
}
|
|
761
793
|
cleanupTmpWav(tmpWavPath);
|
|
762
794
|
return parseWhisperOutput(jsonContent, logger);
|
|
763
|
-
} catch (
|
|
795
|
+
} catch (err2) {
|
|
764
796
|
cleanupTmpWav(tmpWavPath);
|
|
765
|
-
return { ok: false, error: `whisper.cpp \u8F6C\u5199\u5F02\u5E38: ${
|
|
797
|
+
return { ok: false, error: `whisper.cpp \u8F6C\u5199\u5F02\u5E38: ${err2?.message ?? err2}` };
|
|
766
798
|
}
|
|
767
799
|
}
|
|
768
800
|
function getWhisperLocalStatus(dataDir, logger) {
|
|
@@ -830,10 +862,10 @@ function getPhysicalCoreCount() {
|
|
|
830
862
|
}
|
|
831
863
|
function detectAudioFormat(filePath) {
|
|
832
864
|
try {
|
|
833
|
-
const fd = (0,
|
|
865
|
+
const fd = (0, import_node_fs26.openSync)(filePath, "r");
|
|
834
866
|
const buf = Buffer.alloc(12);
|
|
835
|
-
(0,
|
|
836
|
-
(0,
|
|
867
|
+
(0, import_node_fs26.readSync)(fd, buf, 0, 12, 0);
|
|
868
|
+
(0, import_node_fs26.closeSync)(fd);
|
|
837
869
|
const header = buf.toString("ascii", 0, 4);
|
|
838
870
|
const header8 = buf.toString("ascii", 0, 8);
|
|
839
871
|
if (header === "RIFF" && buf.toString("ascii", 8, 12) === "WAVE") return ".wav";
|
|
@@ -868,7 +900,7 @@ function convertToWav(inputPath, outputPath, actualFmt, logger) {
|
|
|
868
900
|
timeout: 12e4,
|
|
869
901
|
stdio: ["pipe", "pipe", "pipe"]
|
|
870
902
|
});
|
|
871
|
-
if (ffmpegResult.status === 0 && (0,
|
|
903
|
+
if (ffmpegResult.status === 0 && (0, import_node_fs26.existsSync)(outputPath)) {
|
|
872
904
|
logger.info(`[whisper-local] ffmpeg \u8F6C\u6362\u5B8C\u6210: ${outputPath}`);
|
|
873
905
|
return { ok: true };
|
|
874
906
|
}
|
|
@@ -881,7 +913,7 @@ function convertToWav(inputPath, outputPath, actualFmt, logger) {
|
|
|
881
913
|
["--rate", "16000", "--mono", inputPath, outputPath],
|
|
882
914
|
{ encoding: "utf-8", timeout: 12e4, stdio: ["pipe", "pipe", "pipe"] }
|
|
883
915
|
);
|
|
884
|
-
if (opusResult.status === 0 && (0,
|
|
916
|
+
if (opusResult.status === 0 && (0, import_node_fs26.existsSync)(outputPath)) {
|
|
885
917
|
logger.info(`[whisper-local] opusdec \u8F6C\u6362\u5B8C\u6210: ${outputPath}`);
|
|
886
918
|
return { ok: true };
|
|
887
919
|
}
|
|
@@ -895,7 +927,7 @@ function convertToWav(inputPath, outputPath, actualFmt, logger) {
|
|
|
895
927
|
if (detectedExt && !inputPath.endsWith(detectedExt)) {
|
|
896
928
|
tmpCopy = inputPath + ".detected" + detectedExt;
|
|
897
929
|
try {
|
|
898
|
-
(0,
|
|
930
|
+
(0, import_node_fs26.copyFileSync)(inputPath, tmpCopy);
|
|
899
931
|
actualInputPath = tmpCopy;
|
|
900
932
|
logger.info(
|
|
901
933
|
`[whisper-local] \u68C0\u6D4B\u5230\u5B9E\u9645\u683C\u5F0F ${detectedExt}\uFF0C\u4E34\u65F6\u91CD\u547D\u540D`
|
|
@@ -919,35 +951,35 @@ function convertToWav(inputPath, outputPath, actualFmt, logger) {
|
|
|
919
951
|
timeout: 12e4,
|
|
920
952
|
stdio: ["pipe", "pipe", "pipe"]
|
|
921
953
|
});
|
|
922
|
-
if (tmpCopy && (0,
|
|
954
|
+
if (tmpCopy && (0, import_node_fs26.existsSync)(tmpCopy)) {
|
|
923
955
|
try {
|
|
924
|
-
(0,
|
|
956
|
+
(0, import_node_fs26.unlinkSync)(tmpCopy);
|
|
925
957
|
} catch {
|
|
926
958
|
}
|
|
927
959
|
}
|
|
928
|
-
if (afResult.status === 0 && (0,
|
|
960
|
+
if (afResult.status === 0 && (0, import_node_fs26.existsSync)(outputPath)) {
|
|
929
961
|
logger.info(`[whisper-local] afconvert \u8F6C\u6362\u5B8C\u6210: ${outputPath}`);
|
|
930
962
|
return { ok: true };
|
|
931
963
|
}
|
|
932
964
|
const stderr = afResult.stderr?.slice(0, 200) ?? "";
|
|
933
965
|
return { ok: false, error: `afconvert \u8F6C\u6362\u5931\u8D25 (exit ${afResult.status}): ${stderr}` };
|
|
934
|
-
} catch (
|
|
935
|
-
if (tmpCopy && (0,
|
|
966
|
+
} catch (err2) {
|
|
967
|
+
if (tmpCopy && (0, import_node_fs26.existsSync)(tmpCopy)) {
|
|
936
968
|
try {
|
|
937
|
-
(0,
|
|
969
|
+
(0, import_node_fs26.unlinkSync)(tmpCopy);
|
|
938
970
|
} catch {
|
|
939
971
|
}
|
|
940
972
|
}
|
|
941
|
-
return { ok: false, error: `afconvert \u4E0D\u53EF\u7528: ${
|
|
973
|
+
return { ok: false, error: `afconvert \u4E0D\u53EF\u7528: ${err2?.message}` };
|
|
942
974
|
}
|
|
943
975
|
}
|
|
944
976
|
const fmtHint = actualFmt === ".ogg" ? "OGG/Opus \u683C\u5F0F\u9700\u8981 ffmpeg \u6216 opus-tools\uFF08brew install opus-tools\uFF09" : "\u8BF7\u5B89\u88C5 ffmpeg\uFF08brew install ffmpeg\uFF09\u6216\u786E\u4FDD\u97F3\u9891\u6587\u4EF6\u4E3A WAV \u683C\u5F0F";
|
|
945
977
|
return { ok: false, error: `\u65E0\u6CD5\u5C06\u97F3\u9891\u8F6C\u6362\u4E3A WAV \u683C\u5F0F\u3002${fmtHint}` };
|
|
946
978
|
}
|
|
947
979
|
function cleanupTmpWav(path2) {
|
|
948
|
-
if (path2 && (0,
|
|
980
|
+
if (path2 && (0, import_node_fs26.existsSync)(path2)) {
|
|
949
981
|
try {
|
|
950
|
-
(0,
|
|
982
|
+
(0, import_node_fs26.unlinkSync)(path2);
|
|
951
983
|
} catch {
|
|
952
984
|
}
|
|
953
985
|
}
|
|
@@ -1011,8 +1043,8 @@ function parseWhisperOutput(stdout, logger) {
|
|
|
1011
1043
|
text: fullText,
|
|
1012
1044
|
segments
|
|
1013
1045
|
};
|
|
1014
|
-
} catch (
|
|
1015
|
-
logger.warn(`[whisper-local] JSON \u89E3\u6790\u5931\u8D25\uFF0C\u5C1D\u8BD5\u7EAF\u6587\u672C: ${
|
|
1046
|
+
} catch (err2) {
|
|
1047
|
+
logger.warn(`[whisper-local] JSON \u89E3\u6790\u5931\u8D25\uFF0C\u5C1D\u8BD5\u7EAF\u6587\u672C: ${err2?.message}`);
|
|
1016
1048
|
return {
|
|
1017
1049
|
ok: true,
|
|
1018
1050
|
text: stdout.trim(),
|
|
@@ -1033,13 +1065,13 @@ function formatBytes2(bytes) {
|
|
|
1033
1065
|
if (bytes < 1024 * 1024 * 1024) return `${(bytes / (1024 * 1024)).toFixed(1)}MB`;
|
|
1034
1066
|
return `${(bytes / (1024 * 1024 * 1024)).toFixed(2)}GB`;
|
|
1035
1067
|
}
|
|
1036
|
-
var import_node_child_process2,
|
|
1068
|
+
var import_node_child_process2, import_node_fs26, import_node_path22, 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;
|
|
1037
1069
|
var init_whisper_local = __esm({
|
|
1038
1070
|
"src/recording/whisper-local.ts"() {
|
|
1039
1071
|
"use strict";
|
|
1040
1072
|
import_node_child_process2 = require("child_process");
|
|
1041
|
-
|
|
1042
|
-
|
|
1073
|
+
import_node_fs26 = require("fs");
|
|
1074
|
+
import_node_path22 = require("path");
|
|
1043
1075
|
import_promises3 = require("stream/promises");
|
|
1044
1076
|
import_node_stream2 = require("stream");
|
|
1045
1077
|
import_node_os4 = require("os");
|
|
@@ -1194,7 +1226,7 @@ async function initializeAsr(config, dataDir, logger) {
|
|
|
1194
1226
|
}
|
|
1195
1227
|
}
|
|
1196
1228
|
async function transcribeAudio(audioFilePath, config, logger, options = {}) {
|
|
1197
|
-
if (!(0,
|
|
1229
|
+
if (!(0, import_node_fs27.existsSync)(audioFilePath)) {
|
|
1198
1230
|
return { ok: false, error: `\u97F3\u9891\u6587\u4EF6\u4E0D\u5B58\u5728: ${audioFilePath}` };
|
|
1199
1231
|
}
|
|
1200
1232
|
logger.info(
|
|
@@ -1218,8 +1250,8 @@ async function transcribeAudio(audioFilePath, config, logger, options = {}) {
|
|
|
1218
1250
|
error: `\u672A\u77E5\u7684 ASR mode: ${config.mode}`
|
|
1219
1251
|
};
|
|
1220
1252
|
}
|
|
1221
|
-
} catch (
|
|
1222
|
-
const msg =
|
|
1253
|
+
} catch (err2) {
|
|
1254
|
+
const msg = err2?.message ?? String(err2);
|
|
1223
1255
|
logger.error(`[asr] \u8F6C\u5199\u5F02\u5E38: ${msg}`);
|
|
1224
1256
|
return { ok: false, error: msg };
|
|
1225
1257
|
}
|
|
@@ -1338,8 +1370,8 @@ async function runTranscriptionWorkflow(params) {
|
|
|
1338
1370
|
createdAt
|
|
1339
1371
|
);
|
|
1340
1372
|
const transcriptDataFilename = buildTranscriptDataFilename(recordingId);
|
|
1341
|
-
const transcriptDataPath = (0,
|
|
1342
|
-
(0,
|
|
1373
|
+
const transcriptDataPath = (0, import_node_path23.join)(transcriptDataDir, transcriptDataFilename);
|
|
1374
|
+
(0, import_node_fs27.writeFileSync)(
|
|
1343
1375
|
transcriptDataPath,
|
|
1344
1376
|
JSON.stringify(transcriptData, null, 2),
|
|
1345
1377
|
"utf-8"
|
|
@@ -1347,14 +1379,14 @@ async function runTranscriptionWorkflow(params) {
|
|
|
1347
1379
|
logger.info(`[asr] \u8F6C\u5199 JSON \u5DF2\u5199\u5165: ${transcriptDataPath}`);
|
|
1348
1380
|
const safeSummary = title.replace(/[/\\:*?"<>|]/g, "").trim().slice(0, 20);
|
|
1349
1381
|
const filename = safeSummary ? `${recordingId}_${safeSummary}.md` : `${recordingId}.md`;
|
|
1350
|
-
const filePath = (0,
|
|
1351
|
-
(0,
|
|
1382
|
+
const filePath = (0, import_node_path23.join)(transcriptsDir, filename);
|
|
1383
|
+
(0, import_node_fs27.writeFileSync)(filePath, markdown, "utf-8");
|
|
1352
1384
|
logger.info(`[asr] \u8F6C\u5199\u6587\u672C\u5DF2\u5199\u5165: ${filePath}`);
|
|
1353
1385
|
let summaryFilename;
|
|
1354
1386
|
if (summary) {
|
|
1355
1387
|
summaryFilename = `${recordingId}.md`;
|
|
1356
|
-
const summaryFilePath = (0,
|
|
1357
|
-
(0,
|
|
1388
|
+
const summaryFilePath = (0, import_node_path23.join)(summariesDir, summaryFilename);
|
|
1389
|
+
(0, import_node_fs27.writeFileSync)(summaryFilePath, summary, "utf-8");
|
|
1358
1390
|
logger.info(`[asr] \u6458\u8981\u6587\u672C\u5DF2\u5199\u5165: ${summaryFilePath}`);
|
|
1359
1391
|
}
|
|
1360
1392
|
return {
|
|
@@ -1439,7 +1471,7 @@ async function transcribeWithModelProxy(audioOssUrl, apiConfig, logger) {
|
|
|
1439
1471
|
}
|
|
1440
1472
|
async function transcribeWithWhisperLocal2(audioFilePath, config, logger) {
|
|
1441
1473
|
const { transcribeWithWhisperLocal: runLocal } = await Promise.resolve().then(() => (init_whisper_local(), whisper_local_exports));
|
|
1442
|
-
const dataDir = process.env.OPENCLAW_STATE_DIR ?? process.env.QCLAW_STATE_DIR ?? (0,
|
|
1474
|
+
const dataDir = process.env.OPENCLAW_STATE_DIR ?? process.env.QCLAW_STATE_DIR ?? (0, import_node_path23.join)(audioFilePath, "..", "..", "..");
|
|
1443
1475
|
const localConfig = config.local ?? {};
|
|
1444
1476
|
const result = await runLocal(
|
|
1445
1477
|
audioFilePath,
|
|
@@ -1742,12 +1774,12 @@ function formatTranscriptSegmentText(segment) {
|
|
|
1742
1774
|
}
|
|
1743
1775
|
return text;
|
|
1744
1776
|
}
|
|
1745
|
-
var
|
|
1777
|
+
var import_node_fs27, import_node_path23, DEFAULT_LONG_RECORDING_POLL_INTERVAL_MS, DEFAULT_LONG_RECORDING_MAX_POLL_ATTEMPTS, LONG_RECORDING_RUNNING_STATUSES, LONG_RECORDING_TERMINAL_FAILURE_STATUSES;
|
|
1746
1778
|
var init_asr = __esm({
|
|
1747
1779
|
"src/recording/asr.ts"() {
|
|
1748
1780
|
"use strict";
|
|
1749
|
-
|
|
1750
|
-
|
|
1781
|
+
import_node_fs27 = require("fs");
|
|
1782
|
+
import_node_path23 = require("path");
|
|
1751
1783
|
init_credentials();
|
|
1752
1784
|
init_env();
|
|
1753
1785
|
init_transcript_document();
|
|
@@ -2143,9 +2175,9 @@ var require_permessage_deflate = __commonJS({
|
|
|
2143
2175
|
*/
|
|
2144
2176
|
decompress(data, fin, callback) {
|
|
2145
2177
|
zlibLimiter.add((done) => {
|
|
2146
|
-
this._decompress(data, fin, (
|
|
2178
|
+
this._decompress(data, fin, (err2, result) => {
|
|
2147
2179
|
done();
|
|
2148
|
-
callback(
|
|
2180
|
+
callback(err2, result);
|
|
2149
2181
|
});
|
|
2150
2182
|
});
|
|
2151
2183
|
}
|
|
@@ -2159,9 +2191,9 @@ var require_permessage_deflate = __commonJS({
|
|
|
2159
2191
|
*/
|
|
2160
2192
|
compress(data, fin, callback) {
|
|
2161
2193
|
zlibLimiter.add((done) => {
|
|
2162
|
-
this._compress(data, fin, (
|
|
2194
|
+
this._compress(data, fin, (err2, result) => {
|
|
2163
2195
|
done();
|
|
2164
|
-
callback(
|
|
2196
|
+
callback(err2, result);
|
|
2165
2197
|
});
|
|
2166
2198
|
});
|
|
2167
2199
|
}
|
|
@@ -2192,11 +2224,11 @@ var require_permessage_deflate = __commonJS({
|
|
|
2192
2224
|
this._inflate.write(data);
|
|
2193
2225
|
if (fin) this._inflate.write(TRAILER);
|
|
2194
2226
|
this._inflate.flush(() => {
|
|
2195
|
-
const
|
|
2196
|
-
if (
|
|
2227
|
+
const err2 = this._inflate[kError];
|
|
2228
|
+
if (err2) {
|
|
2197
2229
|
this._inflate.close();
|
|
2198
2230
|
this._inflate = null;
|
|
2199
|
-
callback(
|
|
2231
|
+
callback(err2);
|
|
2200
2232
|
return;
|
|
2201
2233
|
}
|
|
2202
2234
|
const data2 = bufferUtil.concat(
|
|
@@ -2277,14 +2309,14 @@ var require_permessage_deflate = __commonJS({
|
|
|
2277
2309
|
this.removeListener("data", inflateOnData);
|
|
2278
2310
|
this.reset();
|
|
2279
2311
|
}
|
|
2280
|
-
function inflateOnError(
|
|
2312
|
+
function inflateOnError(err2) {
|
|
2281
2313
|
this[kPerMessageDeflate]._inflate = null;
|
|
2282
2314
|
if (this[kError]) {
|
|
2283
2315
|
this[kCallback](this[kError]);
|
|
2284
2316
|
return;
|
|
2285
2317
|
}
|
|
2286
|
-
|
|
2287
|
-
this[kCallback](
|
|
2318
|
+
err2[kStatusCode] = 1007;
|
|
2319
|
+
this[kCallback](err2);
|
|
2288
2320
|
}
|
|
2289
2321
|
}
|
|
2290
2322
|
});
|
|
@@ -2907,8 +2939,8 @@ var require_receiver = __commonJS({
|
|
|
2907
2939
|
*/
|
|
2908
2940
|
decompress(data, cb) {
|
|
2909
2941
|
const perMessageDeflate = this._extensions[PerMessageDeflate.extensionName];
|
|
2910
|
-
perMessageDeflate.decompress(data, this._fin, (
|
|
2911
|
-
if (
|
|
2942
|
+
perMessageDeflate.decompress(data, this._fin, (err2, buf) => {
|
|
2943
|
+
if (err2) return cb(err2);
|
|
2912
2944
|
if (buf.length) {
|
|
2913
2945
|
this._messageLength += buf.length;
|
|
2914
2946
|
if (this._messageLength > this._maxPayload && this._maxPayload > 0) {
|
|
@@ -3069,13 +3101,13 @@ var require_receiver = __commonJS({
|
|
|
3069
3101
|
createError(ErrorCtor, message, prefix, statusCode, errorCode) {
|
|
3070
3102
|
this._loop = false;
|
|
3071
3103
|
this._errored = true;
|
|
3072
|
-
const
|
|
3104
|
+
const err2 = new ErrorCtor(
|
|
3073
3105
|
prefix ? `Invalid WebSocket frame: ${message}` : message
|
|
3074
3106
|
);
|
|
3075
|
-
Error.captureStackTrace(
|
|
3076
|
-
|
|
3077
|
-
|
|
3078
|
-
return
|
|
3107
|
+
Error.captureStackTrace(err2, this.createError);
|
|
3108
|
+
err2.code = errorCode;
|
|
3109
|
+
err2[kStatusCode] = statusCode;
|
|
3110
|
+
return err2;
|
|
3079
3111
|
}
|
|
3080
3112
|
};
|
|
3081
3113
|
module2.exports = Receiver2;
|
|
@@ -3449,10 +3481,10 @@ var require_sender = __commonJS({
|
|
|
3449
3481
|
this._state = GET_BLOB_DATA;
|
|
3450
3482
|
blob.arrayBuffer().then((arrayBuffer) => {
|
|
3451
3483
|
if (this._socket.destroyed) {
|
|
3452
|
-
const
|
|
3484
|
+
const err2 = new Error(
|
|
3453
3485
|
"The socket was closed while the blob was being read"
|
|
3454
3486
|
);
|
|
3455
|
-
process.nextTick(callCallbacks, this,
|
|
3487
|
+
process.nextTick(callCallbacks, this, err2, cb);
|
|
3456
3488
|
return;
|
|
3457
3489
|
}
|
|
3458
3490
|
this._bufferedBytes -= options[kByteLength];
|
|
@@ -3464,8 +3496,8 @@ var require_sender = __commonJS({
|
|
|
3464
3496
|
} else {
|
|
3465
3497
|
this.dispatch(data, compress, options, cb);
|
|
3466
3498
|
}
|
|
3467
|
-
}).catch((
|
|
3468
|
-
process.nextTick(onError, this,
|
|
3499
|
+
}).catch((err2) => {
|
|
3500
|
+
process.nextTick(onError, this, err2, cb);
|
|
3469
3501
|
});
|
|
3470
3502
|
}
|
|
3471
3503
|
/**
|
|
@@ -3501,10 +3533,10 @@ var require_sender = __commonJS({
|
|
|
3501
3533
|
this._state = DEFLATING;
|
|
3502
3534
|
perMessageDeflate.compress(data, options.fin, (_, buf) => {
|
|
3503
3535
|
if (this._socket.destroyed) {
|
|
3504
|
-
const
|
|
3536
|
+
const err2 = new Error(
|
|
3505
3537
|
"The socket was closed while data was being compressed"
|
|
3506
3538
|
);
|
|
3507
|
-
callCallbacks(this,
|
|
3539
|
+
callCallbacks(this, err2, cb);
|
|
3508
3540
|
return;
|
|
3509
3541
|
}
|
|
3510
3542
|
this._bufferedBytes -= options[kByteLength];
|
|
@@ -3555,17 +3587,17 @@ var require_sender = __commonJS({
|
|
|
3555
3587
|
}
|
|
3556
3588
|
};
|
|
3557
3589
|
module2.exports = Sender2;
|
|
3558
|
-
function callCallbacks(sender,
|
|
3559
|
-
if (typeof cb === "function") cb(
|
|
3590
|
+
function callCallbacks(sender, err2, cb) {
|
|
3591
|
+
if (typeof cb === "function") cb(err2);
|
|
3560
3592
|
for (let i = 0; i < sender._queue.length; i++) {
|
|
3561
3593
|
const params = sender._queue[i];
|
|
3562
3594
|
const callback = params[params.length - 1];
|
|
3563
|
-
if (typeof callback === "function") callback(
|
|
3595
|
+
if (typeof callback === "function") callback(err2);
|
|
3564
3596
|
}
|
|
3565
3597
|
}
|
|
3566
|
-
function onError(sender,
|
|
3567
|
-
callCallbacks(sender,
|
|
3568
|
-
sender.onerror(
|
|
3598
|
+
function onError(sender, err2, cb) {
|
|
3599
|
+
callCallbacks(sender, err2, cb);
|
|
3600
|
+
sender.onerror(err2);
|
|
3569
3601
|
}
|
|
3570
3602
|
}
|
|
3571
3603
|
});
|
|
@@ -4213,8 +4245,8 @@ var require_websocket = __commonJS({
|
|
|
4213
4245
|
return;
|
|
4214
4246
|
}
|
|
4215
4247
|
this._readyState = _WebSocket.CLOSING;
|
|
4216
|
-
this._sender.close(code, data, !this._isServer, (
|
|
4217
|
-
if (
|
|
4248
|
+
this._sender.close(code, data, !this._isServer, (err2) => {
|
|
4249
|
+
if (err2) return;
|
|
4218
4250
|
this._closeFrameSent = true;
|
|
4219
4251
|
if (this._closeFrameReceived || this._receiver._writableState.errorEmitted) {
|
|
4220
4252
|
this._socket.end();
|
|
@@ -4482,11 +4514,11 @@ var require_websocket = __commonJS({
|
|
|
4482
4514
|
invalidUrlMessage = "The URL contains a fragment identifier";
|
|
4483
4515
|
}
|
|
4484
4516
|
if (invalidUrlMessage) {
|
|
4485
|
-
const
|
|
4517
|
+
const err2 = new SyntaxError(invalidUrlMessage);
|
|
4486
4518
|
if (websocket._redirects === 0) {
|
|
4487
|
-
throw
|
|
4519
|
+
throw err2;
|
|
4488
4520
|
} else {
|
|
4489
|
-
emitErrorAndClose(websocket,
|
|
4521
|
+
emitErrorAndClose(websocket, err2);
|
|
4490
4522
|
return;
|
|
4491
4523
|
}
|
|
4492
4524
|
}
|
|
@@ -4581,10 +4613,10 @@ var require_websocket = __commonJS({
|
|
|
4581
4613
|
abortHandshake(websocket, req, "Opening handshake has timed out");
|
|
4582
4614
|
});
|
|
4583
4615
|
}
|
|
4584
|
-
req.on("error", (
|
|
4616
|
+
req.on("error", (err2) => {
|
|
4585
4617
|
if (req === null || req[kAborted]) return;
|
|
4586
4618
|
req = websocket._req = null;
|
|
4587
|
-
emitErrorAndClose(websocket,
|
|
4619
|
+
emitErrorAndClose(websocket, err2);
|
|
4588
4620
|
});
|
|
4589
4621
|
req.on("response", (res) => {
|
|
4590
4622
|
const location = res.headers.location;
|
|
@@ -4599,8 +4631,8 @@ var require_websocket = __commonJS({
|
|
|
4599
4631
|
try {
|
|
4600
4632
|
addr = new URL2(location, address);
|
|
4601
4633
|
} catch (e) {
|
|
4602
|
-
const
|
|
4603
|
-
emitErrorAndClose(websocket,
|
|
4634
|
+
const err2 = new SyntaxError(`Invalid URL: ${location}`);
|
|
4635
|
+
emitErrorAndClose(websocket, err2);
|
|
4604
4636
|
return;
|
|
4605
4637
|
}
|
|
4606
4638
|
initAsClient(websocket, addr, protocols, options);
|
|
@@ -4652,7 +4684,7 @@ var require_websocket = __commonJS({
|
|
|
4652
4684
|
let extensions;
|
|
4653
4685
|
try {
|
|
4654
4686
|
extensions = parse(secWebSocketExtensions);
|
|
4655
|
-
} catch (
|
|
4687
|
+
} catch (err2) {
|
|
4656
4688
|
const message = "Invalid Sec-WebSocket-Extensions header";
|
|
4657
4689
|
abortHandshake(websocket, socket, message);
|
|
4658
4690
|
return;
|
|
@@ -4665,7 +4697,7 @@ var require_websocket = __commonJS({
|
|
|
4665
4697
|
}
|
|
4666
4698
|
try {
|
|
4667
4699
|
perMessageDeflate.accept(extensions[PerMessageDeflate.extensionName]);
|
|
4668
|
-
} catch (
|
|
4700
|
+
} catch (err2) {
|
|
4669
4701
|
const message = "Invalid Sec-WebSocket-Extensions header";
|
|
4670
4702
|
abortHandshake(websocket, socket, message);
|
|
4671
4703
|
return;
|
|
@@ -4685,10 +4717,10 @@ var require_websocket = __commonJS({
|
|
|
4685
4717
|
req.end();
|
|
4686
4718
|
}
|
|
4687
4719
|
}
|
|
4688
|
-
function emitErrorAndClose(websocket,
|
|
4720
|
+
function emitErrorAndClose(websocket, err2) {
|
|
4689
4721
|
websocket._readyState = WebSocket2.CLOSING;
|
|
4690
4722
|
websocket._errorEmitted = true;
|
|
4691
|
-
websocket.emit("error",
|
|
4723
|
+
websocket.emit("error", err2);
|
|
4692
4724
|
websocket.emitClose();
|
|
4693
4725
|
}
|
|
4694
4726
|
function netConnect(options) {
|
|
@@ -4704,17 +4736,17 @@ var require_websocket = __commonJS({
|
|
|
4704
4736
|
}
|
|
4705
4737
|
function abortHandshake(websocket, stream, message) {
|
|
4706
4738
|
websocket._readyState = WebSocket2.CLOSING;
|
|
4707
|
-
const
|
|
4708
|
-
Error.captureStackTrace(
|
|
4739
|
+
const err2 = new Error(message);
|
|
4740
|
+
Error.captureStackTrace(err2, abortHandshake);
|
|
4709
4741
|
if (stream.setHeader) {
|
|
4710
4742
|
stream[kAborted] = true;
|
|
4711
4743
|
stream.abort();
|
|
4712
4744
|
if (stream.socket && !stream.socket.destroyed) {
|
|
4713
4745
|
stream.socket.destroy();
|
|
4714
4746
|
}
|
|
4715
|
-
process.nextTick(emitErrorAndClose, websocket,
|
|
4747
|
+
process.nextTick(emitErrorAndClose, websocket, err2);
|
|
4716
4748
|
} else {
|
|
4717
|
-
stream.destroy(
|
|
4749
|
+
stream.destroy(err2);
|
|
4718
4750
|
stream.once("error", websocket.emit.bind(websocket, "error"));
|
|
4719
4751
|
stream.once("close", websocket.emitClose.bind(websocket));
|
|
4720
4752
|
}
|
|
@@ -4726,10 +4758,10 @@ var require_websocket = __commonJS({
|
|
|
4726
4758
|
else websocket._bufferedAmount += length;
|
|
4727
4759
|
}
|
|
4728
4760
|
if (cb) {
|
|
4729
|
-
const
|
|
4761
|
+
const err2 = new Error(
|
|
4730
4762
|
`WebSocket is not open: readyState ${websocket.readyState} (${readyStates[websocket.readyState]})`
|
|
4731
4763
|
);
|
|
4732
|
-
process.nextTick(cb,
|
|
4764
|
+
process.nextTick(cb, err2);
|
|
4733
4765
|
}
|
|
4734
4766
|
}
|
|
4735
4767
|
function receiverOnConclude(code, reason) {
|
|
@@ -4747,16 +4779,16 @@ var require_websocket = __commonJS({
|
|
|
4747
4779
|
const websocket = this[kWebSocket];
|
|
4748
4780
|
if (!websocket.isPaused) websocket._socket.resume();
|
|
4749
4781
|
}
|
|
4750
|
-
function receiverOnError(
|
|
4782
|
+
function receiverOnError(err2) {
|
|
4751
4783
|
const websocket = this[kWebSocket];
|
|
4752
4784
|
if (websocket._socket[kWebSocket] !== void 0) {
|
|
4753
4785
|
websocket._socket.removeListener("data", socketOnData);
|
|
4754
4786
|
process.nextTick(resume, websocket._socket);
|
|
4755
|
-
websocket.close(
|
|
4787
|
+
websocket.close(err2[kStatusCode]);
|
|
4756
4788
|
}
|
|
4757
4789
|
if (!websocket._errorEmitted) {
|
|
4758
4790
|
websocket._errorEmitted = true;
|
|
4759
|
-
websocket.emit("error",
|
|
4791
|
+
websocket.emit("error", err2);
|
|
4760
4792
|
}
|
|
4761
4793
|
}
|
|
4762
4794
|
function receiverOnFinish() {
|
|
@@ -4776,7 +4808,7 @@ var require_websocket = __commonJS({
|
|
|
4776
4808
|
function resume(stream) {
|
|
4777
4809
|
stream.resume();
|
|
4778
4810
|
}
|
|
4779
|
-
function senderOnError(
|
|
4811
|
+
function senderOnError(err2) {
|
|
4780
4812
|
const websocket = this[kWebSocket];
|
|
4781
4813
|
if (websocket.readyState === WebSocket2.CLOSED) return;
|
|
4782
4814
|
if (websocket.readyState === WebSocket2.OPEN) {
|
|
@@ -4786,7 +4818,7 @@ var require_websocket = __commonJS({
|
|
|
4786
4818
|
this._socket.end();
|
|
4787
4819
|
if (!websocket._errorEmitted) {
|
|
4788
4820
|
websocket._errorEmitted = true;
|
|
4789
|
-
websocket.emit("error",
|
|
4821
|
+
websocket.emit("error", err2);
|
|
4790
4822
|
}
|
|
4791
4823
|
}
|
|
4792
4824
|
function setCloseTimer(websocket) {
|
|
@@ -4852,11 +4884,11 @@ var require_stream = __commonJS({
|
|
|
4852
4884
|
this.destroy();
|
|
4853
4885
|
}
|
|
4854
4886
|
}
|
|
4855
|
-
function duplexOnError(
|
|
4887
|
+
function duplexOnError(err2) {
|
|
4856
4888
|
this.removeListener("error", duplexOnError);
|
|
4857
4889
|
this.destroy();
|
|
4858
4890
|
if (this.listenerCount("error") === 0) {
|
|
4859
|
-
this.emit("error",
|
|
4891
|
+
this.emit("error", err2);
|
|
4860
4892
|
}
|
|
4861
4893
|
}
|
|
4862
4894
|
function createWebSocketStream2(ws, options) {
|
|
@@ -4872,28 +4904,28 @@ var require_stream = __commonJS({
|
|
|
4872
4904
|
const data = !isBinary && duplex._readableState.objectMode ? msg.toString() : msg;
|
|
4873
4905
|
if (!duplex.push(data)) ws.pause();
|
|
4874
4906
|
});
|
|
4875
|
-
ws.once("error", function error(
|
|
4907
|
+
ws.once("error", function error(err2) {
|
|
4876
4908
|
if (duplex.destroyed) return;
|
|
4877
4909
|
terminateOnDestroy = false;
|
|
4878
|
-
duplex.destroy(
|
|
4910
|
+
duplex.destroy(err2);
|
|
4879
4911
|
});
|
|
4880
4912
|
ws.once("close", function close() {
|
|
4881
4913
|
if (duplex.destroyed) return;
|
|
4882
4914
|
duplex.push(null);
|
|
4883
4915
|
});
|
|
4884
|
-
duplex._destroy = function(
|
|
4916
|
+
duplex._destroy = function(err2, callback) {
|
|
4885
4917
|
if (ws.readyState === ws.CLOSED) {
|
|
4886
|
-
callback(
|
|
4918
|
+
callback(err2);
|
|
4887
4919
|
process.nextTick(emitClose, duplex);
|
|
4888
4920
|
return;
|
|
4889
4921
|
}
|
|
4890
4922
|
let called = false;
|
|
4891
|
-
ws.once("error", function error(
|
|
4923
|
+
ws.once("error", function error(err3) {
|
|
4892
4924
|
called = true;
|
|
4893
|
-
callback(
|
|
4925
|
+
callback(err3);
|
|
4894
4926
|
});
|
|
4895
4927
|
ws.once("close", function close() {
|
|
4896
|
-
if (!called) callback(
|
|
4928
|
+
if (!called) callback(err2);
|
|
4897
4929
|
process.nextTick(emitClose, duplex);
|
|
4898
4930
|
});
|
|
4899
4931
|
if (terminateOnDestroy) ws.terminate();
|
|
@@ -5215,7 +5247,7 @@ var require_websocket_server = __commonJS({
|
|
|
5215
5247
|
if (secWebSocketProtocol !== void 0) {
|
|
5216
5248
|
try {
|
|
5217
5249
|
protocols = subprotocol.parse(secWebSocketProtocol);
|
|
5218
|
-
} catch (
|
|
5250
|
+
} catch (err2) {
|
|
5219
5251
|
const message = "Invalid Sec-WebSocket-Protocol header";
|
|
5220
5252
|
abortHandshakeOrEmitwsClientError(this, req, socket, 400, message);
|
|
5221
5253
|
return;
|
|
@@ -5235,7 +5267,7 @@ var require_websocket_server = __commonJS({
|
|
|
5235
5267
|
perMessageDeflate.accept(offers[PerMessageDeflate.extensionName]);
|
|
5236
5268
|
extensions[PerMessageDeflate.extensionName] = perMessageDeflate;
|
|
5237
5269
|
}
|
|
5238
|
-
} catch (
|
|
5270
|
+
} catch (err2) {
|
|
5239
5271
|
const message = "Invalid or unacceptable Sec-WebSocket-Extensions header";
|
|
5240
5272
|
abortHandshakeOrEmitwsClientError(this, req, socket, 400, message);
|
|
5241
5273
|
return;
|
|
@@ -5364,9 +5396,9 @@ var require_websocket_server = __commonJS({
|
|
|
5364
5396
|
}
|
|
5365
5397
|
function abortHandshakeOrEmitwsClientError(server, req, socket, code, message, headers) {
|
|
5366
5398
|
if (server.listenerCount("wsClientError")) {
|
|
5367
|
-
const
|
|
5368
|
-
Error.captureStackTrace(
|
|
5369
|
-
server.emit("wsClientError",
|
|
5399
|
+
const err2 = new Error(message);
|
|
5400
|
+
Error.captureStackTrace(err2, abortHandshakeOrEmitwsClientError);
|
|
5401
|
+
server.emit("wsClientError", err2, socket, req);
|
|
5370
5402
|
} else {
|
|
5371
5403
|
abortHandshake(socket, code, message, headers);
|
|
5372
5404
|
}
|
|
@@ -5392,7 +5424,7 @@ function readBuildInjectedVersion() {
|
|
|
5392
5424
|
if (false) {
|
|
5393
5425
|
return void 0;
|
|
5394
5426
|
}
|
|
5395
|
-
const version = "1.10.
|
|
5427
|
+
const version = "1.10.5".trim();
|
|
5396
5428
|
return version || void 0;
|
|
5397
5429
|
}
|
|
5398
5430
|
function readPluginVersionFromPackageJson() {
|
|
@@ -6649,11 +6681,11 @@ function registerLightRulesGateway(api, registry, logger, rememberBroadcast) {
|
|
|
6649
6681
|
id: rule.name
|
|
6650
6682
|
}));
|
|
6651
6683
|
respond(true, { ok: true, rules });
|
|
6652
|
-
} catch (
|
|
6653
|
-
logger.warn(`lightrules.list failed: ${
|
|
6684
|
+
} catch (err2) {
|
|
6685
|
+
logger.warn(`lightrules.list failed: ${err2?.message}`);
|
|
6654
6686
|
respond(false, null, {
|
|
6655
6687
|
code: "INTERNAL_ERROR",
|
|
6656
|
-
message:
|
|
6688
|
+
message: err2?.message ?? "Unknown error"
|
|
6657
6689
|
});
|
|
6658
6690
|
}
|
|
6659
6691
|
});
|
|
@@ -6683,8 +6715,8 @@ function registerLightRulesGateway(api, registry, logger, rememberBroadcast) {
|
|
|
6683
6715
|
try {
|
|
6684
6716
|
repeatTimes = normalizeRepeatTimes({ repeat, repeat_times });
|
|
6685
6717
|
assertAncsRepeatTimes(repeatTimes);
|
|
6686
|
-
} catch (
|
|
6687
|
-
respond(false, null, { code: "VALIDATION_FAILED", message:
|
|
6718
|
+
} catch (err2) {
|
|
6719
|
+
respond(false, null, { code: "VALIDATION_FAILED", message: err2?.message ?? "Unknown error" });
|
|
6688
6720
|
return;
|
|
6689
6721
|
}
|
|
6690
6722
|
try {
|
|
@@ -6698,12 +6730,12 @@ function registerLightRulesGateway(api, registry, logger, rememberBroadcast) {
|
|
|
6698
6730
|
});
|
|
6699
6731
|
logger.info(`Light rule created: ${name}`);
|
|
6700
6732
|
respond(true, { ok: true, name, cronHint: result.cronHint });
|
|
6701
|
-
} catch (
|
|
6702
|
-
if (
|
|
6703
|
-
respond(false, null, { code:
|
|
6733
|
+
} catch (err2) {
|
|
6734
|
+
if (err2 instanceof LightRuleError) {
|
|
6735
|
+
respond(false, null, { code: err2.code, message: err2.message });
|
|
6704
6736
|
} else {
|
|
6705
|
-
logger.warn(`lightrules.create failed: ${
|
|
6706
|
-
respond(false, null, { code: "INTERNAL_ERROR", message:
|
|
6737
|
+
logger.warn(`lightrules.create failed: ${err2?.message}`);
|
|
6738
|
+
respond(false, null, { code: "INTERNAL_ERROR", message: err2?.message ?? "Unknown error" });
|
|
6707
6739
|
}
|
|
6708
6740
|
}
|
|
6709
6741
|
});
|
|
@@ -6742,8 +6774,8 @@ function registerLightRulesGateway(api, registry, logger, rememberBroadcast) {
|
|
|
6742
6774
|
try {
|
|
6743
6775
|
repeatTimes = normalizeRepeatTimes({ repeat, repeat_times });
|
|
6744
6776
|
assertAncsRepeatTimes(repeatTimes);
|
|
6745
|
-
} catch (
|
|
6746
|
-
respond(false, null, { code: "VALIDATION_FAILED", message:
|
|
6777
|
+
} catch (err2) {
|
|
6778
|
+
respond(false, null, { code: "VALIDATION_FAILED", message: err2?.message ?? "Unknown error" });
|
|
6747
6779
|
return;
|
|
6748
6780
|
}
|
|
6749
6781
|
}
|
|
@@ -6766,12 +6798,12 @@ function registerLightRulesGateway(api, registry, logger, rememberBroadcast) {
|
|
|
6766
6798
|
rule: result.meta,
|
|
6767
6799
|
cronHint: result.cronHint
|
|
6768
6800
|
});
|
|
6769
|
-
} catch (
|
|
6770
|
-
if (
|
|
6771
|
-
respond(false, null, { code:
|
|
6801
|
+
} catch (err2) {
|
|
6802
|
+
if (err2 instanceof LightRuleError) {
|
|
6803
|
+
respond(false, null, { code: err2.code, message: err2.message });
|
|
6772
6804
|
} else {
|
|
6773
|
-
logger.warn(`lightrules.update failed: ${
|
|
6774
|
-
respond(false, null, { code: "INTERNAL_ERROR", message:
|
|
6805
|
+
logger.warn(`lightrules.update failed: ${err2?.message}`);
|
|
6806
|
+
respond(false, null, { code: "INTERNAL_ERROR", message: err2?.message ?? "Unknown error" });
|
|
6775
6807
|
}
|
|
6776
6808
|
}
|
|
6777
6809
|
});
|
|
@@ -6794,273 +6826,17 @@ function registerLightRulesGateway(api, registry, logger, rememberBroadcast) {
|
|
|
6794
6826
|
deleted: true,
|
|
6795
6827
|
cronHint: result.cronHint
|
|
6796
6828
|
});
|
|
6797
|
-
} catch (
|
|
6798
|
-
if (
|
|
6799
|
-
respond(false, null, { code:
|
|
6829
|
+
} catch (err2) {
|
|
6830
|
+
if (err2 instanceof LightRuleError) {
|
|
6831
|
+
respond(false, null, { code: err2.code, message: err2.message });
|
|
6800
6832
|
} else {
|
|
6801
|
-
logger.warn(`lightrules.delete failed: ${
|
|
6802
|
-
respond(false, null, { code: "INTERNAL_ERROR", message:
|
|
6833
|
+
logger.warn(`lightrules.delete failed: ${err2?.message}`);
|
|
6834
|
+
respond(false, null, { code: "INTERNAL_ERROR", message: err2?.message ?? "Unknown error" });
|
|
6803
6835
|
}
|
|
6804
6836
|
}
|
|
6805
6837
|
});
|
|
6806
6838
|
}
|
|
6807
6839
|
|
|
6808
|
-
// src/light-rules/evaluator-job.ts
|
|
6809
|
-
var EVALUATOR_JOB_ID = "light-rules-evaluator";
|
|
6810
|
-
var EVALUATOR_SUBAGENT_SESSION_KEY = EVALUATOR_JOB_ID;
|
|
6811
|
-
var FALLBACK_CRON_EXPR = "0 0 1 1 *";
|
|
6812
|
-
function buildEvaluatorJobMessage(notificationsDir) {
|
|
6813
|
-
return `\u706F\u6548\u89C4\u5219\u8BC4\u4F30\u4EFB\u52A1\u3002
|
|
6814
|
-
|
|
6815
|
-
\u6267\u884C\u6B65\u9AA4\uFF1A
|
|
6816
|
-
1. \u8BFB\u53D6 tasks/light-rules-evaluator/checkpoint.json\uFF08\u8BB0\u5F55\u4E0A\u6B21\u5904\u7406\u8FDB\u5EA6\uFF09
|
|
6817
|
-
2. \u626B\u63CF ${notificationsDir} \u76EE\u5F55\uFF0C\u83B7\u53D6 checkpoint \u4E4B\u540E\u7684\u65B0\u901A\u77E5
|
|
6818
|
-
3. \u626B\u63CF tasks/ \u76EE\u5F55\uFF0C\u8BFB\u53D6\u6240\u6709 type=light-rule \u4E14 enabled=true \u7684 meta.json
|
|
6819
|
-
4. \u5BF9\u6BCF\u6761\u65B0\u901A\u77E5\uFF0C\u9010\u4E00\u5224\u65AD\u662F\u5426\u547D\u4E2D\u6BCF\u6761\u89C4\u5219\u7684 description\uFF08\u8BED\u4E49\u5339\u914D\uFF09
|
|
6820
|
-
5. \u547D\u4E2D\u65F6\uFF1A\u4EE5\u8BE5\u89C4\u5219\u7684 segments \u548C repeat_times \u8C03\u7528 light_control \u5DE5\u5177
|
|
6821
|
-
6. \u66F4\u65B0 checkpoint.json\uFF0C\u8BB0\u5F55\u5DF2\u5904\u7406\u5230\u7684\u6700\u65B0\u901A\u77E5\u4F4D\u7F6E
|
|
6822
|
-
7. \u82E5\u65E0\u65B0\u901A\u77E5\u6216\u65E0 enabled \u89C4\u5219\uFF1A\u8F93\u51FA NO_CHANGE\uFF0C\u76F4\u63A5\u7ED3\u675F`;
|
|
6823
|
-
}
|
|
6824
|
-
var LightRulesEvaluatorJob = class {
|
|
6825
|
-
logger;
|
|
6826
|
-
registry;
|
|
6827
|
-
subagentRunner;
|
|
6828
|
-
getNotificationsDir;
|
|
6829
|
-
/**
|
|
6830
|
-
* 记录本进程生命周期内 job 是否已确认存在。
|
|
6831
|
-
* 仅在 `ensureJobExists` 成功后置 true,避免每次 push 都做检查。
|
|
6832
|
-
*/
|
|
6833
|
-
jobEnsured = false;
|
|
6834
|
-
/**
|
|
6835
|
-
* 首次创建 job 时的并发保护。
|
|
6836
|
-
* 避免冷启动瞬间多条通知并发到达时重复调用 `cron.add`。
|
|
6837
|
-
*/
|
|
6838
|
-
ensureJobPromise = null;
|
|
6839
|
-
/**
|
|
6840
|
-
* subagent fallback 路径的并发保护。
|
|
6841
|
-
* 若评估 session 已在运行中,跳过本次触发(checkpoint 保证下次补处理)。
|
|
6842
|
-
*/
|
|
6843
|
-
subagentInFlight = false;
|
|
6844
|
-
constructor(deps) {
|
|
6845
|
-
this.logger = deps.logger;
|
|
6846
|
-
this.registry = deps.registry;
|
|
6847
|
-
this.subagentRunner = deps.subagentRunner;
|
|
6848
|
-
this.getNotificationsDir = deps.getNotificationsDir ?? (() => void 0);
|
|
6849
|
-
}
|
|
6850
|
-
/**
|
|
6851
|
-
* 通知落盘后调用。若有新增通知且存在 enabled 规则,则触发评估。
|
|
6852
|
-
*
|
|
6853
|
-
* 两条路径:
|
|
6854
|
-
* - cron 不为 null:enqueueRun("force") 入队(gateway context 路径,正常路径)
|
|
6855
|
-
* - cron 为 null:通过 subagentRunner 直接运行(HTTP Relay 路径,fallback)
|
|
6856
|
-
*
|
|
6857
|
-
* @param cron 来自 gateway context 的 CronService;HTTP 路径下为 null
|
|
6858
|
-
* @param insertedCount 本次 ingest 新落盘的通知条数(StoredNotification 去重后)
|
|
6859
|
-
*/
|
|
6860
|
-
async triggerIfNeeded(cron, insertedCount) {
|
|
6861
|
-
if (insertedCount === 0) return;
|
|
6862
|
-
if (this.registry.getEnabled().length === 0) return;
|
|
6863
|
-
if (!cron) {
|
|
6864
|
-
await this.triggerViaSubagent();
|
|
6865
|
-
return;
|
|
6866
|
-
}
|
|
6867
|
-
try {
|
|
6868
|
-
await this.ensureJobExists(cron);
|
|
6869
|
-
} catch (err) {
|
|
6870
|
-
this.logger.warn(`light-rules-evaluator: job ensure failed: ${err?.message ?? err}`);
|
|
6871
|
-
return;
|
|
6872
|
-
}
|
|
6873
|
-
try {
|
|
6874
|
-
const result = await cron.enqueueRun(EVALUATOR_JOB_ID, "force");
|
|
6875
|
-
if (!result.ok) {
|
|
6876
|
-
this.logger.warn("light-rules-evaluator: enqueueRun returned ok=false");
|
|
6877
|
-
return;
|
|
6878
|
-
}
|
|
6879
|
-
if ("enqueued" in result && result.enqueued) {
|
|
6880
|
-
this.logger.info(`light-rules-evaluator: enqueued runId=${result.runId}`);
|
|
6881
|
-
} else if ("reason" in result) {
|
|
6882
|
-
this.logger.info(`light-rules-evaluator: enqueueRun skipped (${result.reason})`);
|
|
6883
|
-
}
|
|
6884
|
-
} catch (err) {
|
|
6885
|
-
this.logger.warn(`light-rules-evaluator: enqueueRun failed: ${err?.message ?? err}`);
|
|
6886
|
-
}
|
|
6887
|
-
}
|
|
6888
|
-
/**
|
|
6889
|
-
* cron service 不可用时的 fallback:直接通过 subagentRunner 运行评估 session。
|
|
6890
|
-
*
|
|
6891
|
-
* 并发保护:若上一次 subagent 运行尚未完成,本次跳过。
|
|
6892
|
-
* checkpoint 保证即使本次跳过,下次触发时会处理所有积压通知。
|
|
6893
|
-
*/
|
|
6894
|
-
async triggerViaSubagent() {
|
|
6895
|
-
if (!this.subagentRunner) {
|
|
6896
|
-
this.logger.warn(
|
|
6897
|
-
"light-rules-evaluator: cron service unavailable and no subagent fallback configured; notifications ingested via HTTP will not trigger light rules until an agent session is active"
|
|
6898
|
-
);
|
|
6899
|
-
return;
|
|
6900
|
-
}
|
|
6901
|
-
if (this.subagentInFlight) {
|
|
6902
|
-
this.logger.info("light-rules-evaluator: subagent run in-flight, skipping this trigger");
|
|
6903
|
-
return;
|
|
6904
|
-
}
|
|
6905
|
-
const notificationsDir = this.getNotificationsDir();
|
|
6906
|
-
if (!notificationsDir) {
|
|
6907
|
-
this.logger.warn("light-rules-evaluator: notifications dir not ready, skipping subagent trigger");
|
|
6908
|
-
return;
|
|
6909
|
-
}
|
|
6910
|
-
this.subagentInFlight = true;
|
|
6911
|
-
try {
|
|
6912
|
-
const result = await this.subagentRunner.run({
|
|
6913
|
-
sessionKey: EVALUATOR_SUBAGENT_SESSION_KEY,
|
|
6914
|
-
message: buildEvaluatorJobMessage(notificationsDir),
|
|
6915
|
-
deliver: false,
|
|
6916
|
-
idempotencyKey: `${EVALUATOR_SUBAGENT_SESSION_KEY}-${Date.now()}`
|
|
6917
|
-
});
|
|
6918
|
-
this.logger.info(`light-rules-evaluator: subagent triggered runId=${result.runId}`);
|
|
6919
|
-
} catch (err) {
|
|
6920
|
-
this.logger.warn(`light-rules-evaluator: subagent trigger failed: ${err?.message ?? err}`);
|
|
6921
|
-
} finally {
|
|
6922
|
-
this.subagentInFlight = false;
|
|
6923
|
-
}
|
|
6924
|
-
}
|
|
6925
|
-
/**
|
|
6926
|
-
* 按需创建 `light-rules-evaluator` job。
|
|
6927
|
-
* 若 job 已存在(内存缓存或 cron store),直接返回;否则调用 `cron.add`。
|
|
6928
|
-
*/
|
|
6929
|
-
async ensureJobExists(cron) {
|
|
6930
|
-
if (this.jobEnsured) return;
|
|
6931
|
-
if (!this.ensureJobPromise) {
|
|
6932
|
-
this.ensureJobPromise = this.createJobIfNeeded(cron).finally(() => {
|
|
6933
|
-
this.ensureJobPromise = null;
|
|
6934
|
-
});
|
|
6935
|
-
}
|
|
6936
|
-
await this.ensureJobPromise;
|
|
6937
|
-
}
|
|
6938
|
-
async createJobIfNeeded(cron) {
|
|
6939
|
-
if (cron.getJob(EVALUATOR_JOB_ID)) {
|
|
6940
|
-
this.jobEnsured = true;
|
|
6941
|
-
return;
|
|
6942
|
-
}
|
|
6943
|
-
try {
|
|
6944
|
-
await cron.add({
|
|
6945
|
-
id: EVALUATOR_JOB_ID,
|
|
6946
|
-
name: "\u706F\u6548\u89C4\u5219\u8BC4\u4F30",
|
|
6947
|
-
description: "\u4E8B\u4EF6\u9A71\u52A8\uFF1A\u901A\u77E5\u5230\u8FBE\u65F6\u8BC4\u4F30\u6240\u6709 enabled \u706F\u6548\u89C4\u5219\uFF0C\u547D\u4E2D\u5219\u8C03\u7528 light_control \u89E6\u53D1\u706F\u6548",
|
|
6948
|
-
enabled: true,
|
|
6949
|
-
schedule: { kind: "cron", expr: FALLBACK_CRON_EXPR },
|
|
6950
|
-
sessionTarget: "isolated",
|
|
6951
|
-
wakeMode: "now",
|
|
6952
|
-
payload: {
|
|
6953
|
-
kind: "agentTurn",
|
|
6954
|
-
message: buildEvaluatorJobMessage(this.getNotificationsDir() ?? "notifications")
|
|
6955
|
-
}
|
|
6956
|
-
});
|
|
6957
|
-
this.logger.info("light-rules-evaluator: job created");
|
|
6958
|
-
} catch (err) {
|
|
6959
|
-
if (!cron.getJob(EVALUATOR_JOB_ID)) {
|
|
6960
|
-
throw err;
|
|
6961
|
-
}
|
|
6962
|
-
}
|
|
6963
|
-
this.jobEnsured = true;
|
|
6964
|
-
}
|
|
6965
|
-
};
|
|
6966
|
-
|
|
6967
|
-
// src/light-rules/migration.ts
|
|
6968
|
-
var import_node_fs6 = require("fs");
|
|
6969
|
-
var import_node_path5 = require("path");
|
|
6970
|
-
var NO_MATCH_FETCH_PY = `#!/usr/bin/env python3
|
|
6971
|
-
# \u6B64\u6587\u4EF6\u7531\u8FC1\u79FB\u5DE5\u5177\u751F\u6210\u3002
|
|
6972
|
-
# \u706F\u6548\u89C4\u5219\u5DF2\u8FC1\u79FB\u81F3\u4E8B\u4EF6\u9A71\u52A8\u67B6\u6784\uFF0C\u6B64 cron job \u4E0D\u518D\u6267\u884C\u5B9E\u9645\u5DE5\u4F5C\u3002
|
|
6973
|
-
print("NO_MATCH")
|
|
6974
|
-
`;
|
|
6975
|
-
function normalizeScriptText(text) {
|
|
6976
|
-
return text.replace(/\r\n/g, "\n").trim();
|
|
6977
|
-
}
|
|
6978
|
-
function resolveTasksDir(ctx) {
|
|
6979
|
-
if (ctx.workspaceDir) return (0, import_node_path5.join)(ctx.workspaceDir, "tasks");
|
|
6980
|
-
if (ctx.stateDir) {
|
|
6981
|
-
const inferredWorkspaceDir = (0, import_node_path5.join)(ctx.stateDir, "workspace");
|
|
6982
|
-
if ((0, import_node_fs6.existsSync)(inferredWorkspaceDir)) return (0, import_node_path5.join)(inferredWorkspaceDir, "tasks");
|
|
6983
|
-
return (0, import_node_path5.join)(ctx.stateDir, "tasks");
|
|
6984
|
-
}
|
|
6985
|
-
return null;
|
|
6986
|
-
}
|
|
6987
|
-
function migrateLegacyLightRuleTasks(ctx, logger) {
|
|
6988
|
-
const tasksDir3 = resolveTasksDir(ctx);
|
|
6989
|
-
if (!tasksDir3 || !(0, import_node_fs6.existsSync)(tasksDir3)) return;
|
|
6990
|
-
try {
|
|
6991
|
-
for (const entry of (0, import_node_fs6.readdirSync)(tasksDir3, { withFileTypes: true })) {
|
|
6992
|
-
if (!entry.isDirectory()) continue;
|
|
6993
|
-
migrateTaskDir((0, import_node_path5.join)(tasksDir3, String(entry.name)), logger);
|
|
6994
|
-
}
|
|
6995
|
-
} catch (err) {
|
|
6996
|
-
logger.warn(`migration: failed to read tasks dir: ${err?.message}`);
|
|
6997
|
-
}
|
|
6998
|
-
}
|
|
6999
|
-
function migrateTaskDir(taskDir, logger) {
|
|
7000
|
-
const metaPath = (0, import_node_path5.join)(taskDir, "meta.json");
|
|
7001
|
-
if (!(0, import_node_fs6.existsSync)(metaPath)) return;
|
|
7002
|
-
let meta;
|
|
7003
|
-
try {
|
|
7004
|
-
meta = JSON.parse((0, import_node_fs6.readFileSync)(metaPath, "utf-8"));
|
|
7005
|
-
} catch {
|
|
7006
|
-
return;
|
|
7007
|
-
}
|
|
7008
|
-
if (meta.type !== "light-rule") return;
|
|
7009
|
-
const name = typeof meta.name === "string" ? meta.name : taskDir;
|
|
7010
|
-
mergeMatchRulesIntoDescription(meta, name, metaPath, logger);
|
|
7011
|
-
replaceFetchPy(taskDir, name, logger);
|
|
7012
|
-
for (const filename of ["README.md", "checkpoint.json"]) {
|
|
7013
|
-
removeFile((0, import_node_path5.join)(taskDir, filename), name, filename, logger);
|
|
7014
|
-
}
|
|
7015
|
-
}
|
|
7016
|
-
function mergeMatchRulesIntoDescription(meta, name, metaPath, logger) {
|
|
7017
|
-
const matchRules = meta.matchRules;
|
|
7018
|
-
if (!matchRules || typeof matchRules !== "object") return;
|
|
7019
|
-
const rules = matchRules;
|
|
7020
|
-
const parts = [];
|
|
7021
|
-
if (rules.appName) parts.push(`app=${rules.appName}`);
|
|
7022
|
-
if (rules.senderKeywords?.length) {
|
|
7023
|
-
parts.push(`\u53D1\u4EF6\u4EBA\u5173\u952E\u8BCD=${rules.senderKeywords.join("\u3001")}`);
|
|
7024
|
-
}
|
|
7025
|
-
if (rules.contentKeywords?.length) {
|
|
7026
|
-
parts.push(`\u5185\u5BB9\u5173\u952E\u8BCD=${rules.contentKeywords.join("\u3001")}`);
|
|
7027
|
-
}
|
|
7028
|
-
if (parts.length > 0) {
|
|
7029
|
-
const existing = typeof meta.description === "string" ? meta.description.trim() : "";
|
|
7030
|
-
meta.description = existing ? `${existing}\u3002\u5339\u914D\u89C4\u5219\uFF1A${parts.join("\uFF0C")}` : `\u5339\u914D\u89C4\u5219\uFF1A${parts.join("\uFF0C")}`;
|
|
7031
|
-
}
|
|
7032
|
-
delete meta.matchRules;
|
|
7033
|
-
try {
|
|
7034
|
-
(0, import_node_fs6.writeFileSync)(metaPath, JSON.stringify(meta, null, 2), "utf-8");
|
|
7035
|
-
logger.info(`migration: merged matchRules into description for light rule: ${name}`);
|
|
7036
|
-
} catch (err) {
|
|
7037
|
-
logger.warn(`migration: failed to update meta.json for ${name}: ${err?.message}`);
|
|
7038
|
-
}
|
|
7039
|
-
}
|
|
7040
|
-
function replaceFetchPy(taskDir, name, logger) {
|
|
7041
|
-
const fetchPyPath = (0, import_node_path5.join)(taskDir, "fetch.py");
|
|
7042
|
-
if (!(0, import_node_fs6.existsSync)(fetchPyPath)) return;
|
|
7043
|
-
try {
|
|
7044
|
-
const existing = (0, import_node_fs6.readFileSync)(fetchPyPath, "utf-8");
|
|
7045
|
-
if (normalizeScriptText(existing) === normalizeScriptText(NO_MATCH_FETCH_PY)) {
|
|
7046
|
-
return;
|
|
7047
|
-
}
|
|
7048
|
-
(0, import_node_fs6.writeFileSync)(fetchPyPath, NO_MATCH_FETCH_PY, "utf-8");
|
|
7049
|
-
logger.info(`migration: replaced fetch.py with NO_MATCH placeholder for ${name}`);
|
|
7050
|
-
} catch (err) {
|
|
7051
|
-
logger.warn(`migration: failed to replace fetch.py for ${name}: ${err?.message}`);
|
|
7052
|
-
}
|
|
7053
|
-
}
|
|
7054
|
-
function removeFile(filePath, ruleName, filename, logger) {
|
|
7055
|
-
if (!(0, import_node_fs6.existsSync)(filePath)) return;
|
|
7056
|
-
try {
|
|
7057
|
-
(0, import_node_fs6.rmSync)(filePath);
|
|
7058
|
-
logger.info(`migration: removed ${filename} for light rule: ${ruleName}`);
|
|
7059
|
-
} catch (err) {
|
|
7060
|
-
logger.warn(`migration: failed to remove ${filename} for ${ruleName}: ${err?.message}`);
|
|
7061
|
-
}
|
|
7062
|
-
}
|
|
7063
|
-
|
|
7064
6840
|
// src/light-rules/registry.ts
|
|
7065
6841
|
var LightRuleRegistry = class {
|
|
7066
6842
|
ctx;
|
|
@@ -7181,1067 +6957,1542 @@ var LightRuleRegistry = class {
|
|
|
7181
6957
|
}
|
|
7182
6958
|
};
|
|
7183
6959
|
|
|
7184
|
-
// src/
|
|
7185
|
-
|
|
7186
|
-
|
|
7187
|
-
|
|
7188
|
-
|
|
7189
|
-
|
|
7190
|
-
|
|
7191
|
-
|
|
7192
|
-
|
|
7193
|
-
|
|
6960
|
+
// src/light/protocol.ts
|
|
6961
|
+
var MAX_LIGHT_SEGMENTS = 12;
|
|
6962
|
+
var PROTOCOL_DIGITS = [
|
|
6963
|
+
"\x80",
|
|
6964
|
+
"\x81",
|
|
6965
|
+
"\x82",
|
|
6966
|
+
"\x83",
|
|
6967
|
+
"\x84",
|
|
6968
|
+
"\x91",
|
|
6969
|
+
"\x92",
|
|
6970
|
+
"\x93",
|
|
6971
|
+
"\x94",
|
|
6972
|
+
"\x95",
|
|
6973
|
+
"\x96",
|
|
6974
|
+
"\x97"
|
|
6975
|
+
];
|
|
6976
|
+
var LED_SEPARATOR_ONCE = "\x9A";
|
|
6977
|
+
var LED_SEPARATOR_LOOP = "\x9B";
|
|
6978
|
+
var DURATION_STEPS_S = [0.5, 1, 2, 3, 5, 6, 8, 16, 24, 32, 48];
|
|
6979
|
+
var INTERVAL_STEPS_MS = [50, 100, 200, 300, 500, 600, 800, 1600, 2400, 3200, 4800];
|
|
6980
|
+
var BREATH_STEPS_MS = [1040, 1560, 2080, 2600, 3100, 4160];
|
|
6981
|
+
var BRIGHTNESS_STEPS = [32, 64, 96, 128, 192, 255];
|
|
6982
|
+
var COLOR_STEPS = [0, 32, 64, 128, 192, 255];
|
|
6983
|
+
var BACKGROUND_BRIGHTNESS_STEPS = [0, 32, 64, 96, 128, 192, 255];
|
|
6984
|
+
var MULTI_CHANNEL_COLOR_COEFFICIENTS = { r: 1, g: 0.25, b: 0.25 };
|
|
6985
|
+
var PURE_WHITE_COLOR_COEFFICIENTS = { r: 1, g: 0.35, b: 0.35 };
|
|
6986
|
+
var MODE_TO_INDEX = {
|
|
6987
|
+
wave: 0,
|
|
6988
|
+
breath: 1,
|
|
6989
|
+
strobe: 2,
|
|
6990
|
+
steady: 3,
|
|
6991
|
+
wave_rainbow: 4,
|
|
6992
|
+
pixel_frame: 5
|
|
6993
|
+
};
|
|
6994
|
+
function buildLightEffectApnsBody(segments, repeatInput) {
|
|
6995
|
+
assertSegmentCount(segments);
|
|
6996
|
+
assertSegmentsValid(segments);
|
|
6997
|
+
const repeatTimes = normalizeRepeatTimes(repeatInput);
|
|
6998
|
+
assertAncsRepeatTimes(repeatTimes);
|
|
6999
|
+
const visibleText = summarizeSegments(segments);
|
|
7000
|
+
const separator = repeatTimes === 0 ? LED_SEPARATOR_LOOP : LED_SEPARATOR_ONCE;
|
|
7001
|
+
const payload = segments.map((segment) => encodeSegment(segment)).join("");
|
|
7002
|
+
return `${visibleText}${separator}${payload}`;
|
|
7003
|
+
}
|
|
7004
|
+
function assertSegmentCount(segments) {
|
|
7005
|
+
if (segments.length < 1 || segments.length > MAX_LIGHT_SEGMENTS) {
|
|
7006
|
+
throw new Error(`light_control supports 1-${MAX_LIGHT_SEGMENTS} segments`);
|
|
7194
7007
|
}
|
|
7195
|
-
return params.envName === "development" ? "beta" : "latest";
|
|
7196
7008
|
}
|
|
7197
|
-
|
|
7198
|
-
|
|
7199
|
-
|
|
7200
|
-
|
|
7201
|
-
// src/update/checker.ts
|
|
7202
|
-
function parseSemver(v) {
|
|
7203
|
-
const [main, pre = null] = v.split("-", 2);
|
|
7204
|
-
const [major = 0, minor = 0, patch = 0] = main.split(".").map(Number);
|
|
7205
|
-
return { major, minor, patch, pre: pre ?? null };
|
|
7009
|
+
function summarizeSegments(segments) {
|
|
7010
|
+
const modeDesc = segments.map((segment) => segment.mode).join("+");
|
|
7011
|
+
return `Effect: ${modeDesc} (${segments.length} segment${segments.length > 1 ? "s" : ""})`;
|
|
7206
7012
|
}
|
|
7207
|
-
function
|
|
7208
|
-
const
|
|
7209
|
-
|
|
7210
|
-
|
|
7211
|
-
|
|
7212
|
-
|
|
7213
|
-
const an = Number(ap);
|
|
7214
|
-
const bn = Number(bp);
|
|
7215
|
-
let diff;
|
|
7216
|
-
if (!Number.isNaN(an) && !Number.isNaN(bn)) {
|
|
7217
|
-
diff = an - bn;
|
|
7218
|
-
} else if (ap < bp) {
|
|
7219
|
-
diff = -1;
|
|
7220
|
-
} else if (ap > bp) {
|
|
7221
|
-
diff = 1;
|
|
7222
|
-
} else {
|
|
7223
|
-
diff = 0;
|
|
7224
|
-
}
|
|
7225
|
-
if (diff !== 0) return diff;
|
|
7013
|
+
function assertSegmentsValid(segments) {
|
|
7014
|
+
const validation = validateSegments(segments);
|
|
7015
|
+
if (!validation.valid) {
|
|
7016
|
+
throw new Error(
|
|
7017
|
+
validation.errors.map((error) => `${error.field}: ${error.message}`).join("; ")
|
|
7018
|
+
);
|
|
7226
7019
|
}
|
|
7227
|
-
return 0;
|
|
7228
7020
|
}
|
|
7229
|
-
function
|
|
7230
|
-
const
|
|
7231
|
-
|
|
7232
|
-
|
|
7233
|
-
|
|
7234
|
-
|
|
7235
|
-
|
|
7236
|
-
|
|
7237
|
-
|
|
7021
|
+
function encodeSegment(segment) {
|
|
7022
|
+
const common = [
|
|
7023
|
+
MODE_TO_INDEX[segment.mode],
|
|
7024
|
+
quantizeDuration(segment.duration_s)
|
|
7025
|
+
];
|
|
7026
|
+
let values;
|
|
7027
|
+
switch (segment.mode) {
|
|
7028
|
+
case "wave":
|
|
7029
|
+
case "wave_rainbow":
|
|
7030
|
+
const color = normalizeProtocolColor(segment.color);
|
|
7031
|
+
const background = normalizeProtocolColor(segment.background);
|
|
7032
|
+
values = [
|
|
7033
|
+
...common,
|
|
7034
|
+
quantize(segment.interval_ms ?? 200, INTERVAL_STEPS_MS),
|
|
7035
|
+
quantizeBrightnessValue(segment.brightness ?? 0),
|
|
7036
|
+
quantize(color.r, COLOR_STEPS),
|
|
7037
|
+
quantize(color.g, COLOR_STEPS),
|
|
7038
|
+
quantize(color.b, COLOR_STEPS),
|
|
7039
|
+
segment.direction === "rtl" ? 1 : 0,
|
|
7040
|
+
quantizeWindow(segment.window ?? 2),
|
|
7041
|
+
quantize(background.r, COLOR_STEPS),
|
|
7042
|
+
quantize(background.g, COLOR_STEPS),
|
|
7043
|
+
quantize(background.b, COLOR_STEPS),
|
|
7044
|
+
quantize(segment.background?.brightness ?? 0, BACKGROUND_BRIGHTNESS_STEPS)
|
|
7045
|
+
];
|
|
7046
|
+
break;
|
|
7047
|
+
case "breath":
|
|
7048
|
+
const breathColor = normalizeProtocolColor(segment.color);
|
|
7049
|
+
values = [
|
|
7050
|
+
...common,
|
|
7051
|
+
quantizeBreathRiseFall(segment.breath_timing?.rise_ms),
|
|
7052
|
+
quantizeBreathHoldOff(segment.breath_timing?.hold_ms),
|
|
7053
|
+
quantizeBreathRiseFall(segment.breath_timing?.fall_ms),
|
|
7054
|
+
quantizeBreathHoldOff(segment.breath_timing?.off_ms),
|
|
7055
|
+
quantizeBrightnessValue(segment.brightness ?? 0),
|
|
7056
|
+
quantize(breathColor.r, COLOR_STEPS),
|
|
7057
|
+
quantize(breathColor.g, COLOR_STEPS),
|
|
7058
|
+
quantize(breathColor.b, COLOR_STEPS)
|
|
7059
|
+
];
|
|
7060
|
+
break;
|
|
7061
|
+
case "strobe":
|
|
7062
|
+
const strobeColor = normalizeProtocolColor(segment.color);
|
|
7063
|
+
values = [
|
|
7064
|
+
...common,
|
|
7065
|
+
quantize(segment.interval_ms ?? 200, INTERVAL_STEPS_MS),
|
|
7066
|
+
quantizeBrightnessValue(segment.brightness ?? 0),
|
|
7067
|
+
quantize(strobeColor.r, COLOR_STEPS),
|
|
7068
|
+
quantize(strobeColor.g, COLOR_STEPS),
|
|
7069
|
+
quantize(strobeColor.b, COLOR_STEPS)
|
|
7070
|
+
];
|
|
7071
|
+
break;
|
|
7072
|
+
case "steady":
|
|
7073
|
+
const steadyColor = normalizeProtocolColor(segment.color);
|
|
7074
|
+
values = [
|
|
7075
|
+
...common,
|
|
7076
|
+
quantizeBrightnessValue(segment.brightness ?? 0),
|
|
7077
|
+
quantize(steadyColor.r, COLOR_STEPS),
|
|
7078
|
+
quantize(steadyColor.g, COLOR_STEPS),
|
|
7079
|
+
quantize(steadyColor.b, COLOR_STEPS)
|
|
7080
|
+
];
|
|
7081
|
+
break;
|
|
7082
|
+
case "pixel_frame":
|
|
7083
|
+
values = encodePixelFrameValues(common, segment);
|
|
7084
|
+
break;
|
|
7085
|
+
}
|
|
7086
|
+
return values.map((value) => PROTOCOL_DIGITS[value]).join("");
|
|
7238
7087
|
}
|
|
7239
|
-
|
|
7240
|
-
|
|
7241
|
-
|
|
7242
|
-
|
|
7243
|
-
|
|
7244
|
-
|
|
7245
|
-
|
|
7246
|
-
|
|
7247
|
-
|
|
7088
|
+
function encodePixelFrameValues(common, segment) {
|
|
7089
|
+
const pixels = segment.pixels ?? [];
|
|
7090
|
+
return [
|
|
7091
|
+
...common,
|
|
7092
|
+
pixels.length - 1,
|
|
7093
|
+
...pixels.flatMap((pixel) => {
|
|
7094
|
+
const color = normalizeProtocolColor(pixel.color);
|
|
7095
|
+
return [
|
|
7096
|
+
pixel.index,
|
|
7097
|
+
quantize(color.r, COLOR_STEPS),
|
|
7098
|
+
quantize(color.g, COLOR_STEPS),
|
|
7099
|
+
quantize(color.b, COLOR_STEPS),
|
|
7100
|
+
quantizeBrightnessValue(pixel.brightness)
|
|
7101
|
+
];
|
|
7102
|
+
})
|
|
7103
|
+
];
|
|
7104
|
+
}
|
|
7105
|
+
function normalizeProtocolColor(color) {
|
|
7106
|
+
const normalized = {
|
|
7107
|
+
r: color?.r ?? 0,
|
|
7108
|
+
g: color?.g ?? 0,
|
|
7109
|
+
b: color?.b ?? 0
|
|
7110
|
+
};
|
|
7111
|
+
if (isPureWhiteProtocolColor(normalized)) {
|
|
7112
|
+
return applyProtocolColorCoefficients(normalized, PURE_WHITE_COLOR_COEFFICIENTS);
|
|
7248
7113
|
}
|
|
7249
|
-
|
|
7250
|
-
|
|
7251
|
-
start() {
|
|
7252
|
-
this.timer = setTimeout(() => {
|
|
7253
|
-
void this.check();
|
|
7254
|
-
this.timer = setInterval(() => void this.check(), this.intervalMs);
|
|
7255
|
-
}, 6e4);
|
|
7114
|
+
if (countActiveProtocolColorChannels(normalized) <= 1) {
|
|
7115
|
+
return normalized;
|
|
7256
7116
|
}
|
|
7257
|
-
|
|
7258
|
-
|
|
7259
|
-
|
|
7260
|
-
|
|
7117
|
+
return applyProtocolColorCoefficients(normalized, MULTI_CHANNEL_COLOR_COEFFICIENTS);
|
|
7118
|
+
}
|
|
7119
|
+
function countActiveProtocolColorChannels(color) {
|
|
7120
|
+
return Number(color.r > 0) + Number(color.g > 0) + Number(color.b > 0);
|
|
7121
|
+
}
|
|
7122
|
+
function isPureWhiteProtocolColor(color) {
|
|
7123
|
+
return color.r === 255 && color.g === 255 && color.b === 255;
|
|
7124
|
+
}
|
|
7125
|
+
function applyProtocolColorCoefficients(color, coefficients) {
|
|
7126
|
+
return {
|
|
7127
|
+
r: scaleProtocolColorChannel(color.r, coefficients.r),
|
|
7128
|
+
g: scaleProtocolColorChannel(color.g, coefficients.g),
|
|
7129
|
+
b: scaleProtocolColorChannel(color.b, coefficients.b)
|
|
7130
|
+
};
|
|
7131
|
+
}
|
|
7132
|
+
function scaleProtocolColorChannel(value, coefficient) {
|
|
7133
|
+
return Math.max(0, Math.min(255, Math.round(value * coefficient)));
|
|
7134
|
+
}
|
|
7135
|
+
function quantize(value, steps) {
|
|
7136
|
+
let bestIndex = 0;
|
|
7137
|
+
let bestDistance = Number.POSITIVE_INFINITY;
|
|
7138
|
+
for (const [index, step] of steps.entries()) {
|
|
7139
|
+
const distance = Math.abs(value - step);
|
|
7140
|
+
if (distance < bestDistance) {
|
|
7141
|
+
bestIndex = index;
|
|
7142
|
+
bestDistance = distance;
|
|
7261
7143
|
}
|
|
7262
7144
|
}
|
|
7263
|
-
|
|
7264
|
-
|
|
7265
|
-
|
|
7266
|
-
|
|
7267
|
-
|
|
7268
|
-
|
|
7269
|
-
|
|
7270
|
-
|
|
7271
|
-
|
|
7272
|
-
|
|
7273
|
-
|
|
7274
|
-
|
|
7275
|
-
|
|
7276
|
-
|
|
7277
|
-
|
|
7278
|
-
|
|
7279
|
-
if (res.ok) {
|
|
7280
|
-
const text = (await res.text()).trim();
|
|
7281
|
-
if (text) return text;
|
|
7282
|
-
}
|
|
7283
|
-
} catch {
|
|
7284
|
-
}
|
|
7285
|
-
try {
|
|
7286
|
-
const res = await fetch(`${NPM_BASE_URL}/${tag}`, {
|
|
7287
|
-
signal: AbortSignal.timeout(FETCH_TIMEOUT_MS)
|
|
7288
|
-
});
|
|
7289
|
-
if (res.ok) {
|
|
7290
|
-
const data = await res.json();
|
|
7291
|
-
return data.version ?? null;
|
|
7292
|
-
}
|
|
7293
|
-
} catch {
|
|
7294
|
-
this.logger.info("\u7248\u672C\u68C0\u67E5\u5931\u8D25\uFF08CDN + npm \u5747\u4E0D\u53EF\u8FBE\uFF09\uFF0C\u8DF3\u8FC7");
|
|
7295
|
-
}
|
|
7296
|
-
return null;
|
|
7145
|
+
return bestIndex;
|
|
7146
|
+
}
|
|
7147
|
+
function quantizeDuration(duration_s) {
|
|
7148
|
+
if (duration_s === 0) return 11;
|
|
7149
|
+
return quantize(duration_s, DURATION_STEPS_S);
|
|
7150
|
+
}
|
|
7151
|
+
function quantizeBrightnessValue(brightness) {
|
|
7152
|
+
if (brightness === 0) return 11;
|
|
7153
|
+
return quantize(brightness, BRIGHTNESS_STEPS);
|
|
7154
|
+
}
|
|
7155
|
+
function quantizeBreathRiseFall(value) {
|
|
7156
|
+
return quantize(value ?? 1040, BREATH_STEPS_MS);
|
|
7157
|
+
}
|
|
7158
|
+
function quantizeBreathHoldOff(value) {
|
|
7159
|
+
if (value === 0) {
|
|
7160
|
+
return 5;
|
|
7297
7161
|
}
|
|
7298
|
-
|
|
7162
|
+
return quantize(value ?? 1040, BREATH_STEPS_MS.slice(0, 5));
|
|
7163
|
+
}
|
|
7164
|
+
function quantizeWindow(value) {
|
|
7165
|
+
return value - 1;
|
|
7166
|
+
}
|
|
7299
7167
|
|
|
7300
|
-
// src/
|
|
7301
|
-
var
|
|
7302
|
-
|
|
7303
|
-
|
|
7304
|
-
|
|
7305
|
-
|
|
7306
|
-
|
|
7307
|
-
|
|
7308
|
-
|
|
7309
|
-
|
|
7310
|
-
|
|
7311
|
-
|
|
7312
|
-
|
|
7313
|
-
|
|
7314
|
-
|
|
7315
|
-
|
|
7316
|
-
|
|
7317
|
-
|
|
7318
|
-
|
|
7319
|
-
|
|
7320
|
-
|
|
7321
|
-
}
|
|
7322
|
-
const buffer = Buffer.from(await response.arrayBuffer());
|
|
7323
|
-
(0, import_node_fs9.writeFileSync)(tgzPath, buffer);
|
|
7324
|
-
logger.info(`\u4E0B\u8F7D\u5B8C\u6210 (${buffer.length} bytes)`);
|
|
7325
|
-
(0, import_node_fs9.mkdirSync)(stagingDir, { recursive: true });
|
|
7326
|
-
const tarResult = await runCommand(
|
|
7327
|
-
["tar", "-xzf", tgzPath, "-C", stagingDir, "--strip-components=1"],
|
|
7328
|
-
{ timeoutMs: 3e4 }
|
|
7329
|
-
);
|
|
7330
|
-
if (tarResult.code !== 0) {
|
|
7331
|
-
const err = tarResult.stderr || tarResult.stdout || "unknown error";
|
|
7332
|
-
return { success: false, message: `\u89E3\u538B\u5931\u8D25: ${err}` };
|
|
7333
|
-
}
|
|
7334
|
-
(0, import_node_fs9.mkdirSync)((0, import_node_path8.dirname)(targetDir), { recursive: true });
|
|
7335
|
-
try {
|
|
7336
|
-
backupDir = `${targetDir}.bak.${Date.now()}`;
|
|
7337
|
-
(0, import_node_fs9.renameSync)(targetDir, backupDir);
|
|
7338
|
-
} catch {
|
|
7339
|
-
backupDir = null;
|
|
7340
|
-
}
|
|
7341
|
-
(0, import_node_fs9.renameSync)(stagingDir, targetDir);
|
|
7342
|
-
try {
|
|
7343
|
-
await updateConfigRecord2(version, tgzUrl);
|
|
7344
|
-
} catch (err) {
|
|
7345
|
-
logger.warn(`\u914D\u7F6E\u8BB0\u5F55\u66F4\u65B0\u5931\u8D25\uFF08\u63D2\u4EF6\u6587\u4EF6\u5DF2\u5C31\u4F4D\uFF09: ${String(err)}`);
|
|
7346
|
-
}
|
|
7347
|
-
if (backupDir) {
|
|
7348
|
-
try {
|
|
7349
|
-
(0, import_node_fs9.rmSync)(backupDir, { force: true, recursive: true });
|
|
7350
|
-
} catch {
|
|
7168
|
+
// src/plugin/light-rules-tools.ts
|
|
7169
|
+
var segmentItemSchema = {
|
|
7170
|
+
type: "object",
|
|
7171
|
+
required: ["mode", "duration_s"],
|
|
7172
|
+
additionalProperties: false,
|
|
7173
|
+
properties: {
|
|
7174
|
+
mode: {
|
|
7175
|
+
type: "string",
|
|
7176
|
+
enum: ["wave", "breath", "strobe", "steady", "wave_rainbow", "pixel_frame"],
|
|
7177
|
+
description: "\u706F\u6548\u6A21\u5F0F\uFF1Awave \u6CE2\u6D6A / breath \u547C\u5438 / strobe \u9891\u95EA / steady \u5E38\u4EAE / wave_rainbow \u6D41\u5149 / pixel_frame \u9010\u7EC4\u50CF\u7D20\u5E27"
|
|
7178
|
+
},
|
|
7179
|
+
duration_s: { type: "number", minimum: 0, description: "\u6301\u7EED\u65F6\u957F\uFF08\u79D2\uFF09\uFF0C0 \u8868\u793A\u65E0\u9650" },
|
|
7180
|
+
brightness: { type: "number", minimum: 0, maximum: 255 },
|
|
7181
|
+
color: {
|
|
7182
|
+
type: "object",
|
|
7183
|
+
required: ["r", "g", "b"],
|
|
7184
|
+
additionalProperties: false,
|
|
7185
|
+
properties: {
|
|
7186
|
+
r: { type: "number", minimum: 0, maximum: 255 },
|
|
7187
|
+
g: { type: "number", minimum: 0, maximum: 255 },
|
|
7188
|
+
b: { type: "number", minimum: 0, maximum: 255 }
|
|
7351
7189
|
}
|
|
7352
|
-
}
|
|
7353
|
-
|
|
7354
|
-
|
|
7355
|
-
|
|
7356
|
-
|
|
7357
|
-
|
|
7358
|
-
|
|
7359
|
-
|
|
7360
|
-
|
|
7361
|
-
|
|
7362
|
-
|
|
7363
|
-
|
|
7190
|
+
},
|
|
7191
|
+
interval_ms: { type: "number", minimum: 0 },
|
|
7192
|
+
direction: { type: "string", enum: ["ltr", "rtl"] },
|
|
7193
|
+
window: { type: "number", enum: [1, 2, 3] },
|
|
7194
|
+
breath_timing: {
|
|
7195
|
+
type: "object",
|
|
7196
|
+
additionalProperties: false,
|
|
7197
|
+
properties: {
|
|
7198
|
+
rise_ms: { type: "number", minimum: 0 },
|
|
7199
|
+
hold_ms: { type: "number", minimum: 0 },
|
|
7200
|
+
fall_ms: { type: "number", minimum: 0 },
|
|
7201
|
+
off_ms: { type: "number", minimum: 0 }
|
|
7202
|
+
}
|
|
7203
|
+
},
|
|
7204
|
+
frames: { type: "array", items: { type: "array", items: { type: "number" } } },
|
|
7205
|
+
frame_duration_ms: { type: "number", minimum: 0 },
|
|
7206
|
+
background: {
|
|
7207
|
+
type: "object",
|
|
7208
|
+
required: ["r", "g", "b"],
|
|
7209
|
+
additionalProperties: false,
|
|
7210
|
+
properties: {
|
|
7211
|
+
r: { type: "number", minimum: 0, maximum: 255 },
|
|
7212
|
+
g: { type: "number", minimum: 0, maximum: 255 },
|
|
7213
|
+
b: { type: "number", minimum: 0, maximum: 255 }
|
|
7364
7214
|
}
|
|
7365
7215
|
}
|
|
7366
|
-
const errMsg = `\u66F4\u65B0\u6267\u884C\u5F02\u5E38: ${String(err)}`;
|
|
7367
|
-
logger.error(errMsg);
|
|
7368
|
-
return { success: false, message: errMsg };
|
|
7369
|
-
} finally {
|
|
7370
|
-
try {
|
|
7371
|
-
(0, import_node_fs9.rmSync)(workDir, { force: true, recursive: true });
|
|
7372
|
-
} catch {
|
|
7373
|
-
}
|
|
7374
|
-
}
|
|
7375
|
-
}
|
|
7376
|
-
|
|
7377
|
-
// src/update/index.ts
|
|
7378
|
-
var PLUGIN_ID = "phone-notifications";
|
|
7379
|
-
function resolveTargetDir(api) {
|
|
7380
|
-
try {
|
|
7381
|
-
const cfg = api.runtime.config?.loadConfig?.();
|
|
7382
|
-
const installPath = cfg?.plugins?.installs?.[PLUGIN_ID]?.installPath;
|
|
7383
|
-
if (installPath) return installPath;
|
|
7384
|
-
} catch {
|
|
7385
7216
|
}
|
|
7386
|
-
|
|
7217
|
+
};
|
|
7218
|
+
var segmentsSchema = {
|
|
7219
|
+
type: "array",
|
|
7220
|
+
description: "\u706F\u6548\u6BB5\u5E8F\u5217\uFF0C1\u201312 \u6BB5\uFF0C\u6309\u987A\u5E8F\u64AD\u653E",
|
|
7221
|
+
minItems: 1,
|
|
7222
|
+
maxItems: MAX_LIGHT_SEGMENTS,
|
|
7223
|
+
items: segmentItemSchema
|
|
7224
|
+
};
|
|
7225
|
+
function ok(data) {
|
|
7226
|
+
return { content: [{ type: "text", text: JSON.stringify(data) }], details: data };
|
|
7387
7227
|
}
|
|
7388
|
-
|
|
7389
|
-
const
|
|
7390
|
-
|
|
7391
|
-
const cfg = configApi.loadConfig();
|
|
7392
|
-
if (!cfg.plugins) cfg.plugins = {};
|
|
7393
|
-
if (!cfg.plugins.installs) cfg.plugins.installs = {};
|
|
7394
|
-
cfg.plugins.installs[PLUGIN_ID] = {
|
|
7395
|
-
...cfg.plugins.installs[PLUGIN_ID],
|
|
7396
|
-
source: "archive",
|
|
7397
|
-
sourcePath: tgzUrl,
|
|
7398
|
-
installPath: targetDir,
|
|
7399
|
-
version,
|
|
7400
|
-
installedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
7401
|
-
};
|
|
7402
|
-
await configApi.writeConfigFile(cfg);
|
|
7228
|
+
function err(code, message) {
|
|
7229
|
+
const data = { ok: false, error: { code, message } };
|
|
7230
|
+
return { content: [{ type: "text", text: JSON.stringify(data) }], details: data };
|
|
7403
7231
|
}
|
|
7404
|
-
function
|
|
7405
|
-
|
|
7406
|
-
|
|
7407
|
-
|
|
7408
|
-
|
|
7409
|
-
},
|
|
7410
|
-
|
|
7411
|
-
|
|
7412
|
-
|
|
7413
|
-
|
|
7414
|
-
|
|
7415
|
-
|
|
7416
|
-
|
|
7417
|
-
|
|
7418
|
-
|
|
7419
|
-
current: update.current,
|
|
7420
|
-
latest: update.latest
|
|
7421
|
-
});
|
|
7422
|
-
pendingGatewayUpdate = null;
|
|
7423
|
-
return true;
|
|
7424
|
-
}
|
|
7425
|
-
api.on("before_prompt_build", () => {
|
|
7426
|
-
if (!pendingUpdate) return;
|
|
7427
|
-
return {
|
|
7428
|
-
appendSystemContext: `[\u7CFB\u7EDF\u901A\u77E5] phone-notifications \u63D2\u4EF6\u6709\u65B0\u7248\u672C ${pendingUpdate.latest} \u53EF\u7528\uFF08\u5F53\u524D\u7248\u672C ${pendingUpdate.current}\uFF09\u3002
|
|
7429
|
-
\u8BF7\u7528\u81EA\u7136\u8BED\u8A00\u5728\u5408\u9002\u65F6\u673A\u544A\u77E5\u7528\u6237\uFF0C\u82E5\u7528\u6237\u540C\u610F\u66F4\u65B0\uFF0C\u8C03\u7528 plugin-update tool \u5E76\u4F20\u5165 version \u53C2\u6570\u3002\u82E5\u7528\u6237\u62D2\u7EDD\uFF0C\u4E0D\u518D\u91CD\u590D\u63D0\u9192\u3002`
|
|
7430
|
-
};
|
|
7232
|
+
function registerLightRulesTools(api, registry, logger) {
|
|
7233
|
+
api.registerTool({
|
|
7234
|
+
name: "lightrules.list",
|
|
7235
|
+
label: "List Light Rules",
|
|
7236
|
+
description: '\u5217\u51FA\u6240\u6709\u706F\u6548\u89C4\u5219\uFF08\u5305\u542B enabled/disabled \u72B6\u6001\uFF09\u3002\u5F53\u7528\u6237\u8BF4"\u5217\u51FA\u706F\u6548\u89C4\u5219"\u3001"\u6709\u54EA\u4E9B\u706F\u6548\u89C4\u5219"\u3001"\u67E5\u770B\u89C4\u5219"\u7B49\u65F6\u8C03\u7528\u3002\u6CE8\u610F\uFF1A\u706F\u6548\u89C4\u5219\u7684\u6240\u6709 CRUD \u64CD\u4F5C\u5FC5\u987B\u901A\u8FC7 lightrules.* \u5DE5\u5177\u5B8C\u6210\uFF0C\u7981\u6B62\u76F4\u63A5\u7528 write/edit \u4FEE\u6539 tasks/*/meta.json\u3002',
|
|
7237
|
+
parameters: { type: "object", properties: {}, additionalProperties: false },
|
|
7238
|
+
async execute(_toolCallId, _params) {
|
|
7239
|
+
try {
|
|
7240
|
+
const rules = registry.list().map((rule) => ({ ...rule, id: rule.name }));
|
|
7241
|
+
return ok({ ok: true, rules });
|
|
7242
|
+
} catch (e) {
|
|
7243
|
+
logger.warn(`lightrules.list tool failed: ${e?.message}`);
|
|
7244
|
+
return err("INTERNAL_ERROR", e?.message ?? "Unknown error");
|
|
7245
|
+
}
|
|
7246
|
+
}
|
|
7431
7247
|
});
|
|
7432
7248
|
api.registerTool({
|
|
7433
|
-
name: "
|
|
7434
|
-
label: "
|
|
7435
|
-
description:
|
|
7249
|
+
name: "lightrules.create",
|
|
7250
|
+
label: "Create Light Rule",
|
|
7251
|
+
description: '\u521B\u5EFA\u4E00\u6761\u706F\u6548\u89C4\u5219\uFF0C\u6307\u5B9A\u540D\u79F0\u3001\u81EA\u7136\u8BED\u8A00\u89E6\u53D1\u63CF\u8FF0\u548C\u706F\u6548\u53C2\u6570\u3002\u5F53\u7528\u6237\u8BF4"\u521B\u5EFA\u706F\u6548\u89C4\u5219"\u3001"\u65B0\u589E\u89C4\u5219"\u7B49\u65F6\u8C03\u7528\u3002',
|
|
7436
7252
|
parameters: {
|
|
7437
7253
|
type: "object",
|
|
7438
|
-
required: ["
|
|
7254
|
+
required: ["name", "description", "segments"],
|
|
7439
7255
|
additionalProperties: false,
|
|
7440
7256
|
properties: {
|
|
7441
|
-
|
|
7257
|
+
name: { type: "string", description: "\u89C4\u5219\u7684\u552F\u4E00\u6807\u8BC6\u7B26\uFF08\u82F1\u6587 slug\uFF0C\u5982 red_light_on_wechat\uFF09" },
|
|
7258
|
+
description: {
|
|
7442
7259
|
type: "string",
|
|
7443
|
-
description: "\
|
|
7260
|
+
description: "\u81EA\u7136\u8BED\u8A00\u89E6\u53D1\u6761\u4EF6\uFF0C\u540C\u65F6\u4F5C\u4E3A\u89C4\u5219\u7528\u9014\u8BF4\u660E\u3002Agent \u6309\u6B64\u5B57\u6BB5\u5224\u65AD\u662F\u5426\u547D\u4E2D\uFF0C\u5FC5\u987B\u6E05\u6670\u63CF\u8FF0\u300C\u4F55\u65F6\u89E6\u53D1\u300D"
|
|
7261
|
+
},
|
|
7262
|
+
segments: segmentsSchema,
|
|
7263
|
+
repeat_times: {
|
|
7264
|
+
type: "number",
|
|
7265
|
+
description: "\u6574\u6761\u706F\u6548\u5E8F\u5217\u91CD\u590D\u6B21\u6570\uFF0C0=\u65E0\u9650\u5FAA\u73AF\uFF0C1=\u64AD\u653E\u4E00\u6B21\uFF08\u9ED8\u8BA4\uFF09"
|
|
7444
7266
|
}
|
|
7445
7267
|
}
|
|
7446
7268
|
},
|
|
7447
7269
|
async execute(_toolCallId, params) {
|
|
7448
|
-
const {
|
|
7449
|
-
|
|
7450
|
-
|
|
7451
|
-
|
|
7452
|
-
|
|
7453
|
-
|
|
7454
|
-
|
|
7455
|
-
|
|
7456
|
-
|
|
7457
|
-
|
|
7458
|
-
|
|
7459
|
-
|
|
7270
|
+
const { name, description, segments, repeat_times } = params;
|
|
7271
|
+
if (!name || typeof name !== "string")
|
|
7272
|
+
return err("INVALID_PARAMS", "name is required");
|
|
7273
|
+
if (!description || typeof description !== "string")
|
|
7274
|
+
return err("INVALID_PARAMS", "description is required");
|
|
7275
|
+
const validation = validateSegments(segments);
|
|
7276
|
+
if (!validation.valid) return err("VALIDATION_FAILED", JSON.stringify(validation.errors));
|
|
7277
|
+
let repeatTimes;
|
|
7278
|
+
try {
|
|
7279
|
+
repeatTimes = normalizeRepeatTimes({ repeat_times });
|
|
7280
|
+
assertAncsRepeatTimes(repeatTimes);
|
|
7281
|
+
} catch (e) {
|
|
7282
|
+
return err("VALIDATION_FAILED", e?.message ?? "Unknown error");
|
|
7283
|
+
}
|
|
7284
|
+
try {
|
|
7285
|
+
const result = await registry.create({
|
|
7286
|
+
name,
|
|
7287
|
+
description,
|
|
7288
|
+
matchRules: {},
|
|
7289
|
+
segments: validation.segments,
|
|
7290
|
+
repeat_times: repeatTimes,
|
|
7291
|
+
cronSchedule: "*/5 * * * *"
|
|
7292
|
+
});
|
|
7293
|
+
logger.info(`lightrules.create tool: created ${name}`);
|
|
7294
|
+
return ok({ ok: true, name, cronHint: result.cronHint });
|
|
7295
|
+
} catch (e) {
|
|
7296
|
+
if (e instanceof LightRuleError) return err(e.code, e.message);
|
|
7297
|
+
logger.warn(`lightrules.create tool failed: ${e?.message}`);
|
|
7298
|
+
return err("INTERNAL_ERROR", e?.message ?? "Unknown error");
|
|
7460
7299
|
}
|
|
7461
|
-
return {
|
|
7462
|
-
content: [{ type: "text", text: result.message }],
|
|
7463
|
-
details: result
|
|
7464
|
-
};
|
|
7465
|
-
}
|
|
7466
|
-
});
|
|
7467
|
-
api.registerGatewayMethod("plugin.update", async ({ params, respond, context }) => {
|
|
7468
|
-
rememberBroadcast?.(context?.broadcast);
|
|
7469
|
-
const version = params.version;
|
|
7470
|
-
if (!version) {
|
|
7471
|
-
respond(false, void 0, {
|
|
7472
|
-
code: "MISSING_VERSION",
|
|
7473
|
-
message: "version is required"
|
|
7474
|
-
});
|
|
7475
|
-
return;
|
|
7476
|
-
}
|
|
7477
|
-
const targetDir = resolveTargetDir(api);
|
|
7478
|
-
const result = await executeUpdate(
|
|
7479
|
-
version,
|
|
7480
|
-
api.runtime.system.runCommandWithTimeout,
|
|
7481
|
-
logger,
|
|
7482
|
-
targetDir,
|
|
7483
|
-
(v, url) => updateConfigRecord(api, v, targetDir, url)
|
|
7484
|
-
);
|
|
7485
|
-
if (result.success) {
|
|
7486
|
-
pendingUpdate = null;
|
|
7487
|
-
externalUpdateNotifier?.clearPendingUpdate();
|
|
7488
|
-
}
|
|
7489
|
-
if (result.success) {
|
|
7490
|
-
respond(true, { message: result.message });
|
|
7491
|
-
} else {
|
|
7492
|
-
respond(false, void 0, {
|
|
7493
|
-
code: "UPDATE_FAILED",
|
|
7494
|
-
message: result.message
|
|
7495
|
-
});
|
|
7496
7300
|
}
|
|
7497
7301
|
});
|
|
7498
|
-
|
|
7499
|
-
|
|
7500
|
-
|
|
7501
|
-
|
|
7502
|
-
|
|
7503
|
-
|
|
7504
|
-
|
|
7505
|
-
|
|
7506
|
-
|
|
7507
|
-
|
|
7508
|
-
|
|
7509
|
-
|
|
7510
|
-
|
|
7302
|
+
api.registerTool({
|
|
7303
|
+
name: "lightrules.update",
|
|
7304
|
+
label: "Update Light Rule",
|
|
7305
|
+
description: '\u4FEE\u6539\u706F\u6548\u89C4\u5219\uFF08\u542F\u7528/\u7981\u7528\u3001\u6539\u63CF\u8FF0\u3001\u6539\u706F\u6548\u53C2\u6570\uFF09\u3002\u5F53\u7528\u6237\u8BF4"\u7981\u7528\u67D0\u6761\u89C4\u5219"\u3001"\u542F\u7528\u89C4\u5219"\u3001"\u4FEE\u6539\u706F\u6548\u89C4\u5219"\u7B49\u65F6\u8C03\u7528\u3002',
|
|
7306
|
+
parameters: {
|
|
7307
|
+
type: "object",
|
|
7308
|
+
required: ["name"],
|
|
7309
|
+
additionalProperties: false,
|
|
7310
|
+
properties: {
|
|
7311
|
+
name: { type: "string", description: "\u8981\u4FEE\u6539\u7684\u89C4\u5219\u540D\u79F0\uFF08\u552F\u4E00\u6807\u8BC6\u7B26\uFF09" },
|
|
7312
|
+
description: { type: "string", description: "\u65B0\u7684\u89E6\u53D1\u6761\u4EF6\u63CF\u8FF0\uFF08\u53EF\u9009\uFF09" },
|
|
7313
|
+
enabled: { type: "boolean", description: "true=\u542F\u7528\uFF0Cfalse=\u7981\u7528" },
|
|
7314
|
+
segments: { ...segmentsSchema, description: "\u65B0\u7684\u706F\u6548\u6BB5\u5E8F\u5217\uFF08\u53EF\u9009\uFF0C\u4E0D\u586B\u5219\u4FDD\u6301\u4E0D\u53D8\uFF09" },
|
|
7315
|
+
repeat_times: { type: "number", description: "\u65B0\u7684\u91CD\u590D\u6B21\u6570\uFF08\u53EF\u9009\uFF09" }
|
|
7511
7316
|
}
|
|
7512
|
-
logger.info(
|
|
7513
|
-
`\u5DF2\u901A\u77E5\u66F4\u65B0 ${update.current} \u2192 ${update.latest}` + (broadcasted ? "\uFF08\u5BF9\u8BDD + \u7F51\u5173\uFF09" : "\uFF08\u5BF9\u8BDD\u901A\u9053\u5DF2\u751F\u6548\uFF0C\u7B49\u5F85\u4E0B\u6B21 gateway \u8BF7\u6C42\u65F6\u8865\u53D1\u5BA2\u6237\u7AEF\u4E8B\u4EF6\uFF09")
|
|
7514
|
-
);
|
|
7515
7317
|
},
|
|
7516
|
-
|
|
7517
|
-
|
|
7518
|
-
|
|
7519
|
-
|
|
7520
|
-
|
|
7521
|
-
|
|
7522
|
-
|
|
7523
|
-
|
|
7524
|
-
|
|
7525
|
-
|
|
7526
|
-
|
|
7318
|
+
async execute(_toolCallId, params) {
|
|
7319
|
+
const { name, description, enabled, segments, repeat_times } = params;
|
|
7320
|
+
if (!name || typeof name !== "string")
|
|
7321
|
+
return err("INVALID_PARAMS", "name is required");
|
|
7322
|
+
let validatedSegments;
|
|
7323
|
+
if (segments !== void 0) {
|
|
7324
|
+
const validation = validateSegments(segments);
|
|
7325
|
+
if (!validation.valid) return err("VALIDATION_FAILED", JSON.stringify(validation.errors));
|
|
7326
|
+
validatedSegments = validation.segments;
|
|
7327
|
+
}
|
|
7328
|
+
let repeatTimes;
|
|
7329
|
+
if (repeat_times !== void 0) {
|
|
7330
|
+
try {
|
|
7331
|
+
repeatTimes = normalizeRepeatTimes({ repeat_times });
|
|
7332
|
+
assertAncsRepeatTimes(repeatTimes);
|
|
7333
|
+
} catch (e) {
|
|
7334
|
+
return err("VALIDATION_FAILED", e?.message ?? "Unknown error");
|
|
7335
|
+
}
|
|
7336
|
+
}
|
|
7337
|
+
try {
|
|
7338
|
+
const result = await registry.update({
|
|
7339
|
+
name,
|
|
7340
|
+
description,
|
|
7341
|
+
segments: validatedSegments,
|
|
7342
|
+
repeat_times: repeatTimes,
|
|
7343
|
+
enabled
|
|
7344
|
+
});
|
|
7345
|
+
logger.info(`lightrules.update tool: updated ${name}`);
|
|
7346
|
+
return ok({
|
|
7347
|
+
ok: true,
|
|
7348
|
+
name: result.meta.name,
|
|
7349
|
+
updated: true,
|
|
7350
|
+
rule: result.meta,
|
|
7351
|
+
cronHint: result.cronHint
|
|
7352
|
+
});
|
|
7353
|
+
} catch (e) {
|
|
7354
|
+
if (e instanceof LightRuleError) return err(e.code, e.message);
|
|
7355
|
+
logger.warn(`lightrules.update tool failed: ${e?.message}`);
|
|
7356
|
+
return err("INTERNAL_ERROR", e?.message ?? "Unknown error");
|
|
7357
|
+
}
|
|
7527
7358
|
}
|
|
7528
|
-
};
|
|
7529
|
-
|
|
7530
|
-
|
|
7531
|
-
|
|
7532
|
-
|
|
7533
|
-
|
|
7534
|
-
|
|
7535
|
-
|
|
7536
|
-
|
|
7537
|
-
|
|
7538
|
-
|
|
7539
|
-
|
|
7540
|
-
} = deps;
|
|
7541
|
-
const autoUpdateConfig = {
|
|
7542
|
-
...config.autoUpdate,
|
|
7543
|
-
channel: resolveUpdateChannel({
|
|
7544
|
-
configuredChannel: config.autoUpdate?.channel,
|
|
7545
|
-
currentVersion: PLUGIN_VERSION,
|
|
7546
|
-
envName: loadEnvName()
|
|
7547
|
-
})
|
|
7548
|
-
};
|
|
7549
|
-
const autoUpdateLifecycle = registerAutoUpdate(
|
|
7550
|
-
api,
|
|
7551
|
-
logger,
|
|
7552
|
-
autoUpdateConfig,
|
|
7553
|
-
getBroadcastFn,
|
|
7554
|
-
cacheBroadcast,
|
|
7555
|
-
tunnelService ? {
|
|
7556
|
-
notifyUpdateAvailable: (update) => tunnelService.notifyUpdateAvailable(update),
|
|
7557
|
-
clearPendingUpdate: () => tunnelService.clearPendingUpdate()
|
|
7558
|
-
} : void 0
|
|
7559
|
-
);
|
|
7560
|
-
api.registerService({
|
|
7561
|
-
id: "update-checker",
|
|
7562
|
-
start() {
|
|
7563
|
-
autoUpdateLifecycle.start();
|
|
7359
|
+
});
|
|
7360
|
+
api.registerTool({
|
|
7361
|
+
name: "lightrules.delete",
|
|
7362
|
+
label: "Delete Light Rule",
|
|
7363
|
+
description: '\u5220\u9664\u4E00\u6761\u706F\u6548\u89C4\u5219\uFF08\u4E0D\u53EF\u6062\u590D\uFF09\u3002\u5F53\u7528\u6237\u8BF4"\u5220\u9664\u706F\u6548\u89C4\u5219"\u3001"\u79FB\u9664\u89C4\u5219"\u7B49\u65F6\u8C03\u7528\u3002',
|
|
7364
|
+
parameters: {
|
|
7365
|
+
type: "object",
|
|
7366
|
+
required: ["name"],
|
|
7367
|
+
additionalProperties: false,
|
|
7368
|
+
properties: {
|
|
7369
|
+
name: { type: "string", description: "\u8981\u5220\u9664\u7684\u89C4\u5219\u540D\u79F0" }
|
|
7370
|
+
}
|
|
7564
7371
|
},
|
|
7565
|
-
|
|
7566
|
-
|
|
7372
|
+
async execute(_toolCallId, params) {
|
|
7373
|
+
const { name } = params;
|
|
7374
|
+
if (!name || typeof name !== "string")
|
|
7375
|
+
return err("INVALID_PARAMS", "name is required");
|
|
7376
|
+
try {
|
|
7377
|
+
const result = await registry.delete(name);
|
|
7378
|
+
logger.info(`lightrules.delete tool: deleted ${name}`);
|
|
7379
|
+
return ok({ ok: true, name: result.name, deleted: true, cronHint: result.cronHint });
|
|
7380
|
+
} catch (e) {
|
|
7381
|
+
if (e instanceof LightRuleError) return err(e.code, e.message);
|
|
7382
|
+
logger.warn(`lightrules.delete tool failed: ${e?.message}`);
|
|
7383
|
+
return err("INTERNAL_ERROR", e?.message ?? "Unknown error");
|
|
7384
|
+
}
|
|
7567
7385
|
}
|
|
7568
7386
|
});
|
|
7569
|
-
logger.info("\u81EA\u52A8\u66F4\u65B0\u68C0\u67E5\u670D\u52A1\u5DF2\u6CE8\u518C");
|
|
7570
|
-
return autoUpdateLifecycle;
|
|
7571
7387
|
}
|
|
7572
7388
|
|
|
7573
|
-
// src/
|
|
7574
|
-
var
|
|
7389
|
+
// src/light-rules/evaluator-job.ts
|
|
7390
|
+
var EVALUATOR_JOB_ID = "light-rules-evaluator";
|
|
7391
|
+
var EVALUATOR_SUBAGENT_SESSION_KEY = EVALUATOR_JOB_ID;
|
|
7392
|
+
var FALLBACK_CRON_EXPR = "0 0 1 1 *";
|
|
7393
|
+
function buildEvaluatorJobMessage(notificationsDir) {
|
|
7394
|
+
return `\u706F\u6548\u89C4\u5219\u8BC4\u4F30\u4EFB\u52A1\u3002
|
|
7575
7395
|
|
|
7576
|
-
|
|
7577
|
-
|
|
7578
|
-
|
|
7579
|
-
|
|
7580
|
-
|
|
7581
|
-
|
|
7582
|
-
|
|
7583
|
-
|
|
7584
|
-
ok: true,
|
|
7585
|
-
apiKey: apiKey.slice(0, 8) + "\u2026",
|
|
7586
|
-
storedAt: credentialsPath()
|
|
7587
|
-
});
|
|
7588
|
-
});
|
|
7589
|
-
auth.command("set-token <token>").description("\uFF08\u517C\u5BB9\uFF09\u8BBE\u7F6E\u7528\u6237 API Key\uFF08\u65E7\u547D\u4EE4\u540D\uFF09").action((token) => {
|
|
7590
|
-
writeCredentials({ ...readCredentials(), apiKey: token, token: void 0 });
|
|
7591
|
-
output({
|
|
7592
|
-
ok: true,
|
|
7593
|
-
apiKey: token.slice(0, 8) + "\u2026",
|
|
7594
|
-
storedAt: credentialsPath()
|
|
7595
|
-
});
|
|
7596
|
-
});
|
|
7597
|
-
auth.command("show").description("\u67E5\u770B\u5F53\u524D\u8BA4\u8BC1\u72B6\u6001").action(() => {
|
|
7598
|
-
const creds = readCredentials();
|
|
7599
|
-
const apiKey = creds.apiKey ?? creds.token;
|
|
7600
|
-
if (apiKey) {
|
|
7601
|
-
output({
|
|
7602
|
-
ok: true,
|
|
7603
|
-
hasApiKey: true,
|
|
7604
|
-
apiKey: apiKey.slice(0, 8) + "\u2026",
|
|
7605
|
-
storedAt: credentialsPath()
|
|
7606
|
-
});
|
|
7607
|
-
} else {
|
|
7608
|
-
output({ ok: true, hasApiKey: false });
|
|
7609
|
-
}
|
|
7610
|
-
});
|
|
7611
|
-
auth.command("clear").description("\u6E05\u9664\u5DF2\u4FDD\u5B58\u7684\u8BA4\u8BC1\u4FE1\u606F").action(() => {
|
|
7612
|
-
const path2 = credentialsPath();
|
|
7613
|
-
if ((0, import_node_fs10.existsSync)(path2)) {
|
|
7614
|
-
const creds = readCredentials();
|
|
7615
|
-
delete creds.apiKey;
|
|
7616
|
-
delete creds.token;
|
|
7617
|
-
if (Object.keys(creds).length === 0) {
|
|
7618
|
-
(0, import_node_fs10.rmSync)(path2, { force: true });
|
|
7619
|
-
} else {
|
|
7620
|
-
writeCredentials(creds);
|
|
7621
|
-
}
|
|
7622
|
-
}
|
|
7623
|
-
output({ ok: true, cleared: true });
|
|
7624
|
-
});
|
|
7396
|
+
\u6267\u884C\u6B65\u9AA4\uFF1A
|
|
7397
|
+
1. \u8BFB\u53D6 tasks/light-rules-evaluator/checkpoint.json\uFF08\u8BB0\u5F55\u4E0A\u6B21\u5904\u7406\u8FDB\u5EA6\uFF09
|
|
7398
|
+
2. \u626B\u63CF ${notificationsDir} \u76EE\u5F55\uFF0C\u83B7\u53D6 checkpoint \u4E4B\u540E\u7684\u65B0\u901A\u77E5
|
|
7399
|
+
3. \u626B\u63CF tasks/ \u76EE\u5F55\uFF0C\u8BFB\u53D6\u6240\u6709 type=light-rule \u4E14 enabled=true \u7684 meta.json
|
|
7400
|
+
4. \u5BF9\u6BCF\u6761\u65B0\u901A\u77E5\uFF0C\u9010\u4E00\u5224\u65AD\u662F\u5426\u547D\u4E2D\u6BCF\u6761\u89C4\u5219\u7684 description\uFF08\u8BED\u4E49\u5339\u914D\uFF09
|
|
7401
|
+
5. \u547D\u4E2D\u65F6\uFF1A\u4EE5\u8BE5\u89C4\u5219\u7684 segments \u548C repeat_times \u8C03\u7528 light_control \u5DE5\u5177
|
|
7402
|
+
6. \u66F4\u65B0 checkpoint.json\uFF0C\u8BB0\u5F55\u5DF2\u5904\u7406\u5230\u7684\u6700\u65B0\u901A\u77E5\u4F4D\u7F6E
|
|
7403
|
+
7. \u82E5\u65E0\u65B0\u901A\u77E5\u6216\u65E0 enabled \u89C4\u5219\uFF1A\u8F93\u51FA NO_CHANGE\uFF0C\u76F4\u63A5\u7ED3\u675F`;
|
|
7625
7404
|
}
|
|
7626
|
-
|
|
7627
|
-
|
|
7628
|
-
|
|
7629
|
-
|
|
7630
|
-
|
|
7631
|
-
|
|
7632
|
-
|
|
7405
|
+
var LightRulesEvaluatorJob = class {
|
|
7406
|
+
logger;
|
|
7407
|
+
registry;
|
|
7408
|
+
subagentRunner;
|
|
7409
|
+
getNotificationsDir;
|
|
7410
|
+
/**
|
|
7411
|
+
* 记录本进程生命周期内 job 是否已确认存在。
|
|
7412
|
+
* 仅在 `ensureJobExists` 成功后置 true,避免每次 push 都做检查。
|
|
7413
|
+
*/
|
|
7414
|
+
jobEnsured = false;
|
|
7415
|
+
/**
|
|
7416
|
+
* 首次创建 job 时的并发保护。
|
|
7417
|
+
* 避免冷启动瞬间多条通知并发到达时重复调用 `cron.add`。
|
|
7418
|
+
*/
|
|
7419
|
+
ensureJobPromise = null;
|
|
7420
|
+
/**
|
|
7421
|
+
* subagent fallback 路径的并发保护。
|
|
7422
|
+
* 若评估 session 已在运行中,跳过本次触发(checkpoint 保证下次补处理)。
|
|
7423
|
+
*/
|
|
7424
|
+
subagentInFlight = false;
|
|
7425
|
+
constructor(deps) {
|
|
7426
|
+
this.logger = deps.logger;
|
|
7427
|
+
this.registry = deps.registry;
|
|
7428
|
+
this.subagentRunner = deps.subagentRunner;
|
|
7429
|
+
this.getNotificationsDir = deps.getNotificationsDir ?? (() => void 0);
|
|
7633
7430
|
}
|
|
7634
|
-
|
|
7635
|
-
|
|
7636
|
-
|
|
7637
|
-
|
|
7638
|
-
|
|
7639
|
-
|
|
7640
|
-
|
|
7641
|
-
|
|
7642
|
-
|
|
7643
|
-
|
|
7644
|
-
|
|
7645
|
-
|
|
7646
|
-
|
|
7647
|
-
|
|
7648
|
-
|
|
7649
|
-
|
|
7650
|
-
|
|
7651
|
-
|
|
7431
|
+
/**
|
|
7432
|
+
* 通知落盘后调用。若有新增通知且存在 enabled 规则,则触发评估。
|
|
7433
|
+
*
|
|
7434
|
+
* 两条路径:
|
|
7435
|
+
* - cron 不为 null:enqueueRun("force") 入队(gateway context 路径,正常路径)
|
|
7436
|
+
* - cron 为 null:通过 subagentRunner 直接运行(HTTP Relay 路径,fallback)
|
|
7437
|
+
*
|
|
7438
|
+
* @param cron 来自 gateway context 的 CronService;HTTP 路径下为 null
|
|
7439
|
+
* @param insertedCount 本次 ingest 新落盘的通知条数(StoredNotification 去重后)
|
|
7440
|
+
*/
|
|
7441
|
+
async triggerIfNeeded(cron, insertedCount) {
|
|
7442
|
+
if (insertedCount === 0) return;
|
|
7443
|
+
if (this.registry.getEnabled().length === 0) return;
|
|
7444
|
+
if (!cron) {
|
|
7445
|
+
await this.triggerViaSubagent();
|
|
7446
|
+
return;
|
|
7447
|
+
}
|
|
7448
|
+
try {
|
|
7449
|
+
await this.ensureJobExists(cron);
|
|
7450
|
+
} catch (err2) {
|
|
7451
|
+
this.logger.warn(`light-rules-evaluator: job ensure failed: ${err2?.message ?? err2}`);
|
|
7452
|
+
return;
|
|
7453
|
+
}
|
|
7454
|
+
try {
|
|
7455
|
+
const result = await cron.enqueueRun(EVALUATOR_JOB_ID, "force");
|
|
7456
|
+
if (!result.ok) {
|
|
7457
|
+
this.logger.warn("light-rules-evaluator: enqueueRun returned ok=false");
|
|
7458
|
+
return;
|
|
7652
7459
|
}
|
|
7653
|
-
|
|
7654
|
-
|
|
7655
|
-
if (
|
|
7656
|
-
|
|
7657
|
-
const toDateKey = hasTo ? opts.to.slice(0, 10) : null;
|
|
7658
|
-
for (const dateKey of keys) {
|
|
7659
|
-
if (fromDateKey && dateKey < fromDateKey) continue;
|
|
7660
|
-
if (toDateKey && dateKey > toDateKey) continue;
|
|
7661
|
-
const items = await readDateFileAsync(dir, dateKey);
|
|
7662
|
-
for (const item of items) {
|
|
7663
|
-
if (!filterItem(item, opts)) continue;
|
|
7664
|
-
const itemTs = Date.parse(item.timestamp);
|
|
7665
|
-
if (Number.isNaN(itemTs)) continue;
|
|
7666
|
-
if (fromTs !== null && itemTs < fromTs) continue;
|
|
7667
|
-
if (toTs !== null && itemTs > toTs) continue;
|
|
7668
|
-
results.push(item);
|
|
7669
|
-
}
|
|
7670
|
-
}
|
|
7671
|
-
} else {
|
|
7672
|
-
for (const dateKey of keys) {
|
|
7673
|
-
const items = await readDateFileAsync(dir, dateKey);
|
|
7674
|
-
for (const item of items) {
|
|
7675
|
-
if (!filterItem(item, opts)) continue;
|
|
7676
|
-
if (Number.isNaN(Date.parse(item.timestamp))) continue;
|
|
7677
|
-
results.push(item);
|
|
7678
|
-
}
|
|
7679
|
-
}
|
|
7460
|
+
if ("enqueued" in result && result.enqueued) {
|
|
7461
|
+
this.logger.info(`light-rules-evaluator: enqueued runId=${result.runId}`);
|
|
7462
|
+
} else if ("reason" in result) {
|
|
7463
|
+
this.logger.info(`light-rules-evaluator: enqueueRun skipped (${result.reason})`);
|
|
7680
7464
|
}
|
|
7681
|
-
|
|
7682
|
-
|
|
7683
|
-
limit
|
|
7684
|
-
);
|
|
7685
|
-
output({
|
|
7686
|
-
ok: true,
|
|
7687
|
-
total: notifications.length,
|
|
7688
|
-
notifications
|
|
7689
|
-
});
|
|
7465
|
+
} catch (err2) {
|
|
7466
|
+
this.logger.warn(`light-rules-evaluator: enqueueRun failed: ${err2?.message ?? err2}`);
|
|
7690
7467
|
}
|
|
7691
|
-
);
|
|
7692
|
-
}
|
|
7693
|
-
|
|
7694
|
-
// src/cli/ntf-stats.ts
|
|
7695
|
-
function registerNtfStats(ntf, ctx) {
|
|
7696
|
-
ntf.command("stats").description("\u901A\u77E5\u7EDF\u8BA1\u5206\u6790\uFF08\u6309\u65E5\u671F/\u5E94\u7528/\u53D1\u9001\u4EBA/\u65F6\u6BB5\u805A\u5408\uFF09").option("--from <date>", "\u5F00\u59CB\u65E5\u671F YYYY-MM-DD", daysAgo(7)).option("--to <date>", "\u7ED3\u675F\u65E5\u671F YYYY-MM-DD", today()).option("--app <name>", "\u53EA\u7EDF\u8BA1\u6307\u5B9A\u5E94\u7528").option("--dim <dimension>", "\u7EDF\u8BA1\u7EF4\u5EA6\uFF1Adate/app/sender/hour/all", "all").action(
|
|
7697
|
-
(opts) => {
|
|
7698
|
-
const dir = resolveNotificationsDir(ctx);
|
|
7699
|
-
if (!dir) exitError("STORAGE_UNAVAILABLE", "\u901A\u77E5\u5B58\u50A8\u76EE\u5F55\u4E0D\u53EF\u7528");
|
|
7700
|
-
const dim = opts.dim;
|
|
7701
|
-
const keys = filterDateRange(listDateKeys(dir), opts.from, opts.to);
|
|
7702
|
-
const byDate = {};
|
|
7703
|
-
const byApp = {};
|
|
7704
|
-
const bySender = {};
|
|
7705
|
-
const byHour = {};
|
|
7706
|
-
let total = 0;
|
|
7707
|
-
for (const dateKey of keys) {
|
|
7708
|
-
const items = readDateFile(dir, dateKey);
|
|
7709
|
-
let dateCount = 0;
|
|
7710
|
-
for (const item of items) {
|
|
7711
|
-
if (opts.app && item.appName !== opts.app) continue;
|
|
7712
|
-
total++;
|
|
7713
|
-
dateCount++;
|
|
7714
|
-
byApp[item.appName] = (byApp[item.appName] || 0) + 1;
|
|
7715
|
-
if (item.title) {
|
|
7716
|
-
bySender[item.title] = (bySender[item.title] || 0) + 1;
|
|
7717
|
-
}
|
|
7718
|
-
const hourMatch = /T(\d{2}):/.exec(item.timestamp);
|
|
7719
|
-
if (hourMatch) {
|
|
7720
|
-
const h = hourMatch[1];
|
|
7721
|
-
byHour[h] = (byHour[h] || 0) + 1;
|
|
7722
|
-
}
|
|
7723
|
-
}
|
|
7724
|
-
byDate[dateKey] = dateCount;
|
|
7725
|
-
}
|
|
7726
|
-
const result = {
|
|
7727
|
-
ok: true,
|
|
7728
|
-
range: { from: opts.from, to: opts.to },
|
|
7729
|
-
total
|
|
7730
|
-
};
|
|
7731
|
-
if (dim === "date" || dim === "all") {
|
|
7732
|
-
result.byDate = byDate;
|
|
7733
|
-
}
|
|
7734
|
-
if (dim === "app" || dim === "all") {
|
|
7735
|
-
result.byApp = byApp;
|
|
7736
|
-
}
|
|
7737
|
-
if (dim === "sender" || dim === "all") {
|
|
7738
|
-
const sorted = Object.entries(bySender).sort((a, b) => b[1] - a[1]).slice(0, 20).map(([sender, count]) => ({ sender, count }));
|
|
7739
|
-
result.bySender = sorted;
|
|
7740
|
-
}
|
|
7741
|
-
if (dim === "hour" || dim === "all") {
|
|
7742
|
-
result.byHour = byHour;
|
|
7743
|
-
}
|
|
7744
|
-
output(result);
|
|
7745
|
-
}
|
|
7746
|
-
);
|
|
7747
|
-
}
|
|
7748
|
-
|
|
7749
|
-
// src/cli/ntf-sync.ts
|
|
7750
|
-
var import_node_fs11 = require("fs");
|
|
7751
|
-
var import_node_path10 = require("path");
|
|
7752
|
-
var SYNC_FETCH_LIMIT = 300;
|
|
7753
|
-
function checkpointPath(dir) {
|
|
7754
|
-
return (0, import_node_path10.join)(dir, ".checkpoint.json");
|
|
7755
|
-
}
|
|
7756
|
-
function readCheckpoint(dir) {
|
|
7757
|
-
const p = checkpointPath(dir);
|
|
7758
|
-
if (!(0, import_node_fs11.existsSync)(p)) return {};
|
|
7759
|
-
try {
|
|
7760
|
-
return JSON.parse((0, import_node_fs11.readFileSync)(p, "utf-8"));
|
|
7761
|
-
} catch {
|
|
7762
|
-
return {};
|
|
7763
7468
|
}
|
|
7764
|
-
|
|
7765
|
-
|
|
7766
|
-
|
|
7767
|
-
|
|
7768
|
-
|
|
7769
|
-
|
|
7770
|
-
|
|
7771
|
-
|
|
7772
|
-
|
|
7773
|
-
|
|
7774
|
-
|
|
7775
|
-
|
|
7776
|
-
let totalPending = 0;
|
|
7777
|
-
for (const dateKey of keys) {
|
|
7778
|
-
const items = readDateFile(dir, dateKey);
|
|
7779
|
-
const lastIndex = checkpoint[dateKey]?.lastIndex ?? -1;
|
|
7780
|
-
const unprocessed = items.length - (lastIndex + 1);
|
|
7781
|
-
if (unprocessed > 0) {
|
|
7782
|
-
pending.push({
|
|
7783
|
-
date: dateKey,
|
|
7784
|
-
count: unprocessed,
|
|
7785
|
-
startIndex: lastIndex + 1
|
|
7786
|
-
});
|
|
7787
|
-
totalPending += unprocessed;
|
|
7788
|
-
}
|
|
7469
|
+
/**
|
|
7470
|
+
* cron service 不可用时的 fallback:直接通过 subagentRunner 运行评估 session。
|
|
7471
|
+
*
|
|
7472
|
+
* 并发保护:若上一次 subagent 运行尚未完成,本次跳过。
|
|
7473
|
+
* checkpoint 保证即使本次跳过,下次触发时会处理所有积压通知。
|
|
7474
|
+
*/
|
|
7475
|
+
async triggerViaSubagent() {
|
|
7476
|
+
if (!this.subagentRunner) {
|
|
7477
|
+
this.logger.warn(
|
|
7478
|
+
"light-rules-evaluator: cron service unavailable and no subagent fallback configured; notifications ingested via HTTP will not trigger light rules until an agent session is active"
|
|
7479
|
+
);
|
|
7480
|
+
return;
|
|
7789
7481
|
}
|
|
7790
|
-
|
|
7791
|
-
|
|
7792
|
-
|
|
7793
|
-
const dir = resolveNotificationsDir(ctx);
|
|
7794
|
-
if (!dir) exitError("STORAGE_UNAVAILABLE", "\u901A\u77E5\u5B58\u50A8\u76EE\u5F55\u4E0D\u53EF\u7528");
|
|
7795
|
-
const items = readDateFile(dir, opts.date);
|
|
7796
|
-
if (items.length === 0) {
|
|
7797
|
-
exitError("NO_DATA", `\u65E5\u671F ${opts.date} \u65E0\u901A\u77E5\u6570\u636E`);
|
|
7482
|
+
if (this.subagentInFlight) {
|
|
7483
|
+
this.logger.info("light-rules-evaluator: subagent run in-flight, skipping this trigger");
|
|
7484
|
+
return;
|
|
7798
7485
|
}
|
|
7799
|
-
const
|
|
7800
|
-
|
|
7801
|
-
|
|
7802
|
-
|
|
7803
|
-
|
|
7804
|
-
|
|
7805
|
-
|
|
7806
|
-
|
|
7807
|
-
|
|
7808
|
-
|
|
7809
|
-
|
|
7810
|
-
|
|
7811
|
-
|
|
7812
|
-
|
|
7813
|
-
|
|
7814
|
-
|
|
7815
|
-
|
|
7816
|
-
|
|
7817
|
-
|
|
7486
|
+
const notificationsDir = this.getNotificationsDir();
|
|
7487
|
+
if (!notificationsDir) {
|
|
7488
|
+
this.logger.warn("light-rules-evaluator: notifications dir not ready, skipping subagent trigger");
|
|
7489
|
+
return;
|
|
7490
|
+
}
|
|
7491
|
+
this.subagentInFlight = true;
|
|
7492
|
+
try {
|
|
7493
|
+
const result = await this.subagentRunner.run({
|
|
7494
|
+
sessionKey: EVALUATOR_SUBAGENT_SESSION_KEY,
|
|
7495
|
+
message: buildEvaluatorJobMessage(notificationsDir),
|
|
7496
|
+
deliver: false,
|
|
7497
|
+
idempotencyKey: `${EVALUATOR_SUBAGENT_SESSION_KEY}-${Date.now()}`
|
|
7498
|
+
});
|
|
7499
|
+
this.logger.info(`light-rules-evaluator: subagent triggered runId=${result.runId}`);
|
|
7500
|
+
} catch (err2) {
|
|
7501
|
+
this.logger.warn(`light-rules-evaluator: subagent trigger failed: ${err2?.message ?? err2}`);
|
|
7502
|
+
} finally {
|
|
7503
|
+
this.subagentInFlight = false;
|
|
7504
|
+
}
|
|
7505
|
+
}
|
|
7506
|
+
/**
|
|
7507
|
+
* 按需创建 `light-rules-evaluator` job。
|
|
7508
|
+
* 若 job 已存在(内存缓存或 cron store),直接返回;否则调用 `cron.add`。
|
|
7509
|
+
*/
|
|
7510
|
+
async ensureJobExists(cron) {
|
|
7511
|
+
if (this.jobEnsured) return;
|
|
7512
|
+
if (!this.ensureJobPromise) {
|
|
7513
|
+
this.ensureJobPromise = this.createJobIfNeeded(cron).finally(() => {
|
|
7514
|
+
this.ensureJobPromise = null;
|
|
7818
7515
|
});
|
|
7516
|
+
}
|
|
7517
|
+
await this.ensureJobPromise;
|
|
7518
|
+
}
|
|
7519
|
+
async createJobIfNeeded(cron) {
|
|
7520
|
+
if (cron.getJob(EVALUATOR_JOB_ID)) {
|
|
7521
|
+
this.jobEnsured = true;
|
|
7819
7522
|
return;
|
|
7820
7523
|
}
|
|
7821
|
-
|
|
7822
|
-
|
|
7823
|
-
|
|
7824
|
-
|
|
7825
|
-
|
|
7826
|
-
|
|
7827
|
-
|
|
7828
|
-
|
|
7829
|
-
|
|
7830
|
-
|
|
7831
|
-
|
|
7832
|
-
|
|
7833
|
-
|
|
7834
|
-
|
|
7835
|
-
|
|
7836
|
-
|
|
7837
|
-
|
|
7838
|
-
|
|
7839
|
-
|
|
7524
|
+
try {
|
|
7525
|
+
await cron.add({
|
|
7526
|
+
id: EVALUATOR_JOB_ID,
|
|
7527
|
+
name: "\u706F\u6548\u89C4\u5219\u8BC4\u4F30",
|
|
7528
|
+
description: "\u4E8B\u4EF6\u9A71\u52A8\uFF1A\u901A\u77E5\u5230\u8FBE\u65F6\u8BC4\u4F30\u6240\u6709 enabled \u706F\u6548\u89C4\u5219\uFF0C\u547D\u4E2D\u5219\u8C03\u7528 light_control \u89E6\u53D1\u706F\u6548",
|
|
7529
|
+
enabled: true,
|
|
7530
|
+
schedule: { kind: "cron", expr: FALLBACK_CRON_EXPR },
|
|
7531
|
+
sessionTarget: "isolated",
|
|
7532
|
+
wakeMode: "now",
|
|
7533
|
+
payload: {
|
|
7534
|
+
kind: "agentTurn",
|
|
7535
|
+
message: buildEvaluatorJobMessage(this.getNotificationsDir() ?? "notifications")
|
|
7536
|
+
}
|
|
7537
|
+
});
|
|
7538
|
+
this.logger.info("light-rules-evaluator: job created");
|
|
7539
|
+
} catch (err2) {
|
|
7540
|
+
if (!cron.getJob(EVALUATOR_JOB_ID)) {
|
|
7541
|
+
throw err2;
|
|
7542
|
+
}
|
|
7840
7543
|
}
|
|
7841
|
-
|
|
7842
|
-
|
|
7843
|
-
|
|
7844
|
-
const hasMore = committedIndex < items.length - 1;
|
|
7845
|
-
checkpoint[opts.date] = { lastIndex: committedIndex };
|
|
7846
|
-
writeCheckpoint(dir, checkpoint);
|
|
7847
|
-
output({
|
|
7848
|
-
ok: true,
|
|
7849
|
-
date: opts.date,
|
|
7850
|
-
committedIndex,
|
|
7851
|
-
limit: SYNC_FETCH_LIMIT,
|
|
7852
|
-
hasMore,
|
|
7853
|
-
nextStartIndex: hasMore ? committedIndex + 1 : null
|
|
7854
|
-
});
|
|
7855
|
-
});
|
|
7856
|
-
}
|
|
7544
|
+
this.jobEnsured = true;
|
|
7545
|
+
}
|
|
7546
|
+
};
|
|
7857
7547
|
|
|
7858
|
-
// src/
|
|
7859
|
-
var
|
|
7860
|
-
var
|
|
7861
|
-
|
|
7862
|
-
|
|
7863
|
-
|
|
7864
|
-
|
|
7548
|
+
// src/light-rules/migration.ts
|
|
7549
|
+
var import_node_fs6 = require("fs");
|
|
7550
|
+
var import_node_path5 = require("path");
|
|
7551
|
+
var NO_MATCH_FETCH_PY = `#!/usr/bin/env python3
|
|
7552
|
+
# \u6B64\u6587\u4EF6\u7531\u8FC1\u79FB\u5DE5\u5177\u751F\u6210\u3002
|
|
7553
|
+
# \u706F\u6548\u89C4\u5219\u5DF2\u8FC1\u79FB\u81F3\u4E8B\u4EF6\u9A71\u52A8\u67B6\u6784\uFF0C\u6B64 cron job \u4E0D\u518D\u6267\u884C\u5B9E\u9645\u5DE5\u4F5C\u3002
|
|
7554
|
+
print("NO_MATCH")
|
|
7555
|
+
`;
|
|
7556
|
+
function normalizeScriptText(text) {
|
|
7557
|
+
return text.replace(/\r\n/g, "\n").trim();
|
|
7865
7558
|
}
|
|
7866
|
-
function
|
|
7867
|
-
|
|
7868
|
-
if (
|
|
7869
|
-
|
|
7870
|
-
|
|
7871
|
-
|
|
7872
|
-
return null;
|
|
7559
|
+
function resolveTasksDir(ctx) {
|
|
7560
|
+
if (ctx.workspaceDir) return (0, import_node_path5.join)(ctx.workspaceDir, "tasks");
|
|
7561
|
+
if (ctx.stateDir) {
|
|
7562
|
+
const inferredWorkspaceDir = (0, import_node_path5.join)(ctx.stateDir, "workspace");
|
|
7563
|
+
if ((0, import_node_fs6.existsSync)(inferredWorkspaceDir)) return (0, import_node_path5.join)(inferredWorkspaceDir, "tasks");
|
|
7564
|
+
return (0, import_node_path5.join)(ctx.stateDir, "tasks");
|
|
7873
7565
|
}
|
|
7566
|
+
return null;
|
|
7874
7567
|
}
|
|
7875
|
-
function
|
|
7876
|
-
|
|
7568
|
+
function migrateLegacyLightRuleTasks(ctx, logger) {
|
|
7569
|
+
const tasksDir3 = resolveTasksDir(ctx);
|
|
7570
|
+
if (!tasksDir3 || !(0, import_node_fs6.existsSync)(tasksDir3)) return;
|
|
7571
|
+
try {
|
|
7572
|
+
for (const entry of (0, import_node_fs6.readdirSync)(tasksDir3, { withFileTypes: true })) {
|
|
7573
|
+
if (!entry.isDirectory()) continue;
|
|
7574
|
+
migrateTaskDir((0, import_node_path5.join)(tasksDir3, String(entry.name)), logger);
|
|
7575
|
+
}
|
|
7576
|
+
} catch (err2) {
|
|
7577
|
+
logger.warn(`migration: failed to read tasks dir: ${err2?.message}`);
|
|
7578
|
+
}
|
|
7877
7579
|
}
|
|
7878
|
-
function
|
|
7879
|
-
|
|
7880
|
-
|
|
7881
|
-
|
|
7882
|
-
|
|
7883
|
-
|
|
7884
|
-
|
|
7885
|
-
|
|
7886
|
-
|
|
7887
|
-
|
|
7888
|
-
|
|
7889
|
-
|
|
7580
|
+
function migrateTaskDir(taskDir, logger) {
|
|
7581
|
+
const metaPath = (0, import_node_path5.join)(taskDir, "meta.json");
|
|
7582
|
+
if (!(0, import_node_fs6.existsSync)(metaPath)) return;
|
|
7583
|
+
let meta;
|
|
7584
|
+
try {
|
|
7585
|
+
meta = JSON.parse((0, import_node_fs6.readFileSync)(metaPath, "utf-8"));
|
|
7586
|
+
} catch {
|
|
7587
|
+
return;
|
|
7588
|
+
}
|
|
7589
|
+
if (meta.type !== "light-rule") return;
|
|
7590
|
+
const name = typeof meta.name === "string" ? meta.name : taskDir;
|
|
7591
|
+
mergeMatchRulesIntoDescription(meta, name, metaPath, logger);
|
|
7592
|
+
replaceFetchPy(taskDir, name, logger);
|
|
7593
|
+
for (const filename of ["README.md", "checkpoint.json"]) {
|
|
7594
|
+
removeFile((0, import_node_path5.join)(taskDir, filename), name, filename, logger);
|
|
7595
|
+
}
|
|
7890
7596
|
}
|
|
7891
|
-
function
|
|
7892
|
-
const
|
|
7893
|
-
|
|
7894
|
-
|
|
7895
|
-
|
|
7896
|
-
|
|
7597
|
+
function mergeMatchRulesIntoDescription(meta, name, metaPath, logger) {
|
|
7598
|
+
const matchRules = meta.matchRules;
|
|
7599
|
+
if (!matchRules || typeof matchRules !== "object") return;
|
|
7600
|
+
const rules = matchRules;
|
|
7601
|
+
const parts = [];
|
|
7602
|
+
if (rules.appName) parts.push(`app=${rules.appName}`);
|
|
7603
|
+
if (rules.senderKeywords?.length) {
|
|
7604
|
+
parts.push(`\u53D1\u4EF6\u4EBA\u5173\u952E\u8BCD=${rules.senderKeywords.join("\u3001")}`);
|
|
7605
|
+
}
|
|
7606
|
+
if (rules.contentKeywords?.length) {
|
|
7607
|
+
parts.push(`\u5185\u5BB9\u5173\u952E\u8BCD=${rules.contentKeywords.join("\u3001")}`);
|
|
7608
|
+
}
|
|
7609
|
+
if (parts.length > 0) {
|
|
7610
|
+
const existing = typeof meta.description === "string" ? meta.description.trim() : "";
|
|
7611
|
+
meta.description = existing ? `${existing}\u3002\u5339\u914D\u89C4\u5219\uFF1A${parts.join("\uFF0C")}` : `\u5339\u914D\u89C4\u5219\uFF1A${parts.join("\uFF0C")}`;
|
|
7612
|
+
}
|
|
7613
|
+
delete meta.matchRules;
|
|
7614
|
+
try {
|
|
7615
|
+
(0, import_node_fs6.writeFileSync)(metaPath, JSON.stringify(meta, null, 2), "utf-8");
|
|
7616
|
+
logger.info(`migration: merged matchRules into description for light rule: ${name}`);
|
|
7617
|
+
} catch (err2) {
|
|
7618
|
+
logger.warn(`migration: failed to update meta.json for ${name}: ${err2?.message}`);
|
|
7619
|
+
}
|
|
7620
|
+
}
|
|
7621
|
+
function replaceFetchPy(taskDir, name, logger) {
|
|
7622
|
+
const fetchPyPath = (0, import_node_path5.join)(taskDir, "fetch.py");
|
|
7623
|
+
if (!(0, import_node_fs6.existsSync)(fetchPyPath)) return;
|
|
7624
|
+
try {
|
|
7625
|
+
const existing = (0, import_node_fs6.readFileSync)(fetchPyPath, "utf-8");
|
|
7626
|
+
if (normalizeScriptText(existing) === normalizeScriptText(NO_MATCH_FETCH_PY)) {
|
|
7897
7627
|
return;
|
|
7898
7628
|
}
|
|
7899
|
-
|
|
7900
|
-
|
|
7901
|
-
|
|
7902
|
-
|
|
7903
|
-
|
|
7629
|
+
(0, import_node_fs6.writeFileSync)(fetchPyPath, NO_MATCH_FETCH_PY, "utf-8");
|
|
7630
|
+
logger.info(`migration: replaced fetch.py with NO_MATCH placeholder for ${name}`);
|
|
7631
|
+
} catch (err2) {
|
|
7632
|
+
logger.warn(`migration: failed to replace fetch.py for ${name}: ${err2?.message}`);
|
|
7633
|
+
}
|
|
7634
|
+
}
|
|
7635
|
+
function removeFile(filePath, ruleName, filename, logger) {
|
|
7636
|
+
if (!(0, import_node_fs6.existsSync)(filePath)) return;
|
|
7637
|
+
try {
|
|
7638
|
+
(0, import_node_fs6.rmSync)(filePath);
|
|
7639
|
+
logger.info(`migration: removed ${filename} for light rule: ${ruleName}`);
|
|
7640
|
+
} catch (err2) {
|
|
7641
|
+
logger.warn(`migration: failed to remove ${filename} for ${ruleName}: ${err2?.message}`);
|
|
7642
|
+
}
|
|
7643
|
+
}
|
|
7644
|
+
|
|
7645
|
+
// src/plugin/auto-update.ts
|
|
7646
|
+
init_env();
|
|
7647
|
+
|
|
7648
|
+
// src/update/channel.ts
|
|
7649
|
+
function resolveUpdateChannel(params) {
|
|
7650
|
+
if (params.configuredChannel) {
|
|
7651
|
+
return params.configuredChannel;
|
|
7652
|
+
}
|
|
7653
|
+
if (params.currentVersion.includes("-")) {
|
|
7654
|
+
return "beta";
|
|
7655
|
+
}
|
|
7656
|
+
return params.envName === "development" ? "beta" : "latest";
|
|
7657
|
+
}
|
|
7658
|
+
|
|
7659
|
+
// src/update/index.ts
|
|
7660
|
+
var import_node_path10 = require("path");
|
|
7661
|
+
|
|
7662
|
+
// src/update/checker.ts
|
|
7663
|
+
function parseSemver(v) {
|
|
7664
|
+
const [main, pre = null] = v.split("-", 2);
|
|
7665
|
+
const [major = 0, minor = 0, patch = 0] = main.split(".").map(Number);
|
|
7666
|
+
return { major, minor, patch, pre: pre ?? null };
|
|
7667
|
+
}
|
|
7668
|
+
function comparePreRelease(a, b) {
|
|
7669
|
+
const aParts = a.split(".");
|
|
7670
|
+
const bParts = b.split(".");
|
|
7671
|
+
for (let i = 0; i < Math.max(aParts.length, bParts.length); i++) {
|
|
7672
|
+
const ap = aParts[i] ?? "";
|
|
7673
|
+
const bp = bParts[i] ?? "";
|
|
7674
|
+
const an = Number(ap);
|
|
7675
|
+
const bn = Number(bp);
|
|
7676
|
+
let diff;
|
|
7677
|
+
if (!Number.isNaN(an) && !Number.isNaN(bn)) {
|
|
7678
|
+
diff = an - bn;
|
|
7679
|
+
} else if (ap < bp) {
|
|
7680
|
+
diff = -1;
|
|
7681
|
+
} else if (ap > bp) {
|
|
7682
|
+
diff = 1;
|
|
7683
|
+
} else {
|
|
7684
|
+
diff = 0;
|
|
7904
7685
|
}
|
|
7905
|
-
|
|
7906
|
-
}
|
|
7907
|
-
|
|
7908
|
-
|
|
7909
|
-
|
|
7910
|
-
|
|
7911
|
-
|
|
7912
|
-
|
|
7913
|
-
|
|
7914
|
-
|
|
7915
|
-
|
|
7916
|
-
|
|
7686
|
+
if (diff !== 0) return diff;
|
|
7687
|
+
}
|
|
7688
|
+
return 0;
|
|
7689
|
+
}
|
|
7690
|
+
function isNewerVersion(candidate, current) {
|
|
7691
|
+
const a = parseSemver(candidate);
|
|
7692
|
+
const b = parseSemver(current);
|
|
7693
|
+
if (a.major !== b.major) return a.major > b.major;
|
|
7694
|
+
if (a.minor !== b.minor) return a.minor > b.minor;
|
|
7695
|
+
if (a.patch !== b.patch) return a.patch > b.patch;
|
|
7696
|
+
if (a.pre === null) return b.pre !== null;
|
|
7697
|
+
if (b.pre === null) return false;
|
|
7698
|
+
return comparePreRelease(a.pre, b.pre) > 0;
|
|
7699
|
+
}
|
|
7700
|
+
var CDN_BASE_URL = "https://artifact.yoooclaw.com/plugin";
|
|
7701
|
+
var NPM_BASE_URL = "https://registry.npmjs.org/@yoooclaw/phone-notifications";
|
|
7702
|
+
var FETCH_TIMEOUT_MS = 5e3;
|
|
7703
|
+
var UpdateChecker = class {
|
|
7704
|
+
constructor(logger, onUpdateFound, intervalMs, channel) {
|
|
7705
|
+
this.logger = logger;
|
|
7706
|
+
this.onUpdateFound = onUpdateFound;
|
|
7707
|
+
this.intervalMs = intervalMs;
|
|
7708
|
+
this.channel = channel;
|
|
7709
|
+
}
|
|
7710
|
+
timer = null;
|
|
7711
|
+
notifiedVersion = null;
|
|
7712
|
+
start() {
|
|
7713
|
+
this.timer = setTimeout(() => {
|
|
7714
|
+
void this.check();
|
|
7715
|
+
this.timer = setInterval(() => void this.check(), this.intervalMs);
|
|
7716
|
+
}, 6e4);
|
|
7717
|
+
}
|
|
7718
|
+
stop() {
|
|
7719
|
+
if (this.timer) {
|
|
7720
|
+
clearInterval(this.timer);
|
|
7721
|
+
this.timer = null;
|
|
7722
|
+
}
|
|
7723
|
+
}
|
|
7724
|
+
async check() {
|
|
7725
|
+
const latest = await this.fetchLatestVersion();
|
|
7726
|
+
if (!latest || latest === PLUGIN_VERSION) return;
|
|
7727
|
+
if (latest === this.notifiedVersion) return;
|
|
7728
|
+
if (PLUGIN_VERSION.includes("-") && this.channel !== "beta") return;
|
|
7729
|
+
if (!isNewerVersion(latest, PLUGIN_VERSION)) return;
|
|
7730
|
+
this.notifiedVersion = latest;
|
|
7731
|
+
this.logger.info(`\u53D1\u73B0\u65B0\u7248\u672C: ${PLUGIN_VERSION} \u2192 ${latest}`);
|
|
7732
|
+
this.onUpdateFound({ current: PLUGIN_VERSION, latest });
|
|
7733
|
+
}
|
|
7734
|
+
async fetchLatestVersion() {
|
|
7735
|
+
const tag = this.channel === "beta" ? "beta" : "latest";
|
|
7736
|
+
try {
|
|
7737
|
+
const res = await fetch(`${CDN_BASE_URL}/${tag}`, {
|
|
7738
|
+
signal: AbortSignal.timeout(FETCH_TIMEOUT_MS)
|
|
7739
|
+
});
|
|
7740
|
+
if (res.ok) {
|
|
7741
|
+
const text = (await res.text()).trim();
|
|
7742
|
+
if (text) return text;
|
|
7917
7743
|
}
|
|
7744
|
+
} catch {
|
|
7918
7745
|
}
|
|
7919
|
-
|
|
7920
|
-
|
|
7921
|
-
|
|
7922
|
-
|
|
7923
|
-
|
|
7924
|
-
|
|
7925
|
-
|
|
7926
|
-
});
|
|
7927
|
-
monitor.command("create <name>").description("\u521B\u5EFA\u76D1\u63A7\u4EFB\u52A1").requiredOption("--description <text>", "\u4EFB\u52A1\u63CF\u8FF0").requiredOption("--match-rules <json>", "\u5339\u914D\u89C4\u5219 JSON").requiredOption("--schedule <cron>", "cron \u8868\u8FBE\u5F0F").action(
|
|
7928
|
-
(name, opts) => {
|
|
7929
|
-
const dir = tasksDir2(ctx);
|
|
7930
|
-
const taskDir = (0, import_node_path11.join)(dir, name);
|
|
7931
|
-
if ((0, import_node_fs12.existsSync)(taskDir)) {
|
|
7932
|
-
exitError("ALREADY_EXISTS", `\u76D1\u63A7\u4EFB\u52A1 '${name}' \u5DF2\u5B58\u5728`);
|
|
7746
|
+
try {
|
|
7747
|
+
const res = await fetch(`${NPM_BASE_URL}/${tag}`, {
|
|
7748
|
+
signal: AbortSignal.timeout(FETCH_TIMEOUT_MS)
|
|
7749
|
+
});
|
|
7750
|
+
if (res.ok) {
|
|
7751
|
+
const data = await res.json();
|
|
7752
|
+
return data.version ?? null;
|
|
7933
7753
|
}
|
|
7934
|
-
|
|
7754
|
+
} catch {
|
|
7755
|
+
this.logger.info("\u7248\u672C\u68C0\u67E5\u5931\u8D25\uFF08CDN + npm \u5747\u4E0D\u53EF\u8FBE\uFF09\uFF0C\u8DF3\u8FC7");
|
|
7756
|
+
}
|
|
7757
|
+
return null;
|
|
7758
|
+
}
|
|
7759
|
+
};
|
|
7760
|
+
|
|
7761
|
+
// src/update/executor.ts
|
|
7762
|
+
var import_node_fs10 = require("fs");
|
|
7763
|
+
var import_node_path9 = require("path");
|
|
7764
|
+
var import_node_os = require("os");
|
|
7765
|
+
var VERSION_PATTERN = /^\d+\.\d+\.\d+(-[\w.]+)?$/;
|
|
7766
|
+
var BASE_URL = "https://artifact.yoooclaw.com/plugin";
|
|
7767
|
+
async function executeUpdate(version, runCommand, logger, targetDir, updateConfigRecord2) {
|
|
7768
|
+
if (!VERSION_PATTERN.test(version)) {
|
|
7769
|
+
return { success: false, message: `\u975E\u6CD5\u7248\u672C\u53F7: ${version}` };
|
|
7770
|
+
}
|
|
7771
|
+
const tgzUrl = `${BASE_URL}/v${version}/yoooclaw-phone-notifications-${version}.tgz`;
|
|
7772
|
+
logger.info(`\u6267\u884C\u66F4\u65B0: ${tgzUrl} \u2192 ${targetDir}`);
|
|
7773
|
+
const workDir = (0, import_node_fs10.mkdtempSync)((0, import_node_path9.join)((0, import_node_os.tmpdir)(), ".openclaw-plugin-update-"));
|
|
7774
|
+
const tgzPath = (0, import_node_path9.join)(workDir, "plugin.tgz");
|
|
7775
|
+
const stagingDir = (0, import_node_path9.join)(workDir, "staged");
|
|
7776
|
+
let backupDir = null;
|
|
7777
|
+
try {
|
|
7778
|
+
logger.info("\u4E0B\u8F7D\u63D2\u4EF6\u5305...");
|
|
7779
|
+
const response = await fetch(tgzUrl, { signal: AbortSignal.timeout(6e4) });
|
|
7780
|
+
if (!response.ok) {
|
|
7781
|
+
return { success: false, message: `\u4E0B\u8F7D\u5931\u8D25 (HTTP ${response.status}): ${tgzUrl}` };
|
|
7782
|
+
}
|
|
7783
|
+
const buffer = Buffer.from(await response.arrayBuffer());
|
|
7784
|
+
(0, import_node_fs10.writeFileSync)(tgzPath, buffer);
|
|
7785
|
+
logger.info(`\u4E0B\u8F7D\u5B8C\u6210 (${buffer.length} bytes)`);
|
|
7786
|
+
(0, import_node_fs10.mkdirSync)(stagingDir, { recursive: true });
|
|
7787
|
+
const tarResult = await runCommand(
|
|
7788
|
+
["tar", "-xzf", tgzPath, "-C", stagingDir, "--strip-components=1"],
|
|
7789
|
+
{ timeoutMs: 3e4 }
|
|
7790
|
+
);
|
|
7791
|
+
if (tarResult.code !== 0) {
|
|
7792
|
+
const err2 = tarResult.stderr || tarResult.stdout || "unknown error";
|
|
7793
|
+
return { success: false, message: `\u89E3\u538B\u5931\u8D25: ${err2}` };
|
|
7794
|
+
}
|
|
7795
|
+
(0, import_node_fs10.mkdirSync)((0, import_node_path9.dirname)(targetDir), { recursive: true });
|
|
7796
|
+
try {
|
|
7797
|
+
backupDir = `${targetDir}.bak.${Date.now()}`;
|
|
7798
|
+
(0, import_node_fs10.renameSync)(targetDir, backupDir);
|
|
7799
|
+
} catch {
|
|
7800
|
+
backupDir = null;
|
|
7801
|
+
}
|
|
7802
|
+
(0, import_node_fs10.renameSync)(stagingDir, targetDir);
|
|
7803
|
+
try {
|
|
7804
|
+
await updateConfigRecord2(version, tgzUrl);
|
|
7805
|
+
} catch (err2) {
|
|
7806
|
+
logger.warn(`\u914D\u7F6E\u8BB0\u5F55\u66F4\u65B0\u5931\u8D25\uFF08\u63D2\u4EF6\u6587\u4EF6\u5DF2\u5C31\u4F4D\uFF09: ${String(err2)}`);
|
|
7807
|
+
}
|
|
7808
|
+
if (backupDir) {
|
|
7935
7809
|
try {
|
|
7936
|
-
|
|
7810
|
+
(0, import_node_fs10.rmSync)(backupDir, { force: true, recursive: true });
|
|
7937
7811
|
} catch {
|
|
7938
|
-
exitError(
|
|
7939
|
-
"VALIDATION_FAILED",
|
|
7940
|
-
"match-rules \u5FC5\u987B\u662F\u5408\u6CD5\u7684 JSON"
|
|
7941
|
-
);
|
|
7942
7812
|
}
|
|
7943
|
-
(0, import_node_fs12.mkdirSync)(taskDir, { recursive: true });
|
|
7944
|
-
const meta = {
|
|
7945
|
-
name,
|
|
7946
|
-
description: opts.description,
|
|
7947
|
-
matchRules,
|
|
7948
|
-
cronSchedule: opts.schedule,
|
|
7949
|
-
enabled: true,
|
|
7950
|
-
createdAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
7951
|
-
};
|
|
7952
|
-
writeMeta2(taskDir, meta);
|
|
7953
|
-
(0, import_node_fs12.writeFileSync)(
|
|
7954
|
-
(0, import_node_path11.join)(taskDir, "fetch.py"),
|
|
7955
|
-
generateFetchPy(name, matchRules),
|
|
7956
|
-
"utf-8"
|
|
7957
|
-
);
|
|
7958
|
-
(0, import_node_fs12.writeFileSync)(
|
|
7959
|
-
(0, import_node_path11.join)(taskDir, "README.md"),
|
|
7960
|
-
generateReadme(name, opts.description),
|
|
7961
|
-
"utf-8"
|
|
7962
|
-
);
|
|
7963
|
-
output({
|
|
7964
|
-
ok: true,
|
|
7965
|
-
name,
|
|
7966
|
-
created: {
|
|
7967
|
-
script: `tasks/${name}/fetch.py`,
|
|
7968
|
-
readme: `tasks/${name}/README.md`,
|
|
7969
|
-
cronJob: `notif-${name}`
|
|
7970
|
-
},
|
|
7971
|
-
cronHint: {
|
|
7972
|
-
action: "add",
|
|
7973
|
-
job: {
|
|
7974
|
-
name: `notif-${name}`,
|
|
7975
|
-
schedule: opts.schedule,
|
|
7976
|
-
sessionTarget: "isolated",
|
|
7977
|
-
message: `\u624B\u673A\u901A\u77E5\u5DF2\u7531\u72EC\u7ACB\u670D\u52A1\u5B9E\u65F6\u6355\u83B7\u5230 notifications/ \u76EE\u5F55\u7684 JSON \u6587\u4EF6\u4E2D\u3002
|
|
7978
|
-
\u6267\u884C\uFF1Apython3 tasks/${name}/fetch.py --notifications-dir notifications
|
|
7979
|
-
- NO_CHANGE \u6216 NO_MATCH \u2192 \u4E0D\u56DE\u590D\uFF0C\u76F4\u63A5\u7ED3\u675F\u3002
|
|
7980
|
-
- \u6709\u8F93\u51FA \u2192 \u8BFB tasks/${name}/README.md \u4E86\u89E3\u5982\u4F55\u5904\u7406\u6570\u636E\u5E76\u901A\u77E5\u7528\u6237\u3002`
|
|
7981
|
-
}
|
|
7982
|
-
}
|
|
7983
|
-
});
|
|
7984
7813
|
}
|
|
7985
|
-
|
|
7986
|
-
|
|
7987
|
-
|
|
7988
|
-
|
|
7989
|
-
|
|
7814
|
+
const msg = `\u5DF2\u66F4\u65B0\u5230 ${version}\uFF0C\u8BF7\u91CD\u542F gateway \u751F\u6548`;
|
|
7815
|
+
logger.info(msg);
|
|
7816
|
+
return { success: true, message: msg };
|
|
7817
|
+
} catch (err2) {
|
|
7818
|
+
if (backupDir) {
|
|
7819
|
+
try {
|
|
7820
|
+
(0, import_node_fs10.rmSync)(targetDir, { force: true, recursive: true });
|
|
7821
|
+
(0, import_node_fs10.renameSync)(backupDir, targetDir);
|
|
7822
|
+
logger.info("\u5DF2\u56DE\u6EDA\u5230\u4E4B\u524D\u7248\u672C");
|
|
7823
|
+
} catch (rollbackErr) {
|
|
7824
|
+
logger.error(`\u56DE\u6EDA\u5931\u8D25: ${String(rollbackErr)}`);
|
|
7825
|
+
}
|
|
7990
7826
|
}
|
|
7991
|
-
|
|
7992
|
-
|
|
7993
|
-
|
|
7994
|
-
|
|
7995
|
-
|
|
7996
|
-
|
|
7997
|
-
|
|
7998
|
-
});
|
|
7999
|
-
process.exit(1);
|
|
7827
|
+
const errMsg = `\u66F4\u65B0\u6267\u884C\u5F02\u5E38: ${String(err2)}`;
|
|
7828
|
+
logger.error(errMsg);
|
|
7829
|
+
return { success: false, message: errMsg };
|
|
7830
|
+
} finally {
|
|
7831
|
+
try {
|
|
7832
|
+
(0, import_node_fs10.rmSync)(workDir, { force: true, recursive: true });
|
|
7833
|
+
} catch {
|
|
8000
7834
|
}
|
|
8001
|
-
|
|
8002
|
-
output({
|
|
8003
|
-
ok: true,
|
|
8004
|
-
name,
|
|
8005
|
-
deleted: true,
|
|
8006
|
-
cronHint: {
|
|
8007
|
-
action: "remove",
|
|
8008
|
-
name: `notif-${name}`
|
|
8009
|
-
}
|
|
8010
|
-
});
|
|
8011
|
-
});
|
|
8012
|
-
monitor.command("enable <name>").description("\u542F\u7528\u76D1\u63A7\u4EFB\u52A1").action((name) => {
|
|
8013
|
-
const taskDir = (0, import_node_path11.join)(tasksDir2(ctx), name);
|
|
8014
|
-
const meta = readMeta2(taskDir);
|
|
8015
|
-
if (!meta) exitError("NOT_FOUND", `\u76D1\u63A7\u4EFB\u52A1 '${name}' \u4E0D\u5B58\u5728`);
|
|
8016
|
-
meta.enabled = true;
|
|
8017
|
-
writeMeta2(taskDir, meta);
|
|
8018
|
-
output({ ok: true, name, enabled: true });
|
|
8019
|
-
});
|
|
8020
|
-
monitor.command("disable <name>").description("\u6682\u505C\u76D1\u63A7\u4EFB\u52A1").action((name) => {
|
|
8021
|
-
const taskDir = (0, import_node_path11.join)(tasksDir2(ctx), name);
|
|
8022
|
-
const meta = readMeta2(taskDir);
|
|
8023
|
-
if (!meta) exitError("NOT_FOUND", `\u76D1\u63A7\u4EFB\u52A1 '${name}' \u4E0D\u5B58\u5728`);
|
|
8024
|
-
meta.enabled = false;
|
|
8025
|
-
writeMeta2(taskDir, meta);
|
|
8026
|
-
output({ ok: true, name, enabled: false });
|
|
8027
|
-
});
|
|
7835
|
+
}
|
|
8028
7836
|
}
|
|
8029
7837
|
|
|
8030
|
-
// src/
|
|
8031
|
-
|
|
8032
|
-
|
|
8033
|
-
|
|
8034
|
-
|
|
8035
|
-
|
|
8036
|
-
|
|
8037
|
-
|
|
8038
|
-
var PROTOCOL_DIGITS = [
|
|
8039
|
-
"\x80",
|
|
8040
|
-
"\x81",
|
|
8041
|
-
"\x82",
|
|
8042
|
-
"\x83",
|
|
8043
|
-
"\x84",
|
|
8044
|
-
"\x91",
|
|
8045
|
-
"\x92",
|
|
8046
|
-
"\x93",
|
|
8047
|
-
"\x94",
|
|
8048
|
-
"\x95",
|
|
8049
|
-
"\x96",
|
|
8050
|
-
"\x97"
|
|
8051
|
-
];
|
|
8052
|
-
var LED_SEPARATOR_ONCE = "\x9A";
|
|
8053
|
-
var LED_SEPARATOR_LOOP = "\x9B";
|
|
8054
|
-
var DURATION_STEPS_S = [0.5, 1, 2, 3, 5, 6, 8, 16, 24, 32, 48];
|
|
8055
|
-
var INTERVAL_STEPS_MS = [50, 100, 200, 300, 500, 600, 800, 1600, 2400, 3200, 4800];
|
|
8056
|
-
var BREATH_STEPS_MS = [1040, 1560, 2080, 2600, 3100, 4160];
|
|
8057
|
-
var BRIGHTNESS_STEPS = [32, 64, 96, 128, 192, 255];
|
|
8058
|
-
var COLOR_STEPS = [0, 32, 64, 128, 192, 255];
|
|
8059
|
-
var BACKGROUND_BRIGHTNESS_STEPS = [0, 32, 64, 96, 128, 192, 255];
|
|
8060
|
-
var MULTI_CHANNEL_COLOR_COEFFICIENTS = { r: 1, g: 0.25, b: 0.25 };
|
|
8061
|
-
var PURE_WHITE_COLOR_COEFFICIENTS = { r: 1, g: 0.35, b: 0.35 };
|
|
8062
|
-
var MODE_TO_INDEX = {
|
|
8063
|
-
wave: 0,
|
|
8064
|
-
breath: 1,
|
|
8065
|
-
strobe: 2,
|
|
8066
|
-
steady: 3,
|
|
8067
|
-
wave_rainbow: 4,
|
|
8068
|
-
pixel_frame: 5
|
|
8069
|
-
};
|
|
8070
|
-
function buildLightEffectApnsBody(segments, repeatInput) {
|
|
8071
|
-
assertSegmentCount(segments);
|
|
8072
|
-
assertSegmentsValid(segments);
|
|
8073
|
-
const repeatTimes = normalizeRepeatTimes(repeatInput);
|
|
8074
|
-
assertAncsRepeatTimes(repeatTimes);
|
|
8075
|
-
const visibleText = summarizeSegments(segments);
|
|
8076
|
-
const separator = repeatTimes === 0 ? LED_SEPARATOR_LOOP : LED_SEPARATOR_ONCE;
|
|
8077
|
-
const payload = segments.map((segment) => encodeSegment(segment)).join("");
|
|
8078
|
-
return `${visibleText}${separator}${payload}`;
|
|
8079
|
-
}
|
|
8080
|
-
function assertSegmentCount(segments) {
|
|
8081
|
-
if (segments.length < 1 || segments.length > MAX_LIGHT_SEGMENTS) {
|
|
8082
|
-
throw new Error(`light_control supports 1-${MAX_LIGHT_SEGMENTS} segments`);
|
|
7838
|
+
// src/update/index.ts
|
|
7839
|
+
var PLUGIN_ID = "phone-notifications";
|
|
7840
|
+
function resolveTargetDir(api) {
|
|
7841
|
+
try {
|
|
7842
|
+
const cfg = api.runtime.config?.loadConfig?.();
|
|
7843
|
+
const installPath = cfg?.plugins?.installs?.[PLUGIN_ID]?.installPath;
|
|
7844
|
+
if (installPath) return installPath;
|
|
7845
|
+
} catch {
|
|
8083
7846
|
}
|
|
7847
|
+
return (0, import_node_path10.join)(api.runtime.state.resolveStateDir(), "extensions", PLUGIN_ID);
|
|
8084
7848
|
}
|
|
8085
|
-
function
|
|
8086
|
-
const
|
|
8087
|
-
|
|
7849
|
+
async function updateConfigRecord(api, version, targetDir, tgzUrl) {
|
|
7850
|
+
const configApi = api.runtime.config;
|
|
7851
|
+
if (!configApi) return;
|
|
7852
|
+
const cfg = configApi.loadConfig();
|
|
7853
|
+
if (!cfg.plugins) cfg.plugins = {};
|
|
7854
|
+
if (!cfg.plugins.installs) cfg.plugins.installs = {};
|
|
7855
|
+
cfg.plugins.installs[PLUGIN_ID] = {
|
|
7856
|
+
...cfg.plugins.installs[PLUGIN_ID],
|
|
7857
|
+
source: "archive",
|
|
7858
|
+
sourcePath: tgzUrl,
|
|
7859
|
+
installPath: targetDir,
|
|
7860
|
+
version,
|
|
7861
|
+
installedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
7862
|
+
};
|
|
7863
|
+
await configApi.writeConfigFile(cfg);
|
|
8088
7864
|
}
|
|
8089
|
-
function
|
|
8090
|
-
|
|
8091
|
-
|
|
8092
|
-
|
|
8093
|
-
|
|
8094
|
-
)
|
|
7865
|
+
function registerAutoUpdate(api, logger, config, getBroadcast, rememberBroadcast, externalUpdateNotifier) {
|
|
7866
|
+
if (config.enabled === false) {
|
|
7867
|
+
logger.info("\u81EA\u52A8\u66F4\u65B0\u5DF2\u7981\u7528 (autoUpdate.enabled = false)");
|
|
7868
|
+
return { start() {
|
|
7869
|
+
}, stop() {
|
|
7870
|
+
}, notifyBroadcastReady() {
|
|
7871
|
+
} };
|
|
8095
7872
|
}
|
|
8096
|
-
|
|
8097
|
-
|
|
8098
|
-
|
|
8099
|
-
|
|
8100
|
-
|
|
8101
|
-
|
|
8102
|
-
|
|
8103
|
-
|
|
8104
|
-
|
|
8105
|
-
|
|
8106
|
-
|
|
8107
|
-
|
|
8108
|
-
values = [
|
|
8109
|
-
...common,
|
|
8110
|
-
quantize(segment.interval_ms ?? 200, INTERVAL_STEPS_MS),
|
|
8111
|
-
quantizeBrightnessValue(segment.brightness ?? 0),
|
|
8112
|
-
quantize(color.r, COLOR_STEPS),
|
|
8113
|
-
quantize(color.g, COLOR_STEPS),
|
|
8114
|
-
quantize(color.b, COLOR_STEPS),
|
|
8115
|
-
segment.direction === "rtl" ? 1 : 0,
|
|
8116
|
-
quantizeWindow(segment.window ?? 2),
|
|
8117
|
-
quantize(background.r, COLOR_STEPS),
|
|
8118
|
-
quantize(background.g, COLOR_STEPS),
|
|
8119
|
-
quantize(background.b, COLOR_STEPS),
|
|
8120
|
-
quantize(segment.background?.brightness ?? 0, BACKGROUND_BRIGHTNESS_STEPS)
|
|
8121
|
-
];
|
|
8122
|
-
break;
|
|
8123
|
-
case "breath":
|
|
8124
|
-
const breathColor = normalizeProtocolColor(segment.color);
|
|
8125
|
-
values = [
|
|
8126
|
-
...common,
|
|
8127
|
-
quantizeBreathRiseFall(segment.breath_timing?.rise_ms),
|
|
8128
|
-
quantizeBreathHoldOff(segment.breath_timing?.hold_ms),
|
|
8129
|
-
quantizeBreathRiseFall(segment.breath_timing?.fall_ms),
|
|
8130
|
-
quantizeBreathHoldOff(segment.breath_timing?.off_ms),
|
|
8131
|
-
quantizeBrightnessValue(segment.brightness ?? 0),
|
|
8132
|
-
quantize(breathColor.r, COLOR_STEPS),
|
|
8133
|
-
quantize(breathColor.g, COLOR_STEPS),
|
|
8134
|
-
quantize(breathColor.b, COLOR_STEPS)
|
|
8135
|
-
];
|
|
8136
|
-
break;
|
|
8137
|
-
case "strobe":
|
|
8138
|
-
const strobeColor = normalizeProtocolColor(segment.color);
|
|
8139
|
-
values = [
|
|
8140
|
-
...common,
|
|
8141
|
-
quantize(segment.interval_ms ?? 200, INTERVAL_STEPS_MS),
|
|
8142
|
-
quantizeBrightnessValue(segment.brightness ?? 0),
|
|
8143
|
-
quantize(strobeColor.r, COLOR_STEPS),
|
|
8144
|
-
quantize(strobeColor.g, COLOR_STEPS),
|
|
8145
|
-
quantize(strobeColor.b, COLOR_STEPS)
|
|
8146
|
-
];
|
|
8147
|
-
break;
|
|
8148
|
-
case "steady":
|
|
8149
|
-
const steadyColor = normalizeProtocolColor(segment.color);
|
|
8150
|
-
values = [
|
|
8151
|
-
...common,
|
|
8152
|
-
quantizeBrightnessValue(segment.brightness ?? 0),
|
|
8153
|
-
quantize(steadyColor.r, COLOR_STEPS),
|
|
8154
|
-
quantize(steadyColor.g, COLOR_STEPS),
|
|
8155
|
-
quantize(steadyColor.b, COLOR_STEPS)
|
|
8156
|
-
];
|
|
8157
|
-
break;
|
|
8158
|
-
case "pixel_frame":
|
|
8159
|
-
values = encodePixelFrameValues(common, segment);
|
|
8160
|
-
break;
|
|
7873
|
+
let pendingUpdate = null;
|
|
7874
|
+
let pendingGatewayUpdate = null;
|
|
7875
|
+
function tryBroadcastUpdate(update) {
|
|
7876
|
+
const broadcast = getBroadcast();
|
|
7877
|
+
if (!broadcast) return false;
|
|
7878
|
+
broadcast("plugin.updateAvailable", {
|
|
7879
|
+
pluginId: "phone-notifications",
|
|
7880
|
+
current: update.current,
|
|
7881
|
+
latest: update.latest
|
|
7882
|
+
});
|
|
7883
|
+
pendingGatewayUpdate = null;
|
|
7884
|
+
return true;
|
|
8161
7885
|
}
|
|
8162
|
-
|
|
8163
|
-
|
|
8164
|
-
|
|
8165
|
-
|
|
8166
|
-
|
|
8167
|
-
|
|
8168
|
-
|
|
8169
|
-
|
|
8170
|
-
|
|
8171
|
-
|
|
8172
|
-
|
|
8173
|
-
|
|
8174
|
-
|
|
8175
|
-
|
|
8176
|
-
|
|
8177
|
-
|
|
8178
|
-
|
|
8179
|
-
|
|
7886
|
+
api.on("before_prompt_build", () => {
|
|
7887
|
+
if (!pendingUpdate) return;
|
|
7888
|
+
return {
|
|
7889
|
+
appendSystemContext: `[\u7CFB\u7EDF\u901A\u77E5] phone-notifications \u63D2\u4EF6\u6709\u65B0\u7248\u672C ${pendingUpdate.latest} \u53EF\u7528\uFF08\u5F53\u524D\u7248\u672C ${pendingUpdate.current}\uFF09\u3002
|
|
7890
|
+
\u8BF7\u7528\u81EA\u7136\u8BED\u8A00\u5728\u5408\u9002\u65F6\u673A\u544A\u77E5\u7528\u6237\uFF0C\u82E5\u7528\u6237\u540C\u610F\u66F4\u65B0\uFF0C\u8C03\u7528 plugin-update tool \u5E76\u4F20\u5165 version \u53C2\u6570\u3002\u82E5\u7528\u6237\u62D2\u7EDD\uFF0C\u4E0D\u518D\u91CD\u590D\u63D0\u9192\u3002`
|
|
7891
|
+
};
|
|
7892
|
+
});
|
|
7893
|
+
api.registerTool({
|
|
7894
|
+
name: "plugin-update",
|
|
7895
|
+
label: "Plugin Update",
|
|
7896
|
+
description: "\u5C06 phone-notifications \u63D2\u4EF6\u66F4\u65B0\u5230\u6307\u5B9A\u7248\u672C\u3002\u4EC5\u5728\u7528\u6237\u660E\u786E\u540C\u610F\u66F4\u65B0\u540E\u8C03\u7528\u3002\u66F4\u65B0\u5B8C\u6210\u540E\u9700\u91CD\u542F gateway \u751F\u6548\u3002",
|
|
7897
|
+
parameters: {
|
|
7898
|
+
type: "object",
|
|
7899
|
+
required: ["version"],
|
|
7900
|
+
additionalProperties: false,
|
|
7901
|
+
properties: {
|
|
7902
|
+
version: {
|
|
7903
|
+
type: "string",
|
|
7904
|
+
description: "\u76EE\u6807\u7248\u672C\u53F7\uFF0C\u5982 1.11.0"
|
|
7905
|
+
}
|
|
7906
|
+
}
|
|
7907
|
+
},
|
|
7908
|
+
async execute(_toolCallId, params) {
|
|
7909
|
+
const { version } = params;
|
|
7910
|
+
const targetDir = resolveTargetDir(api);
|
|
7911
|
+
const result = await executeUpdate(
|
|
7912
|
+
version,
|
|
7913
|
+
api.runtime.system.runCommandWithTimeout,
|
|
7914
|
+
logger,
|
|
7915
|
+
targetDir,
|
|
7916
|
+
(v, url) => updateConfigRecord(api, v, targetDir, url)
|
|
7917
|
+
);
|
|
7918
|
+
if (result.success) {
|
|
7919
|
+
pendingUpdate = null;
|
|
7920
|
+
externalUpdateNotifier?.clearPendingUpdate();
|
|
7921
|
+
}
|
|
7922
|
+
return {
|
|
7923
|
+
content: [{ type: "text", text: result.message }],
|
|
7924
|
+
details: result
|
|
7925
|
+
};
|
|
7926
|
+
}
|
|
7927
|
+
});
|
|
7928
|
+
api.registerGatewayMethod("plugin.update", async ({ params, respond, context }) => {
|
|
7929
|
+
rememberBroadcast?.(context?.broadcast);
|
|
7930
|
+
const version = params.version;
|
|
7931
|
+
if (!version) {
|
|
7932
|
+
respond(false, void 0, {
|
|
7933
|
+
code: "MISSING_VERSION",
|
|
7934
|
+
message: "version is required"
|
|
7935
|
+
});
|
|
7936
|
+
return;
|
|
7937
|
+
}
|
|
7938
|
+
const targetDir = resolveTargetDir(api);
|
|
7939
|
+
const result = await executeUpdate(
|
|
7940
|
+
version,
|
|
7941
|
+
api.runtime.system.runCommandWithTimeout,
|
|
7942
|
+
logger,
|
|
7943
|
+
targetDir,
|
|
7944
|
+
(v, url) => updateConfigRecord(api, v, targetDir, url)
|
|
7945
|
+
);
|
|
7946
|
+
if (result.success) {
|
|
7947
|
+
pendingUpdate = null;
|
|
7948
|
+
externalUpdateNotifier?.clearPendingUpdate();
|
|
7949
|
+
}
|
|
7950
|
+
if (result.success) {
|
|
7951
|
+
respond(true, { message: result.message });
|
|
7952
|
+
} else {
|
|
7953
|
+
respond(false, void 0, {
|
|
7954
|
+
code: "UPDATE_FAILED",
|
|
7955
|
+
message: result.message
|
|
7956
|
+
});
|
|
7957
|
+
}
|
|
7958
|
+
});
|
|
7959
|
+
const intervalMs = (config.checkIntervalHours ?? 4) * 36e5;
|
|
7960
|
+
const channel = config.channel ?? "latest";
|
|
7961
|
+
logger.info(
|
|
7962
|
+
`\u81EA\u52A8\u66F4\u65B0\u5DF2\u542F\u7528: channel=${channel}, checkIntervalHours=${config.checkIntervalHours ?? 4}`
|
|
7963
|
+
);
|
|
7964
|
+
const checker = new UpdateChecker(
|
|
7965
|
+
logger,
|
|
7966
|
+
(update) => {
|
|
7967
|
+
pendingUpdate = update;
|
|
7968
|
+
const broadcasted = tryBroadcastUpdate(update);
|
|
7969
|
+
if (!broadcasted) pendingGatewayUpdate = update;
|
|
7970
|
+
if (!broadcasted) {
|
|
7971
|
+
externalUpdateNotifier?.notifyUpdateAvailable(update);
|
|
7972
|
+
}
|
|
7973
|
+
logger.info(
|
|
7974
|
+
`\u5DF2\u901A\u77E5\u66F4\u65B0 ${update.current} \u2192 ${update.latest}` + (broadcasted ? "\uFF08\u5BF9\u8BDD + \u7F51\u5173\uFF09" : "\uFF08\u5BF9\u8BDD\u901A\u9053\u5DF2\u751F\u6548\uFF0C\u7B49\u5F85\u4E0B\u6B21 gateway \u8BF7\u6C42\u65F6\u8865\u53D1\u5BA2\u6237\u7AEF\u4E8B\u4EF6\uFF09")
|
|
7975
|
+
);
|
|
7976
|
+
},
|
|
7977
|
+
intervalMs,
|
|
7978
|
+
channel
|
|
7979
|
+
);
|
|
7980
|
+
return {
|
|
7981
|
+
start: () => checker.start(),
|
|
7982
|
+
stop: () => checker.stop(),
|
|
7983
|
+
notifyBroadcastReady() {
|
|
7984
|
+
const update = pendingGatewayUpdate;
|
|
7985
|
+
if (!update) return;
|
|
7986
|
+
if (!tryBroadcastUpdate(update)) return;
|
|
7987
|
+
logger.info(`\u5DF2\u8865\u53D1\u63D2\u4EF6\u66F4\u65B0\u4E8B\u4EF6 ${update.current} \u2192 ${update.latest}`);
|
|
7988
|
+
}
|
|
7989
|
+
};
|
|
8180
7990
|
}
|
|
8181
|
-
|
|
8182
|
-
|
|
8183
|
-
|
|
8184
|
-
|
|
8185
|
-
|
|
7991
|
+
|
|
7992
|
+
// src/plugin/auto-update.ts
|
|
7993
|
+
function registerAutoUpdateLifecycle(deps) {
|
|
7994
|
+
const {
|
|
7995
|
+
api,
|
|
7996
|
+
config,
|
|
7997
|
+
logger,
|
|
7998
|
+
getBroadcastFn,
|
|
7999
|
+
cacheBroadcast,
|
|
8000
|
+
tunnelService
|
|
8001
|
+
} = deps;
|
|
8002
|
+
const autoUpdateConfig = {
|
|
8003
|
+
...config.autoUpdate,
|
|
8004
|
+
channel: resolveUpdateChannel({
|
|
8005
|
+
configuredChannel: config.autoUpdate?.channel,
|
|
8006
|
+
currentVersion: PLUGIN_VERSION,
|
|
8007
|
+
envName: loadEnvName()
|
|
8008
|
+
})
|
|
8186
8009
|
};
|
|
8187
|
-
|
|
8188
|
-
|
|
8189
|
-
|
|
8190
|
-
|
|
8191
|
-
|
|
8010
|
+
const autoUpdateLifecycle = registerAutoUpdate(
|
|
8011
|
+
api,
|
|
8012
|
+
logger,
|
|
8013
|
+
autoUpdateConfig,
|
|
8014
|
+
getBroadcastFn,
|
|
8015
|
+
cacheBroadcast,
|
|
8016
|
+
tunnelService ? {
|
|
8017
|
+
notifyUpdateAvailable: (update) => tunnelService.notifyUpdateAvailable(update),
|
|
8018
|
+
clearPendingUpdate: () => tunnelService.clearPendingUpdate()
|
|
8019
|
+
} : void 0
|
|
8020
|
+
);
|
|
8021
|
+
api.registerService({
|
|
8022
|
+
id: "update-checker",
|
|
8023
|
+
start() {
|
|
8024
|
+
autoUpdateLifecycle.start();
|
|
8025
|
+
},
|
|
8026
|
+
stop() {
|
|
8027
|
+
autoUpdateLifecycle.stop();
|
|
8028
|
+
}
|
|
8029
|
+
});
|
|
8030
|
+
logger.info("\u81EA\u52A8\u66F4\u65B0\u68C0\u67E5\u670D\u52A1\u5DF2\u6CE8\u518C");
|
|
8031
|
+
return autoUpdateLifecycle;
|
|
8032
|
+
}
|
|
8033
|
+
|
|
8034
|
+
// src/plugin/cli.ts
|
|
8035
|
+
var import_node_path18 = require("path");
|
|
8036
|
+
|
|
8037
|
+
// src/cli/auth.ts
|
|
8038
|
+
var import_node_fs11 = require("fs");
|
|
8039
|
+
init_credentials();
|
|
8040
|
+
function registerAuthCli(program) {
|
|
8041
|
+
const auth = program.command("auth").description("\u7528\u6237\u8BA4\u8BC1\u7BA1\u7406");
|
|
8042
|
+
auth.command("set-api-key <apiKey>").description("\u8BBE\u7F6E\u7528\u6237 API Key\uFF08\u6301\u4E45\u5316\u5230\u672C\u5730\u914D\u7F6E\uFF09").action((apiKey) => {
|
|
8043
|
+
writeCredentials({ ...readCredentials(), apiKey, token: void 0 });
|
|
8044
|
+
output({
|
|
8045
|
+
ok: true,
|
|
8046
|
+
apiKey: apiKey.slice(0, 8) + "\u2026",
|
|
8047
|
+
storedAt: credentialsPath()
|
|
8048
|
+
});
|
|
8049
|
+
});
|
|
8050
|
+
auth.command("set-token <token>").description("\uFF08\u517C\u5BB9\uFF09\u8BBE\u7F6E\u7528\u6237 API Key\uFF08\u65E7\u547D\u4EE4\u540D\uFF09").action((token) => {
|
|
8051
|
+
writeCredentials({ ...readCredentials(), apiKey: token, token: void 0 });
|
|
8052
|
+
output({
|
|
8053
|
+
ok: true,
|
|
8054
|
+
apiKey: token.slice(0, 8) + "\u2026",
|
|
8055
|
+
storedAt: credentialsPath()
|
|
8056
|
+
});
|
|
8057
|
+
});
|
|
8058
|
+
auth.command("show").description("\u67E5\u770B\u5F53\u524D\u8BA4\u8BC1\u72B6\u6001").action(() => {
|
|
8059
|
+
const creds = readCredentials();
|
|
8060
|
+
const apiKey = creds.apiKey ?? creds.token;
|
|
8061
|
+
if (apiKey) {
|
|
8062
|
+
output({
|
|
8063
|
+
ok: true,
|
|
8064
|
+
hasApiKey: true,
|
|
8065
|
+
apiKey: apiKey.slice(0, 8) + "\u2026",
|
|
8066
|
+
storedAt: credentialsPath()
|
|
8067
|
+
});
|
|
8068
|
+
} else {
|
|
8069
|
+
output({ ok: true, hasApiKey: false });
|
|
8070
|
+
}
|
|
8071
|
+
});
|
|
8072
|
+
auth.command("clear").description("\u6E05\u9664\u5DF2\u4FDD\u5B58\u7684\u8BA4\u8BC1\u4FE1\u606F").action(() => {
|
|
8073
|
+
const path2 = credentialsPath();
|
|
8074
|
+
if ((0, import_node_fs11.existsSync)(path2)) {
|
|
8075
|
+
const creds = readCredentials();
|
|
8076
|
+
delete creds.apiKey;
|
|
8077
|
+
delete creds.token;
|
|
8078
|
+
if (Object.keys(creds).length === 0) {
|
|
8079
|
+
(0, import_node_fs11.rmSync)(path2, { force: true });
|
|
8080
|
+
} else {
|
|
8081
|
+
writeCredentials(creds);
|
|
8082
|
+
}
|
|
8083
|
+
}
|
|
8084
|
+
output({ ok: true, cleared: true });
|
|
8085
|
+
});
|
|
8086
|
+
}
|
|
8087
|
+
|
|
8088
|
+
// src/cli/ntf-search.ts
|
|
8089
|
+
function filterItem(item, opts) {
|
|
8090
|
+
if (opts.app && item.appName !== opts.app) return false;
|
|
8091
|
+
if (opts.sender && !item.title.includes(opts.sender)) return false;
|
|
8092
|
+
if (opts.keyword && !item.title.includes(opts.keyword) && !item.content.includes(opts.keyword)) {
|
|
8093
|
+
return false;
|
|
8192
8094
|
}
|
|
8193
|
-
return
|
|
8095
|
+
return true;
|
|
8194
8096
|
}
|
|
8195
|
-
function
|
|
8196
|
-
|
|
8097
|
+
function registerNtfSearch(ntf, ctx) {
|
|
8098
|
+
ntf.command("search").description("\u67E5\u8BE2\u901A\u77E5\uFF08\u6309\u65F6\u95F4/\u5E94\u7528/\u53D1\u9001\u4EBA/\u5173\u952E\u8BCD\u7B5B\u9009\uFF09").option("--from <time>", "\u5F00\u59CB\u65F6\u95F4 ISO 8601\uFF0C\u4F8B\u5982 2026-03-01T09:00:00+08:00").option("--to <time>", "\u7ED3\u675F\u65F6\u95F4 ISO 8601\uFF0C\u4F8B\u5982 2026-03-01T18:00:00+08:00").option("--app <name>", "\u6309\u5E94\u7528\u540D\u8FC7\u6EE4").option("--sender <name>", "\u6309\u53D1\u9001\u4EBA\u8FC7\u6EE4").option("--keyword <text>", "\u5728\u6807\u9898\u548C\u5185\u5BB9\u4E2D\u641C\u7D22\u5173\u952E\u8BCD").option("--limit <n>", "\u6700\u5927\u8FD4\u56DE\u6761\u6570", "100").action(
|
|
8099
|
+
async (opts) => {
|
|
8100
|
+
const dir = resolveNotificationsDir(ctx);
|
|
8101
|
+
if (!dir) exitError("STORAGE_UNAVAILABLE", "\u901A\u77E5\u5B58\u50A8\u76EE\u5F55\u4E0D\u53EF\u7528");
|
|
8102
|
+
progress("\u6B63\u5728\u641C\u7D22\u901A\u77E5...");
|
|
8103
|
+
const limit = parseInt(opts.limit, 10) || 100;
|
|
8104
|
+
if (limit <= 0) {
|
|
8105
|
+
exitError("INVALID_LIMIT", "--limit \u5FC5\u987B\u662F\u5927\u4E8E 0 \u7684\u6574\u6570");
|
|
8106
|
+
}
|
|
8107
|
+
const hasFrom = typeof opts.from === "string" && opts.from.length > 0;
|
|
8108
|
+
const hasTo = typeof opts.to === "string" && opts.to.length > 0;
|
|
8109
|
+
const fromTs = hasFrom ? parseIsoTime(opts.from, "--from") : null;
|
|
8110
|
+
const toTs = hasTo ? parseIsoTime(opts.to, "--to") : null;
|
|
8111
|
+
if (fromTs !== null && toTs !== null && fromTs > toTs) {
|
|
8112
|
+
exitError("INVALID_TIME_RANGE", "--from \u4E0D\u80FD\u665A\u4E8E --to");
|
|
8113
|
+
}
|
|
8114
|
+
const keys = await listDateKeysAsync(dir);
|
|
8115
|
+
const results = [];
|
|
8116
|
+
if (fromTs !== null || toTs !== null) {
|
|
8117
|
+
const fromDateKey = hasFrom ? opts.from.slice(0, 10) : null;
|
|
8118
|
+
const toDateKey = hasTo ? opts.to.slice(0, 10) : null;
|
|
8119
|
+
for (const dateKey of keys) {
|
|
8120
|
+
if (fromDateKey && dateKey < fromDateKey) continue;
|
|
8121
|
+
if (toDateKey && dateKey > toDateKey) continue;
|
|
8122
|
+
const items = await readDateFileAsync(dir, dateKey);
|
|
8123
|
+
for (const item of items) {
|
|
8124
|
+
if (!filterItem(item, opts)) continue;
|
|
8125
|
+
const itemTs = Date.parse(item.timestamp);
|
|
8126
|
+
if (Number.isNaN(itemTs)) continue;
|
|
8127
|
+
if (fromTs !== null && itemTs < fromTs) continue;
|
|
8128
|
+
if (toTs !== null && itemTs > toTs) continue;
|
|
8129
|
+
results.push(item);
|
|
8130
|
+
}
|
|
8131
|
+
}
|
|
8132
|
+
} else {
|
|
8133
|
+
for (const dateKey of keys) {
|
|
8134
|
+
const items = await readDateFileAsync(dir, dateKey);
|
|
8135
|
+
for (const item of items) {
|
|
8136
|
+
if (!filterItem(item, opts)) continue;
|
|
8137
|
+
if (Number.isNaN(Date.parse(item.timestamp))) continue;
|
|
8138
|
+
results.push(item);
|
|
8139
|
+
}
|
|
8140
|
+
}
|
|
8141
|
+
}
|
|
8142
|
+
const notifications = sortNotificationsByTimestampDesc(results).slice(
|
|
8143
|
+
0,
|
|
8144
|
+
limit
|
|
8145
|
+
);
|
|
8146
|
+
output({
|
|
8147
|
+
ok: true,
|
|
8148
|
+
total: notifications.length,
|
|
8149
|
+
notifications
|
|
8150
|
+
});
|
|
8151
|
+
}
|
|
8152
|
+
);
|
|
8197
8153
|
}
|
|
8198
|
-
|
|
8199
|
-
|
|
8154
|
+
|
|
8155
|
+
// src/cli/ntf-stats.ts
|
|
8156
|
+
function registerNtfStats(ntf, ctx) {
|
|
8157
|
+
ntf.command("stats").description("\u901A\u77E5\u7EDF\u8BA1\u5206\u6790\uFF08\u6309\u65E5\u671F/\u5E94\u7528/\u53D1\u9001\u4EBA/\u65F6\u6BB5\u805A\u5408\uFF09").option("--from <date>", "\u5F00\u59CB\u65E5\u671F YYYY-MM-DD", daysAgo(7)).option("--to <date>", "\u7ED3\u675F\u65E5\u671F YYYY-MM-DD", today()).option("--app <name>", "\u53EA\u7EDF\u8BA1\u6307\u5B9A\u5E94\u7528").option("--dim <dimension>", "\u7EDF\u8BA1\u7EF4\u5EA6\uFF1Adate/app/sender/hour/all", "all").action(
|
|
8158
|
+
(opts) => {
|
|
8159
|
+
const dir = resolveNotificationsDir(ctx);
|
|
8160
|
+
if (!dir) exitError("STORAGE_UNAVAILABLE", "\u901A\u77E5\u5B58\u50A8\u76EE\u5F55\u4E0D\u53EF\u7528");
|
|
8161
|
+
const dim = opts.dim;
|
|
8162
|
+
const keys = filterDateRange(listDateKeys(dir), opts.from, opts.to);
|
|
8163
|
+
const byDate = {};
|
|
8164
|
+
const byApp = {};
|
|
8165
|
+
const bySender = {};
|
|
8166
|
+
const byHour = {};
|
|
8167
|
+
let total = 0;
|
|
8168
|
+
for (const dateKey of keys) {
|
|
8169
|
+
const items = readDateFile(dir, dateKey);
|
|
8170
|
+
let dateCount = 0;
|
|
8171
|
+
for (const item of items) {
|
|
8172
|
+
if (opts.app && item.appName !== opts.app) continue;
|
|
8173
|
+
total++;
|
|
8174
|
+
dateCount++;
|
|
8175
|
+
byApp[item.appName] = (byApp[item.appName] || 0) + 1;
|
|
8176
|
+
if (item.title) {
|
|
8177
|
+
bySender[item.title] = (bySender[item.title] || 0) + 1;
|
|
8178
|
+
}
|
|
8179
|
+
const hourMatch = /T(\d{2}):/.exec(item.timestamp);
|
|
8180
|
+
if (hourMatch) {
|
|
8181
|
+
const h = hourMatch[1];
|
|
8182
|
+
byHour[h] = (byHour[h] || 0) + 1;
|
|
8183
|
+
}
|
|
8184
|
+
}
|
|
8185
|
+
byDate[dateKey] = dateCount;
|
|
8186
|
+
}
|
|
8187
|
+
const result = {
|
|
8188
|
+
ok: true,
|
|
8189
|
+
range: { from: opts.from, to: opts.to },
|
|
8190
|
+
total
|
|
8191
|
+
};
|
|
8192
|
+
if (dim === "date" || dim === "all") {
|
|
8193
|
+
result.byDate = byDate;
|
|
8194
|
+
}
|
|
8195
|
+
if (dim === "app" || dim === "all") {
|
|
8196
|
+
result.byApp = byApp;
|
|
8197
|
+
}
|
|
8198
|
+
if (dim === "sender" || dim === "all") {
|
|
8199
|
+
const sorted = Object.entries(bySender).sort((a, b) => b[1] - a[1]).slice(0, 20).map(([sender, count]) => ({ sender, count }));
|
|
8200
|
+
result.bySender = sorted;
|
|
8201
|
+
}
|
|
8202
|
+
if (dim === "hour" || dim === "all") {
|
|
8203
|
+
result.byHour = byHour;
|
|
8204
|
+
}
|
|
8205
|
+
output(result);
|
|
8206
|
+
}
|
|
8207
|
+
);
|
|
8200
8208
|
}
|
|
8201
|
-
|
|
8202
|
-
|
|
8203
|
-
|
|
8204
|
-
|
|
8205
|
-
|
|
8206
|
-
|
|
8209
|
+
|
|
8210
|
+
// src/cli/ntf-sync.ts
|
|
8211
|
+
var import_node_fs12 = require("fs");
|
|
8212
|
+
var import_node_path11 = require("path");
|
|
8213
|
+
var SYNC_FETCH_LIMIT = 300;
|
|
8214
|
+
function checkpointPath(dir) {
|
|
8215
|
+
return (0, import_node_path11.join)(dir, ".checkpoint.json");
|
|
8207
8216
|
}
|
|
8208
|
-
function
|
|
8209
|
-
|
|
8217
|
+
function readCheckpoint(dir) {
|
|
8218
|
+
const p = checkpointPath(dir);
|
|
8219
|
+
if (!(0, import_node_fs12.existsSync)(p)) return {};
|
|
8220
|
+
try {
|
|
8221
|
+
return JSON.parse((0, import_node_fs12.readFileSync)(p, "utf-8"));
|
|
8222
|
+
} catch {
|
|
8223
|
+
return {};
|
|
8224
|
+
}
|
|
8210
8225
|
}
|
|
8211
|
-
function
|
|
8212
|
-
|
|
8213
|
-
|
|
8214
|
-
|
|
8215
|
-
|
|
8216
|
-
|
|
8217
|
-
|
|
8218
|
-
|
|
8226
|
+
function writeCheckpoint(dir, data) {
|
|
8227
|
+
(0, import_node_fs12.writeFileSync)(checkpointPath(dir), JSON.stringify(data, null, 2), "utf-8");
|
|
8228
|
+
}
|
|
8229
|
+
function registerNtfSync(ntf, ctx) {
|
|
8230
|
+
const sync = ntf.command("sync").description("\u540C\u6B65\u901A\u77E5\u5230\u8BB0\u5FC6\u7CFB\u7EDF");
|
|
8231
|
+
sync.command("scan").description("\u626B\u63CF\u672A\u5904\u7406\u7684\u901A\u77E5\uFF0C\u8FD4\u56DE\u5F85\u540C\u6B65\u6458\u8981").action(() => {
|
|
8232
|
+
const dir = resolveNotificationsDir(ctx);
|
|
8233
|
+
if (!dir) exitError("STORAGE_UNAVAILABLE", "\u901A\u77E5\u5B58\u50A8\u76EE\u5F55\u4E0D\u53EF\u7528");
|
|
8234
|
+
const checkpoint = readCheckpoint(dir);
|
|
8235
|
+
const keys = listDateKeys(dir);
|
|
8236
|
+
const pending = [];
|
|
8237
|
+
let totalPending = 0;
|
|
8238
|
+
for (const dateKey of keys) {
|
|
8239
|
+
const items = readDateFile(dir, dateKey);
|
|
8240
|
+
const lastIndex = checkpoint[dateKey]?.lastIndex ?? -1;
|
|
8241
|
+
const unprocessed = items.length - (lastIndex + 1);
|
|
8242
|
+
if (unprocessed > 0) {
|
|
8243
|
+
pending.push({
|
|
8244
|
+
date: dateKey,
|
|
8245
|
+
count: unprocessed,
|
|
8246
|
+
startIndex: lastIndex + 1
|
|
8247
|
+
});
|
|
8248
|
+
totalPending += unprocessed;
|
|
8249
|
+
}
|
|
8250
|
+
}
|
|
8251
|
+
output({ ok: true, pending, totalPending });
|
|
8252
|
+
});
|
|
8253
|
+
sync.command("fetch").description("\u83B7\u53D6\u6307\u5B9A\u65E5\u671F\u7684\u672A\u5904\u7406\u901A\u77E5\u8BE6\u60C5").requiredOption("--date <date>", "\u76EE\u6807\u65E5\u671F YYYY-MM-DD").action((opts) => {
|
|
8254
|
+
const dir = resolveNotificationsDir(ctx);
|
|
8255
|
+
if (!dir) exitError("STORAGE_UNAVAILABLE", "\u901A\u77E5\u5B58\u50A8\u76EE\u5F55\u4E0D\u53EF\u7528");
|
|
8256
|
+
const items = readDateFile(dir, opts.date);
|
|
8257
|
+
if (items.length === 0) {
|
|
8258
|
+
exitError("NO_DATA", `\u65E5\u671F ${opts.date} \u65E0\u901A\u77E5\u6570\u636E`);
|
|
8259
|
+
}
|
|
8260
|
+
const checkpoint = readCheckpoint(dir);
|
|
8261
|
+
const lastIndex = checkpoint[opts.date]?.lastIndex ?? -1;
|
|
8262
|
+
const startIndex = lastIndex + 1;
|
|
8263
|
+
const unprocessed = items.slice(startIndex);
|
|
8264
|
+
const notifications = unprocessed.slice(0, SYNC_FETCH_LIMIT);
|
|
8265
|
+
const endIndex = notifications.length > 0 ? startIndex + notifications.length - 1 : lastIndex;
|
|
8266
|
+
const hasMore = unprocessed.length > notifications.length;
|
|
8267
|
+
if (unprocessed.length === 0) {
|
|
8268
|
+
output({
|
|
8269
|
+
ok: true,
|
|
8270
|
+
date: opts.date,
|
|
8271
|
+
startIndex,
|
|
8272
|
+
endIndex,
|
|
8273
|
+
nextStartIndex: null,
|
|
8274
|
+
limit: SYNC_FETCH_LIMIT,
|
|
8275
|
+
returned: 0,
|
|
8276
|
+
totalUnprocessed: 0,
|
|
8277
|
+
hasMore: false,
|
|
8278
|
+
notifications: []
|
|
8279
|
+
});
|
|
8280
|
+
return;
|
|
8219
8281
|
}
|
|
8220
|
-
|
|
8221
|
-
|
|
8282
|
+
output({
|
|
8283
|
+
ok: true,
|
|
8284
|
+
date: opts.date,
|
|
8285
|
+
startIndex,
|
|
8286
|
+
endIndex,
|
|
8287
|
+
nextStartIndex: hasMore ? endIndex + 1 : null,
|
|
8288
|
+
limit: SYNC_FETCH_LIMIT,
|
|
8289
|
+
returned: notifications.length,
|
|
8290
|
+
totalUnprocessed: unprocessed.length,
|
|
8291
|
+
hasMore,
|
|
8292
|
+
notifications
|
|
8293
|
+
});
|
|
8294
|
+
});
|
|
8295
|
+
sync.command("commit").description("\u6807\u8BB0\u6307\u5B9A\u65E5\u671F\u5F53\u524D\u6279\u6B21\u5904\u7406\u5B8C\u6210\uFF0C\u66F4\u65B0 checkpoint").requiredOption("--date <date>", "\u76EE\u6807\u65E5\u671F YYYY-MM-DD").action((opts) => {
|
|
8296
|
+
const dir = resolveNotificationsDir(ctx);
|
|
8297
|
+
if (!dir) exitError("STORAGE_UNAVAILABLE", "\u901A\u77E5\u5B58\u50A8\u76EE\u5F55\u4E0D\u53EF\u7528");
|
|
8298
|
+
const items = readDateFile(dir, opts.date);
|
|
8299
|
+
if (items.length === 0) {
|
|
8300
|
+
exitError("NO_DATA", `\u65E5\u671F ${opts.date} \u65E0\u901A\u77E5\u6570\u636E`);
|
|
8301
|
+
}
|
|
8302
|
+
const checkpoint = readCheckpoint(dir);
|
|
8303
|
+
const lastIndex = checkpoint[opts.date]?.lastIndex ?? -1;
|
|
8304
|
+
const committedIndex = Math.min(items.length - 1, lastIndex + SYNC_FETCH_LIMIT);
|
|
8305
|
+
const hasMore = committedIndex < items.length - 1;
|
|
8306
|
+
checkpoint[opts.date] = { lastIndex: committedIndex };
|
|
8307
|
+
writeCheckpoint(dir, checkpoint);
|
|
8308
|
+
output({
|
|
8309
|
+
ok: true,
|
|
8310
|
+
date: opts.date,
|
|
8311
|
+
committedIndex,
|
|
8312
|
+
limit: SYNC_FETCH_LIMIT,
|
|
8313
|
+
hasMore,
|
|
8314
|
+
nextStartIndex: hasMore ? committedIndex + 1 : null
|
|
8315
|
+
});
|
|
8316
|
+
});
|
|
8222
8317
|
}
|
|
8223
|
-
|
|
8224
|
-
|
|
8225
|
-
|
|
8318
|
+
|
|
8319
|
+
// src/cli/ntf-monitor.ts
|
|
8320
|
+
var import_node_fs13 = require("fs");
|
|
8321
|
+
var import_node_path12 = require("path");
|
|
8322
|
+
function tasksDir2(ctx) {
|
|
8323
|
+
const base = ctx.workspaceDir || ctx.stateDir;
|
|
8324
|
+
if (!base) throw new Error("workspaceDir and stateDir both unavailable");
|
|
8325
|
+
return (0, import_node_path12.join)(base, "tasks");
|
|
8226
8326
|
}
|
|
8227
|
-
function
|
|
8228
|
-
|
|
8229
|
-
|
|
8327
|
+
function readMeta2(taskDir) {
|
|
8328
|
+
const metaPath = (0, import_node_path12.join)(taskDir, "meta.json");
|
|
8329
|
+
if (!(0, import_node_fs13.existsSync)(metaPath)) return null;
|
|
8330
|
+
try {
|
|
8331
|
+
return JSON.parse((0, import_node_fs13.readFileSync)(metaPath, "utf-8"));
|
|
8332
|
+
} catch {
|
|
8333
|
+
return null;
|
|
8334
|
+
}
|
|
8230
8335
|
}
|
|
8231
|
-
function
|
|
8232
|
-
|
|
8336
|
+
function writeMeta2(taskDir, meta) {
|
|
8337
|
+
(0, import_node_fs13.writeFileSync)((0, import_node_path12.join)(taskDir, "meta.json"), JSON.stringify(meta, null, 2), "utf-8");
|
|
8233
8338
|
}
|
|
8234
|
-
function
|
|
8235
|
-
|
|
8236
|
-
|
|
8237
|
-
|
|
8238
|
-
|
|
8339
|
+
function generateReadme(name, description) {
|
|
8340
|
+
return `# Monitor Task: ${name}
|
|
8341
|
+
|
|
8342
|
+
## \u63CF\u8FF0
|
|
8343
|
+
${description}
|
|
8344
|
+
|
|
8345
|
+
## \u5904\u7406\u6307\u5357
|
|
8346
|
+
\u5F53 fetch.py \u8F93\u51FA\u5339\u914D\u7684\u901A\u77E5\u65F6\uFF0C\u8BF7\uFF1A
|
|
8347
|
+
1. \u9605\u8BFB\u5339\u914D\u5230\u7684\u901A\u77E5\u5185\u5BB9
|
|
8348
|
+
2. \u6839\u636E\u4EFB\u52A1\u63CF\u8FF0\u5224\u65AD\u662F\u5426\u9700\u8981\u901A\u77E5\u7528\u6237
|
|
8349
|
+
3. \u5982\u9700\u901A\u77E5\u7528\u6237\uFF0C\u4F7F\u7528 message \u5DE5\u5177\u53D1\u9001\u6458\u8981
|
|
8350
|
+
`;
|
|
8239
8351
|
}
|
|
8240
|
-
function
|
|
8241
|
-
|
|
8352
|
+
function registerNtfMonitor(ntf, ctx) {
|
|
8353
|
+
const monitor = ntf.command("monitor").description("\u901A\u77E5\u76D1\u63A7\u4EFB\u52A1\u7BA1\u7406");
|
|
8354
|
+
monitor.command("list").description("\u5217\u51FA\u6240\u6709\u76D1\u63A7\u4EFB\u52A1").action(() => {
|
|
8355
|
+
const dir = tasksDir2(ctx);
|
|
8356
|
+
if (!(0, import_node_fs13.existsSync)(dir)) {
|
|
8357
|
+
output({ ok: true, tasks: [] });
|
|
8358
|
+
return;
|
|
8359
|
+
}
|
|
8360
|
+
const tasks = [];
|
|
8361
|
+
for (const entry of (0, import_node_fs13.readdirSync)(dir, { withFileTypes: true })) {
|
|
8362
|
+
if (!entry.isDirectory()) continue;
|
|
8363
|
+
const meta = readMeta2((0, import_node_path12.join)(dir, entry.name));
|
|
8364
|
+
if (meta) tasks.push(meta);
|
|
8365
|
+
}
|
|
8366
|
+
output({ ok: true, tasks });
|
|
8367
|
+
});
|
|
8368
|
+
monitor.command("show <name>").description("\u67E5\u770B\u76D1\u63A7\u4EFB\u52A1\u8BE6\u60C5").action((name) => {
|
|
8369
|
+
const taskDir = (0, import_node_path12.join)(tasksDir2(ctx), name);
|
|
8370
|
+
const meta = readMeta2(taskDir);
|
|
8371
|
+
if (!meta) exitError("NOT_FOUND", `\u76D1\u63A7\u4EFB\u52A1 '${name}' \u4E0D\u5B58\u5728`);
|
|
8372
|
+
const checkpointPath2 = (0, import_node_path12.join)(taskDir, "checkpoint.json");
|
|
8373
|
+
let checkpoint = {};
|
|
8374
|
+
if ((0, import_node_fs13.existsSync)(checkpointPath2)) {
|
|
8375
|
+
try {
|
|
8376
|
+
checkpoint = JSON.parse((0, import_node_fs13.readFileSync)(checkpointPath2, "utf-8"));
|
|
8377
|
+
} catch {
|
|
8378
|
+
}
|
|
8379
|
+
}
|
|
8380
|
+
output({
|
|
8381
|
+
ok: true,
|
|
8382
|
+
...meta,
|
|
8383
|
+
matchScript: `tasks/${name}/fetch.py`,
|
|
8384
|
+
readme: `tasks/${name}/README.md`,
|
|
8385
|
+
checkpoint
|
|
8386
|
+
});
|
|
8387
|
+
});
|
|
8388
|
+
monitor.command("create <name>").description("\u521B\u5EFA\u76D1\u63A7\u4EFB\u52A1").requiredOption("--description <text>", "\u4EFB\u52A1\u63CF\u8FF0").requiredOption("--match-rules <json>", "\u5339\u914D\u89C4\u5219 JSON").requiredOption("--schedule <cron>", "cron \u8868\u8FBE\u5F0F").action(
|
|
8389
|
+
(name, opts) => {
|
|
8390
|
+
const dir = tasksDir2(ctx);
|
|
8391
|
+
const taskDir = (0, import_node_path12.join)(dir, name);
|
|
8392
|
+
if ((0, import_node_fs13.existsSync)(taskDir)) {
|
|
8393
|
+
exitError("ALREADY_EXISTS", `\u76D1\u63A7\u4EFB\u52A1 '${name}' \u5DF2\u5B58\u5728`);
|
|
8394
|
+
}
|
|
8395
|
+
let matchRules;
|
|
8396
|
+
try {
|
|
8397
|
+
matchRules = JSON.parse(opts.matchRules);
|
|
8398
|
+
} catch {
|
|
8399
|
+
exitError(
|
|
8400
|
+
"VALIDATION_FAILED",
|
|
8401
|
+
"match-rules \u5FC5\u987B\u662F\u5408\u6CD5\u7684 JSON"
|
|
8402
|
+
);
|
|
8403
|
+
}
|
|
8404
|
+
(0, import_node_fs13.mkdirSync)(taskDir, { recursive: true });
|
|
8405
|
+
const meta = {
|
|
8406
|
+
name,
|
|
8407
|
+
description: opts.description,
|
|
8408
|
+
matchRules,
|
|
8409
|
+
cronSchedule: opts.schedule,
|
|
8410
|
+
enabled: true,
|
|
8411
|
+
createdAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
8412
|
+
};
|
|
8413
|
+
writeMeta2(taskDir, meta);
|
|
8414
|
+
(0, import_node_fs13.writeFileSync)(
|
|
8415
|
+
(0, import_node_path12.join)(taskDir, "fetch.py"),
|
|
8416
|
+
generateFetchPy(name, matchRules),
|
|
8417
|
+
"utf-8"
|
|
8418
|
+
);
|
|
8419
|
+
(0, import_node_fs13.writeFileSync)(
|
|
8420
|
+
(0, import_node_path12.join)(taskDir, "README.md"),
|
|
8421
|
+
generateReadme(name, opts.description),
|
|
8422
|
+
"utf-8"
|
|
8423
|
+
);
|
|
8424
|
+
output({
|
|
8425
|
+
ok: true,
|
|
8426
|
+
name,
|
|
8427
|
+
created: {
|
|
8428
|
+
script: `tasks/${name}/fetch.py`,
|
|
8429
|
+
readme: `tasks/${name}/README.md`,
|
|
8430
|
+
cronJob: `notif-${name}`
|
|
8431
|
+
},
|
|
8432
|
+
cronHint: {
|
|
8433
|
+
action: "add",
|
|
8434
|
+
job: {
|
|
8435
|
+
name: `notif-${name}`,
|
|
8436
|
+
schedule: opts.schedule,
|
|
8437
|
+
sessionTarget: "isolated",
|
|
8438
|
+
message: `\u624B\u673A\u901A\u77E5\u5DF2\u7531\u72EC\u7ACB\u670D\u52A1\u5B9E\u65F6\u6355\u83B7\u5230 notifications/ \u76EE\u5F55\u7684 JSON \u6587\u4EF6\u4E2D\u3002
|
|
8439
|
+
\u6267\u884C\uFF1Apython3 tasks/${name}/fetch.py --notifications-dir notifications
|
|
8440
|
+
- NO_CHANGE \u6216 NO_MATCH \u2192 \u4E0D\u56DE\u590D\uFF0C\u76F4\u63A5\u7ED3\u675F\u3002
|
|
8441
|
+
- \u6709\u8F93\u51FA \u2192 \u8BFB tasks/${name}/README.md \u4E86\u89E3\u5982\u4F55\u5904\u7406\u6570\u636E\u5E76\u901A\u77E5\u7528\u6237\u3002`
|
|
8442
|
+
}
|
|
8443
|
+
}
|
|
8444
|
+
});
|
|
8445
|
+
}
|
|
8446
|
+
);
|
|
8447
|
+
monitor.command("delete <name>").description("\u5220\u9664\u76D1\u63A7\u4EFB\u52A1").option("--yes", "\u8DF3\u8FC7\u786E\u8BA4").action((name, opts) => {
|
|
8448
|
+
const taskDir = (0, import_node_path12.join)(tasksDir2(ctx), name);
|
|
8449
|
+
if (!(0, import_node_fs13.existsSync)(taskDir)) {
|
|
8450
|
+
exitError("NOT_FOUND", `\u76D1\u63A7\u4EFB\u52A1 '${name}' \u4E0D\u5B58\u5728`);
|
|
8451
|
+
}
|
|
8452
|
+
if (!opts.yes) {
|
|
8453
|
+
output({
|
|
8454
|
+
ok: false,
|
|
8455
|
+
error: {
|
|
8456
|
+
code: "CONFIRMATION_REQUIRED",
|
|
8457
|
+
message: `\u786E\u8BA4\u5220\u9664\u76D1\u63A7\u4EFB\u52A1 '${name}'\uFF1F\u6DFB\u52A0 --yes \u8DF3\u8FC7\u786E\u8BA4`
|
|
8458
|
+
}
|
|
8459
|
+
});
|
|
8460
|
+
process.exit(1);
|
|
8461
|
+
}
|
|
8462
|
+
(0, import_node_fs13.rmSync)(taskDir, { recursive: true, force: true });
|
|
8463
|
+
output({
|
|
8464
|
+
ok: true,
|
|
8465
|
+
name,
|
|
8466
|
+
deleted: true,
|
|
8467
|
+
cronHint: {
|
|
8468
|
+
action: "remove",
|
|
8469
|
+
name: `notif-${name}`
|
|
8470
|
+
}
|
|
8471
|
+
});
|
|
8472
|
+
});
|
|
8473
|
+
monitor.command("enable <name>").description("\u542F\u7528\u76D1\u63A7\u4EFB\u52A1").action((name) => {
|
|
8474
|
+
const taskDir = (0, import_node_path12.join)(tasksDir2(ctx), name);
|
|
8475
|
+
const meta = readMeta2(taskDir);
|
|
8476
|
+
if (!meta) exitError("NOT_FOUND", `\u76D1\u63A7\u4EFB\u52A1 '${name}' \u4E0D\u5B58\u5728`);
|
|
8477
|
+
meta.enabled = true;
|
|
8478
|
+
writeMeta2(taskDir, meta);
|
|
8479
|
+
output({ ok: true, name, enabled: true });
|
|
8480
|
+
});
|
|
8481
|
+
monitor.command("disable <name>").description("\u6682\u505C\u76D1\u63A7\u4EFB\u52A1").action((name) => {
|
|
8482
|
+
const taskDir = (0, import_node_path12.join)(tasksDir2(ctx), name);
|
|
8483
|
+
const meta = readMeta2(taskDir);
|
|
8484
|
+
if (!meta) exitError("NOT_FOUND", `\u76D1\u63A7\u4EFB\u52A1 '${name}' \u4E0D\u5B58\u5728`);
|
|
8485
|
+
meta.enabled = false;
|
|
8486
|
+
writeMeta2(taskDir, meta);
|
|
8487
|
+
output({ ok: true, name, enabled: false });
|
|
8488
|
+
});
|
|
8242
8489
|
}
|
|
8243
8490
|
|
|
8491
|
+
// src/cli/light-send.ts
|
|
8492
|
+
init_credentials();
|
|
8493
|
+
|
|
8244
8494
|
// src/light/sender.ts
|
|
8495
|
+
var import_node_crypto2 = require("crypto");
|
|
8245
8496
|
init_env();
|
|
8246
8497
|
async function sendLightEffect(apiKey, segments, logger, repeatInput, reason) {
|
|
8247
8498
|
const apiUrl = getEnvUrls().lightApiUrl;
|
|
@@ -8327,9 +8578,9 @@ function registerLightSend(light) {
|
|
|
8327
8578
|
}
|
|
8328
8579
|
|
|
8329
8580
|
// src/cli/light-setup-tools.ts
|
|
8330
|
-
var
|
|
8581
|
+
var import_node_fs14 = require("fs");
|
|
8331
8582
|
var import_node_os2 = require("os");
|
|
8332
|
-
var
|
|
8583
|
+
var import_node_path13 = require("path");
|
|
8333
8584
|
function isObject(value) {
|
|
8334
8585
|
return !!value && typeof value === "object" && !Array.isArray(value);
|
|
8335
8586
|
}
|
|
@@ -8343,13 +8594,25 @@ function ensureArray(obj, key) {
|
|
|
8343
8594
|
function resolveConfigPath2() {
|
|
8344
8595
|
const fromEnv = process.env.OPENCLAW_CONFIG_PATH?.trim();
|
|
8345
8596
|
if (fromEnv) return fromEnv;
|
|
8346
|
-
return (0,
|
|
8347
|
-
}
|
|
8597
|
+
return (0, import_node_path13.join)((0, import_node_os2.homedir)(), ".openclaw", "openclaw.json");
|
|
8598
|
+
}
|
|
8599
|
+
var LIGHT_TOOLS = [
|
|
8600
|
+
"light_control",
|
|
8601
|
+
"lightrules.list",
|
|
8602
|
+
"lightrules.create",
|
|
8603
|
+
"lightrules.update",
|
|
8604
|
+
"lightrules.delete"
|
|
8605
|
+
];
|
|
8348
8606
|
function upsertLightControlAlsoAllow(cfg) {
|
|
8349
8607
|
if (!isObject(cfg.tools)) cfg.tools = {};
|
|
8350
8608
|
const toolsAlsoAllow = ensureArray(cfg.tools, "alsoAllow");
|
|
8351
|
-
|
|
8352
|
-
|
|
8609
|
+
let globalChanged = false;
|
|
8610
|
+
for (const tool of LIGHT_TOOLS) {
|
|
8611
|
+
if (!toolsAlsoAllow.includes(tool)) {
|
|
8612
|
+
toolsAlsoAllow.push(tool);
|
|
8613
|
+
globalChanged = true;
|
|
8614
|
+
}
|
|
8615
|
+
}
|
|
8353
8616
|
if (!isObject(cfg.agents)) cfg.agents = {};
|
|
8354
8617
|
const agents = cfg.agents;
|
|
8355
8618
|
const list = ensureArray(agents, "list");
|
|
@@ -8362,33 +8625,35 @@ function upsertLightControlAlsoAllow(cfg) {
|
|
|
8362
8625
|
}
|
|
8363
8626
|
if (!isObject(mainAgent.tools)) mainAgent.tools = {};
|
|
8364
8627
|
const mainAlsoAllow = ensureArray(mainAgent.tools, "alsoAllow");
|
|
8365
|
-
|
|
8366
|
-
|
|
8367
|
-
|
|
8368
|
-
|
|
8369
|
-
|
|
8370
|
-
|
|
8628
|
+
let mainAgentChanged = false;
|
|
8629
|
+
for (const tool of LIGHT_TOOLS) {
|
|
8630
|
+
if (!mainAlsoAllow.includes(tool)) {
|
|
8631
|
+
mainAlsoAllow.push(tool);
|
|
8632
|
+
mainAgentChanged = true;
|
|
8633
|
+
}
|
|
8634
|
+
}
|
|
8635
|
+
return { globalChanged, mainAgentChanged };
|
|
8371
8636
|
}
|
|
8372
8637
|
function registerLightSetupTools(light) {
|
|
8373
8638
|
light.command("setup").description("\u81EA\u52A8\u653E\u884C light_control\uFF08\u5199\u5165 tools.alsoAllow \u4E0E agents.main.tools.alsoAllow\uFF09").action(() => {
|
|
8374
8639
|
const configPath = resolveConfigPath2();
|
|
8375
|
-
if (!(0,
|
|
8640
|
+
if (!(0, import_node_fs14.existsSync)(configPath)) {
|
|
8376
8641
|
exitError("CONFIG_NOT_FOUND", `\u672A\u627E\u5230\u914D\u7F6E\u6587\u4EF6: ${configPath}`);
|
|
8377
8642
|
}
|
|
8378
8643
|
let cfg = {};
|
|
8379
8644
|
try {
|
|
8380
|
-
const raw = (0,
|
|
8645
|
+
const raw = (0, import_node_fs14.readFileSync)(configPath, "utf-8");
|
|
8381
8646
|
const parsed = JSON.parse(raw);
|
|
8382
8647
|
if (isObject(parsed)) cfg = parsed;
|
|
8383
|
-
} catch (
|
|
8384
|
-
exitError("CONFIG_INVALID", `\u8BFB\u53D6/\u89E3\u6790\u914D\u7F6E\u5931\u8D25: ${
|
|
8648
|
+
} catch (err2) {
|
|
8649
|
+
exitError("CONFIG_INVALID", `\u8BFB\u53D6/\u89E3\u6790\u914D\u7F6E\u5931\u8D25: ${err2?.message ?? String(err2)}`);
|
|
8385
8650
|
}
|
|
8386
8651
|
const result = upsertLightControlAlsoAllow(cfg);
|
|
8387
8652
|
try {
|
|
8388
|
-
(0,
|
|
8389
|
-
(0,
|
|
8390
|
-
} catch (
|
|
8391
|
-
exitError("WRITE_FAILED", `\u5199\u5165\u914D\u7F6E\u5931\u8D25: ${
|
|
8653
|
+
(0, import_node_fs14.mkdirSync)((0, import_node_path13.dirname)(configPath), { recursive: true });
|
|
8654
|
+
(0, import_node_fs14.writeFileSync)(configPath, JSON.stringify(cfg, null, 2) + "\n", "utf-8");
|
|
8655
|
+
} catch (err2) {
|
|
8656
|
+
exitError("WRITE_FAILED", `\u5199\u5165\u914D\u7F6E\u5931\u8D25: ${err2?.message ?? String(err2)}`);
|
|
8392
8657
|
}
|
|
8393
8658
|
output({
|
|
8394
8659
|
ok: true,
|
|
@@ -8404,17 +8669,17 @@ function registerLightSetupTools(light) {
|
|
|
8404
8669
|
}
|
|
8405
8670
|
|
|
8406
8671
|
// src/cli/tunnel-status.ts
|
|
8407
|
-
var
|
|
8408
|
-
var
|
|
8672
|
+
var import_node_fs15 = require("fs");
|
|
8673
|
+
var import_node_path14 = require("path");
|
|
8409
8674
|
init_credentials();
|
|
8410
8675
|
init_env();
|
|
8411
|
-
var STATUS_REL_PATH = (0,
|
|
8676
|
+
var STATUS_REL_PATH = (0, import_node_path14.join)("plugins", "phone-notifications", "tunnel-status.json");
|
|
8412
8677
|
function readTunnelStatus(ctx) {
|
|
8413
8678
|
if (!ctx.stateDir) return null;
|
|
8414
|
-
const filePath = (0,
|
|
8415
|
-
if (!(0,
|
|
8679
|
+
const filePath = (0, import_node_path14.join)(ctx.stateDir, STATUS_REL_PATH);
|
|
8680
|
+
if (!(0, import_node_fs15.existsSync)(filePath)) return null;
|
|
8416
8681
|
try {
|
|
8417
|
-
return JSON.parse((0,
|
|
8682
|
+
return JSON.parse((0, import_node_fs15.readFileSync)(filePath, "utf-8"));
|
|
8418
8683
|
} catch {
|
|
8419
8684
|
return null;
|
|
8420
8685
|
}
|
|
@@ -8456,9 +8721,9 @@ function registerTunnelStatus(ntf, ctx) {
|
|
|
8456
8721
|
"\u672A\u627E\u5230\u96A7\u9053\u72B6\u6001\u6587\u4EF6\uFF0C\u96A7\u9053\u670D\u52A1\u53EF\u80FD\u5C1A\u672A\u542F\u52A8\u8FC7\u3002\u8BF7\u786E\u8BA4 openclaw \u4E3B\u8FDB\u7A0B\u6B63\u5728\u8FD0\u884C\u3002"
|
|
8457
8722
|
);
|
|
8458
8723
|
}
|
|
8459
|
-
const
|
|
8724
|
+
const ok2 = status.state === "connected";
|
|
8460
8725
|
output({
|
|
8461
|
-
ok,
|
|
8726
|
+
ok: ok2,
|
|
8462
8727
|
tunnelUrl,
|
|
8463
8728
|
connection: {
|
|
8464
8729
|
state: status.state,
|
|
@@ -8468,7 +8733,7 @@ function registerTunnelStatus(ntf, ctx) {
|
|
|
8468
8733
|
},
|
|
8469
8734
|
message: formatMessage(status)
|
|
8470
8735
|
});
|
|
8471
|
-
if (!
|
|
8736
|
+
if (!ok2) process.exit(1);
|
|
8472
8737
|
});
|
|
8473
8738
|
}
|
|
8474
8739
|
|
|
@@ -8482,24 +8747,24 @@ function registerNtfStoragePath(ntf, ctx) {
|
|
|
8482
8747
|
}
|
|
8483
8748
|
|
|
8484
8749
|
// src/cli/log-search.ts
|
|
8485
|
-
var
|
|
8486
|
-
var
|
|
8750
|
+
var import_node_fs16 = require("fs");
|
|
8751
|
+
var import_node_path15 = require("path");
|
|
8487
8752
|
function resolveLogsDir(ctx) {
|
|
8488
8753
|
if (ctx.stateDir) {
|
|
8489
|
-
const dir = (0,
|
|
8754
|
+
const dir = (0, import_node_path15.join)(
|
|
8490
8755
|
ctx.stateDir,
|
|
8491
8756
|
"plugins",
|
|
8492
8757
|
"phone-notifications",
|
|
8493
8758
|
"logs"
|
|
8494
8759
|
);
|
|
8495
|
-
if ((0,
|
|
8760
|
+
if ((0, import_node_fs16.existsSync)(dir)) return dir;
|
|
8496
8761
|
}
|
|
8497
8762
|
return null;
|
|
8498
8763
|
}
|
|
8499
8764
|
function listLogDateKeys(dir) {
|
|
8500
8765
|
const pattern = /^(\d{4}-\d{2}-\d{2})\.log$/;
|
|
8501
8766
|
const keys = [];
|
|
8502
|
-
for (const entry of (0,
|
|
8767
|
+
for (const entry of (0, import_node_fs16.readdirSync)(dir, { withFileTypes: true })) {
|
|
8503
8768
|
if (!entry.isFile()) continue;
|
|
8504
8769
|
const m = pattern.exec(entry.name);
|
|
8505
8770
|
if (m) keys.push(m[1]);
|
|
@@ -8507,9 +8772,9 @@ function listLogDateKeys(dir) {
|
|
|
8507
8772
|
return keys.sort().reverse();
|
|
8508
8773
|
}
|
|
8509
8774
|
function collectLogLines(dir, dateKey, keyword, limit, collected) {
|
|
8510
|
-
const filePath = (0,
|
|
8511
|
-
if (!(0,
|
|
8512
|
-
const content = (0,
|
|
8775
|
+
const filePath = (0, import_node_path15.join)(dir, `${dateKey}.log`);
|
|
8776
|
+
if (!(0, import_node_fs16.existsSync)(filePath)) return;
|
|
8777
|
+
const content = (0, import_node_fs16.readFileSync)(filePath, "utf-8");
|
|
8513
8778
|
const lowerKeyword = keyword?.toLowerCase();
|
|
8514
8779
|
for (const line of content.split("\n")) {
|
|
8515
8780
|
if (collected.length >= limit) return;
|
|
@@ -8576,12 +8841,12 @@ function registerEnvCli(ntf) {
|
|
|
8576
8841
|
}
|
|
8577
8842
|
|
|
8578
8843
|
// src/cli/doctor.ts
|
|
8579
|
-
var
|
|
8844
|
+
var import_node_fs20 = require("fs");
|
|
8580
8845
|
var import_node_readline = require("readline");
|
|
8581
8846
|
init_host();
|
|
8582
8847
|
|
|
8583
8848
|
// src/cli/doctor/check-dangerous-flags.ts
|
|
8584
|
-
var
|
|
8849
|
+
var import_node_fs17 = require("fs");
|
|
8585
8850
|
function isObject2(v) {
|
|
8586
8851
|
return !!v && typeof v === "object" && !Array.isArray(v);
|
|
8587
8852
|
}
|
|
@@ -8598,13 +8863,13 @@ var checkDangerousFlags = ({ cfg, configPath }) => {
|
|
|
8598
8863
|
detail: "\u8FD9\u4F1A\u5173\u95ED Control UI \u7684\u8BBE\u5907\u8EAB\u4EFD\u9A8C\u8BC1\uFF0C\u4EFB\u4F55\u4EBA\u90FD\u53EF\u4EE5\u8BBF\u95EE\u63A7\u5236\u9762\u677F\u3002",
|
|
8599
8864
|
fixDescription: "\u8BBE\u4E3A false",
|
|
8600
8865
|
fix: () => {
|
|
8601
|
-
const raw = (0,
|
|
8866
|
+
const raw = (0, import_node_fs17.readFileSync)(configPath, "utf-8");
|
|
8602
8867
|
const config = JSON.parse(raw);
|
|
8603
8868
|
const gw = config.gateway;
|
|
8604
8869
|
const cui = gw.controlUi;
|
|
8605
8870
|
cui.dangerouslyDisableDeviceAuth = false;
|
|
8606
|
-
(0,
|
|
8607
|
-
(0,
|
|
8871
|
+
(0, import_node_fs17.copyFileSync)(configPath, configPath + ".bak");
|
|
8872
|
+
(0, import_node_fs17.writeFileSync)(configPath, JSON.stringify(config, null, 2) + "\n", "utf-8");
|
|
8608
8873
|
}
|
|
8609
8874
|
};
|
|
8610
8875
|
};
|
|
@@ -8652,11 +8917,11 @@ function warnEmpty() {
|
|
|
8652
8917
|
}
|
|
8653
8918
|
|
|
8654
8919
|
// src/cli/doctor/check-state-dir-perms.ts
|
|
8655
|
-
var
|
|
8920
|
+
var import_node_fs18 = require("fs");
|
|
8656
8921
|
var checkStateDirPerms = ({ stateDir }) => {
|
|
8657
8922
|
let mode;
|
|
8658
8923
|
try {
|
|
8659
|
-
mode = (0,
|
|
8924
|
+
mode = (0, import_node_fs18.statSync)(stateDir).mode;
|
|
8660
8925
|
} catch {
|
|
8661
8926
|
return null;
|
|
8662
8927
|
}
|
|
@@ -8670,7 +8935,7 @@ var checkStateDirPerms = ({ stateDir }) => {
|
|
|
8670
8935
|
detail: "\u5176\u4ED6\u7528\u6237\u53EF\u4EE5\u8BFB\u53D6\u8BE5\u76EE\u5F55\u4E0B\u7684\u51ED\u8BC1\u548C\u914D\u7F6E\u6587\u4EF6\u3002",
|
|
8671
8936
|
fixDescription: "chmod 700 " + stateDir,
|
|
8672
8937
|
fix: () => {
|
|
8673
|
-
(0,
|
|
8938
|
+
(0, import_node_fs18.chmodSync)(stateDir, 448);
|
|
8674
8939
|
}
|
|
8675
8940
|
};
|
|
8676
8941
|
};
|
|
@@ -8725,16 +8990,16 @@ var checkCredentials = () => {
|
|
|
8725
8990
|
};
|
|
8726
8991
|
|
|
8727
8992
|
// src/cli/doctor/check-tunnel.ts
|
|
8728
|
-
var
|
|
8729
|
-
var
|
|
8730
|
-
var STATUS_REL_PATH2 = (0,
|
|
8993
|
+
var import_node_fs19 = require("fs");
|
|
8994
|
+
var import_node_path16 = require("path");
|
|
8995
|
+
var STATUS_REL_PATH2 = (0, import_node_path16.join)(
|
|
8731
8996
|
"plugins",
|
|
8732
8997
|
"phone-notifications",
|
|
8733
8998
|
"tunnel-status.json"
|
|
8734
8999
|
);
|
|
8735
9000
|
var checkTunnel = ({ stateDir }) => {
|
|
8736
|
-
const filePath = (0,
|
|
8737
|
-
if (!(0,
|
|
9001
|
+
const filePath = (0, import_node_path16.join)(stateDir, STATUS_REL_PATH2);
|
|
9002
|
+
if (!(0, import_node_fs19.existsSync)(filePath)) {
|
|
8738
9003
|
return {
|
|
8739
9004
|
id: "tunnel",
|
|
8740
9005
|
severity: "warn",
|
|
@@ -8746,7 +9011,7 @@ var checkTunnel = ({ stateDir }) => {
|
|
|
8746
9011
|
}
|
|
8747
9012
|
let status;
|
|
8748
9013
|
try {
|
|
8749
|
-
status = JSON.parse((0,
|
|
9014
|
+
status = JSON.parse((0, import_node_fs19.readFileSync)(filePath, "utf-8"));
|
|
8750
9015
|
} catch {
|
|
8751
9016
|
return {
|
|
8752
9017
|
id: "tunnel",
|
|
@@ -8839,9 +9104,9 @@ function isObject5(v) {
|
|
|
8839
9104
|
return !!v && typeof v === "object" && !Array.isArray(v);
|
|
8840
9105
|
}
|
|
8841
9106
|
function readConfig(configPath) {
|
|
8842
|
-
if (!(0,
|
|
9107
|
+
if (!(0, import_node_fs20.existsSync)(configPath)) return {};
|
|
8843
9108
|
try {
|
|
8844
|
-
const parsed = JSON.parse((0,
|
|
9109
|
+
const parsed = JSON.parse((0, import_node_fs20.readFileSync)(configPath, "utf-8"));
|
|
8845
9110
|
return isObject5(parsed) ? parsed : {};
|
|
8846
9111
|
} catch {
|
|
8847
9112
|
return {};
|
|
@@ -8950,9 +9215,9 @@ async function runDoctor(ctx, json, fix) {
|
|
|
8950
9215
|
await issue.fix();
|
|
8951
9216
|
log(`\x1B[32m\u2705\x1B[0m ${issue.title} \u2192 ${issue.fixDescription}`);
|
|
8952
9217
|
fixed++;
|
|
8953
|
-
} catch (
|
|
9218
|
+
} catch (err2) {
|
|
8954
9219
|
log(
|
|
8955
|
-
`\x1B[31m\u274C\x1B[0m ${issue.title} \u4FEE\u590D\u5931\u8D25: ${
|
|
9220
|
+
`\x1B[31m\u274C\x1B[0m ${issue.title} \u4FEE\u590D\u5931\u8D25: ${err2?.message ?? String(err2)}`
|
|
8956
9221
|
);
|
|
8957
9222
|
}
|
|
8958
9223
|
}
|
|
@@ -9042,7 +9307,7 @@ function registerRecStoragePath(rec, ctx) {
|
|
|
9042
9307
|
|
|
9043
9308
|
// src/cli/rec-setup.ts
|
|
9044
9309
|
var import_node_readline2 = require("readline");
|
|
9045
|
-
var
|
|
9310
|
+
var import_node_fs21 = require("fs");
|
|
9046
9311
|
function ask(rl, question) {
|
|
9047
9312
|
return new Promise((resolve) => rl.question(question, resolve));
|
|
9048
9313
|
}
|
|
@@ -9128,9 +9393,9 @@ async function setupLocal(rl) {
|
|
|
9128
9393
|
function registerRecSetup(rec, ctx) {
|
|
9129
9394
|
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 () => {
|
|
9130
9395
|
const configPath = resolveAsrConfigPath(ctx);
|
|
9131
|
-
if ((0,
|
|
9396
|
+
if ((0, import_node_fs21.existsSync)(configPath)) {
|
|
9132
9397
|
try {
|
|
9133
|
-
const existing = JSON.parse((0,
|
|
9398
|
+
const existing = JSON.parse((0, import_node_fs21.readFileSync)(configPath, "utf-8"));
|
|
9134
9399
|
process.stderr.write(`\u5F53\u524D\u5DF2\u6709\u914D\u7F6E\uFF1Amode = ${existing.mode}`);
|
|
9135
9400
|
if (existing.updatedAt) process.stderr.write(`\uFF0C\u66F4\u65B0\u4E8E ${existing.updatedAt}`);
|
|
9136
9401
|
process.stderr.write("\n");
|
|
@@ -9143,7 +9408,7 @@ function registerRecSetup(rec, ctx) {
|
|
|
9143
9408
|
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"]);
|
|
9144
9409
|
const config = modeIdx === 0 ? await setupApi(rl) : await setupLocal(rl);
|
|
9145
9410
|
const stored = { ...config, updatedAt: (/* @__PURE__ */ new Date()).toISOString() };
|
|
9146
|
-
(0,
|
|
9411
|
+
(0, import_node_fs21.writeFileSync)(configPath, JSON.stringify(stored, null, 2), "utf-8");
|
|
9147
9412
|
process.stderr.write(`
|
|
9148
9413
|
\u2713 \u914D\u7F6E\u5DF2\u4FDD\u5B58\u5230 ${configPath}
|
|
9149
9414
|
|
|
@@ -9157,8 +9422,8 @@ function registerRecSetup(rec, ctx) {
|
|
|
9157
9422
|
|
|
9158
9423
|
// src/cli/update.ts
|
|
9159
9424
|
var import_node_child_process = require("child_process");
|
|
9160
|
-
var
|
|
9161
|
-
var
|
|
9425
|
+
var import_node_fs22 = require("fs");
|
|
9426
|
+
var import_node_path17 = require("path");
|
|
9162
9427
|
var import_node_os3 = __toESM(require("os"), 1);
|
|
9163
9428
|
init_host();
|
|
9164
9429
|
var BASE_URL2 = "https://artifact.yoooclaw.com/plugin";
|
|
@@ -9182,8 +9447,8 @@ async function runUpdate(ctx, opts) {
|
|
|
9182
9447
|
let latest;
|
|
9183
9448
|
try {
|
|
9184
9449
|
latest = (await fetchText(`${BASE_URL2}/${channel}`)).trim();
|
|
9185
|
-
} catch (
|
|
9186
|
-
const msg = `\u65E0\u6CD5\u83B7\u53D6\u6700\u65B0\u7248\u672C: ${
|
|
9450
|
+
} catch (err2) {
|
|
9451
|
+
const msg = `\u65E0\u6CD5\u83B7\u53D6\u6700\u65B0\u7248\u672C: ${err2?.message ?? String(err2)}`;
|
|
9187
9452
|
if (json) {
|
|
9188
9453
|
output({ ok: false, error: { code: "FETCH_FAILED", message: msg } });
|
|
9189
9454
|
process.exit(1);
|
|
@@ -9207,8 +9472,8 @@ async function runUpdate(ctx, opts) {
|
|
|
9207
9472
|
let installScript;
|
|
9208
9473
|
try {
|
|
9209
9474
|
installScript = await fetchText(`${BASE_URL2}/install-core.mjs`);
|
|
9210
|
-
} catch (
|
|
9211
|
-
const msg = `\u4E0B\u8F7D\u5B89\u88C5\u811A\u672C\u5931\u8D25: ${
|
|
9475
|
+
} catch (err2) {
|
|
9476
|
+
const msg = `\u4E0B\u8F7D\u5B89\u88C5\u811A\u672C\u5931\u8D25: ${err2?.message ?? String(err2)}`;
|
|
9212
9477
|
if (json) {
|
|
9213
9478
|
output({ ok: false, error: { code: "DOWNLOAD_FAILED", message: msg } });
|
|
9214
9479
|
process.exit(1);
|
|
@@ -9217,11 +9482,11 @@ async function runUpdate(ctx, opts) {
|
|
|
9217
9482
|
`);
|
|
9218
9483
|
process.exit(1);
|
|
9219
9484
|
}
|
|
9220
|
-
const tmpScript = (0,
|
|
9485
|
+
const tmpScript = (0, import_node_path17.join)(import_node_os3.default.tmpdir(), `openclaw-install-${Date.now()}.mjs`);
|
|
9221
9486
|
try {
|
|
9222
|
-
(0,
|
|
9223
|
-
} catch (
|
|
9224
|
-
const msg = `\u5199\u5165\u4E34\u65F6\u6587\u4EF6\u5931\u8D25: ${
|
|
9487
|
+
(0, import_node_fs22.writeFileSync)(tmpScript, installScript, "utf-8");
|
|
9488
|
+
} catch (err2) {
|
|
9489
|
+
const msg = `\u5199\u5165\u4E34\u65F6\u6587\u4EF6\u5931\u8D25: ${err2?.message ?? String(err2)}`;
|
|
9225
9490
|
if (json) {
|
|
9226
9491
|
output({ ok: false, error: { code: "WRITE_FAILED", message: msg } });
|
|
9227
9492
|
process.exit(1);
|
|
@@ -9237,7 +9502,7 @@ async function runUpdate(ctx, opts) {
|
|
|
9237
9502
|
{ stdio: "inherit" }
|
|
9238
9503
|
);
|
|
9239
9504
|
try {
|
|
9240
|
-
(0,
|
|
9505
|
+
(0, import_node_fs22.unlinkSync)(tmpScript);
|
|
9241
9506
|
} catch {
|
|
9242
9507
|
}
|
|
9243
9508
|
if (result.error) {
|
|
@@ -9301,10 +9566,10 @@ function inferOpenClawRootDir(workspaceDir) {
|
|
|
9301
9566
|
if (!workspaceDir) {
|
|
9302
9567
|
return void 0;
|
|
9303
9568
|
}
|
|
9304
|
-
if ((0,
|
|
9569
|
+
if ((0, import_node_path18.basename)(workspaceDir) !== "workspace") {
|
|
9305
9570
|
return void 0;
|
|
9306
9571
|
}
|
|
9307
|
-
return (0,
|
|
9572
|
+
return (0, import_node_path18.dirname)(workspaceDir);
|
|
9308
9573
|
}
|
|
9309
9574
|
function registerPluginCli(api, params) {
|
|
9310
9575
|
const { logger, openclawDir } = params;
|
|
@@ -9550,12 +9815,12 @@ function registerLightControlTool(api, logger) {
|
|
|
9550
9815
|
}
|
|
9551
9816
|
|
|
9552
9817
|
// src/plugin/lifecycle.ts
|
|
9553
|
-
var
|
|
9818
|
+
var import_node_fs31 = require("fs");
|
|
9554
9819
|
init_host();
|
|
9555
9820
|
|
|
9556
9821
|
// src/notification/app-name-map.ts
|
|
9557
|
-
var
|
|
9558
|
-
var
|
|
9822
|
+
var import_node_fs23 = require("fs");
|
|
9823
|
+
var import_node_path19 = require("path");
|
|
9559
9824
|
init_credentials();
|
|
9560
9825
|
init_env();
|
|
9561
9826
|
var PLUGIN_STATE_DIR = "phone-notifications";
|
|
@@ -9576,7 +9841,7 @@ function isAppNameMapApiResponse(v) {
|
|
|
9576
9841
|
);
|
|
9577
9842
|
}
|
|
9578
9843
|
function getCachePath(stateDir) {
|
|
9579
|
-
return (0,
|
|
9844
|
+
return (0, import_node_path19.join)(stateDir, "plugins", PLUGIN_STATE_DIR, CACHE_FILE);
|
|
9580
9845
|
}
|
|
9581
9846
|
function createAppNameMapProvider(opts) {
|
|
9582
9847
|
const { stateDir, logger } = opts;
|
|
@@ -9588,9 +9853,9 @@ function createAppNameMapProvider(opts) {
|
|
|
9588
9853
|
let inFlightFetch = null;
|
|
9589
9854
|
function loadFromDisk() {
|
|
9590
9855
|
const path2 = getCachePath(stateDir);
|
|
9591
|
-
if (!(0,
|
|
9856
|
+
if (!(0, import_node_fs23.existsSync)(path2)) return;
|
|
9592
9857
|
try {
|
|
9593
|
-
const raw = JSON.parse((0,
|
|
9858
|
+
const raw = JSON.parse((0, import_node_fs23.readFileSync)(path2, "utf-8"));
|
|
9594
9859
|
if (!isRecordOfStrings(raw)) return;
|
|
9595
9860
|
map.clear();
|
|
9596
9861
|
for (const [k, v] of Object.entries(raw)) map.set(k, v);
|
|
@@ -9634,10 +9899,10 @@ function createAppNameMapProvider(opts) {
|
|
|
9634
9899
|
logger.warn("[app-name-map] refresh succeeded but got 0 entries");
|
|
9635
9900
|
return;
|
|
9636
9901
|
}
|
|
9637
|
-
const dir = (0,
|
|
9638
|
-
(0,
|
|
9902
|
+
const dir = (0, import_node_path19.join)(stateDir, "plugins", PLUGIN_STATE_DIR);
|
|
9903
|
+
(0, import_node_fs23.mkdirSync)(dir, { recursive: true });
|
|
9639
9904
|
const cachePath = getCachePath(stateDir);
|
|
9640
|
-
(0,
|
|
9905
|
+
(0, import_node_fs23.writeFileSync)(cachePath, JSON.stringify(Object.fromEntries(map), null, 2), "utf-8");
|
|
9641
9906
|
logger.info(`[app-name-map] refreshed ${map.size} entries from server and saved: ${cachePath}`);
|
|
9642
9907
|
} catch (e) {
|
|
9643
9908
|
const message = e instanceof Error ? e.message : String(e);
|
|
@@ -9681,8 +9946,8 @@ function createAppNameMapProvider(opts) {
|
|
|
9681
9946
|
}
|
|
9682
9947
|
|
|
9683
9948
|
// src/recording/storage.ts
|
|
9684
|
-
var
|
|
9685
|
-
var
|
|
9949
|
+
var import_node_fs24 = require("fs");
|
|
9950
|
+
var import_node_path20 = require("path");
|
|
9686
9951
|
|
|
9687
9952
|
// src/recording/state-machine.ts
|
|
9688
9953
|
var VALID_TRANSITIONS = /* @__PURE__ */ new Map([
|
|
@@ -9743,7 +10008,7 @@ function stripMarkdownFence(markdown) {
|
|
|
9743
10008
|
}
|
|
9744
10009
|
function deriveTitleFromTranscriptPath(transcriptFile, recordingId) {
|
|
9745
10010
|
if (!transcriptFile) return void 0;
|
|
9746
|
-
const name = (0,
|
|
10011
|
+
const name = (0, import_node_path20.basename)(transcriptFile, ".md");
|
|
9747
10012
|
const prefix = `${recordingId}_`;
|
|
9748
10013
|
if (name.startsWith(prefix)) {
|
|
9749
10014
|
const derived = name.slice(prefix.length).trim();
|
|
@@ -9771,22 +10036,22 @@ function extractTranscriptContent(markdown) {
|
|
|
9771
10036
|
return lines.join("\n").replace(/\n{3,}/g, "\n\n").trim();
|
|
9772
10037
|
}
|
|
9773
10038
|
function resolveRecordingStorageDir(ctx, logger) {
|
|
9774
|
-
const stateRecDir = (0,
|
|
10039
|
+
const stateRecDir = (0, import_node_path20.join)(
|
|
9775
10040
|
ctx.stateDir,
|
|
9776
10041
|
"plugins",
|
|
9777
10042
|
"phone-notifications",
|
|
9778
10043
|
RECORDINGS_DIR
|
|
9779
10044
|
);
|
|
9780
10045
|
try {
|
|
9781
|
-
(0,
|
|
10046
|
+
(0, import_node_fs24.mkdirSync)(stateRecDir, { recursive: true });
|
|
9782
10047
|
logger.info(`\u5F55\u97F3\u5C06\u5199\u5165 stateDir \u8DEF\u5F84: ${stateRecDir}`);
|
|
9783
10048
|
return stateRecDir;
|
|
9784
10049
|
} catch {
|
|
9785
10050
|
}
|
|
9786
10051
|
if (ctx.workspaceDir) {
|
|
9787
|
-
const wsRecDir = (0,
|
|
10052
|
+
const wsRecDir = (0, import_node_path20.join)(ctx.workspaceDir, RECORDINGS_DIR);
|
|
9788
10053
|
try {
|
|
9789
|
-
(0,
|
|
10054
|
+
(0, import_node_fs24.mkdirSync)(wsRecDir, { recursive: true });
|
|
9790
10055
|
logger.warn(`stateDir \u4E0D\u53EF\u7528\uFF0C\u5F55\u97F3\u5DF2\u56DE\u9000\u5230 workspace \u8DEF\u5F84: ${wsRecDir}`);
|
|
9791
10056
|
return wsRecDir;
|
|
9792
10057
|
} catch {
|
|
@@ -9798,11 +10063,11 @@ var RecordingStorage = class {
|
|
|
9798
10063
|
constructor(dir, logger) {
|
|
9799
10064
|
this.logger = logger;
|
|
9800
10065
|
this.dir = dir;
|
|
9801
|
-
this.audioDir = (0,
|
|
9802
|
-
this.transcriptDataDir = (0,
|
|
9803
|
-
this.transcriptsDir = (0,
|
|
9804
|
-
this.summariesDir = (0,
|
|
9805
|
-
this.indexPath = (0,
|
|
10066
|
+
this.audioDir = (0, import_node_path20.join)(dir, AUDIO_DIR);
|
|
10067
|
+
this.transcriptDataDir = (0, import_node_path20.join)(dir, TRANSCRIPT_DATA_DIR);
|
|
10068
|
+
this.transcriptsDir = (0, import_node_path20.join)(dir, TRANSCRIPTS_DIR);
|
|
10069
|
+
this.summariesDir = (0, import_node_path20.join)(dir, SUMMARIES_DIR);
|
|
10070
|
+
this.indexPath = (0, import_node_path20.join)(dir, INDEX_FILE);
|
|
9806
10071
|
}
|
|
9807
10072
|
dir;
|
|
9808
10073
|
audioDir;
|
|
@@ -9813,10 +10078,10 @@ var RecordingStorage = class {
|
|
|
9813
10078
|
index = { recordings: [] };
|
|
9814
10079
|
/** 初始化目录结构并加载索引 */
|
|
9815
10080
|
async init() {
|
|
9816
|
-
(0,
|
|
9817
|
-
(0,
|
|
9818
|
-
(0,
|
|
9819
|
-
(0,
|
|
10081
|
+
(0, import_node_fs24.mkdirSync)(this.audioDir, { recursive: true });
|
|
10082
|
+
(0, import_node_fs24.mkdirSync)(this.transcriptDataDir, { recursive: true });
|
|
10083
|
+
(0, import_node_fs24.mkdirSync)(this.transcriptsDir, { recursive: true });
|
|
10084
|
+
(0, import_node_fs24.mkdirSync)(this.summariesDir, { recursive: true });
|
|
9820
10085
|
this.loadIndex();
|
|
9821
10086
|
this.logger.info(
|
|
9822
10087
|
`\u5F55\u97F3\u5B58\u50A8\u5DF2\u521D\u59CB\u5316: ${this.dir}\uFF08\u5171 ${this.index.recordings.length} \u6761\u8BB0\u5F55\uFF09`
|
|
@@ -9860,13 +10125,13 @@ var RecordingStorage = class {
|
|
|
9860
10125
|
return id;
|
|
9861
10126
|
}
|
|
9862
10127
|
if (existing.transcriptDataFile) {
|
|
9863
|
-
(0,
|
|
10128
|
+
(0, import_node_fs24.rmSync)((0, import_node_path20.join)(this.dir, existing.transcriptDataFile), { force: true });
|
|
9864
10129
|
}
|
|
9865
10130
|
if (existing.transcriptFile) {
|
|
9866
|
-
(0,
|
|
10131
|
+
(0, import_node_fs24.rmSync)((0, import_node_path20.join)(this.dir, existing.transcriptFile), { force: true });
|
|
9867
10132
|
}
|
|
9868
10133
|
if (existing.summaryFile) {
|
|
9869
|
-
(0,
|
|
10134
|
+
(0, import_node_fs24.rmSync)((0, import_node_path20.join)(this.dir, existing.summaryFile), { force: true });
|
|
9870
10135
|
}
|
|
9871
10136
|
existing.metadata = metadata;
|
|
9872
10137
|
existing.status = "syncing_openclaw";
|
|
@@ -9934,7 +10199,7 @@ var RecordingStorage = class {
|
|
|
9934
10199
|
if (!entry) return;
|
|
9935
10200
|
const nextTranscriptDataFile = `${TRANSCRIPT_DATA_DIR}/${filename}`;
|
|
9936
10201
|
if (entry.transcriptDataFile && entry.transcriptDataFile !== nextTranscriptDataFile) {
|
|
9937
|
-
(0,
|
|
10202
|
+
(0, import_node_fs24.rmSync)((0, import_node_path20.join)(this.dir, entry.transcriptDataFile), { force: true });
|
|
9938
10203
|
}
|
|
9939
10204
|
entry.transcriptDataFile = nextTranscriptDataFile;
|
|
9940
10205
|
entry.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
@@ -9948,7 +10213,7 @@ var RecordingStorage = class {
|
|
|
9948
10213
|
if (!entry) return;
|
|
9949
10214
|
const nextTranscriptFile = `${TRANSCRIPTS_DIR}/${filename}`;
|
|
9950
10215
|
if (entry.transcriptFile && entry.transcriptFile !== nextTranscriptFile) {
|
|
9951
|
-
(0,
|
|
10216
|
+
(0, import_node_fs24.rmSync)((0, import_node_path20.join)(this.dir, entry.transcriptFile), { force: true });
|
|
9952
10217
|
}
|
|
9953
10218
|
entry.transcriptFile = nextTranscriptFile;
|
|
9954
10219
|
entry.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
@@ -9962,7 +10227,7 @@ var RecordingStorage = class {
|
|
|
9962
10227
|
if (!entry) return;
|
|
9963
10228
|
const nextSummaryFile = `${SUMMARIES_DIR}/${filename}`;
|
|
9964
10229
|
if (entry.summaryFile && entry.summaryFile !== nextSummaryFile) {
|
|
9965
|
-
(0,
|
|
10230
|
+
(0, import_node_fs24.rmSync)((0, import_node_path20.join)(this.dir, entry.summaryFile), { force: true });
|
|
9966
10231
|
}
|
|
9967
10232
|
entry.summaryFile = nextSummaryFile;
|
|
9968
10233
|
entry.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
@@ -10060,24 +10325,24 @@ var RecordingStorage = class {
|
|
|
10060
10325
|
const entry = this.findById(recordingId);
|
|
10061
10326
|
if (!entry) return false;
|
|
10062
10327
|
if (entry.audioFile) {
|
|
10063
|
-
const audioPath = (0,
|
|
10064
|
-
(0,
|
|
10328
|
+
const audioPath = (0, import_node_path20.join)(this.dir, entry.audioFile);
|
|
10329
|
+
(0, import_node_fs24.rmSync)(audioPath, { force: true });
|
|
10065
10330
|
}
|
|
10066
10331
|
if (entry.srtFile) {
|
|
10067
|
-
const srtPath = (0,
|
|
10068
|
-
(0,
|
|
10332
|
+
const srtPath = (0, import_node_path20.join)(this.dir, entry.srtFile);
|
|
10333
|
+
(0, import_node_fs24.rmSync)(srtPath, { force: true });
|
|
10069
10334
|
}
|
|
10070
10335
|
if (entry.transcriptDataFile) {
|
|
10071
|
-
const transcriptDataPath = (0,
|
|
10072
|
-
(0,
|
|
10336
|
+
const transcriptDataPath = (0, import_node_path20.join)(this.dir, entry.transcriptDataFile);
|
|
10337
|
+
(0, import_node_fs24.rmSync)(transcriptDataPath, { force: true });
|
|
10073
10338
|
}
|
|
10074
10339
|
if (entry.transcriptFile) {
|
|
10075
|
-
const transcriptPath = (0,
|
|
10076
|
-
(0,
|
|
10340
|
+
const transcriptPath = (0, import_node_path20.join)(this.dir, entry.transcriptFile);
|
|
10341
|
+
(0, import_node_fs24.rmSync)(transcriptPath, { force: true });
|
|
10077
10342
|
}
|
|
10078
10343
|
if (entry.summaryFile) {
|
|
10079
|
-
const summaryPath = (0,
|
|
10080
|
-
(0,
|
|
10344
|
+
const summaryPath = (0, import_node_path20.join)(this.dir, entry.summaryFile);
|
|
10345
|
+
(0, import_node_fs24.rmSync)(summaryPath, { force: true });
|
|
10081
10346
|
}
|
|
10082
10347
|
if (opts?.localOnly) {
|
|
10083
10348
|
entry.audioFile = void 0;
|
|
@@ -10135,34 +10400,34 @@ var RecordingStorage = class {
|
|
|
10135
10400
|
* 获取音频文件的绝对路径。ossUrl 用于推断文件扩展名
|
|
10136
10401
|
*/
|
|
10137
10402
|
getAudioFilePath(recordingId, ossUrl) {
|
|
10138
|
-
return (0,
|
|
10403
|
+
return (0, import_node_path20.join)(this.audioDir, this.buildAudioFilename(recordingId, ossUrl));
|
|
10139
10404
|
}
|
|
10140
10405
|
/**
|
|
10141
10406
|
* 获取打点文件的绝对路径
|
|
10142
10407
|
*/
|
|
10143
10408
|
getSrtFilePath(recordingId) {
|
|
10144
|
-
return (0,
|
|
10409
|
+
return (0, import_node_path20.join)(this.audioDir, this.buildSrtFilename(recordingId));
|
|
10145
10410
|
}
|
|
10146
10411
|
/**
|
|
10147
10412
|
* 获取转写 JSON 文件的绝对路径
|
|
10148
10413
|
*/
|
|
10149
10414
|
getTranscriptDataFilePath(recordingId) {
|
|
10150
|
-
return (0,
|
|
10415
|
+
return (0, import_node_path20.join)(this.transcriptDataDir, this.buildTranscriptDataFilename(recordingId));
|
|
10151
10416
|
}
|
|
10152
10417
|
/**
|
|
10153
10418
|
* 获取摘要文件的绝对路径
|
|
10154
10419
|
*/
|
|
10155
10420
|
getSummaryFilePath(recordingId) {
|
|
10156
|
-
return (0,
|
|
10421
|
+
return (0, import_node_path20.join)(this.summariesDir, this.buildSummaryFilename(recordingId));
|
|
10157
10422
|
}
|
|
10158
10423
|
// ─── Persistence ───
|
|
10159
10424
|
loadIndex() {
|
|
10160
|
-
if (!(0,
|
|
10425
|
+
if (!(0, import_node_fs24.existsSync)(this.indexPath)) {
|
|
10161
10426
|
this.index = { recordings: [] };
|
|
10162
10427
|
return;
|
|
10163
10428
|
}
|
|
10164
10429
|
try {
|
|
10165
|
-
const raw = JSON.parse((0,
|
|
10430
|
+
const raw = JSON.parse((0, import_node_fs24.readFileSync)(this.indexPath, "utf-8"));
|
|
10166
10431
|
if (raw && Array.isArray(raw.recordings)) {
|
|
10167
10432
|
let needsRewrite = false;
|
|
10168
10433
|
const normalized = raw.recordings.filter((entry) => entry && typeof entry === "object").map((entry) => {
|
|
@@ -10200,8 +10465,8 @@ var RecordingStorage = class {
|
|
|
10200
10465
|
segments: []
|
|
10201
10466
|
});
|
|
10202
10467
|
const transcriptDataFilename = this.buildTranscriptDataFilename(compacted.id);
|
|
10203
|
-
(0,
|
|
10204
|
-
(0,
|
|
10468
|
+
(0, import_node_fs24.writeFileSync)(
|
|
10469
|
+
(0, import_node_path20.join)(this.transcriptDataDir, transcriptDataFilename),
|
|
10205
10470
|
JSON.stringify(transcriptDoc, null, 2),
|
|
10206
10471
|
"utf-8"
|
|
10207
10472
|
);
|
|
@@ -10213,8 +10478,8 @@ var RecordingStorage = class {
|
|
|
10213
10478
|
compacted.summaryFile = entry.summaryFile;
|
|
10214
10479
|
} else if (typeof entry.summary === "string" && entry.summary.trim()) {
|
|
10215
10480
|
const summaryFilename = this.buildSummaryFilename(entry.id);
|
|
10216
|
-
(0,
|
|
10217
|
-
(0,
|
|
10481
|
+
(0, import_node_fs24.writeFileSync)(
|
|
10482
|
+
(0, import_node_path20.join)(this.summariesDir, summaryFilename),
|
|
10218
10483
|
entry.summary.trim(),
|
|
10219
10484
|
"utf-8"
|
|
10220
10485
|
);
|
|
@@ -10224,8 +10489,8 @@ var RecordingStorage = class {
|
|
|
10224
10489
|
const summaryFromDocument = extractTranscriptSummaryFromDocument(transcriptDoc);
|
|
10225
10490
|
if (summaryFromDocument) {
|
|
10226
10491
|
const summaryFilename = this.buildSummaryFilename(entry.id);
|
|
10227
|
-
(0,
|
|
10228
|
-
(0,
|
|
10492
|
+
(0, import_node_fs24.writeFileSync)(
|
|
10493
|
+
(0, import_node_path20.join)(this.summariesDir, summaryFilename),
|
|
10229
10494
|
summaryFromDocument,
|
|
10230
10495
|
"utf-8"
|
|
10231
10496
|
);
|
|
@@ -10265,7 +10530,7 @@ var RecordingStorage = class {
|
|
|
10265
10530
|
}
|
|
10266
10531
|
readRelativeTextFile(relativePath) {
|
|
10267
10532
|
try {
|
|
10268
|
-
return (0,
|
|
10533
|
+
return (0, import_node_fs24.readFileSync)((0, import_node_path20.join)(this.dir, relativePath), "utf-8");
|
|
10269
10534
|
} catch {
|
|
10270
10535
|
return void 0;
|
|
10271
10536
|
}
|
|
@@ -10278,7 +10543,7 @@ var RecordingStorage = class {
|
|
|
10278
10543
|
return parseTranscriptDocument(raw);
|
|
10279
10544
|
}
|
|
10280
10545
|
saveIndex() {
|
|
10281
|
-
(0,
|
|
10546
|
+
(0, import_node_fs24.writeFileSync)(
|
|
10282
10547
|
this.indexPath,
|
|
10283
10548
|
JSON.stringify(this.index, null, 2),
|
|
10284
10549
|
"utf-8"
|
|
@@ -10292,8 +10557,8 @@ var RecordingStorage = class {
|
|
|
10292
10557
|
init_transcript_document();
|
|
10293
10558
|
|
|
10294
10559
|
// src/recording/downloader.ts
|
|
10295
|
-
var
|
|
10296
|
-
var
|
|
10560
|
+
var import_node_fs25 = require("fs");
|
|
10561
|
+
var import_node_path21 = require("path");
|
|
10297
10562
|
var import_promises2 = require("stream/promises");
|
|
10298
10563
|
var import_node_stream = require("stream");
|
|
10299
10564
|
var DEFAULT_TIMEOUT_MS = 5 * 60 * 1e3;
|
|
@@ -10303,7 +10568,7 @@ async function downloadFile(url, destPath, logger, options) {
|
|
|
10303
10568
|
const timeoutMs = options?.timeoutMs ?? DEFAULT_TIMEOUT_MS;
|
|
10304
10569
|
const maxRetries = options?.maxRetries ?? DEFAULT_MAX_RETRIES;
|
|
10305
10570
|
const retryBackoffMs = options?.retryBackoffMs ?? DEFAULT_RETRY_BACKOFF_MS;
|
|
10306
|
-
(0,
|
|
10571
|
+
(0, import_node_fs25.mkdirSync)((0, import_node_path21.dirname)(destPath), { recursive: true });
|
|
10307
10572
|
let lastError;
|
|
10308
10573
|
for (let attempt = 1; attempt <= maxRetries; attempt++) {
|
|
10309
10574
|
const startMs = Date.now();
|
|
@@ -10321,11 +10586,11 @@ async function downloadFile(url, destPath, logger, options) {
|
|
|
10321
10586
|
if (!res.body) {
|
|
10322
10587
|
throw new Error("\u54CD\u5E94\u4F53\u4E3A\u7A7A");
|
|
10323
10588
|
}
|
|
10324
|
-
const writeStream = (0,
|
|
10589
|
+
const writeStream = (0, import_node_fs25.createWriteStream)(destPath);
|
|
10325
10590
|
const readable = import_node_stream.Readable.fromWeb(res.body);
|
|
10326
10591
|
await (0, import_promises2.pipeline)(readable, writeStream);
|
|
10327
10592
|
const elapsed = Date.now() - startMs;
|
|
10328
|
-
const fileSize = (0,
|
|
10593
|
+
const fileSize = (0, import_node_fs25.existsSync)(destPath) ? (0, import_node_fs25.statSync)(destPath).size : 0;
|
|
10329
10594
|
logger.info(
|
|
10330
10595
|
`[downloader] \u4E0B\u8F7D\u5B8C\u6210: ${destPath} (${formatBytes(fileSize)}, ${elapsed}ms)`
|
|
10331
10596
|
);
|
|
@@ -10333,13 +10598,13 @@ async function downloadFile(url, destPath, logger, options) {
|
|
|
10333
10598
|
} finally {
|
|
10334
10599
|
clearTimeout(timer);
|
|
10335
10600
|
}
|
|
10336
|
-
} catch (
|
|
10337
|
-
lastError =
|
|
10601
|
+
} catch (err2) {
|
|
10602
|
+
lastError = err2?.message ?? String(err2);
|
|
10338
10603
|
try {
|
|
10339
|
-
if ((0,
|
|
10604
|
+
if ((0, import_node_fs25.existsSync)(destPath)) (0, import_node_fs25.unlinkSync)(destPath);
|
|
10340
10605
|
} catch {
|
|
10341
10606
|
}
|
|
10342
|
-
const isAbort =
|
|
10607
|
+
const isAbort = err2?.name === "AbortError";
|
|
10343
10608
|
logger.warn(
|
|
10344
10609
|
`[downloader] \u4E0B\u8F7D\u5931\u8D25 (attempt ${attempt}/${maxRetries}): ${isAbort ? "\u8D85\u65F6" : lastError}`
|
|
10345
10610
|
);
|
|
@@ -10401,9 +10666,9 @@ function emitRecordingStatus(recordingId, storage, logger, notifyStatus, error,
|
|
|
10401
10666
|
updatedAt: entry.updatedAt,
|
|
10402
10667
|
error
|
|
10403
10668
|
});
|
|
10404
|
-
} catch (
|
|
10669
|
+
} catch (err2) {
|
|
10405
10670
|
logger.error(
|
|
10406
|
-
`[recording-status] \u72B6\u6001\u4E8B\u4EF6\u53D1\u9001\u5931\u8D25: ${recordingId}, ${
|
|
10671
|
+
`[recording-status] \u72B6\u6001\u4E8B\u4EF6\u53D1\u9001\u5931\u8D25: ${recordingId}, ${err2?.message ?? err2}`
|
|
10407
10672
|
);
|
|
10408
10673
|
}
|
|
10409
10674
|
}
|
|
@@ -10467,8 +10732,8 @@ async function handleRecordingSync(recordingId, metadata, storage, asrConfig, lo
|
|
|
10467
10732
|
asrConfig,
|
|
10468
10733
|
logger,
|
|
10469
10734
|
options
|
|
10470
|
-
).catch((
|
|
10471
|
-
const error = `\u5F55\u97F3\u540C\u6B65\u5931\u8D25: ${
|
|
10735
|
+
).catch((err2) => {
|
|
10736
|
+
const error = `\u5F55\u97F3\u540C\u6B65\u5931\u8D25: ${err2?.message ?? err2}`;
|
|
10472
10737
|
logger.error(`[recording-sync] ${error}: ${recordingId}`);
|
|
10473
10738
|
emitRecordingStatus(
|
|
10474
10739
|
recordingId,
|
|
@@ -10491,9 +10756,9 @@ async function handleRecordingSync(recordingId, metadata, storage, asrConfig, lo
|
|
|
10491
10756
|
asrConfig,
|
|
10492
10757
|
logger,
|
|
10493
10758
|
options
|
|
10494
|
-
).catch((
|
|
10759
|
+
).catch((err2) => {
|
|
10495
10760
|
logger.error(
|
|
10496
|
-
`[asr-trigger] \u8F6C\u5199\u89E6\u53D1\u5931\u8D25: ${recordingId}, ${
|
|
10761
|
+
`[asr-trigger] \u8F6C\u5199\u89E6\u53D1\u5931\u8D25: ${recordingId}, ${err2?.message ?? err2}`
|
|
10497
10762
|
);
|
|
10498
10763
|
});
|
|
10499
10764
|
}
|
|
@@ -10574,13 +10839,13 @@ async function triggerTranscription(recordingId, storage, asrConfig, logger, opt
|
|
|
10574
10839
|
}
|
|
10575
10840
|
|
|
10576
10841
|
// src/tunnel/service.ts
|
|
10577
|
-
var
|
|
10578
|
-
var
|
|
10842
|
+
var import_node_fs30 = require("fs");
|
|
10843
|
+
var import_node_path26 = require("path");
|
|
10579
10844
|
init_credentials();
|
|
10580
10845
|
|
|
10581
10846
|
// src/tunnel/relay-client.ts
|
|
10582
|
-
var
|
|
10583
|
-
var
|
|
10847
|
+
var import_node_fs28 = require("fs");
|
|
10848
|
+
var import_node_path24 = require("path");
|
|
10584
10849
|
|
|
10585
10850
|
// node_modules/.pnpm/ws@8.19.0/node_modules/ws/wrapper.mjs
|
|
10586
10851
|
var import_stream = __toESM(require_stream(), 1);
|
|
@@ -10624,8 +10889,8 @@ var RelayClient = class {
|
|
|
10624
10889
|
lastDisconnectReason
|
|
10625
10890
|
};
|
|
10626
10891
|
try {
|
|
10627
|
-
(0,
|
|
10628
|
-
(0,
|
|
10892
|
+
(0, import_node_fs28.mkdirSync)((0, import_node_path24.dirname)(this.opts.statusFilePath), { recursive: true });
|
|
10893
|
+
(0, import_node_fs28.writeFileSync)(this.opts.statusFilePath, JSON.stringify(info, null, 2));
|
|
10629
10894
|
} catch {
|
|
10630
10895
|
}
|
|
10631
10896
|
}
|
|
@@ -10829,9 +11094,9 @@ var RelayClient = class {
|
|
|
10829
11094
|
}
|
|
10830
11095
|
settle();
|
|
10831
11096
|
});
|
|
10832
|
-
ws.on("error", (
|
|
11097
|
+
ws.on("error", (err2) => {
|
|
10833
11098
|
this.opts.logger.error(
|
|
10834
|
-
`Relay tunnel: WebSocket error: ${
|
|
11099
|
+
`Relay tunnel: WebSocket error: ${err2.message} (readyState=${ws.readyState}, reconnectAttempt=${this.reconnectAttempt}, url=${wsUrl.toString()})`
|
|
10835
11100
|
);
|
|
10836
11101
|
settle();
|
|
10837
11102
|
});
|
|
@@ -10839,9 +11104,9 @@ var RelayClient = class {
|
|
|
10839
11104
|
}
|
|
10840
11105
|
emitConnected() {
|
|
10841
11106
|
for (const handler of this.connectedHandlers) {
|
|
10842
|
-
Promise.resolve(handler()).catch((
|
|
11107
|
+
Promise.resolve(handler()).catch((err2) => {
|
|
10843
11108
|
this.opts.logger.warn(
|
|
10844
|
-
`Relay tunnel: onConnected handler failed: ${
|
|
11109
|
+
`Relay tunnel: onConnected handler failed: ${err2 instanceof Error ? err2.message : String(err2)}`
|
|
10845
11110
|
);
|
|
10846
11111
|
});
|
|
10847
11112
|
}
|
|
@@ -10869,12 +11134,12 @@ var RelayClient = class {
|
|
|
10869
11134
|
try {
|
|
10870
11135
|
const result = handler(frame);
|
|
10871
11136
|
if (result instanceof Promise) {
|
|
10872
|
-
result.catch((
|
|
10873
|
-
this.opts.logger.error(`Relay tunnel: handler error: ${
|
|
11137
|
+
result.catch((err2) => {
|
|
11138
|
+
this.opts.logger.error(`Relay tunnel: handler error: ${err2}`);
|
|
10874
11139
|
});
|
|
10875
11140
|
}
|
|
10876
|
-
} catch (
|
|
10877
|
-
this.opts.logger.error(`Relay tunnel: handler error: ${
|
|
11141
|
+
} catch (err2) {
|
|
11142
|
+
this.opts.logger.error(`Relay tunnel: handler error: ${err2}`);
|
|
10878
11143
|
}
|
|
10879
11144
|
}
|
|
10880
11145
|
}
|
|
@@ -10963,8 +11228,8 @@ var RelayClient = class {
|
|
|
10963
11228
|
this.reconnectTimer = setTimeout(() => {
|
|
10964
11229
|
this.reconnectTimer = null;
|
|
10965
11230
|
if (!this.aborted) {
|
|
10966
|
-
this.connect().catch((
|
|
10967
|
-
this.opts.logger.error(`Relay tunnel: reconnect failed: ${
|
|
11231
|
+
this.connect().catch((err2) => {
|
|
11232
|
+
this.opts.logger.error(`Relay tunnel: reconnect failed: ${err2}`);
|
|
10968
11233
|
});
|
|
10969
11234
|
}
|
|
10970
11235
|
}, delayMs);
|
|
@@ -11007,8 +11272,8 @@ init_host();
|
|
|
11007
11272
|
|
|
11008
11273
|
// src/tunnel/device-identity.ts
|
|
11009
11274
|
var import_node_crypto3 = __toESM(require("crypto"), 1);
|
|
11010
|
-
var
|
|
11011
|
-
var
|
|
11275
|
+
var import_node_fs29 = __toESM(require("fs"), 1);
|
|
11276
|
+
var import_node_path25 = __toESM(require("path"), 1);
|
|
11012
11277
|
init_host();
|
|
11013
11278
|
var ED25519_SPKI_PREFIX = Buffer.from("302a300506032b6570032100", "hex");
|
|
11014
11279
|
function base64UrlEncode(buf) {
|
|
@@ -11053,10 +11318,10 @@ function resolveClientStateDir(stateDir) {
|
|
|
11053
11318
|
return stateDir ?? resolveStateDir();
|
|
11054
11319
|
}
|
|
11055
11320
|
function ensureDir(filePath) {
|
|
11056
|
-
|
|
11321
|
+
import_node_fs29.default.mkdirSync(import_node_path25.default.dirname(filePath), { recursive: true });
|
|
11057
11322
|
}
|
|
11058
11323
|
function resolveIdentityPath(stateDir) {
|
|
11059
|
-
return
|
|
11324
|
+
return import_node_path25.default.join(stateDir, "identity", "device.json");
|
|
11060
11325
|
}
|
|
11061
11326
|
function normalizeDeviceAuthRole(role) {
|
|
11062
11327
|
return role.trim();
|
|
@@ -11072,12 +11337,12 @@ function normalizeDeviceAuthScopes(scopes) {
|
|
|
11072
11337
|
return [...out].sort();
|
|
11073
11338
|
}
|
|
11074
11339
|
function resolveDeviceAuthPath(stateDir) {
|
|
11075
|
-
return
|
|
11340
|
+
return import_node_path25.default.join(stateDir, "identity", "device-auth.json");
|
|
11076
11341
|
}
|
|
11077
11342
|
function readDeviceAuthStore(filePath) {
|
|
11078
11343
|
try {
|
|
11079
|
-
if (!
|
|
11080
|
-
const raw =
|
|
11344
|
+
if (!import_node_fs29.default.existsSync(filePath)) return null;
|
|
11345
|
+
const raw = import_node_fs29.default.readFileSync(filePath, "utf8");
|
|
11081
11346
|
const parsed = JSON.parse(raw);
|
|
11082
11347
|
if (parsed?.version !== 1 || typeof parsed.deviceId !== "string") return null;
|
|
11083
11348
|
if (!parsed.tokens || typeof parsed.tokens !== "object") return null;
|
|
@@ -11088,12 +11353,12 @@ function readDeviceAuthStore(filePath) {
|
|
|
11088
11353
|
}
|
|
11089
11354
|
function writeDeviceAuthStore(filePath, store) {
|
|
11090
11355
|
ensureDir(filePath);
|
|
11091
|
-
|
|
11356
|
+
import_node_fs29.default.writeFileSync(filePath, `${JSON.stringify(store, null, 2)}
|
|
11092
11357
|
`, {
|
|
11093
11358
|
mode: 384
|
|
11094
11359
|
});
|
|
11095
11360
|
try {
|
|
11096
|
-
|
|
11361
|
+
import_node_fs29.default.chmodSync(filePath, 384);
|
|
11097
11362
|
} catch {
|
|
11098
11363
|
}
|
|
11099
11364
|
}
|
|
@@ -11140,8 +11405,8 @@ function clearDeviceAuthToken(params) {
|
|
|
11140
11405
|
function loadOrCreateDeviceIdentity(stateDir) {
|
|
11141
11406
|
const filePath = resolveIdentityPath(stateDir);
|
|
11142
11407
|
try {
|
|
11143
|
-
if (
|
|
11144
|
-
const raw =
|
|
11408
|
+
if (import_node_fs29.default.existsSync(filePath)) {
|
|
11409
|
+
const raw = import_node_fs29.default.readFileSync(filePath, "utf8");
|
|
11145
11410
|
const parsed = JSON.parse(raw);
|
|
11146
11411
|
if (parsed?.version === 1 && typeof parsed.deviceId === "string" && typeof parsed.publicKeyPem === "string" && typeof parsed.privateKeyPem === "string") {
|
|
11147
11412
|
const derivedId = fingerprintPublicKey(parsed.publicKeyPem);
|
|
@@ -11162,14 +11427,14 @@ function loadOrCreateDeviceIdentity(stateDir) {
|
|
|
11162
11427
|
publicKeyPem,
|
|
11163
11428
|
privateKeyPem
|
|
11164
11429
|
};
|
|
11165
|
-
|
|
11430
|
+
import_node_fs29.default.mkdirSync(import_node_path25.default.dirname(filePath), { recursive: true });
|
|
11166
11431
|
const stored = {
|
|
11167
11432
|
version: 1,
|
|
11168
11433
|
...identity,
|
|
11169
11434
|
createdAtMs: Date.now()
|
|
11170
11435
|
};
|
|
11171
11436
|
ensureDir(filePath);
|
|
11172
|
-
|
|
11437
|
+
import_node_fs29.default.writeFileSync(filePath, `${JSON.stringify(stored, null, 2)}
|
|
11173
11438
|
`, {
|
|
11174
11439
|
mode: 384
|
|
11175
11440
|
});
|
|
@@ -11272,8 +11537,8 @@ async function handleHttpRequest(opts, frame) {
|
|
|
11272
11537
|
});
|
|
11273
11538
|
return;
|
|
11274
11539
|
}
|
|
11275
|
-
} catch (
|
|
11276
|
-
const message =
|
|
11540
|
+
} catch (err2) {
|
|
11541
|
+
const message = err2 instanceof Error ? err2.message : String(err2);
|
|
11277
11542
|
opts.logger.error(
|
|
11278
11543
|
`TunnelProxy: HTTP id=${frame.id} ${frame.method} ${mappedPath} failed after ${Date.now() - startedAtMs}ms: ${message}`
|
|
11279
11544
|
);
|
|
@@ -11343,15 +11608,15 @@ async function streamResponse(opts, requestId, res, startedAtMs) {
|
|
|
11343
11608
|
state: "end",
|
|
11344
11609
|
data: ""
|
|
11345
11610
|
});
|
|
11346
|
-
} catch (
|
|
11611
|
+
} catch (err2) {
|
|
11347
11612
|
opts.logger.error(
|
|
11348
|
-
`TunnelProxy: stream error id=${requestId} after ${chunkCount} chunks and ${Date.now() - startedAtMs}ms: ${
|
|
11613
|
+
`TunnelProxy: stream error id=${requestId} after ${chunkCount} chunks and ${Date.now() - startedAtMs}ms: ${err2 instanceof Error ? err2.message : String(err2)}`
|
|
11349
11614
|
);
|
|
11350
11615
|
opts.client.send({
|
|
11351
11616
|
type: "proxy_error",
|
|
11352
11617
|
id: requestId,
|
|
11353
11618
|
status: 502,
|
|
11354
|
-
message: `stream error: ${
|
|
11619
|
+
message: `stream error: ${err2 instanceof Error ? err2.message : String(err2)}`
|
|
11355
11620
|
});
|
|
11356
11621
|
}
|
|
11357
11622
|
}
|
|
@@ -11418,27 +11683,27 @@ var WsProxy = class {
|
|
|
11418
11683
|
reason: reason.toString()
|
|
11419
11684
|
});
|
|
11420
11685
|
});
|
|
11421
|
-
ws.on("error", (
|
|
11686
|
+
ws.on("error", (err2) => {
|
|
11422
11687
|
this.opts.logger.warn(
|
|
11423
|
-
`TunnelProxy: WS id=${frame.id} error: ${
|
|
11688
|
+
`TunnelProxy: WS id=${frame.id} error: ${err2.message}, active=${this.connections.size}`
|
|
11424
11689
|
);
|
|
11425
11690
|
this.connections.delete(frame.id);
|
|
11426
11691
|
this.opts.client.send({
|
|
11427
11692
|
type: "ws_close",
|
|
11428
11693
|
id: frame.id,
|
|
11429
11694
|
code: 1011,
|
|
11430
|
-
reason:
|
|
11695
|
+
reason: err2.message
|
|
11431
11696
|
});
|
|
11432
11697
|
});
|
|
11433
|
-
} catch (
|
|
11698
|
+
} catch (err2) {
|
|
11434
11699
|
this.opts.logger.error(
|
|
11435
|
-
`TunnelProxy: WS id=${frame.id} failed to connect: ${
|
|
11700
|
+
`TunnelProxy: WS id=${frame.id} failed to connect: ${err2 instanceof Error ? err2.message : String(err2)}`
|
|
11436
11701
|
);
|
|
11437
11702
|
this.opts.client.send({
|
|
11438
11703
|
type: "ws_close",
|
|
11439
11704
|
id: frame.id,
|
|
11440
11705
|
code: 1011,
|
|
11441
|
-
reason: `failed to connect: ${
|
|
11706
|
+
reason: `failed to connect: ${err2 instanceof Error ? err2.message : String(err2)}`
|
|
11442
11707
|
});
|
|
11443
11708
|
}
|
|
11444
11709
|
}
|
|
@@ -11478,19 +11743,19 @@ var WsProxy = class {
|
|
|
11478
11743
|
var MAX_AUTO_PAIRING_APPROVALS = 3;
|
|
11479
11744
|
var approveDevicePairingPromise = null;
|
|
11480
11745
|
var approveDevicePairingWarned = false;
|
|
11481
|
-
function formatErrorMessage(
|
|
11482
|
-
if (
|
|
11483
|
-
return String(
|
|
11746
|
+
function formatErrorMessage(err2) {
|
|
11747
|
+
if (err2 instanceof Error && err2.message) return err2.message;
|
|
11748
|
+
return String(err2);
|
|
11484
11749
|
}
|
|
11485
11750
|
async function loadApproveDevicePairing(logger) {
|
|
11486
11751
|
if (!approveDevicePairingPromise) {
|
|
11487
11752
|
approveDevicePairingPromise = import("openclaw/plugin-sdk/device-bootstrap").then(
|
|
11488
11753
|
(mod) => typeof mod.approveDevicePairing === "function" ? mod.approveDevicePairing : null
|
|
11489
|
-
).catch((
|
|
11754
|
+
).catch((err2) => {
|
|
11490
11755
|
if (!approveDevicePairingWarned) {
|
|
11491
11756
|
approveDevicePairingWarned = true;
|
|
11492
11757
|
logger.warn(
|
|
11493
|
-
`TunnelProxy: local gateway auto-pairing disabled because current OpenClaw runtime does not expose device bootstrap SDK (${formatErrorMessage(
|
|
11758
|
+
`TunnelProxy: local gateway auto-pairing disabled because current OpenClaw runtime does not expose device bootstrap SDK (${formatErrorMessage(err2)})`
|
|
11494
11759
|
);
|
|
11495
11760
|
}
|
|
11496
11761
|
return null;
|
|
@@ -11715,9 +11980,9 @@ var TunnelProxy = class {
|
|
|
11715
11980
|
`TunnelProxy: auto-approved local gateway pairing request ${requestId} (reason=${reason || "not-paired"}, hostStateDir=${this.hostStateDir}, approval=${this.gatewayWsAutoPairingApprovals}/${MAX_AUTO_PAIRING_APPROVALS})`
|
|
11716
11981
|
);
|
|
11717
11982
|
return true;
|
|
11718
|
-
} catch (
|
|
11983
|
+
} catch (err2) {
|
|
11719
11984
|
this.opts.logger.warn(
|
|
11720
|
-
`TunnelProxy: failed to auto-approve gateway pairing request ${requestId}: ${
|
|
11985
|
+
`TunnelProxy: failed to auto-approve gateway pairing request ${requestId}: ${err2?.message ?? String(err2)}`
|
|
11721
11986
|
);
|
|
11722
11987
|
return false;
|
|
11723
11988
|
}
|
|
@@ -11831,9 +12096,9 @@ var TunnelProxy = class {
|
|
|
11831
12096
|
queueMicrotask(() => this.ensureGatewayWs());
|
|
11832
12097
|
}
|
|
11833
12098
|
});
|
|
11834
|
-
ws.on("error", (
|
|
12099
|
+
ws.on("error", (err2) => {
|
|
11835
12100
|
this.opts.logger.warn(
|
|
11836
|
-
`TunnelProxy: RPC WS error: ${
|
|
12101
|
+
`TunnelProxy: RPC WS error: ${err2.message} (ready=${this.gatewayWsReady}, pending=${this.gatewayWsPending.length}, activeWs=${this.wsProxy.activeCount})`
|
|
11837
12102
|
);
|
|
11838
12103
|
this.gatewayWsConnecting = false;
|
|
11839
12104
|
if (this.gatewayWs === ws) {
|
|
@@ -11933,13 +12198,13 @@ function createTunnelService(opts) {
|
|
|
11933
12198
|
try {
|
|
11934
12199
|
process.kill(pid, 0);
|
|
11935
12200
|
return true;
|
|
11936
|
-
} catch (
|
|
11937
|
-
return
|
|
12201
|
+
} catch (err2) {
|
|
12202
|
+
return err2?.code === "EPERM";
|
|
11938
12203
|
}
|
|
11939
12204
|
}
|
|
11940
12205
|
function readLockOwner(filePath) {
|
|
11941
12206
|
try {
|
|
11942
|
-
const parsed = JSON.parse((0,
|
|
12207
|
+
const parsed = JSON.parse((0, import_node_fs30.readFileSync)(filePath, "utf-8"));
|
|
11943
12208
|
return typeof parsed.pid === "number" ? parsed.pid : null;
|
|
11944
12209
|
} catch {
|
|
11945
12210
|
return null;
|
|
@@ -11952,23 +12217,23 @@ function createTunnelService(opts) {
|
|
|
11952
12217
|
lockFd = null;
|
|
11953
12218
|
if (fd !== null) {
|
|
11954
12219
|
try {
|
|
11955
|
-
(0,
|
|
12220
|
+
(0, import_node_fs30.closeSync)(fd);
|
|
11956
12221
|
} catch {
|
|
11957
12222
|
}
|
|
11958
12223
|
}
|
|
11959
12224
|
if (filePath) {
|
|
11960
12225
|
try {
|
|
11961
|
-
(0,
|
|
12226
|
+
(0, import_node_fs30.unlinkSync)(filePath);
|
|
11962
12227
|
} catch {
|
|
11963
12228
|
}
|
|
11964
12229
|
}
|
|
11965
12230
|
}
|
|
11966
12231
|
function acquireLock(filePath) {
|
|
11967
|
-
(0,
|
|
12232
|
+
(0, import_node_fs30.mkdirSync)((0, import_node_path26.dirname)(filePath), { recursive: true });
|
|
11968
12233
|
for (let attempt = 0; attempt < 2; attempt++) {
|
|
11969
12234
|
try {
|
|
11970
|
-
const fd = (0,
|
|
11971
|
-
(0,
|
|
12235
|
+
const fd = (0, import_node_fs30.openSync)(filePath, "wx", 384);
|
|
12236
|
+
(0, import_node_fs30.writeFileSync)(
|
|
11972
12237
|
fd,
|
|
11973
12238
|
JSON.stringify({
|
|
11974
12239
|
pid: process.pid,
|
|
@@ -11979,10 +12244,10 @@ function createTunnelService(opts) {
|
|
|
11979
12244
|
lockFilePath = filePath;
|
|
11980
12245
|
lockFd = fd;
|
|
11981
12246
|
return true;
|
|
11982
|
-
} catch (
|
|
11983
|
-
if (
|
|
12247
|
+
} catch (err2) {
|
|
12248
|
+
if (err2?.code !== "EEXIST") {
|
|
11984
12249
|
opts.logger.error(
|
|
11985
|
-
`Relay tunnel: failed to acquire local lock ${filePath}: ${String(
|
|
12250
|
+
`Relay tunnel: failed to acquire local lock ${filePath}: ${String(err2)}`
|
|
11986
12251
|
);
|
|
11987
12252
|
return false;
|
|
11988
12253
|
}
|
|
@@ -11992,7 +12257,7 @@ function createTunnelService(opts) {
|
|
|
11992
12257
|
`Relay tunnel: removing stale local lock owned by dead pid=${ownerPid}`
|
|
11993
12258
|
);
|
|
11994
12259
|
try {
|
|
11995
|
-
(0,
|
|
12260
|
+
(0, import_node_fs30.unlinkSync)(filePath);
|
|
11996
12261
|
} catch {
|
|
11997
12262
|
}
|
|
11998
12263
|
continue;
|
|
@@ -12037,12 +12302,12 @@ function createTunnelService(opts) {
|
|
|
12037
12302
|
return;
|
|
12038
12303
|
}
|
|
12039
12304
|
const { logger } = opts;
|
|
12040
|
-
const baseStateDir = (0,
|
|
12305
|
+
const baseStateDir = (0, import_node_path26.join)(ctx.stateDir, "plugins", "phone-notifications");
|
|
12041
12306
|
logger.info(
|
|
12042
12307
|
`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})`
|
|
12043
12308
|
);
|
|
12044
|
-
const statusFilePath = (0,
|
|
12045
|
-
const lockPath = (0,
|
|
12309
|
+
const statusFilePath = (0, import_node_path26.join)(baseStateDir, "tunnel-status.json");
|
|
12310
|
+
const lockPath = (0, import_node_path26.join)(baseStateDir, "relay-tunnel.lock");
|
|
12046
12311
|
if (!acquireLock(lockPath)) {
|
|
12047
12312
|
return;
|
|
12048
12313
|
}
|
|
@@ -12070,14 +12335,14 @@ function createTunnelService(opts) {
|
|
|
12070
12335
|
emitPendingPluginUpdate("relay connected");
|
|
12071
12336
|
});
|
|
12072
12337
|
abortController = new AbortController();
|
|
12073
|
-
client.connectWithAutoReconnect(abortController.signal).catch((
|
|
12338
|
+
client.connectWithAutoReconnect(abortController.signal).catch((err2) => {
|
|
12074
12339
|
releaseLock();
|
|
12075
|
-
logger.error(`Relay tunnel: unexpected error: ${
|
|
12340
|
+
logger.error(`Relay tunnel: unexpected error: ${err2}`);
|
|
12076
12341
|
});
|
|
12077
12342
|
logger.info("Relay tunnel \u670D\u52A1\u5DF2\u542F\u52A8");
|
|
12078
|
-
} catch (
|
|
12343
|
+
} catch (err2) {
|
|
12079
12344
|
releaseLock();
|
|
12080
|
-
throw
|
|
12345
|
+
throw err2;
|
|
12081
12346
|
}
|
|
12082
12347
|
},
|
|
12083
12348
|
async stop() {
|
|
@@ -12182,9 +12447,9 @@ function readHostGatewayConfig(params) {
|
|
|
12182
12447
|
let configData;
|
|
12183
12448
|
if (configPath) {
|
|
12184
12449
|
try {
|
|
12185
|
-
configData = JSON.parse((0,
|
|
12186
|
-
} catch (
|
|
12187
|
-
if (
|
|
12450
|
+
configData = JSON.parse((0, import_node_fs31.readFileSync)(configPath, "utf-8"));
|
|
12451
|
+
} catch (err2) {
|
|
12452
|
+
if (err2?.code !== "ENOENT") {
|
|
12188
12453
|
params.logger.warn(
|
|
12189
12454
|
`Relay tunnel: \u65E0\u6CD5\u8BFB\u53D6 gateway \u9274\u6743\u914D\u7F6E (${configPath})`
|
|
12190
12455
|
);
|
|
@@ -12294,7 +12559,7 @@ function registerNotificationInterfaces(deps) {
|
|
|
12294
12559
|
} = deps;
|
|
12295
12560
|
function triggerAfterIngest(insertedCount, cron) {
|
|
12296
12561
|
if (insertedCount <= 0 || !onAfterIngest) return;
|
|
12297
|
-
void Promise.resolve().then(() => onAfterIngest(insertedCount, cron)).catch((
|
|
12562
|
+
void Promise.resolve().then(() => onAfterIngest(insertedCount, cron)).catch((err2) => logger.warn(`onAfterIngest failed: ${err2?.message ?? err2}`));
|
|
12298
12563
|
}
|
|
12299
12564
|
registerGatewayMethod(
|
|
12300
12565
|
"notifications.push",
|
|
@@ -12687,9 +12952,9 @@ function registerRecordingInterfaces(deps) {
|
|
|
12687
12952
|
}
|
|
12688
12953
|
triggerTranscription(recordingId, recordingStorage, asr, logger, {
|
|
12689
12954
|
notifyStatus: notifyRecordingStatus
|
|
12690
|
-
}).catch((
|
|
12955
|
+
}).catch((err2) => {
|
|
12691
12956
|
logger.error(
|
|
12692
|
-
`[recordings.retranscribe] \u91CD\u8BD5\u8F6C\u5199\u5931\u8D25: ${recordingId}, ${
|
|
12957
|
+
`[recordings.retranscribe] \u91CD\u8BD5\u8F6C\u5199\u5931\u8D25: ${recordingId}, ${err2?.message ?? err2}`
|
|
12693
12958
|
);
|
|
12694
12959
|
});
|
|
12695
12960
|
respond(true, { ok: true, recordingId, message: "\u8F6C\u5199\u5DF2\u91CD\u65B0\u89E6\u53D1" });
|
|
@@ -12935,8 +13200,9 @@ var index_default = {
|
|
|
12935
13200
|
tunnelService
|
|
12936
13201
|
});
|
|
12937
13202
|
registerLightRulesGateway(api, lightRuleRegistry, logger, cacheBroadcast);
|
|
13203
|
+
registerLightRulesTools(api, lightRuleRegistry, logger);
|
|
12938
13204
|
logger.info(
|
|
12939
|
-
"
|
|
13205
|
+
"\u706F\u6548\u89C4\u5219\u65B9\u6CD5\u5DF2\u6CE8\u518C: lightrules.list / lightrules.create / lightrules.update / lightrules.delete"
|
|
12940
13206
|
);
|
|
12941
13207
|
autoUpdateLifecycle = registerAutoUpdateLifecycle({
|
|
12942
13208
|
api,
|