@yoooclaw/phone-notifications 1.11.0-beta.0 → 1.11.0-beta.2
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 +1433 -1626
- package/dist/index.cjs.map +1 -1
- package/package.json +1 -1
- package/skills/notification-to-memory/references/step-2-process-dates.md +15 -17
package/dist/index.cjs
CHANGED
|
@@ -52,24 +52,24 @@ function expandUserPath(value) {
|
|
|
52
52
|
return homeDir();
|
|
53
53
|
}
|
|
54
54
|
if (value.startsWith("~/")) {
|
|
55
|
-
return (0,
|
|
55
|
+
return (0, import_node_path4.join)(homeDir(), value.slice(2));
|
|
56
56
|
}
|
|
57
57
|
return value;
|
|
58
58
|
}
|
|
59
59
|
function candidateMetaPaths() {
|
|
60
60
|
const home = homeDir();
|
|
61
61
|
return [
|
|
62
|
-
(0,
|
|
63
|
-
(0,
|
|
62
|
+
(0, import_node_path4.join)(home, ".qclaw", "qclaw.json"),
|
|
63
|
+
(0, import_node_path4.join)(home, ".qclow", "qclaw.json")
|
|
64
64
|
];
|
|
65
65
|
}
|
|
66
66
|
function loadQClawMeta() {
|
|
67
67
|
for (const metaPath of candidateMetaPaths()) {
|
|
68
|
-
if (!(0,
|
|
68
|
+
if (!(0, import_node_fs5.existsSync)(metaPath)) {
|
|
69
69
|
continue;
|
|
70
70
|
}
|
|
71
71
|
try {
|
|
72
|
-
const parsed = JSON.parse((0,
|
|
72
|
+
const parsed = JSON.parse((0, import_node_fs5.readFileSync)(metaPath, "utf-8"));
|
|
73
73
|
return {
|
|
74
74
|
stateDir: expandUserPath(trimToUndefined(parsed?.stateDir)),
|
|
75
75
|
configPath: expandUserPath(trimToUndefined(parsed?.configPath))
|
|
@@ -90,12 +90,12 @@ function resolveConfigPathFromEnv() {
|
|
|
90
90
|
);
|
|
91
91
|
}
|
|
92
92
|
function hasOpenClawMarkers() {
|
|
93
|
-
const baseDir = (0,
|
|
93
|
+
const baseDir = (0, import_node_path4.join)(homeDir(), ".openclaw");
|
|
94
94
|
return [
|
|
95
|
-
(0,
|
|
96
|
-
(0,
|
|
97
|
-
(0,
|
|
98
|
-
].some((candidate) => (0,
|
|
95
|
+
(0, import_node_path4.join)(baseDir, "openclaw.json"),
|
|
96
|
+
(0, import_node_path4.join)(baseDir, "credentials.json"),
|
|
97
|
+
(0, import_node_path4.join)(baseDir, "extensions")
|
|
98
|
+
].some((candidate) => (0, import_node_fs5.existsSync)(candidate));
|
|
99
99
|
}
|
|
100
100
|
function resolveStateDir() {
|
|
101
101
|
const envDir = resolveStateDirFromEnv();
|
|
@@ -103,16 +103,16 @@ function resolveStateDir() {
|
|
|
103
103
|
return envDir;
|
|
104
104
|
}
|
|
105
105
|
if (hasOpenClawMarkers()) {
|
|
106
|
-
return (0,
|
|
106
|
+
return (0, import_node_path4.join)(homeDir(), ".openclaw");
|
|
107
107
|
}
|
|
108
108
|
const meta = loadQClawMeta();
|
|
109
109
|
if (meta?.stateDir) {
|
|
110
110
|
return meta.stateDir;
|
|
111
111
|
}
|
|
112
112
|
if (meta?.configPath) {
|
|
113
|
-
return (0,
|
|
113
|
+
return (0, import_node_path4.dirname)(meta.configPath);
|
|
114
114
|
}
|
|
115
|
-
return (0,
|
|
115
|
+
return (0, import_node_path4.join)(homeDir(), ".openclaw");
|
|
116
116
|
}
|
|
117
117
|
function resolveConfigPath(stateDir = resolveStateDir()) {
|
|
118
118
|
const envConfigPath = resolveConfigPathFromEnv();
|
|
@@ -123,17 +123,17 @@ function resolveConfigPath(stateDir = resolveStateDir()) {
|
|
|
123
123
|
if (meta?.configPath && (!meta.stateDir || !stateDir || meta.stateDir === stateDir)) {
|
|
124
124
|
return meta.configPath;
|
|
125
125
|
}
|
|
126
|
-
return (0,
|
|
126
|
+
return (0, import_node_path4.join)(stateDir, "openclaw.json");
|
|
127
127
|
}
|
|
128
128
|
function resolveStateFile(filename) {
|
|
129
|
-
return (0,
|
|
129
|
+
return (0, import_node_path4.join)(resolveStateDir(), filename);
|
|
130
130
|
}
|
|
131
|
-
var
|
|
131
|
+
var import_node_fs5, import_node_path4;
|
|
132
132
|
var init_host = __esm({
|
|
133
133
|
"src/host.ts"() {
|
|
134
134
|
"use strict";
|
|
135
|
-
|
|
136
|
-
|
|
135
|
+
import_node_fs5 = require("fs");
|
|
136
|
+
import_node_path4 = require("path");
|
|
137
137
|
}
|
|
138
138
|
});
|
|
139
139
|
|
|
@@ -143,17 +143,17 @@ function credentialsPath() {
|
|
|
143
143
|
}
|
|
144
144
|
function readCredentials() {
|
|
145
145
|
const path2 = credentialsPath();
|
|
146
|
-
if (!(0,
|
|
146
|
+
if (!(0, import_node_fs6.existsSync)(path2)) return {};
|
|
147
147
|
try {
|
|
148
|
-
return JSON.parse((0,
|
|
148
|
+
return JSON.parse((0, import_node_fs6.readFileSync)(path2, "utf-8"));
|
|
149
149
|
} catch {
|
|
150
150
|
return {};
|
|
151
151
|
}
|
|
152
152
|
}
|
|
153
153
|
function writeCredentials(creds) {
|
|
154
154
|
const path2 = credentialsPath();
|
|
155
|
-
(0,
|
|
156
|
-
(0,
|
|
155
|
+
(0, import_node_fs6.mkdirSync)((0, import_node_path5.dirname)(path2), { recursive: true, mode: 448 });
|
|
156
|
+
(0, import_node_fs6.writeFileSync)(path2, JSON.stringify(creds, null, 2), {
|
|
157
157
|
encoding: "utf-8",
|
|
158
158
|
mode: 384
|
|
159
159
|
});
|
|
@@ -173,8 +173,8 @@ function requireApiKey() {
|
|
|
173
173
|
}
|
|
174
174
|
function watchCredentials(onChange) {
|
|
175
175
|
const path2 = credentialsPath();
|
|
176
|
-
const dir = (0,
|
|
177
|
-
const filename = (0,
|
|
176
|
+
const dir = (0, import_node_path5.dirname)(path2);
|
|
177
|
+
const filename = (0, import_node_path5.basename)(path2);
|
|
178
178
|
let debounceTimer = null;
|
|
179
179
|
const delayMs = 200;
|
|
180
180
|
const listener = (_event, changedName) => {
|
|
@@ -187,7 +187,7 @@ function watchCredentials(onChange) {
|
|
|
187
187
|
};
|
|
188
188
|
let watcher = null;
|
|
189
189
|
try {
|
|
190
|
-
watcher = (0,
|
|
190
|
+
watcher = (0, import_node_fs6.watch)(dir, { persistent: false }, listener);
|
|
191
191
|
} catch {
|
|
192
192
|
}
|
|
193
193
|
return () => {
|
|
@@ -195,12 +195,12 @@ function watchCredentials(onChange) {
|
|
|
195
195
|
watcher?.close();
|
|
196
196
|
};
|
|
197
197
|
}
|
|
198
|
-
var
|
|
198
|
+
var import_node_fs6, import_node_path5;
|
|
199
199
|
var init_credentials = __esm({
|
|
200
200
|
"src/auth/credentials.ts"() {
|
|
201
201
|
"use strict";
|
|
202
|
-
|
|
203
|
-
|
|
202
|
+
import_node_fs6 = require("fs");
|
|
203
|
+
import_node_path5 = require("path");
|
|
204
204
|
init_host();
|
|
205
205
|
}
|
|
206
206
|
});
|
|
@@ -208,8 +208,8 @@ var init_credentials = __esm({
|
|
|
208
208
|
// src/env.ts
|
|
209
209
|
function writeDotEnv(key, value) {
|
|
210
210
|
const path2 = resolveStateFile(".env");
|
|
211
|
-
(0,
|
|
212
|
-
const existing = (0,
|
|
211
|
+
(0, import_node_fs7.mkdirSync)((0, import_node_path6.dirname)(path2), { recursive: true });
|
|
212
|
+
const existing = (0, import_node_fs7.existsSync)(path2) ? (0, import_node_fs7.readFileSync)(path2, "utf-8") : "";
|
|
213
213
|
const lines = existing.split("\n");
|
|
214
214
|
const prefix = `${key}=`;
|
|
215
215
|
const idx = lines.findIndex((l) => l.startsWith(prefix));
|
|
@@ -220,13 +220,13 @@ function writeDotEnv(key, value) {
|
|
|
220
220
|
if (existing && !existing.endsWith("\n")) lines.push("");
|
|
221
221
|
lines.push(newLine);
|
|
222
222
|
}
|
|
223
|
-
(0,
|
|
223
|
+
(0, import_node_fs7.writeFileSync)(path2, lines.join("\n"), "utf-8");
|
|
224
224
|
}
|
|
225
225
|
function readDotEnv() {
|
|
226
226
|
const path2 = resolveStateFile(".env");
|
|
227
|
-
if (!(0,
|
|
227
|
+
if (!(0, import_node_fs7.existsSync)(path2)) return {};
|
|
228
228
|
return Object.fromEntries(
|
|
229
|
-
(0,
|
|
229
|
+
(0, import_node_fs7.readFileSync)(path2, "utf-8").split("\n").flatMap((line) => {
|
|
230
230
|
const eq = line.indexOf("=");
|
|
231
231
|
if (eq < 1) return [];
|
|
232
232
|
return [[line.slice(0, eq).trim(), line.slice(eq + 1).trim()]];
|
|
@@ -256,12 +256,12 @@ function getEnvUrls(env) {
|
|
|
256
256
|
function getAvailableEnvs() {
|
|
257
257
|
return Object.keys(ENV_CONFIG);
|
|
258
258
|
}
|
|
259
|
-
var
|
|
259
|
+
var import_node_fs7, import_node_path6, ENV_CONFIG, VALID_ENVS;
|
|
260
260
|
var init_env = __esm({
|
|
261
261
|
"src/env.ts"() {
|
|
262
262
|
"use strict";
|
|
263
|
-
|
|
264
|
-
|
|
263
|
+
import_node_fs7 = require("fs");
|
|
264
|
+
import_node_path6 = require("path");
|
|
265
265
|
init_credentials();
|
|
266
266
|
init_host();
|
|
267
267
|
ENV_CONFIG = {
|
|
@@ -290,7 +290,7 @@ function buildTranscriptDataFilename(recordingId) {
|
|
|
290
290
|
}
|
|
291
291
|
function buildTranscriptDocument(params) {
|
|
292
292
|
const segments = normalizeSegments(params.segments);
|
|
293
|
-
const text =
|
|
293
|
+
const text = normalizePossiblyEmptyText(params.text) ?? joinSegmentsText(segments) ?? "";
|
|
294
294
|
return {
|
|
295
295
|
schemaVersion: TRANSCRIPT_DOCUMENT_SCHEMA_VERSION,
|
|
296
296
|
recordingId: params.recordingId,
|
|
@@ -304,7 +304,7 @@ function buildTranscriptDocument(params) {
|
|
|
304
304
|
normalized: {
|
|
305
305
|
title: normalizeOptionalText(params.title),
|
|
306
306
|
category: normalizeOptionalText(params.category),
|
|
307
|
-
summary:
|
|
307
|
+
summary: normalizePossiblyEmptyText(params.summary),
|
|
308
308
|
text,
|
|
309
309
|
segments
|
|
310
310
|
},
|
|
@@ -335,11 +335,11 @@ function parseTranscriptDocument(value) {
|
|
|
335
335
|
}
|
|
336
336
|
function extractTranscriptTextFromDocument(doc) {
|
|
337
337
|
if (!doc) return void 0;
|
|
338
|
-
return
|
|
338
|
+
return normalizePossiblyEmptyText(doc.normalized.text) ?? joinSegmentsText(doc.normalized.segments);
|
|
339
339
|
}
|
|
340
340
|
function extractTranscriptSummaryFromDocument(doc) {
|
|
341
341
|
if (!doc) return void 0;
|
|
342
|
-
return
|
|
342
|
+
return normalizePossiblyEmptyText(doc.normalized.summary);
|
|
343
343
|
}
|
|
344
344
|
function extractTranscriptTitleFromDocument(doc) {
|
|
345
345
|
if (!doc) return void 0;
|
|
@@ -392,14 +392,14 @@ function normalizeDocumentBody(value) {
|
|
|
392
392
|
}
|
|
393
393
|
const normalized = value;
|
|
394
394
|
const segments = normalizeSegments(normalized.segments);
|
|
395
|
-
const text =
|
|
396
|
-
if (
|
|
395
|
+
const text = normalizePossiblyEmptyText(normalized.text) ?? joinSegmentsText(segments);
|
|
396
|
+
if (text === void 0) {
|
|
397
397
|
return void 0;
|
|
398
398
|
}
|
|
399
399
|
return {
|
|
400
400
|
title: normalizeOptionalText(normalized.title),
|
|
401
401
|
category: normalizeOptionalText(normalized.category),
|
|
402
|
-
summary:
|
|
402
|
+
summary: normalizePossiblyEmptyText(normalized.summary),
|
|
403
403
|
text,
|
|
404
404
|
segments
|
|
405
405
|
};
|
|
@@ -465,6 +465,9 @@ function extractRawSourceTextList(raw) {
|
|
|
465
465
|
function normalizeOptionalText(value) {
|
|
466
466
|
return typeof value === "string" && value.trim() ? value.trim() : void 0;
|
|
467
467
|
}
|
|
468
|
+
function normalizePossiblyEmptyText(value) {
|
|
469
|
+
return typeof value === "string" ? value.trim() : void 0;
|
|
470
|
+
}
|
|
468
471
|
function normalizeOptionalNumber(value) {
|
|
469
472
|
return typeof value === "number" && Number.isFinite(value) ? value : void 0;
|
|
470
473
|
}
|
|
@@ -1342,7 +1345,7 @@ async function runTranscriptionWorkflow(params) {
|
|
|
1342
1345
|
return { ok: false, error: result.error };
|
|
1343
1346
|
}
|
|
1344
1347
|
const title = normalizeOptionalText2(result.summary) ? normalizeOptionalText2(result.summary) : extractSummary(result.text ?? "");
|
|
1345
|
-
const summary =
|
|
1348
|
+
const summary = result.summaryText ?? "";
|
|
1346
1349
|
result.summary = title;
|
|
1347
1350
|
const transcriptData = buildTranscriptDocument({
|
|
1348
1351
|
recordingId,
|
|
@@ -1603,16 +1606,11 @@ function buildLongRecordingSuccessResult(taskId, requestId, data, logger) {
|
|
|
1603
1606
|
);
|
|
1604
1607
|
const listResult = extractLongRecordingTextFromList(sourceTextList);
|
|
1605
1608
|
const sourceText = normalizeOptionalText2(data?.recordResult?.sourceText);
|
|
1606
|
-
const summaryText = normalizeOptionalText2(data?.recordResult?.summaryResult);
|
|
1609
|
+
const summaryText = normalizeOptionalText2(data?.recordResult?.summaryResult) ?? "";
|
|
1607
1610
|
const title = normalizeOptionalText2(data?.recordResult?.title);
|
|
1608
1611
|
const category = normalizeOptionalText2(data?.recordResult?.category);
|
|
1609
1612
|
const text = listResult.text ?? sourceText ?? summaryText;
|
|
1610
|
-
|
|
1611
|
-
return {
|
|
1612
|
-
ok: false,
|
|
1613
|
-
error: "Model Proxy ASR \u4EFB\u52A1\u5DF2\u5B8C\u6210\uFF0C\u4F46\u672A\u8FD4\u56DE sourceTextList\u3001sourceText \u6216 summaryResult"
|
|
1614
|
-
};
|
|
1615
|
-
}
|
|
1613
|
+
const status = normalizeLongRecordingStatus(data?.status) ?? "SUCCEEDED";
|
|
1616
1614
|
if (!listResult.text && !sourceText && summaryText) {
|
|
1617
1615
|
logger.warn(
|
|
1618
1616
|
`[asr] Model Proxy \u957F\u5F55\u97F3\u7ED3\u679C\u7F3A\u5C11 sourceTextList/sourceText\uFF0C\u5DF2\u56DE\u9000\u4F7F\u7528 summaryResult \u4F5C\u4E3A\u8F6C\u5199\u6587\u672C: taskId=${taskId}`
|
|
@@ -1632,7 +1630,7 @@ function buildLongRecordingSuccessResult(taskId, requestId, data, logger) {
|
|
|
1632
1630
|
provider: "model-proxy",
|
|
1633
1631
|
taskId,
|
|
1634
1632
|
requestId,
|
|
1635
|
-
status
|
|
1633
|
+
status
|
|
1636
1634
|
},
|
|
1637
1635
|
rawResponse: data
|
|
1638
1636
|
};
|
|
@@ -5424,7 +5422,7 @@ function readBuildInjectedVersion() {
|
|
|
5424
5422
|
if (false) {
|
|
5425
5423
|
return void 0;
|
|
5426
5424
|
}
|
|
5427
|
-
const version = "1.11.0-beta.
|
|
5425
|
+
const version = "1.11.0-beta.2".trim();
|
|
5428
5426
|
return version || void 0;
|
|
5429
5427
|
}
|
|
5430
5428
|
function readPluginVersionFromPackageJson() {
|
|
@@ -5549,1103 +5547,787 @@ function formatLocalTimestamp(d) {
|
|
|
5549
5547
|
return `${y}-${m}-${day}T${hh}:${mm}:${ss}.${ms}${sign}${offsetHours}:${offsetMins}`;
|
|
5550
5548
|
}
|
|
5551
5549
|
|
|
5552
|
-
// src/
|
|
5550
|
+
// src/light/repeat.ts
|
|
5551
|
+
function normalizeRepeatTimes(input) {
|
|
5552
|
+
if (typeof input === "boolean") {
|
|
5553
|
+
return input ? 0 : 1;
|
|
5554
|
+
}
|
|
5555
|
+
if (typeof input === "number") {
|
|
5556
|
+
return validateRepeatTimes(input);
|
|
5557
|
+
}
|
|
5558
|
+
if (!input) {
|
|
5559
|
+
return 1;
|
|
5560
|
+
}
|
|
5561
|
+
if (input.repeat_times !== void 0) {
|
|
5562
|
+
return validateRepeatTimes(input.repeat_times);
|
|
5563
|
+
}
|
|
5564
|
+
if (input.repeat !== void 0) {
|
|
5565
|
+
return input.repeat ? 0 : 1;
|
|
5566
|
+
}
|
|
5567
|
+
return 1;
|
|
5568
|
+
}
|
|
5569
|
+
function assertAncsRepeatTimes(repeatTimes) {
|
|
5570
|
+
if (repeatTimes !== 0 && repeatTimes !== 1) {
|
|
5571
|
+
throw new Error(
|
|
5572
|
+
"\u5F53\u524D ANCS \u8DEF\u5F84\u4EC5\u652F\u6301 repeat_times=0\uFF08\u65E0\u9650\u5FAA\u73AF\uFF09\u6216 1\uFF08\u64AD\u653E\u4E00\u8F6E\uFF09\uFF1BN>=2 \u9700\u975E ANCS \u8DEF\u5F84"
|
|
5573
|
+
);
|
|
5574
|
+
}
|
|
5575
|
+
}
|
|
5576
|
+
function validateRepeatTimes(value) {
|
|
5577
|
+
if (!Number.isInteger(value) || value < 0) {
|
|
5578
|
+
throw new Error("repeat_times \u5FC5\u987B\u662F >=0 \u7684\u6574\u6570");
|
|
5579
|
+
}
|
|
5580
|
+
return value;
|
|
5581
|
+
}
|
|
5582
|
+
|
|
5583
|
+
// src/cli/helpers.ts
|
|
5553
5584
|
var import_node_fs3 = require("fs");
|
|
5554
|
-
var
|
|
5585
|
+
var import_promises = require("fs/promises");
|
|
5555
5586
|
var import_node_path2 = require("path");
|
|
5556
|
-
|
|
5557
|
-
|
|
5558
|
-
|
|
5559
|
-
|
|
5560
|
-
|
|
5587
|
+
function resolveNotificationsDir(ctx) {
|
|
5588
|
+
if (ctx.stateDir) {
|
|
5589
|
+
const dir = (0, import_node_path2.join)(
|
|
5590
|
+
ctx.stateDir,
|
|
5591
|
+
"plugins",
|
|
5592
|
+
"phone-notifications",
|
|
5593
|
+
"notifications"
|
|
5594
|
+
);
|
|
5595
|
+
if ((0, import_node_fs3.existsSync)(dir)) return dir;
|
|
5596
|
+
}
|
|
5597
|
+
if (ctx.workspaceDir) {
|
|
5598
|
+
const dir = (0, import_node_path2.join)(ctx.workspaceDir, "notifications");
|
|
5599
|
+
if ((0, import_node_fs3.existsSync)(dir)) return dir;
|
|
5600
|
+
}
|
|
5601
|
+
return null;
|
|
5561
5602
|
}
|
|
5562
|
-
function
|
|
5603
|
+
function listDateKeys(dir) {
|
|
5604
|
+
const pattern = /^(\d{4}-\d{2}-\d{2})\.json$/;
|
|
5605
|
+
const keys = [];
|
|
5606
|
+
for (const entry of (0, import_node_fs3.readdirSync)(dir, { withFileTypes: true })) {
|
|
5607
|
+
if (!entry.isFile()) continue;
|
|
5608
|
+
const m = pattern.exec(entry.name);
|
|
5609
|
+
if (m) keys.push(m[1]);
|
|
5610
|
+
}
|
|
5611
|
+
return keys.sort().reverse();
|
|
5612
|
+
}
|
|
5613
|
+
function readDateFile(dir, dateKey) {
|
|
5614
|
+
const filePath = (0, import_node_path2.join)(dir, `${dateKey}.json`);
|
|
5615
|
+
if (!(0, import_node_fs3.existsSync)(filePath)) return [];
|
|
5563
5616
|
try {
|
|
5564
|
-
(0, import_node_fs3.
|
|
5565
|
-
(0, import_node_fs3.accessSync)(dir, import_node_fs3.constants.R_OK | import_node_fs3.constants.W_OK);
|
|
5566
|
-
return true;
|
|
5617
|
+
return JSON.parse((0, import_node_fs3.readFileSync)(filePath, "utf-8"));
|
|
5567
5618
|
} catch {
|
|
5568
|
-
return
|
|
5619
|
+
return [];
|
|
5569
5620
|
}
|
|
5570
5621
|
}
|
|
5571
|
-
function
|
|
5572
|
-
|
|
5573
|
-
|
|
5574
|
-
|
|
5575
|
-
|
|
5622
|
+
function today() {
|
|
5623
|
+
return formatDate2(/* @__PURE__ */ new Date());
|
|
5624
|
+
}
|
|
5625
|
+
function formatDate2(d) {
|
|
5626
|
+
const y = d.getFullYear();
|
|
5627
|
+
const m = String(d.getMonth() + 1).padStart(2, "0");
|
|
5628
|
+
const day = String(d.getDate()).padStart(2, "0");
|
|
5629
|
+
return `${y}-${m}-${day}`;
|
|
5630
|
+
}
|
|
5631
|
+
function daysAgo(n) {
|
|
5632
|
+
const d = /* @__PURE__ */ new Date();
|
|
5633
|
+
d.setDate(d.getDate() - n);
|
|
5634
|
+
return formatDate2(d);
|
|
5635
|
+
}
|
|
5636
|
+
function filterDateRange(keys, from, to) {
|
|
5637
|
+
return keys.filter((k) => k >= from && k <= to);
|
|
5638
|
+
}
|
|
5639
|
+
function parseIsoTime(value, optionName) {
|
|
5640
|
+
const isoPattern = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}(?::\d{2}(?:\.\d{1,3})?)?(Z|[+-]\d{2}:\d{2})$/;
|
|
5641
|
+
if (!isoPattern.test(value)) {
|
|
5642
|
+
exitError(
|
|
5643
|
+
"INVALID_TIME",
|
|
5644
|
+
`${optionName} \u5FC5\u987B\u662F ISO 8601 \u65F6\u95F4\uFF0C\u4F8B\u5982 2026-03-01T09:00:00+08:00`
|
|
5645
|
+
);
|
|
5646
|
+
}
|
|
5647
|
+
const ts = Date.parse(value);
|
|
5648
|
+
if (Number.isNaN(ts)) {
|
|
5649
|
+
exitError(
|
|
5650
|
+
"INVALID_TIME",
|
|
5651
|
+
`${optionName} \u4E0D\u662F\u5408\u6CD5\u65F6\u95F4\uFF0C\u4F8B\u5982 2026-03-01T09:00:00+08:00`
|
|
5652
|
+
);
|
|
5653
|
+
}
|
|
5654
|
+
return ts;
|
|
5655
|
+
}
|
|
5656
|
+
function sortNotificationsByTimestampDesc(items) {
|
|
5657
|
+
return items.sort((a, b) => Date.parse(b.timestamp) - Date.parse(a.timestamp));
|
|
5658
|
+
}
|
|
5659
|
+
async function listDateKeysAsync(dir) {
|
|
5660
|
+
const pattern = /^(\d{4}-\d{2}-\d{2})\.json$/;
|
|
5661
|
+
const keys = [];
|
|
5662
|
+
const entries = await (0, import_promises.readdir)(dir, { withFileTypes: true });
|
|
5663
|
+
for (const entry of entries) {
|
|
5664
|
+
if (!entry.isFile()) continue;
|
|
5665
|
+
const m = pattern.exec(entry.name);
|
|
5666
|
+
if (m) keys.push(m[1]);
|
|
5667
|
+
}
|
|
5668
|
+
return keys.sort().reverse();
|
|
5669
|
+
}
|
|
5670
|
+
async function readDateFileAsync(dir, dateKey) {
|
|
5671
|
+
const filePath = (0, import_node_path2.join)(dir, `${dateKey}.json`);
|
|
5672
|
+
if (!(0, import_node_fs3.existsSync)(filePath)) return [];
|
|
5673
|
+
try {
|
|
5674
|
+
const content = await (0, import_promises.readFile)(filePath, "utf-8");
|
|
5675
|
+
return JSON.parse(content);
|
|
5676
|
+
} catch {
|
|
5677
|
+
return [];
|
|
5678
|
+
}
|
|
5679
|
+
}
|
|
5680
|
+
function progress(message) {
|
|
5681
|
+
process.stderr.write(message + "\n");
|
|
5682
|
+
}
|
|
5683
|
+
function output(data) {
|
|
5684
|
+
process.stdout.write(JSON.stringify(data, null, 2) + "\n");
|
|
5685
|
+
}
|
|
5686
|
+
function exitError(code, message) {
|
|
5687
|
+
output({ ok: false, error: { code, message } });
|
|
5688
|
+
process.exit(1);
|
|
5689
|
+
}
|
|
5690
|
+
function resolveRecordingsDir(ctx) {
|
|
5691
|
+
if (ctx.stateDir) {
|
|
5692
|
+
const dir = (0, import_node_path2.join)(
|
|
5693
|
+
ctx.stateDir,
|
|
5694
|
+
"plugins",
|
|
5695
|
+
"phone-notifications",
|
|
5696
|
+
"recordings"
|
|
5697
|
+
);
|
|
5698
|
+
if ((0, import_node_fs3.existsSync)(dir)) return dir;
|
|
5576
5699
|
}
|
|
5577
5700
|
if (ctx.workspaceDir) {
|
|
5578
|
-
const
|
|
5579
|
-
if (
|
|
5580
|
-
logger.warn(
|
|
5581
|
-
`stateDir \u4E0D\u53EF\u7528\uFF0C\u901A\u77E5\u5DF2\u56DE\u9000\u5230 workspace \u8DEF\u5F84: ${workspaceDir}`
|
|
5582
|
-
);
|
|
5583
|
-
return workspaceDir;
|
|
5584
|
-
}
|
|
5701
|
+
const dir = (0, import_node_path2.join)(ctx.workspaceDir, "recordings");
|
|
5702
|
+
if ((0, import_node_fs3.existsSync)(dir)) return dir;
|
|
5585
5703
|
}
|
|
5586
|
-
|
|
5704
|
+
return null;
|
|
5587
5705
|
}
|
|
5588
|
-
|
|
5589
|
-
|
|
5590
|
-
|
|
5591
|
-
|
|
5592
|
-
|
|
5593
|
-
|
|
5594
|
-
|
|
5595
|
-
|
|
5706
|
+
function resolveAsrConfigPath(ctx) {
|
|
5707
|
+
let base;
|
|
5708
|
+
if (ctx.stateDir) {
|
|
5709
|
+
base = (0, import_node_path2.join)(ctx.stateDir, "plugins", "phone-notifications", "recordings");
|
|
5710
|
+
} else if (ctx.workspaceDir) {
|
|
5711
|
+
base = (0, import_node_path2.join)(ctx.workspaceDir, "recordings");
|
|
5712
|
+
} else {
|
|
5713
|
+
exitError(
|
|
5714
|
+
"STORAGE_UNAVAILABLE",
|
|
5715
|
+
"\u65E0\u6CD5\u786E\u5B9A\u5F55\u97F3\u5B58\u50A8\u76EE\u5F55\uFF1AstateDir \u548C workspaceDir \u5747\u672A\u8BBE\u7F6E"
|
|
5716
|
+
);
|
|
5596
5717
|
}
|
|
5597
|
-
|
|
5598
|
-
|
|
5599
|
-
|
|
5600
|
-
|
|
5601
|
-
|
|
5602
|
-
|
|
5603
|
-
|
|
5604
|
-
|
|
5605
|
-
|
|
5606
|
-
|
|
5607
|
-
|
|
5608
|
-
(0, import_node_fs3.mkdirSync)(this.contentKeyIndexDir, { recursive: true });
|
|
5718
|
+
(0, import_node_fs3.mkdirSync)(base, { recursive: true });
|
|
5719
|
+
return (0, import_node_path2.join)(base, "asr-config.json");
|
|
5720
|
+
}
|
|
5721
|
+
function readRecordingIndex(dir) {
|
|
5722
|
+
const indexPath = (0, import_node_path2.join)(dir, "index.json");
|
|
5723
|
+
if (!(0, import_node_fs3.existsSync)(indexPath)) return [];
|
|
5724
|
+
try {
|
|
5725
|
+
const raw = JSON.parse((0, import_node_fs3.readFileSync)(indexPath, "utf-8"));
|
|
5726
|
+
return Array.isArray(raw?.recordings) ? raw.recordings : [];
|
|
5727
|
+
} catch {
|
|
5728
|
+
return [];
|
|
5609
5729
|
}
|
|
5610
|
-
|
|
5611
|
-
|
|
5612
|
-
|
|
5613
|
-
|
|
5614
|
-
|
|
5615
|
-
|
|
5616
|
-
|
|
5617
|
-
|
|
5730
|
+
}
|
|
5731
|
+
|
|
5732
|
+
// src/light/validators.ts
|
|
5733
|
+
var VALID_MODES = [
|
|
5734
|
+
"wave",
|
|
5735
|
+
"breath",
|
|
5736
|
+
"strobe",
|
|
5737
|
+
"steady",
|
|
5738
|
+
"wave_rainbow",
|
|
5739
|
+
"pixel_frame"
|
|
5740
|
+
];
|
|
5741
|
+
var MAX_SEGMENTS = 12;
|
|
5742
|
+
function validateSegments(segments) {
|
|
5743
|
+
if (!Array.isArray(segments)) {
|
|
5744
|
+
return { valid: false, errors: [{ field: "segments", message: "\u5FC5\u987B\u662F\u6570\u7EC4" }] };
|
|
5745
|
+
}
|
|
5746
|
+
if (segments.length === 0) {
|
|
5747
|
+
return { valid: false, errors: [{ field: "segments", message: "\u4E0D\u80FD\u4E3A\u7A7A" }] };
|
|
5748
|
+
}
|
|
5749
|
+
if (segments.length > MAX_SEGMENTS) {
|
|
5750
|
+
return {
|
|
5751
|
+
valid: false,
|
|
5752
|
+
errors: [{ field: "segments", message: `\u6700\u591A ${MAX_SEGMENTS} \u6BB5` }]
|
|
5618
5753
|
};
|
|
5619
|
-
for (const n of items) {
|
|
5620
|
-
const outcome = await this.writeNotification(n);
|
|
5621
|
-
switch (outcome.kind) {
|
|
5622
|
-
case "ingested":
|
|
5623
|
-
result.ingested += 1;
|
|
5624
|
-
result.inserted.push(outcome.entry);
|
|
5625
|
-
break;
|
|
5626
|
-
case "dedupedById":
|
|
5627
|
-
result.dedupedById += 1;
|
|
5628
|
-
break;
|
|
5629
|
-
case "dedupedByContent":
|
|
5630
|
-
result.dedupedByContent += 1;
|
|
5631
|
-
break;
|
|
5632
|
-
case "invalid":
|
|
5633
|
-
result.invalid += 1;
|
|
5634
|
-
break;
|
|
5635
|
-
}
|
|
5636
|
-
}
|
|
5637
|
-
this.prune();
|
|
5638
|
-
return result;
|
|
5639
5754
|
}
|
|
5640
|
-
|
|
5641
|
-
|
|
5642
|
-
|
|
5643
|
-
this.logger.warn(`\u5FFD\u7565\u975E\u6CD5 timestamp \u7684\u901A\u77E5: ${n.id}`);
|
|
5644
|
-
return { kind: "invalid" };
|
|
5645
|
-
}
|
|
5646
|
-
const dateKey = this.formatDate(ts);
|
|
5647
|
-
const filePath = (0, import_node_path2.join)(this.dir, `${dateKey}.json`);
|
|
5648
|
-
const normalizedId = typeof n.id === "string" ? n.id.trim() : "";
|
|
5649
|
-
const entry = this.buildStoredNotification(n);
|
|
5650
|
-
return this.withDateWriteLock(dateKey, async () => {
|
|
5651
|
-
if (normalizedId && this.hasNotificationId(dateKey, normalizedId)) {
|
|
5652
|
-
return { kind: "dedupedById" };
|
|
5653
|
-
}
|
|
5654
|
-
if (this.hasNotificationContentKey(dateKey, filePath, entry)) {
|
|
5655
|
-
return { kind: "dedupedByContent" };
|
|
5656
|
-
}
|
|
5657
|
-
const appDisplayName = this.resolveDisplayName ? await this.resolveDisplayName(entry.appName) : entry.appName;
|
|
5658
|
-
const storedEntry = {
|
|
5659
|
-
...entry,
|
|
5660
|
-
appDisplayName
|
|
5661
|
-
};
|
|
5662
|
-
const arr = this.readStoredNotifications(filePath);
|
|
5663
|
-
arr.push(storedEntry);
|
|
5664
|
-
(0, import_node_fs3.writeFileSync)(filePath, JSON.stringify(arr, null, 2), "utf-8");
|
|
5665
|
-
if (normalizedId) {
|
|
5666
|
-
this.recordNotificationId(dateKey, normalizedId);
|
|
5667
|
-
}
|
|
5668
|
-
this.recordNotificationContentKey(dateKey, filePath, storedEntry);
|
|
5669
|
-
return { kind: "ingested", entry: storedEntry };
|
|
5670
|
-
});
|
|
5755
|
+
const errors = [];
|
|
5756
|
+
for (let i = 0; i < segments.length; i++) {
|
|
5757
|
+
validateSegment(segments[i], `segments[${i}]`, errors);
|
|
5671
5758
|
}
|
|
5672
|
-
|
|
5673
|
-
|
|
5674
|
-
|
|
5675
|
-
|
|
5676
|
-
|
|
5677
|
-
|
|
5678
|
-
|
|
5759
|
+
if (errors.length > 0) return { valid: false, errors };
|
|
5760
|
+
return { valid: true, segments };
|
|
5761
|
+
}
|
|
5762
|
+
function parseAndValidateSegments(json) {
|
|
5763
|
+
let parsed;
|
|
5764
|
+
try {
|
|
5765
|
+
parsed = JSON.parse(json);
|
|
5766
|
+
} catch {
|
|
5767
|
+
exitError("VALIDATION_FAILED", "segments \u5FC5\u987B\u662F\u5408\u6CD5\u7684 JSON");
|
|
5679
5768
|
}
|
|
5680
|
-
|
|
5681
|
-
|
|
5682
|
-
|
|
5683
|
-
|
|
5684
|
-
}
|
|
5685
|
-
const fallback = [];
|
|
5686
|
-
if (n.category) {
|
|
5687
|
-
fallback.push(`category:${n.category}`);
|
|
5688
|
-
}
|
|
5689
|
-
if (n.metadata && Object.keys(n.metadata).length > 0) {
|
|
5690
|
-
fallback.push(`metadata:${JSON.stringify(n.metadata)}`);
|
|
5691
|
-
}
|
|
5692
|
-
return fallback.join(" ; ") || "-";
|
|
5769
|
+
const result = validateSegments(parsed);
|
|
5770
|
+
if (!result.valid) {
|
|
5771
|
+
output({ ok: false, error: { code: "VALIDATION_FAILED", details: result.errors } });
|
|
5772
|
+
process.exit(1);
|
|
5693
5773
|
}
|
|
5694
|
-
|
|
5695
|
-
|
|
5696
|
-
|
|
5697
|
-
|
|
5698
|
-
|
|
5774
|
+
return result.segments;
|
|
5775
|
+
}
|
|
5776
|
+
function validateSegment(seg, prefix, errors) {
|
|
5777
|
+
if (!isRecord(seg)) {
|
|
5778
|
+
errors.push({ field: prefix, message: "\u5FC5\u987B\u662F\u5BF9\u8C61" });
|
|
5779
|
+
return;
|
|
5699
5780
|
}
|
|
5700
|
-
|
|
5701
|
-
|
|
5781
|
+
const mode = seg.mode;
|
|
5782
|
+
if (!VALID_MODES.includes(mode)) {
|
|
5783
|
+
errors.push({
|
|
5784
|
+
field: `${prefix}.mode`,
|
|
5785
|
+
message: `\u4E0D\u652F\u6301\u7684\u6A21\u5F0F '${String(mode)}'\uFF0C\u53EF\u9009\uFF1A${VALID_MODES.join("/")}`
|
|
5786
|
+
});
|
|
5702
5787
|
}
|
|
5703
|
-
|
|
5704
|
-
|
|
5705
|
-
|
|
5706
|
-
|
|
5707
|
-
|
|
5708
|
-
|
|
5709
|
-
|
|
5710
|
-
|
|
5711
|
-
|
|
5712
|
-
|
|
5713
|
-
|
|
5714
|
-
|
|
5715
|
-
|
|
5716
|
-
|
|
5788
|
+
validateNonNegativeNumber(seg.duration_s, `${prefix}.duration_s`, errors, "\u5FC5\u987B\u662F \u22650 \u7684\u6570\u5B57\uFF080 \u8868\u793A\u65E0\u9650\u65F6\u957F\uFF09");
|
|
5789
|
+
switch (mode) {
|
|
5790
|
+
case "wave":
|
|
5791
|
+
validateForegroundSegment(seg, prefix, errors);
|
|
5792
|
+
validateOptionalNonNegativeNumber(seg.interval_ms, `${prefix}.interval_ms`, errors);
|
|
5793
|
+
validateOptionalDirection(seg.direction, `${prefix}.direction`, errors);
|
|
5794
|
+
validateOptionalWindow(seg.window, `${prefix}.window`, errors);
|
|
5795
|
+
validateOptionalBackground(seg.background, `${prefix}.background`, errors);
|
|
5796
|
+
break;
|
|
5797
|
+
case "wave_rainbow":
|
|
5798
|
+
validateForegroundSegment(seg, prefix, errors);
|
|
5799
|
+
validateOptionalNonNegativeNumber(seg.interval_ms, `${prefix}.interval_ms`, errors);
|
|
5800
|
+
validateOptionalDirection(seg.direction, `${prefix}.direction`, errors);
|
|
5801
|
+
validateOptionalWindow(seg.window, `${prefix}.window`, errors);
|
|
5802
|
+
validateOptionalBackground(seg.background, `${prefix}.background`, errors);
|
|
5803
|
+
if (!hasNonZeroRgb(seg.color) && !hasNonZeroRgb(seg.background)) {
|
|
5804
|
+
errors.push({
|
|
5805
|
+
field: prefix,
|
|
5806
|
+
message: "wave_rainbow \u81F3\u5C11\u9700\u8981\u4E00\u7EC4\u975E\u96F6\u989C\u8272\u951A\u70B9\uFF08color \u6216 background\uFF09"
|
|
5807
|
+
});
|
|
5717
5808
|
}
|
|
5718
|
-
|
|
5719
|
-
|
|
5720
|
-
|
|
5809
|
+
break;
|
|
5810
|
+
case "breath":
|
|
5811
|
+
validateForegroundSegment(seg, prefix, errors);
|
|
5812
|
+
validateOptionalBreathTiming(seg.breath_timing, `${prefix}.breath_timing`, errors);
|
|
5813
|
+
break;
|
|
5814
|
+
case "strobe":
|
|
5815
|
+
validateForegroundSegment(seg, prefix, errors);
|
|
5816
|
+
validateOptionalNonNegativeNumber(seg.interval_ms, `${prefix}.interval_ms`, errors);
|
|
5817
|
+
break;
|
|
5818
|
+
case "steady":
|
|
5819
|
+
validateForegroundSegment(seg, prefix, errors);
|
|
5820
|
+
break;
|
|
5821
|
+
case "pixel_frame":
|
|
5822
|
+
validatePixelFrame(seg.pixels, `${prefix}.pixels`, errors);
|
|
5823
|
+
break;
|
|
5824
|
+
default:
|
|
5825
|
+
validateOptionalNonNegativeNumber(seg.brightness, `${prefix}.brightness`, errors);
|
|
5826
|
+
validateOptionalColor(seg.color, `${prefix}.color`, errors);
|
|
5827
|
+
validateOptionalNonNegativeNumber(seg.interval_ms, `${prefix}.interval_ms`, errors);
|
|
5828
|
+
validateOptionalDirection(seg.direction, `${prefix}.direction`, errors);
|
|
5829
|
+
validateOptionalWindow(seg.window, `${prefix}.window`, errors);
|
|
5830
|
+
validateOptionalBreathTiming(seg.breath_timing, `${prefix}.breath_timing`, errors);
|
|
5831
|
+
validateOptionalBackground(seg.background, `${prefix}.background`, errors);
|
|
5721
5832
|
}
|
|
5722
|
-
|
|
5723
|
-
|
|
5833
|
+
}
|
|
5834
|
+
function validateForegroundSegment(seg, prefix, errors) {
|
|
5835
|
+
validateNumberInRange(
|
|
5836
|
+
seg.brightness,
|
|
5837
|
+
`${prefix}.brightness`,
|
|
5838
|
+
errors,
|
|
5839
|
+
0,
|
|
5840
|
+
255,
|
|
5841
|
+
"\u5FC5\u987B\u662F 0\u2013255 \u7684\u6570\u5B57"
|
|
5842
|
+
);
|
|
5843
|
+
validateColor(seg.color, `${prefix}.color`, errors);
|
|
5844
|
+
if (seg.mode !== "steady" && seg.brightness === 0) {
|
|
5845
|
+
errors.push({
|
|
5846
|
+
field: `${prefix}.brightness`,
|
|
5847
|
+
message: "brightness=0 \u4EC5 steady \u6A21\u5F0F\u5141\u8BB8\uFF1B\u5176\u5B83\u6A21\u5F0F\u4F1A\u5728\u56FA\u4EF6\u4FA7\u88AB\u8FC7\u6EE4"
|
|
5848
|
+
});
|
|
5724
5849
|
}
|
|
5725
|
-
|
|
5726
|
-
|
|
5850
|
+
}
|
|
5851
|
+
function validatePixelFrame(value, field, errors) {
|
|
5852
|
+
if (!Array.isArray(value)) {
|
|
5853
|
+
errors.push({ field, message: "pixel_frame \u5FC5\u987B\u63D0\u4F9B pixels \u6570\u7EC4\uFF081\u20137 \u9879\uFF09" });
|
|
5854
|
+
return;
|
|
5727
5855
|
}
|
|
5728
|
-
|
|
5729
|
-
|
|
5730
|
-
|
|
5731
|
-
|
|
5732
|
-
|
|
5733
|
-
const
|
|
5734
|
-
const
|
|
5735
|
-
if ((
|
|
5736
|
-
|
|
5737
|
-
|
|
5738
|
-
}
|
|
5856
|
+
if (value.length < 1 || value.length > 7) {
|
|
5857
|
+
errors.push({ field, message: "pixels \u5FC5\u987B\u4E3A 1\u20137 \u9879" });
|
|
5858
|
+
}
|
|
5859
|
+
const seen = /* @__PURE__ */ new Set();
|
|
5860
|
+
for (let i = 0; i < value.length; i++) {
|
|
5861
|
+
const pixel = value[i];
|
|
5862
|
+
const prefix = `${field}[${i}]`;
|
|
5863
|
+
if (!isRecord(pixel)) {
|
|
5864
|
+
errors.push({ field: prefix, message: "\u5FC5\u987B\u662F\u5BF9\u8C61" });
|
|
5865
|
+
continue;
|
|
5739
5866
|
}
|
|
5740
|
-
|
|
5741
|
-
|
|
5742
|
-
`, "
|
|
5743
|
-
} else if (
|
|
5744
|
-
|
|
5867
|
+
const idx = pixel.index;
|
|
5868
|
+
if (!Number.isInteger(idx) || idx < 0 || idx > 6) {
|
|
5869
|
+
errors.push({ field: `${prefix}.index`, message: "index \u5FC5\u987B\u662F 0\u20136 \u7684\u6574\u6570" });
|
|
5870
|
+
} else if (seen.has(idx)) {
|
|
5871
|
+
errors.push({ field: `${prefix}.index`, message: `index=${idx} \u91CD\u590D` });
|
|
5872
|
+
} else {
|
|
5873
|
+
seen.add(idx);
|
|
5745
5874
|
}
|
|
5746
|
-
|
|
5747
|
-
|
|
5748
|
-
|
|
5749
|
-
|
|
5750
|
-
|
|
5751
|
-
|
|
5875
|
+
validateNumberInRange(
|
|
5876
|
+
pixel.brightness,
|
|
5877
|
+
`${prefix}.brightness`,
|
|
5878
|
+
errors,
|
|
5879
|
+
0,
|
|
5880
|
+
255,
|
|
5881
|
+
"\u5FC5\u987B\u662F 0\u2013255 \u7684\u6570\u5B57"
|
|
5752
5882
|
);
|
|
5883
|
+
validateColor(pixel.color, `${prefix}.color`, errors);
|
|
5753
5884
|
}
|
|
5754
|
-
|
|
5755
|
-
|
|
5756
|
-
|
|
5757
|
-
|
|
5758
|
-
}
|
|
5759
|
-
|
|
5760
|
-
`, "utf-8");
|
|
5761
|
-
ids.add(id);
|
|
5885
|
+
}
|
|
5886
|
+
function validateOptionalBreathTiming(value, field, errors) {
|
|
5887
|
+
if (value === void 0) return;
|
|
5888
|
+
if (!isRecord(value)) {
|
|
5889
|
+
errors.push({ field, message: "\u5FC5\u987B\u662F\u5BF9\u8C61" });
|
|
5890
|
+
return;
|
|
5762
5891
|
}
|
|
5763
|
-
|
|
5764
|
-
|
|
5765
|
-
|
|
5766
|
-
|
|
5767
|
-
|
|
5768
|
-
|
|
5769
|
-
|
|
5770
|
-
|
|
5771
|
-
|
|
5772
|
-
|
|
5773
|
-
|
|
5774
|
-
|
|
5775
|
-
|
|
5776
|
-
|
|
5777
|
-
|
|
5778
|
-
|
|
5779
|
-
|
|
5780
|
-
|
|
5781
|
-
|
|
5782
|
-
|
|
5783
|
-
}
|
|
5784
|
-
|
|
5785
|
-
|
|
5786
|
-
|
|
5787
|
-
|
|
5788
|
-
|
|
5789
|
-
|
|
5790
|
-
|
|
5791
|
-
|
|
5792
|
-
|
|
5793
|
-
const chain = previous.then(() => current);
|
|
5794
|
-
this.dateWriteChains.set(dateKey, chain);
|
|
5795
|
-
await previous;
|
|
5796
|
-
try {
|
|
5797
|
-
return await task();
|
|
5798
|
-
} finally {
|
|
5799
|
-
release();
|
|
5800
|
-
if (this.dateWriteChains.get(dateKey) === chain) {
|
|
5801
|
-
this.dateWriteChains.delete(dateKey);
|
|
5802
|
-
}
|
|
5803
|
-
}
|
|
5804
|
-
}
|
|
5805
|
-
prune() {
|
|
5806
|
-
const retentionDays = this.config.retentionDays;
|
|
5807
|
-
if (retentionDays === void 0) {
|
|
5808
|
-
return;
|
|
5809
|
-
}
|
|
5810
|
-
const cutoffMs = Date.now() - retentionDays * 24 * 60 * 60 * 1e3;
|
|
5811
|
-
const cutoffDate = this.formatDate(new Date(cutoffMs));
|
|
5812
|
-
this.pruneDataFiles(cutoffDate);
|
|
5813
|
-
this.pruneIdIndex(cutoffDate);
|
|
5814
|
-
this.pruneContentKeyIndex(cutoffDate);
|
|
5815
|
-
}
|
|
5816
|
-
/** Remove expired .json, legacy .md files, and legacy date directories */
|
|
5817
|
-
pruneDataFiles(cutoffDate) {
|
|
5818
|
-
const dateFilePattern = /^(\d{4}-\d{2}-\d{2})\.(json|md)$/;
|
|
5819
|
-
const dateDirPattern = /^\d{4}-\d{2}-\d{2}$/;
|
|
5820
|
-
try {
|
|
5821
|
-
for (const entry of (0, import_node_fs3.readdirSync)(this.dir, { withFileTypes: true })) {
|
|
5822
|
-
if (entry.isFile()) {
|
|
5823
|
-
const match = dateFilePattern.exec(entry.name);
|
|
5824
|
-
if (match && match[1] < cutoffDate) {
|
|
5825
|
-
(0, import_node_fs3.rmSync)((0, import_node_path2.join)(this.dir, entry.name), { force: true });
|
|
5826
|
-
}
|
|
5827
|
-
} else if (entry.isDirectory() && dateDirPattern.test(entry.name) && entry.name < cutoffDate) {
|
|
5828
|
-
(0, import_node_fs3.rmSync)((0, import_node_path2.join)(this.dir, entry.name), { recursive: true, force: true });
|
|
5829
|
-
}
|
|
5830
|
-
}
|
|
5831
|
-
} catch {
|
|
5832
|
-
}
|
|
5833
|
-
}
|
|
5834
|
-
/** Remove expired .ids index files */
|
|
5835
|
-
pruneIdIndex(cutoffDate) {
|
|
5836
|
-
try {
|
|
5837
|
-
for (const entry of (0, import_node_fs3.readdirSync)(this.idIndexDir, { withFileTypes: true })) {
|
|
5838
|
-
if (!entry.isFile()) continue;
|
|
5839
|
-
const match = /^(\d{4}-\d{2}-\d{2})\.ids$/.exec(entry.name);
|
|
5840
|
-
if (match && match[1] < cutoffDate) {
|
|
5841
|
-
(0, import_node_fs3.rmSync)((0, import_node_path2.join)(this.idIndexDir, entry.name), { force: true });
|
|
5842
|
-
this.idCache.delete(match[1]);
|
|
5843
|
-
}
|
|
5844
|
-
}
|
|
5845
|
-
} catch {
|
|
5846
|
-
}
|
|
5847
|
-
}
|
|
5848
|
-
pruneContentKeyIndex(cutoffDate) {
|
|
5849
|
-
try {
|
|
5850
|
-
for (const entry of (0, import_node_fs3.readdirSync)(this.contentKeyIndexDir, { withFileTypes: true })) {
|
|
5851
|
-
if (!entry.isFile()) continue;
|
|
5852
|
-
const match = /^(\d{4}-\d{2}-\d{2})\.keys$/.exec(entry.name);
|
|
5853
|
-
if (match && match[1] < cutoffDate) {
|
|
5854
|
-
(0, import_node_fs3.rmSync)((0, import_node_path2.join)(this.contentKeyIndexDir, entry.name), { force: true });
|
|
5855
|
-
this.contentKeyCache.delete(match[1]);
|
|
5856
|
-
}
|
|
5857
|
-
}
|
|
5858
|
-
} catch {
|
|
5859
|
-
}
|
|
5860
|
-
}
|
|
5861
|
-
async close() {
|
|
5862
|
-
this.idCache.clear();
|
|
5863
|
-
this.contentKeyCache.clear();
|
|
5864
|
-
this.dateWriteChains.clear();
|
|
5865
|
-
}
|
|
5866
|
-
};
|
|
5867
|
-
|
|
5868
|
-
// src/light/repeat.ts
|
|
5869
|
-
function normalizeRepeatTimes(input) {
|
|
5870
|
-
if (typeof input === "boolean") {
|
|
5871
|
-
return input ? 0 : 1;
|
|
5872
|
-
}
|
|
5873
|
-
if (typeof input === "number") {
|
|
5874
|
-
return validateRepeatTimes(input);
|
|
5875
|
-
}
|
|
5876
|
-
if (!input) {
|
|
5877
|
-
return 1;
|
|
5878
|
-
}
|
|
5879
|
-
if (input.repeat_times !== void 0) {
|
|
5880
|
-
return validateRepeatTimes(input.repeat_times);
|
|
5881
|
-
}
|
|
5882
|
-
if (input.repeat !== void 0) {
|
|
5883
|
-
return input.repeat ? 0 : 1;
|
|
5884
|
-
}
|
|
5885
|
-
return 1;
|
|
5886
|
-
}
|
|
5887
|
-
function assertAncsRepeatTimes(repeatTimes) {
|
|
5888
|
-
if (repeatTimes !== 0 && repeatTimes !== 1) {
|
|
5889
|
-
throw new Error(
|
|
5890
|
-
"\u5F53\u524D ANCS \u8DEF\u5F84\u4EC5\u652F\u6301 repeat_times=0\uFF08\u65E0\u9650\u5FAA\u73AF\uFF09\u6216 1\uFF08\u64AD\u653E\u4E00\u8F6E\uFF09\uFF1BN>=2 \u9700\u975E ANCS \u8DEF\u5F84"
|
|
5891
|
-
);
|
|
5892
|
+
validatePositiveNumber(
|
|
5893
|
+
value.rise_ms,
|
|
5894
|
+
`${field}.rise_ms`,
|
|
5895
|
+
errors,
|
|
5896
|
+
"rise_ms \u5FC5\u987B\u662F >0 \u7684\u6570\u5B57\uFF08\u4E0D\u652F\u6301 0ms\uFF09"
|
|
5897
|
+
);
|
|
5898
|
+
validateNonNegativeNumber(
|
|
5899
|
+
value.hold_ms,
|
|
5900
|
+
`${field}.hold_ms`,
|
|
5901
|
+
errors,
|
|
5902
|
+
"hold_ms \u5FC5\u987B\u662F \u22650 \u7684\u6570\u5B57"
|
|
5903
|
+
);
|
|
5904
|
+
validatePositiveNumber(
|
|
5905
|
+
value.fall_ms,
|
|
5906
|
+
`${field}.fall_ms`,
|
|
5907
|
+
errors,
|
|
5908
|
+
"fall_ms \u5FC5\u987B\u662F >0 \u7684\u6570\u5B57\uFF08\u4E0D\u652F\u6301 0ms\uFF09"
|
|
5909
|
+
);
|
|
5910
|
+
validateNonNegativeNumber(
|
|
5911
|
+
value.off_ms,
|
|
5912
|
+
`${field}.off_ms`,
|
|
5913
|
+
errors,
|
|
5914
|
+
"off_ms \u5FC5\u987B\u662F \u22650 \u7684\u6570\u5B57"
|
|
5915
|
+
);
|
|
5916
|
+
}
|
|
5917
|
+
function validateOptionalBackground(value, field, errors) {
|
|
5918
|
+
if (value === void 0) return;
|
|
5919
|
+
if (!isRecord(value)) {
|
|
5920
|
+
errors.push({ field, message: "\u5FC5\u987B\u5305\u542B r/g/b/brightness \u6570\u503C" });
|
|
5921
|
+
return;
|
|
5892
5922
|
}
|
|
5923
|
+
validateColor(value, field, errors);
|
|
5924
|
+
validateNumberInRange(
|
|
5925
|
+
value.brightness,
|
|
5926
|
+
`${field}.brightness`,
|
|
5927
|
+
errors,
|
|
5928
|
+
0,
|
|
5929
|
+
255,
|
|
5930
|
+
"\u5FC5\u987B\u662F 0\u2013255 \u7684\u6570\u5B57"
|
|
5931
|
+
);
|
|
5893
5932
|
}
|
|
5894
|
-
function
|
|
5895
|
-
if (
|
|
5896
|
-
|
|
5897
|
-
}
|
|
5898
|
-
return value;
|
|
5933
|
+
function validateOptionalColor(value, field, errors) {
|
|
5934
|
+
if (value === void 0) return;
|
|
5935
|
+
validateColor(value, field, errors);
|
|
5899
5936
|
}
|
|
5900
|
-
|
|
5901
|
-
|
|
5902
|
-
|
|
5903
|
-
|
|
5904
|
-
var import_node_path3 = require("path");
|
|
5905
|
-
function resolveNotificationsDir(ctx) {
|
|
5906
|
-
if (ctx.stateDir) {
|
|
5907
|
-
const dir = (0, import_node_path3.join)(
|
|
5908
|
-
ctx.stateDir,
|
|
5909
|
-
"plugins",
|
|
5910
|
-
"phone-notifications",
|
|
5911
|
-
"notifications"
|
|
5912
|
-
);
|
|
5913
|
-
if ((0, import_node_fs4.existsSync)(dir)) return dir;
|
|
5914
|
-
}
|
|
5915
|
-
if (ctx.workspaceDir) {
|
|
5916
|
-
const dir = (0, import_node_path3.join)(ctx.workspaceDir, "notifications");
|
|
5917
|
-
if ((0, import_node_fs4.existsSync)(dir)) return dir;
|
|
5937
|
+
function validateColor(value, field, errors) {
|
|
5938
|
+
if (!isRecord(value)) {
|
|
5939
|
+
errors.push({ field, message: "\u5FC5\u987B\u5305\u542B r/g/b \u6570\u503C" });
|
|
5940
|
+
return;
|
|
5918
5941
|
}
|
|
5919
|
-
|
|
5942
|
+
validateNumberInRange(value.r, `${field}.r`, errors, 0, 255, "\u5FC5\u987B\u662F 0\u2013255 \u7684\u6570\u5B57");
|
|
5943
|
+
validateNumberInRange(value.g, `${field}.g`, errors, 0, 255, "\u5FC5\u987B\u662F 0\u2013255 \u7684\u6570\u5B57");
|
|
5944
|
+
validateNumberInRange(value.b, `${field}.b`, errors, 0, 255, "\u5FC5\u987B\u662F 0\u2013255 \u7684\u6570\u5B57");
|
|
5920
5945
|
}
|
|
5921
|
-
function
|
|
5922
|
-
|
|
5923
|
-
|
|
5924
|
-
|
|
5925
|
-
if (!entry.isFile()) continue;
|
|
5926
|
-
const m = pattern.exec(entry.name);
|
|
5927
|
-
if (m) keys.push(m[1]);
|
|
5946
|
+
function validateOptionalDirection(value, field, errors) {
|
|
5947
|
+
if (value === void 0) return;
|
|
5948
|
+
if (value !== "ltr" && value !== "rtl") {
|
|
5949
|
+
errors.push({ field, message: "direction \u5FC5\u987B\u662F ltr \u6216 rtl" });
|
|
5928
5950
|
}
|
|
5929
|
-
return keys.sort().reverse();
|
|
5930
5951
|
}
|
|
5931
|
-
function
|
|
5932
|
-
|
|
5933
|
-
if (
|
|
5934
|
-
|
|
5935
|
-
return JSON.parse((0, import_node_fs4.readFileSync)(filePath, "utf-8"));
|
|
5936
|
-
} catch {
|
|
5937
|
-
return [];
|
|
5952
|
+
function validateOptionalWindow(value, field, errors) {
|
|
5953
|
+
if (value === void 0) return;
|
|
5954
|
+
if (value !== 1 && value !== 2 && value !== 3) {
|
|
5955
|
+
errors.push({ field, message: "window \u4EC5\u652F\u6301 1/2/3" });
|
|
5938
5956
|
}
|
|
5939
5957
|
}
|
|
5940
|
-
function
|
|
5941
|
-
|
|
5942
|
-
|
|
5943
|
-
function formatDate2(d) {
|
|
5944
|
-
const y = d.getFullYear();
|
|
5945
|
-
const m = String(d.getMonth() + 1).padStart(2, "0");
|
|
5946
|
-
const day = String(d.getDate()).padStart(2, "0");
|
|
5947
|
-
return `${y}-${m}-${day}`;
|
|
5948
|
-
}
|
|
5949
|
-
function daysAgo(n) {
|
|
5950
|
-
const d = /* @__PURE__ */ new Date();
|
|
5951
|
-
d.setDate(d.getDate() - n);
|
|
5952
|
-
return formatDate2(d);
|
|
5953
|
-
}
|
|
5954
|
-
function filterDateRange(keys, from, to) {
|
|
5955
|
-
return keys.filter((k) => k >= from && k <= to);
|
|
5958
|
+
function validateOptionalNonNegativeNumber(value, field, errors) {
|
|
5959
|
+
if (value === void 0) return;
|
|
5960
|
+
validateNonNegativeNumber(value, field, errors, "\u5FC5\u987B\u662F \u22650 \u7684\u6570\u5B57");
|
|
5956
5961
|
}
|
|
5957
|
-
function
|
|
5958
|
-
|
|
5959
|
-
if (!
|
|
5960
|
-
|
|
5961
|
-
"INVALID_TIME",
|
|
5962
|
-
`${optionName} \u5FC5\u987B\u662F ISO 8601 \u65F6\u95F4\uFF0C\u4F8B\u5982 2026-03-01T09:00:00+08:00`
|
|
5963
|
-
);
|
|
5964
|
-
}
|
|
5965
|
-
const ts = Date.parse(value);
|
|
5966
|
-
if (Number.isNaN(ts)) {
|
|
5967
|
-
exitError(
|
|
5968
|
-
"INVALID_TIME",
|
|
5969
|
-
`${optionName} \u4E0D\u662F\u5408\u6CD5\u65F6\u95F4\uFF0C\u4F8B\u5982 2026-03-01T09:00:00+08:00`
|
|
5970
|
-
);
|
|
5962
|
+
function validatePositiveNumber(value, field, errors, message) {
|
|
5963
|
+
if (value === void 0) return;
|
|
5964
|
+
if (!isFiniteNumber(value) || value <= 0) {
|
|
5965
|
+
errors.push({ field, message });
|
|
5971
5966
|
}
|
|
5972
|
-
return ts;
|
|
5973
|
-
}
|
|
5974
|
-
function sortNotificationsByTimestampDesc(items) {
|
|
5975
|
-
return items.sort((a, b) => Date.parse(b.timestamp) - Date.parse(a.timestamp));
|
|
5976
5967
|
}
|
|
5977
|
-
|
|
5978
|
-
|
|
5979
|
-
|
|
5980
|
-
const entries = await (0, import_promises.readdir)(dir, { withFileTypes: true });
|
|
5981
|
-
for (const entry of entries) {
|
|
5982
|
-
if (!entry.isFile()) continue;
|
|
5983
|
-
const m = pattern.exec(entry.name);
|
|
5984
|
-
if (m) keys.push(m[1]);
|
|
5968
|
+
function validateNonNegativeNumber(value, field, errors, message) {
|
|
5969
|
+
if (!isFiniteNumber(value) || value < 0) {
|
|
5970
|
+
errors.push({ field, message });
|
|
5985
5971
|
}
|
|
5986
|
-
return keys.sort().reverse();
|
|
5987
5972
|
}
|
|
5988
|
-
|
|
5989
|
-
|
|
5990
|
-
|
|
5991
|
-
try {
|
|
5992
|
-
const content = await (0, import_promises.readFile)(filePath, "utf-8");
|
|
5993
|
-
return JSON.parse(content);
|
|
5994
|
-
} catch {
|
|
5995
|
-
return [];
|
|
5973
|
+
function validateNumberInRange(value, field, errors, min, max, message) {
|
|
5974
|
+
if (!isFiniteNumber(value) || value < min || value > max) {
|
|
5975
|
+
errors.push({ field, message });
|
|
5996
5976
|
}
|
|
5997
5977
|
}
|
|
5998
|
-
function
|
|
5999
|
-
|
|
6000
|
-
|
|
6001
|
-
function output(data) {
|
|
6002
|
-
process.stdout.write(JSON.stringify(data, null, 2) + "\n");
|
|
5978
|
+
function hasNonZeroRgb(value) {
|
|
5979
|
+
if (!value) return false;
|
|
5980
|
+
return [value.r, value.g, value.b].some((channel) => isFiniteNumber(channel) && channel > 0);
|
|
6003
5981
|
}
|
|
6004
|
-
function
|
|
6005
|
-
|
|
6006
|
-
process.exit(1);
|
|
5982
|
+
function isFiniteNumber(value) {
|
|
5983
|
+
return typeof value === "number" && Number.isFinite(value);
|
|
6007
5984
|
}
|
|
6008
|
-
function
|
|
6009
|
-
|
|
6010
|
-
const dir = (0, import_node_path3.join)(
|
|
6011
|
-
ctx.stateDir,
|
|
6012
|
-
"plugins",
|
|
6013
|
-
"phone-notifications",
|
|
6014
|
-
"recordings"
|
|
6015
|
-
);
|
|
6016
|
-
if ((0, import_node_fs4.existsSync)(dir)) return dir;
|
|
6017
|
-
}
|
|
6018
|
-
if (ctx.workspaceDir) {
|
|
6019
|
-
const dir = (0, import_node_path3.join)(ctx.workspaceDir, "recordings");
|
|
6020
|
-
if ((0, import_node_fs4.existsSync)(dir)) return dir;
|
|
6021
|
-
}
|
|
6022
|
-
return null;
|
|
5985
|
+
function isRecord(value) {
|
|
5986
|
+
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
6023
5987
|
}
|
|
6024
|
-
|
|
6025
|
-
|
|
6026
|
-
|
|
6027
|
-
|
|
6028
|
-
|
|
6029
|
-
|
|
6030
|
-
|
|
6031
|
-
|
|
6032
|
-
|
|
6033
|
-
|
|
6034
|
-
|
|
6035
|
-
|
|
6036
|
-
|
|
6037
|
-
|
|
5988
|
+
|
|
5989
|
+
// src/light-rules/storage.ts
|
|
5990
|
+
var import_node_fs4 = require("fs");
|
|
5991
|
+
var import_node_path3 = require("path");
|
|
5992
|
+
|
|
5993
|
+
// src/monitor/fetch-gen.ts
|
|
5994
|
+
function generateFetchPy(name, matchRules) {
|
|
5995
|
+
const appName = typeof matchRules.appName === "string" ? matchRules.appName : "";
|
|
5996
|
+
const senderKeywords = stringArray(matchRules.senderKeywords);
|
|
5997
|
+
const contentKeywords = stringArray(matchRules.contentKeywords);
|
|
5998
|
+
return `#!/usr/bin/env python3
|
|
5999
|
+
"""Auto-generated fetch script for monitor task: ${name}"""
|
|
6000
|
+
import json, sys, os
|
|
6001
|
+
from pathlib import Path
|
|
6002
|
+
|
|
6003
|
+
def matches(notification: dict) -> bool:
|
|
6004
|
+
app = str(notification.get("appName", "") or "")
|
|
6005
|
+
title = str(notification.get("title", "") or "")
|
|
6006
|
+
content = str(notification.get("content", "") or "")
|
|
6007
|
+
body = str(notification.get("body", "") or "")
|
|
6008
|
+
|
|
6009
|
+
app_name = ${pyLiteral(appName)}
|
|
6010
|
+
if app_name and app != app_name:
|
|
6011
|
+
return False
|
|
6012
|
+
|
|
6013
|
+
sender_keywords = ${pyLiteral(senderKeywords)}
|
|
6014
|
+
sender_haystack = f"{title}\\n{content}\\n{body}"
|
|
6015
|
+
if sender_keywords and not any(keyword in sender_haystack for keyword in sender_keywords):
|
|
6016
|
+
return False
|
|
6017
|
+
|
|
6018
|
+
content_keywords = ${pyLiteral(contentKeywords)}
|
|
6019
|
+
content_haystack = f"{content}\\n{body}"
|
|
6020
|
+
if content_keywords and not any(keyword in content_haystack for keyword in content_keywords):
|
|
6021
|
+
return False
|
|
6022
|
+
|
|
6023
|
+
return True
|
|
6024
|
+
|
|
6025
|
+
def main():
|
|
6026
|
+
import argparse
|
|
6027
|
+
parser = argparse.ArgumentParser()
|
|
6028
|
+
parser.add_argument("--notifications-dir", required=True)
|
|
6029
|
+
args = parser.parse_args()
|
|
6030
|
+
|
|
6031
|
+
checkpoint_path = Path(__file__).parent / "checkpoint.json"
|
|
6032
|
+
checkpoint = {}
|
|
6033
|
+
if checkpoint_path.exists():
|
|
6034
|
+
checkpoint = json.loads(checkpoint_path.read_text())
|
|
6035
|
+
|
|
6036
|
+
ntf_dir = Path(args.notifications_dir)
|
|
6037
|
+
matched = []
|
|
6038
|
+
new_checkpoint = dict(checkpoint)
|
|
6039
|
+
|
|
6040
|
+
for f in sorted(ntf_dir.glob("*.json")):
|
|
6041
|
+
date_key = f.stem
|
|
6042
|
+
items = json.loads(f.read_text())
|
|
6043
|
+
last_idx = checkpoint.get(date_key, {}).get("lastIndex", -1)
|
|
6044
|
+
for i, item in enumerate(items):
|
|
6045
|
+
if i <= last_idx:
|
|
6046
|
+
continue
|
|
6047
|
+
if matches(item):
|
|
6048
|
+
matched.append(item)
|
|
6049
|
+
new_checkpoint[date_key] = {"lastIndex": len(items) - 1}
|
|
6050
|
+
|
|
6051
|
+
checkpoint_path.write_text(json.dumps(new_checkpoint, indent=2))
|
|
6052
|
+
|
|
6053
|
+
if not matched:
|
|
6054
|
+
print("NO_MATCH")
|
|
6055
|
+
return
|
|
6056
|
+
|
|
6057
|
+
for m in matched:
|
|
6058
|
+
print(json.dumps(m, ensure_ascii=False))
|
|
6059
|
+
|
|
6060
|
+
if __name__ == "__main__":
|
|
6061
|
+
main()
|
|
6062
|
+
`;
|
|
6038
6063
|
}
|
|
6039
|
-
function
|
|
6040
|
-
|
|
6041
|
-
|
|
6042
|
-
|
|
6043
|
-
|
|
6044
|
-
|
|
6045
|
-
} catch {
|
|
6046
|
-
return [];
|
|
6047
|
-
}
|
|
6064
|
+
function stringArray(value) {
|
|
6065
|
+
if (!Array.isArray(value)) return [];
|
|
6066
|
+
return value.filter((item) => typeof item === "string" && item.length > 0);
|
|
6067
|
+
}
|
|
6068
|
+
function pyLiteral(value) {
|
|
6069
|
+
return JSON.stringify(value);
|
|
6048
6070
|
}
|
|
6049
6071
|
|
|
6050
|
-
// src/light/
|
|
6051
|
-
var
|
|
6052
|
-
|
|
6053
|
-
|
|
6054
|
-
|
|
6055
|
-
|
|
6056
|
-
|
|
6057
|
-
|
|
6058
|
-
|
|
6059
|
-
|
|
6060
|
-
|
|
6061
|
-
|
|
6062
|
-
|
|
6063
|
-
|
|
6064
|
-
|
|
6065
|
-
|
|
6072
|
+
// src/light-rules/storage.ts
|
|
6073
|
+
var LEGACY_DEFAULT_CRON_SCHEDULE = "*/5 * * * *";
|
|
6074
|
+
function legacyReadMatchRules(input) {
|
|
6075
|
+
return input.matchRules ?? {};
|
|
6076
|
+
}
|
|
6077
|
+
function legacyReadCronSchedule(input) {
|
|
6078
|
+
return input.cronSchedule ?? LEGACY_DEFAULT_CRON_SCHEDULE;
|
|
6079
|
+
}
|
|
6080
|
+
function legacyHasMatchRules(input) {
|
|
6081
|
+
return input.matchRules !== void 0;
|
|
6082
|
+
}
|
|
6083
|
+
function legacyHasCronSchedule(input) {
|
|
6084
|
+
return input.cronSchedule !== void 0;
|
|
6085
|
+
}
|
|
6086
|
+
function legacyAssignMatchRules(meta, matchRules) {
|
|
6087
|
+
meta.matchRules = matchRules;
|
|
6088
|
+
}
|
|
6089
|
+
function legacyAssignCronSchedule(meta, cronSchedule) {
|
|
6090
|
+
meta.cronSchedule = cronSchedule;
|
|
6091
|
+
}
|
|
6092
|
+
function legacyReadMetaMatchRules(meta) {
|
|
6093
|
+
return meta.matchRules ?? {};
|
|
6094
|
+
}
|
|
6095
|
+
function legacyReadMetaCronSchedule(meta) {
|
|
6096
|
+
return meta.cronSchedule;
|
|
6097
|
+
}
|
|
6098
|
+
function resolveBaseDir(ctx) {
|
|
6099
|
+
if (ctx.workspaceDir) return ctx.workspaceDir;
|
|
6100
|
+
if (ctx.stateDir) {
|
|
6101
|
+
const inferredWorkspaceDir = (0, import_node_path3.join)(ctx.stateDir, "workspace");
|
|
6102
|
+
if ((0, import_node_fs4.existsSync)(inferredWorkspaceDir)) return inferredWorkspaceDir;
|
|
6103
|
+
return ctx.stateDir;
|
|
6066
6104
|
}
|
|
6067
|
-
|
|
6105
|
+
throw new Error("workspaceDir and stateDir both unavailable");
|
|
6106
|
+
}
|
|
6107
|
+
function tasksDir(ctx) {
|
|
6108
|
+
return (0, import_node_path3.join)(resolveBaseDir(ctx), "tasks");
|
|
6109
|
+
}
|
|
6110
|
+
function normalizeLightRuleLookupName(name) {
|
|
6111
|
+
return name.trim().replace(/\.json$/i, "");
|
|
6112
|
+
}
|
|
6113
|
+
function resolveLightRuleTask(ctx, name) {
|
|
6114
|
+
const dir = tasksDir(ctx);
|
|
6115
|
+
const normalizedName = normalizeLightRuleLookupName(name);
|
|
6116
|
+
if (!normalizedName) return null;
|
|
6117
|
+
const directTaskDir = (0, import_node_path3.join)(dir, normalizedName);
|
|
6118
|
+
const directMeta = readMeta(directTaskDir);
|
|
6119
|
+
if (directMeta) {
|
|
6068
6120
|
return {
|
|
6069
|
-
|
|
6070
|
-
|
|
6121
|
+
taskDir: directTaskDir,
|
|
6122
|
+
meta: directMeta
|
|
6071
6123
|
};
|
|
6072
6124
|
}
|
|
6073
|
-
|
|
6074
|
-
for (
|
|
6075
|
-
|
|
6125
|
+
if (!(0, import_node_fs4.existsSync)(dir)) return null;
|
|
6126
|
+
for (const entry of (0, import_node_fs4.readdirSync)(dir, { withFileTypes: true })) {
|
|
6127
|
+
if (!entry.isDirectory()) continue;
|
|
6128
|
+
const taskDir = (0, import_node_path3.join)(dir, entry.name);
|
|
6129
|
+
const meta = readMeta(taskDir);
|
|
6130
|
+
if (meta?.name === normalizedName) {
|
|
6131
|
+
return {
|
|
6132
|
+
taskDir,
|
|
6133
|
+
meta
|
|
6134
|
+
};
|
|
6135
|
+
}
|
|
6076
6136
|
}
|
|
6077
|
-
|
|
6078
|
-
return { valid: true, segments };
|
|
6137
|
+
return null;
|
|
6079
6138
|
}
|
|
6080
|
-
function
|
|
6081
|
-
|
|
6139
|
+
function readOptionalString(value) {
|
|
6140
|
+
if (typeof value !== "string") return void 0;
|
|
6141
|
+
const trimmed = value.trim();
|
|
6142
|
+
return trimmed || void 0;
|
|
6143
|
+
}
|
|
6144
|
+
function isLegacyLightRuleWithoutType(raw) {
|
|
6145
|
+
return raw.type === void 0 && readOptionalString(raw.name) !== void 0 && readOptionalString(raw.description) !== void 0 && Array.isArray(raw.segments);
|
|
6146
|
+
}
|
|
6147
|
+
function readMeta(taskDir) {
|
|
6148
|
+
const metaPath = (0, import_node_path3.join)(taskDir, "meta.json");
|
|
6149
|
+
if (!(0, import_node_fs4.existsSync)(metaPath)) return null;
|
|
6082
6150
|
try {
|
|
6083
|
-
|
|
6151
|
+
const raw = JSON.parse((0, import_node_fs4.readFileSync)(metaPath, "utf-8"));
|
|
6152
|
+
if (!raw || typeof raw !== "object" || Array.isArray(raw)) return null;
|
|
6153
|
+
if (raw.type !== "light-rule" && !isLegacyLightRuleWithoutType(raw)) return null;
|
|
6154
|
+
if (!Array.isArray(raw.segments)) return null;
|
|
6155
|
+
const name = readOptionalString(raw.name) ?? (0, import_node_path3.basename)(taskDir);
|
|
6156
|
+
const description = readOptionalString(raw.description) ?? readOptionalString(raw.reason) ?? name;
|
|
6157
|
+
const createdAt = readOptionalString(raw.createdAt) ?? (0, import_node_fs4.statSync)(metaPath).birthtime.toISOString();
|
|
6158
|
+
const enabled = typeof raw.enabled === "boolean" ? raw.enabled : true;
|
|
6159
|
+
const repeatTimes = normalizeRepeatTimes({
|
|
6160
|
+
repeat: raw.repeat,
|
|
6161
|
+
repeat_times: raw.repeat_times
|
|
6162
|
+
});
|
|
6163
|
+
assertAncsRepeatTimes(repeatTimes);
|
|
6164
|
+
return {
|
|
6165
|
+
...raw,
|
|
6166
|
+
name,
|
|
6167
|
+
type: "light-rule",
|
|
6168
|
+
description,
|
|
6169
|
+
segments: raw.segments,
|
|
6170
|
+
repeat_times: repeatTimes,
|
|
6171
|
+
enabled,
|
|
6172
|
+
createdAt
|
|
6173
|
+
};
|
|
6084
6174
|
} catch {
|
|
6085
|
-
|
|
6086
|
-
}
|
|
6087
|
-
const result = validateSegments(parsed);
|
|
6088
|
-
if (!result.valid) {
|
|
6089
|
-
output({ ok: false, error: { code: "VALIDATION_FAILED", details: result.errors } });
|
|
6090
|
-
process.exit(1);
|
|
6175
|
+
return null;
|
|
6091
6176
|
}
|
|
6092
|
-
return result.segments;
|
|
6093
6177
|
}
|
|
6094
|
-
function
|
|
6095
|
-
|
|
6096
|
-
|
|
6097
|
-
|
|
6178
|
+
function writeMeta(taskDir, meta) {
|
|
6179
|
+
(0, import_node_fs4.writeFileSync)((0, import_node_path3.join)(taskDir, "meta.json"), JSON.stringify(meta, null, 2), "utf-8");
|
|
6180
|
+
}
|
|
6181
|
+
function generateLightRuleReadme(name, description, segments, repeatTimes) {
|
|
6182
|
+
const segmentsJson = JSON.stringify(segments, null, 2);
|
|
6183
|
+
return `# ${name}
|
|
6184
|
+
|
|
6185
|
+
## \u76D1\u63A7\u76EE\u6807
|
|
6186
|
+
|
|
6187
|
+
${description}
|
|
6188
|
+
|
|
6189
|
+
## \u89E6\u53D1\u540E\u5982\u4F55\u5904\u7406
|
|
6190
|
+
|
|
6191
|
+
\u5F53\u4EFB\u52A1\u811A\u672C\u8F93\u51FA\u5339\u914D\u7ED3\u679C\u65F6\uFF1A
|
|
6192
|
+
|
|
6193
|
+
1. \u5C06\u8F93\u51FA\u6309"\u6BCF\u884C\u4E00\u4E2A JSON \u5BF9\u8C61"\u89E3\u6790
|
|
6194
|
+
2. \u8FD9\u4E9B\u662F\u5019\u9009\u901A\u77E5\uFF0C\u4E0D\u4EE3\u8868\u5DF2\u7ECF\u547D\u4E2D
|
|
6195
|
+
3. \u6839\u636E\u76D1\u63A7\u76EE\u6807\u5224\u65AD\u662F\u5426\u771F\u6B63\u547D\u4E2D
|
|
6196
|
+
|
|
6197
|
+
**\u547D\u4E2D\u540E\u6267\u884C\u706F\u6548**\uFF1A\u8C03\u7528 light_control \u5DE5\u5177\uFF0C\u4F20\u5165\u4EE5\u4E0B\u706F\u6548\u53C2\u6570\uFF1A
|
|
6198
|
+
|
|
6199
|
+
\`\`\`json
|
|
6200
|
+
${segmentsJson}
|
|
6201
|
+
\`\`\`
|
|
6202
|
+
|
|
6203
|
+
repeat_times: ${repeatTimes}
|
|
6204
|
+
reason: "${description}"
|
|
6205
|
+
|
|
6206
|
+
\u53EA\u6267\u884C\u706F\u6548\uFF0C\u4E0D\u4E3B\u52A8\u53D1\u9001\u901A\u77E5\u6458\u8981\u6216\u56DE\u590D\u7528\u6237\u3002
|
|
6207
|
+
`;
|
|
6208
|
+
}
|
|
6209
|
+
function listLightRules(ctx) {
|
|
6210
|
+
const dir = tasksDir(ctx);
|
|
6211
|
+
if (!(0, import_node_fs4.existsSync)(dir)) return [];
|
|
6212
|
+
const rules = [];
|
|
6213
|
+
for (const entry of (0, import_node_fs4.readdirSync)(dir, { withFileTypes: true })) {
|
|
6214
|
+
if (!entry.isDirectory()) continue;
|
|
6215
|
+
const meta = readMeta((0, import_node_path3.join)(dir, entry.name));
|
|
6216
|
+
if (meta) rules.push(meta);
|
|
6098
6217
|
}
|
|
6099
|
-
|
|
6100
|
-
|
|
6101
|
-
|
|
6102
|
-
|
|
6103
|
-
|
|
6104
|
-
|
|
6218
|
+
return rules;
|
|
6219
|
+
}
|
|
6220
|
+
function createLightRule(ctx, params) {
|
|
6221
|
+
const dir = tasksDir(ctx);
|
|
6222
|
+
const taskDir = (0, import_node_path3.join)(dir, params.name);
|
|
6223
|
+
if ((0, import_node_fs4.existsSync)(taskDir)) {
|
|
6224
|
+
throw new LightRuleError("ALREADY_EXISTS", `\u706F\u6548\u89C4\u5219 '${params.name}' \u5DF2\u5B58\u5728`);
|
|
6105
6225
|
}
|
|
6106
|
-
|
|
6107
|
-
|
|
6108
|
-
|
|
6109
|
-
|
|
6110
|
-
|
|
6111
|
-
|
|
6112
|
-
|
|
6113
|
-
|
|
6114
|
-
|
|
6115
|
-
|
|
6116
|
-
|
|
6117
|
-
|
|
6118
|
-
|
|
6119
|
-
|
|
6120
|
-
|
|
6121
|
-
|
|
6122
|
-
|
|
6123
|
-
|
|
6124
|
-
|
|
6125
|
-
|
|
6226
|
+
(0, import_node_fs4.mkdirSync)(taskDir, { recursive: true });
|
|
6227
|
+
const repeatTimes = normalizeRepeatTimes({
|
|
6228
|
+
repeat: params.repeat,
|
|
6229
|
+
repeat_times: params.repeat_times
|
|
6230
|
+
});
|
|
6231
|
+
assertAncsRepeatTimes(repeatTimes);
|
|
6232
|
+
const effectiveMatchRules = legacyReadMatchRules(params);
|
|
6233
|
+
const effectiveCronSchedule = legacyReadCronSchedule(params);
|
|
6234
|
+
const meta = {
|
|
6235
|
+
name: params.name,
|
|
6236
|
+
type: "light-rule",
|
|
6237
|
+
description: params.description,
|
|
6238
|
+
segments: params.segments,
|
|
6239
|
+
repeat_times: repeatTimes,
|
|
6240
|
+
enabled: true,
|
|
6241
|
+
createdAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
6242
|
+
};
|
|
6243
|
+
legacyAssignMatchRules(meta, effectiveMatchRules);
|
|
6244
|
+
legacyAssignCronSchedule(meta, effectiveCronSchedule);
|
|
6245
|
+
writeMeta(taskDir, meta);
|
|
6246
|
+
(0, import_node_fs4.writeFileSync)(
|
|
6247
|
+
(0, import_node_path3.join)(taskDir, "fetch.py"),
|
|
6248
|
+
generateFetchPy(params.name, effectiveMatchRules),
|
|
6249
|
+
"utf-8"
|
|
6250
|
+
);
|
|
6251
|
+
(0, import_node_fs4.writeFileSync)(
|
|
6252
|
+
(0, import_node_path3.join)(taskDir, "README.md"),
|
|
6253
|
+
generateLightRuleReadme(params.name, params.description, params.segments, repeatTimes),
|
|
6254
|
+
"utf-8"
|
|
6255
|
+
);
|
|
6256
|
+
return {
|
|
6257
|
+
meta,
|
|
6258
|
+
cronHint: {
|
|
6259
|
+
action: "add",
|
|
6260
|
+
job: {
|
|
6261
|
+
name: `notif-${params.name}`,
|
|
6262
|
+
schedule: effectiveCronSchedule,
|
|
6263
|
+
sessionTarget: "isolated",
|
|
6264
|
+
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
|
|
6265
|
+
\u6267\u884C\uFF1Apython3 tasks/${params.name}/fetch.py --notifications-dir notifications
|
|
6266
|
+
- NO_CHANGE \u6216 NO_MATCH \u2192 \u4E0D\u56DE\u590D\uFF0C\u76F4\u63A5\u7ED3\u675F\u3002
|
|
6267
|
+
- \u6709\u8F93\u51FA \u2192 \u8BFB tasks/${params.name}/README.md \u4E86\u89E3\u5982\u4F55\u5904\u7406\u6570\u636E\u5E76\u901A\u77E5\u7528\u6237\u3002`
|
|
6126
6268
|
}
|
|
6127
|
-
|
|
6128
|
-
|
|
6129
|
-
validateForegroundSegment(seg, prefix, errors);
|
|
6130
|
-
validateOptionalBreathTiming(seg.breath_timing, `${prefix}.breath_timing`, errors);
|
|
6131
|
-
break;
|
|
6132
|
-
case "strobe":
|
|
6133
|
-
validateForegroundSegment(seg, prefix, errors);
|
|
6134
|
-
validateOptionalNonNegativeNumber(seg.interval_ms, `${prefix}.interval_ms`, errors);
|
|
6135
|
-
break;
|
|
6136
|
-
case "steady":
|
|
6137
|
-
validateForegroundSegment(seg, prefix, errors);
|
|
6138
|
-
break;
|
|
6139
|
-
case "pixel_frame":
|
|
6140
|
-
validatePixelFrame(seg.pixels, `${prefix}.pixels`, errors);
|
|
6141
|
-
break;
|
|
6142
|
-
default:
|
|
6143
|
-
validateOptionalNonNegativeNumber(seg.brightness, `${prefix}.brightness`, errors);
|
|
6144
|
-
validateOptionalColor(seg.color, `${prefix}.color`, errors);
|
|
6145
|
-
validateOptionalNonNegativeNumber(seg.interval_ms, `${prefix}.interval_ms`, errors);
|
|
6146
|
-
validateOptionalDirection(seg.direction, `${prefix}.direction`, errors);
|
|
6147
|
-
validateOptionalWindow(seg.window, `${prefix}.window`, errors);
|
|
6148
|
-
validateOptionalBreathTiming(seg.breath_timing, `${prefix}.breath_timing`, errors);
|
|
6149
|
-
validateOptionalBackground(seg.background, `${prefix}.background`, errors);
|
|
6150
|
-
}
|
|
6269
|
+
}
|
|
6270
|
+
};
|
|
6151
6271
|
}
|
|
6152
|
-
function
|
|
6153
|
-
|
|
6154
|
-
|
|
6155
|
-
|
|
6156
|
-
|
|
6157
|
-
|
|
6158
|
-
255,
|
|
6159
|
-
"\u5FC5\u987B\u662F 0\u2013255 \u7684\u6570\u5B57"
|
|
6160
|
-
);
|
|
6161
|
-
validateColor(seg.color, `${prefix}.color`, errors);
|
|
6162
|
-
if (seg.mode !== "steady" && seg.brightness === 0) {
|
|
6163
|
-
errors.push({
|
|
6164
|
-
field: `${prefix}.brightness`,
|
|
6165
|
-
message: "brightness=0 \u4EC5 steady \u6A21\u5F0F\u5141\u8BB8\uFF1B\u5176\u5B83\u6A21\u5F0F\u4F1A\u5728\u56FA\u4EF6\u4FA7\u88AB\u8FC7\u6EE4"
|
|
6166
|
-
});
|
|
6272
|
+
function updateLightRule(ctx, params) {
|
|
6273
|
+
const resolved = resolveLightRuleTask(ctx, params.name);
|
|
6274
|
+
const taskDir = resolved?.taskDir;
|
|
6275
|
+
const meta = resolved?.meta;
|
|
6276
|
+
if (!taskDir || !meta) {
|
|
6277
|
+
throw new LightRuleError("NOT_FOUND", `\u706F\u6548\u89C4\u5219 '${params.name}' \u4E0D\u5B58\u5728`);
|
|
6167
6278
|
}
|
|
6168
|
-
|
|
6169
|
-
|
|
6170
|
-
if (
|
|
6171
|
-
|
|
6172
|
-
|
|
6279
|
+
let regenerateFetch = false;
|
|
6280
|
+
let regenerateReadme = false;
|
|
6281
|
+
if (params.description !== void 0) {
|
|
6282
|
+
meta.description = params.description;
|
|
6283
|
+
regenerateReadme = true;
|
|
6173
6284
|
}
|
|
6174
|
-
if (
|
|
6175
|
-
|
|
6285
|
+
if (legacyHasMatchRules(params)) {
|
|
6286
|
+
legacyAssignMatchRules(meta, legacyReadMatchRules(params));
|
|
6287
|
+
regenerateFetch = true;
|
|
6176
6288
|
}
|
|
6177
|
-
|
|
6178
|
-
|
|
6179
|
-
|
|
6180
|
-
const prefix = `${field}[${i}]`;
|
|
6181
|
-
if (!isRecord(pixel)) {
|
|
6182
|
-
errors.push({ field: prefix, message: "\u5FC5\u987B\u662F\u5BF9\u8C61" });
|
|
6183
|
-
continue;
|
|
6184
|
-
}
|
|
6185
|
-
const idx = pixel.index;
|
|
6186
|
-
if (!Number.isInteger(idx) || idx < 0 || idx > 6) {
|
|
6187
|
-
errors.push({ field: `${prefix}.index`, message: "index \u5FC5\u987B\u662F 0\u20136 \u7684\u6574\u6570" });
|
|
6188
|
-
} else if (seen.has(idx)) {
|
|
6189
|
-
errors.push({ field: `${prefix}.index`, message: `index=${idx} \u91CD\u590D` });
|
|
6190
|
-
} else {
|
|
6191
|
-
seen.add(idx);
|
|
6192
|
-
}
|
|
6193
|
-
validateNumberInRange(
|
|
6194
|
-
pixel.brightness,
|
|
6195
|
-
`${prefix}.brightness`,
|
|
6196
|
-
errors,
|
|
6197
|
-
0,
|
|
6198
|
-
255,
|
|
6199
|
-
"\u5FC5\u987B\u662F 0\u2013255 \u7684\u6570\u5B57"
|
|
6200
|
-
);
|
|
6201
|
-
validateColor(pixel.color, `${prefix}.color`, errors);
|
|
6289
|
+
if (params.segments !== void 0) {
|
|
6290
|
+
meta.segments = params.segments;
|
|
6291
|
+
regenerateReadme = true;
|
|
6202
6292
|
}
|
|
6203
|
-
|
|
6204
|
-
|
|
6205
|
-
|
|
6206
|
-
|
|
6207
|
-
|
|
6208
|
-
|
|
6293
|
+
if (params.repeat !== void 0 || params.repeat_times !== void 0) {
|
|
6294
|
+
meta.repeat_times = normalizeRepeatTimes({
|
|
6295
|
+
repeat: params.repeat,
|
|
6296
|
+
repeat_times: params.repeat_times
|
|
6297
|
+
});
|
|
6298
|
+
assertAncsRepeatTimes(meta.repeat_times);
|
|
6299
|
+
regenerateReadme = true;
|
|
6209
6300
|
}
|
|
6210
|
-
|
|
6211
|
-
|
|
6212
|
-
`${field}.rise_ms`,
|
|
6213
|
-
errors,
|
|
6214
|
-
"rise_ms \u5FC5\u987B\u662F >0 \u7684\u6570\u5B57\uFF08\u4E0D\u652F\u6301 0ms\uFF09"
|
|
6215
|
-
);
|
|
6216
|
-
validateNonNegativeNumber(
|
|
6217
|
-
value.hold_ms,
|
|
6218
|
-
`${field}.hold_ms`,
|
|
6219
|
-
errors,
|
|
6220
|
-
"hold_ms \u5FC5\u987B\u662F \u22650 \u7684\u6570\u5B57"
|
|
6221
|
-
);
|
|
6222
|
-
validatePositiveNumber(
|
|
6223
|
-
value.fall_ms,
|
|
6224
|
-
`${field}.fall_ms`,
|
|
6225
|
-
errors,
|
|
6226
|
-
"fall_ms \u5FC5\u987B\u662F >0 \u7684\u6570\u5B57\uFF08\u4E0D\u652F\u6301 0ms\uFF09"
|
|
6227
|
-
);
|
|
6228
|
-
validateNonNegativeNumber(
|
|
6229
|
-
value.off_ms,
|
|
6230
|
-
`${field}.off_ms`,
|
|
6231
|
-
errors,
|
|
6232
|
-
"off_ms \u5FC5\u987B\u662F \u22650 \u7684\u6570\u5B57"
|
|
6233
|
-
);
|
|
6234
|
-
}
|
|
6235
|
-
function validateOptionalBackground(value, field, errors) {
|
|
6236
|
-
if (value === void 0) return;
|
|
6237
|
-
if (!isRecord(value)) {
|
|
6238
|
-
errors.push({ field, message: "\u5FC5\u987B\u5305\u542B r/g/b/brightness \u6570\u503C" });
|
|
6239
|
-
return;
|
|
6301
|
+
if (legacyHasCronSchedule(params)) {
|
|
6302
|
+
legacyAssignCronSchedule(meta, legacyReadCronSchedule(params));
|
|
6240
6303
|
}
|
|
6241
|
-
|
|
6242
|
-
|
|
6243
|
-
value.brightness,
|
|
6244
|
-
`${field}.brightness`,
|
|
6245
|
-
errors,
|
|
6246
|
-
0,
|
|
6247
|
-
255,
|
|
6248
|
-
"\u5FC5\u987B\u662F 0\u2013255 \u7684\u6570\u5B57"
|
|
6249
|
-
);
|
|
6250
|
-
}
|
|
6251
|
-
function validateOptionalColor(value, field, errors) {
|
|
6252
|
-
if (value === void 0) return;
|
|
6253
|
-
validateColor(value, field, errors);
|
|
6254
|
-
}
|
|
6255
|
-
function validateColor(value, field, errors) {
|
|
6256
|
-
if (!isRecord(value)) {
|
|
6257
|
-
errors.push({ field, message: "\u5FC5\u987B\u5305\u542B r/g/b \u6570\u503C" });
|
|
6258
|
-
return;
|
|
6304
|
+
if (params.enabled !== void 0) {
|
|
6305
|
+
meta.enabled = params.enabled;
|
|
6259
6306
|
}
|
|
6260
|
-
|
|
6261
|
-
|
|
6262
|
-
|
|
6263
|
-
|
|
6264
|
-
|
|
6265
|
-
|
|
6266
|
-
|
|
6267
|
-
|
|
6307
|
+
meta.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
6308
|
+
writeMeta(taskDir, meta);
|
|
6309
|
+
if (regenerateFetch) {
|
|
6310
|
+
(0, import_node_fs4.writeFileSync)(
|
|
6311
|
+
(0, import_node_path3.join)(taskDir, "fetch.py"),
|
|
6312
|
+
generateFetchPy(meta.name, legacyReadMetaMatchRules(meta)),
|
|
6313
|
+
"utf-8"
|
|
6314
|
+
);
|
|
6268
6315
|
}
|
|
6269
|
-
|
|
6270
|
-
|
|
6271
|
-
|
|
6272
|
-
|
|
6273
|
-
|
|
6316
|
+
if (regenerateReadme) {
|
|
6317
|
+
(0, import_node_fs4.writeFileSync)(
|
|
6318
|
+
(0, import_node_path3.join)(taskDir, "README.md"),
|
|
6319
|
+
generateLightRuleReadme(meta.name, meta.description, meta.segments, meta.repeat_times),
|
|
6320
|
+
"utf-8"
|
|
6321
|
+
);
|
|
6274
6322
|
}
|
|
6275
|
-
|
|
6276
|
-
|
|
6277
|
-
|
|
6278
|
-
|
|
6279
|
-
|
|
6280
|
-
|
|
6281
|
-
|
|
6282
|
-
|
|
6283
|
-
errors.push({ field, message });
|
|
6284
|
-
}
|
|
6285
|
-
}
|
|
6286
|
-
function validateNonNegativeNumber(value, field, errors, message) {
|
|
6287
|
-
if (!isFiniteNumber(value) || value < 0) {
|
|
6288
|
-
errors.push({ field, message });
|
|
6289
|
-
}
|
|
6290
|
-
}
|
|
6291
|
-
function validateNumberInRange(value, field, errors, min, max, message) {
|
|
6292
|
-
if (!isFiniteNumber(value) || value < min || value > max) {
|
|
6293
|
-
errors.push({ field, message });
|
|
6294
|
-
}
|
|
6295
|
-
}
|
|
6296
|
-
function hasNonZeroRgb(value) {
|
|
6297
|
-
if (!value) return false;
|
|
6298
|
-
return [value.r, value.g, value.b].some((channel) => isFiniteNumber(channel) && channel > 0);
|
|
6299
|
-
}
|
|
6300
|
-
function isFiniteNumber(value) {
|
|
6301
|
-
return typeof value === "number" && Number.isFinite(value);
|
|
6302
|
-
}
|
|
6303
|
-
function isRecord(value) {
|
|
6304
|
-
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
6305
|
-
}
|
|
6306
|
-
|
|
6307
|
-
// src/light-rules/storage.ts
|
|
6308
|
-
var import_node_fs5 = require("fs");
|
|
6309
|
-
var import_node_path4 = require("path");
|
|
6310
|
-
|
|
6311
|
-
// src/monitor/fetch-gen.ts
|
|
6312
|
-
function generateFetchPy(name, matchRules) {
|
|
6313
|
-
const appName = typeof matchRules.appName === "string" ? matchRules.appName : "";
|
|
6314
|
-
const senderKeywords = stringArray(matchRules.senderKeywords);
|
|
6315
|
-
const contentKeywords = stringArray(matchRules.contentKeywords);
|
|
6316
|
-
return `#!/usr/bin/env python3
|
|
6317
|
-
"""Auto-generated fetch script for monitor task: ${name}"""
|
|
6318
|
-
import json, sys, os
|
|
6319
|
-
from pathlib import Path
|
|
6320
|
-
|
|
6321
|
-
def matches(notification: dict) -> bool:
|
|
6322
|
-
app = str(notification.get("appName", "") or "")
|
|
6323
|
-
title = str(notification.get("title", "") or "")
|
|
6324
|
-
content = str(notification.get("content", "") or "")
|
|
6325
|
-
body = str(notification.get("body", "") or "")
|
|
6326
|
-
|
|
6327
|
-
app_name = ${pyLiteral(appName)}
|
|
6328
|
-
if app_name and app != app_name:
|
|
6329
|
-
return False
|
|
6330
|
-
|
|
6331
|
-
sender_keywords = ${pyLiteral(senderKeywords)}
|
|
6332
|
-
sender_haystack = f"{title}\\n{content}\\n{body}"
|
|
6333
|
-
if sender_keywords and not any(keyword in sender_haystack for keyword in sender_keywords):
|
|
6334
|
-
return False
|
|
6335
|
-
|
|
6336
|
-
content_keywords = ${pyLiteral(contentKeywords)}
|
|
6337
|
-
content_haystack = f"{content}\\n{body}"
|
|
6338
|
-
if content_keywords and not any(keyword in content_haystack for keyword in content_keywords):
|
|
6339
|
-
return False
|
|
6340
|
-
|
|
6341
|
-
return True
|
|
6342
|
-
|
|
6343
|
-
def main():
|
|
6344
|
-
import argparse
|
|
6345
|
-
parser = argparse.ArgumentParser()
|
|
6346
|
-
parser.add_argument("--notifications-dir", required=True)
|
|
6347
|
-
args = parser.parse_args()
|
|
6348
|
-
|
|
6349
|
-
checkpoint_path = Path(__file__).parent / "checkpoint.json"
|
|
6350
|
-
checkpoint = {}
|
|
6351
|
-
if checkpoint_path.exists():
|
|
6352
|
-
checkpoint = json.loads(checkpoint_path.read_text())
|
|
6353
|
-
|
|
6354
|
-
ntf_dir = Path(args.notifications_dir)
|
|
6355
|
-
matched = []
|
|
6356
|
-
new_checkpoint = dict(checkpoint)
|
|
6357
|
-
|
|
6358
|
-
for f in sorted(ntf_dir.glob("*.json")):
|
|
6359
|
-
date_key = f.stem
|
|
6360
|
-
items = json.loads(f.read_text())
|
|
6361
|
-
last_idx = checkpoint.get(date_key, {}).get("lastIndex", -1)
|
|
6362
|
-
for i, item in enumerate(items):
|
|
6363
|
-
if i <= last_idx:
|
|
6364
|
-
continue
|
|
6365
|
-
if matches(item):
|
|
6366
|
-
matched.append(item)
|
|
6367
|
-
new_checkpoint[date_key] = {"lastIndex": len(items) - 1}
|
|
6368
|
-
|
|
6369
|
-
checkpoint_path.write_text(json.dumps(new_checkpoint, indent=2))
|
|
6370
|
-
|
|
6371
|
-
if not matched:
|
|
6372
|
-
print("NO_MATCH")
|
|
6373
|
-
return
|
|
6374
|
-
|
|
6375
|
-
for m in matched:
|
|
6376
|
-
print(json.dumps(m, ensure_ascii=False))
|
|
6377
|
-
|
|
6378
|
-
if __name__ == "__main__":
|
|
6379
|
-
main()
|
|
6380
|
-
`;
|
|
6381
|
-
}
|
|
6382
|
-
function stringArray(value) {
|
|
6383
|
-
if (!Array.isArray(value)) return [];
|
|
6384
|
-
return value.filter((item) => typeof item === "string" && item.length > 0);
|
|
6385
|
-
}
|
|
6386
|
-
function pyLiteral(value) {
|
|
6387
|
-
return JSON.stringify(value);
|
|
6388
|
-
}
|
|
6389
|
-
|
|
6390
|
-
// src/light-rules/storage.ts
|
|
6391
|
-
var LEGACY_DEFAULT_CRON_SCHEDULE = "*/5 * * * *";
|
|
6392
|
-
function legacyReadMatchRules(input) {
|
|
6393
|
-
return input.matchRules ?? {};
|
|
6394
|
-
}
|
|
6395
|
-
function legacyReadCronSchedule(input) {
|
|
6396
|
-
return input.cronSchedule ?? LEGACY_DEFAULT_CRON_SCHEDULE;
|
|
6397
|
-
}
|
|
6398
|
-
function legacyHasMatchRules(input) {
|
|
6399
|
-
return input.matchRules !== void 0;
|
|
6400
|
-
}
|
|
6401
|
-
function legacyHasCronSchedule(input) {
|
|
6402
|
-
return input.cronSchedule !== void 0;
|
|
6403
|
-
}
|
|
6404
|
-
function legacyAssignMatchRules(meta, matchRules) {
|
|
6405
|
-
meta.matchRules = matchRules;
|
|
6406
|
-
}
|
|
6407
|
-
function legacyAssignCronSchedule(meta, cronSchedule) {
|
|
6408
|
-
meta.cronSchedule = cronSchedule;
|
|
6409
|
-
}
|
|
6410
|
-
function legacyReadMetaMatchRules(meta) {
|
|
6411
|
-
return meta.matchRules ?? {};
|
|
6412
|
-
}
|
|
6413
|
-
function legacyReadMetaCronSchedule(meta) {
|
|
6414
|
-
return meta.cronSchedule;
|
|
6415
|
-
}
|
|
6416
|
-
function resolveBaseDir(ctx) {
|
|
6417
|
-
if (ctx.workspaceDir) return ctx.workspaceDir;
|
|
6418
|
-
if (ctx.stateDir) {
|
|
6419
|
-
const inferredWorkspaceDir = (0, import_node_path4.join)(ctx.stateDir, "workspace");
|
|
6420
|
-
if ((0, import_node_fs5.existsSync)(inferredWorkspaceDir)) return inferredWorkspaceDir;
|
|
6421
|
-
return ctx.stateDir;
|
|
6422
|
-
}
|
|
6423
|
-
throw new Error("workspaceDir and stateDir both unavailable");
|
|
6424
|
-
}
|
|
6425
|
-
function tasksDir(ctx) {
|
|
6426
|
-
return (0, import_node_path4.join)(resolveBaseDir(ctx), "tasks");
|
|
6427
|
-
}
|
|
6428
|
-
function normalizeLightRuleLookupName(name) {
|
|
6429
|
-
return name.trim().replace(/\.json$/i, "");
|
|
6430
|
-
}
|
|
6431
|
-
function resolveLightRuleTask(ctx, name) {
|
|
6432
|
-
const dir = tasksDir(ctx);
|
|
6433
|
-
const normalizedName = normalizeLightRuleLookupName(name);
|
|
6434
|
-
if (!normalizedName) return null;
|
|
6435
|
-
const directTaskDir = (0, import_node_path4.join)(dir, normalizedName);
|
|
6436
|
-
const directMeta = readMeta(directTaskDir);
|
|
6437
|
-
if (directMeta) {
|
|
6438
|
-
return {
|
|
6439
|
-
taskDir: directTaskDir,
|
|
6440
|
-
meta: directMeta
|
|
6441
|
-
};
|
|
6442
|
-
}
|
|
6443
|
-
if (!(0, import_node_fs5.existsSync)(dir)) return null;
|
|
6444
|
-
for (const entry of (0, import_node_fs5.readdirSync)(dir, { withFileTypes: true })) {
|
|
6445
|
-
if (!entry.isDirectory()) continue;
|
|
6446
|
-
const taskDir = (0, import_node_path4.join)(dir, entry.name);
|
|
6447
|
-
const meta = readMeta(taskDir);
|
|
6448
|
-
if (meta?.name === normalizedName) {
|
|
6449
|
-
return {
|
|
6450
|
-
taskDir,
|
|
6451
|
-
meta
|
|
6452
|
-
};
|
|
6453
|
-
}
|
|
6454
|
-
}
|
|
6455
|
-
return null;
|
|
6456
|
-
}
|
|
6457
|
-
function readOptionalString(value) {
|
|
6458
|
-
if (typeof value !== "string") return void 0;
|
|
6459
|
-
const trimmed = value.trim();
|
|
6460
|
-
return trimmed || void 0;
|
|
6461
|
-
}
|
|
6462
|
-
function isLegacyLightRuleWithoutType(raw) {
|
|
6463
|
-
return raw.type === void 0 && readOptionalString(raw.name) !== void 0 && readOptionalString(raw.description) !== void 0 && Array.isArray(raw.segments);
|
|
6464
|
-
}
|
|
6465
|
-
function readMeta(taskDir) {
|
|
6466
|
-
const metaPath = (0, import_node_path4.join)(taskDir, "meta.json");
|
|
6467
|
-
if (!(0, import_node_fs5.existsSync)(metaPath)) return null;
|
|
6468
|
-
try {
|
|
6469
|
-
const raw = JSON.parse((0, import_node_fs5.readFileSync)(metaPath, "utf-8"));
|
|
6470
|
-
if (!raw || typeof raw !== "object" || Array.isArray(raw)) return null;
|
|
6471
|
-
if (raw.type !== "light-rule" && !isLegacyLightRuleWithoutType(raw)) return null;
|
|
6472
|
-
if (!Array.isArray(raw.segments)) return null;
|
|
6473
|
-
const name = readOptionalString(raw.name) ?? (0, import_node_path4.basename)(taskDir);
|
|
6474
|
-
const description = readOptionalString(raw.description) ?? readOptionalString(raw.reason) ?? name;
|
|
6475
|
-
const createdAt = readOptionalString(raw.createdAt) ?? (0, import_node_fs5.statSync)(metaPath).birthtime.toISOString();
|
|
6476
|
-
const enabled = typeof raw.enabled === "boolean" ? raw.enabled : true;
|
|
6477
|
-
const repeatTimes = normalizeRepeatTimes({
|
|
6478
|
-
repeat: raw.repeat,
|
|
6479
|
-
repeat_times: raw.repeat_times
|
|
6480
|
-
});
|
|
6481
|
-
assertAncsRepeatTimes(repeatTimes);
|
|
6482
|
-
return {
|
|
6483
|
-
...raw,
|
|
6484
|
-
name,
|
|
6485
|
-
type: "light-rule",
|
|
6486
|
-
description,
|
|
6487
|
-
segments: raw.segments,
|
|
6488
|
-
repeat_times: repeatTimes,
|
|
6489
|
-
enabled,
|
|
6490
|
-
createdAt
|
|
6491
|
-
};
|
|
6492
|
-
} catch {
|
|
6493
|
-
return null;
|
|
6494
|
-
}
|
|
6495
|
-
}
|
|
6496
|
-
function writeMeta(taskDir, meta) {
|
|
6497
|
-
(0, import_node_fs5.writeFileSync)((0, import_node_path4.join)(taskDir, "meta.json"), JSON.stringify(meta, null, 2), "utf-8");
|
|
6498
|
-
}
|
|
6499
|
-
function generateLightRuleReadme(name, description, segments, repeatTimes) {
|
|
6500
|
-
const segmentsJson = JSON.stringify(segments, null, 2);
|
|
6501
|
-
return `# ${name}
|
|
6502
|
-
|
|
6503
|
-
## \u76D1\u63A7\u76EE\u6807
|
|
6504
|
-
|
|
6505
|
-
${description}
|
|
6506
|
-
|
|
6507
|
-
## \u89E6\u53D1\u540E\u5982\u4F55\u5904\u7406
|
|
6508
|
-
|
|
6509
|
-
\u5F53\u4EFB\u52A1\u811A\u672C\u8F93\u51FA\u5339\u914D\u7ED3\u679C\u65F6\uFF1A
|
|
6510
|
-
|
|
6511
|
-
1. \u5C06\u8F93\u51FA\u6309"\u6BCF\u884C\u4E00\u4E2A JSON \u5BF9\u8C61"\u89E3\u6790
|
|
6512
|
-
2. \u8FD9\u4E9B\u662F\u5019\u9009\u901A\u77E5\uFF0C\u4E0D\u4EE3\u8868\u5DF2\u7ECF\u547D\u4E2D
|
|
6513
|
-
3. \u6839\u636E\u76D1\u63A7\u76EE\u6807\u5224\u65AD\u662F\u5426\u771F\u6B63\u547D\u4E2D
|
|
6514
|
-
|
|
6515
|
-
**\u547D\u4E2D\u540E\u6267\u884C\u706F\u6548**\uFF1A\u8C03\u7528 light_control \u5DE5\u5177\uFF0C\u4F20\u5165\u4EE5\u4E0B\u706F\u6548\u53C2\u6570\uFF1A
|
|
6516
|
-
|
|
6517
|
-
\`\`\`json
|
|
6518
|
-
${segmentsJson}
|
|
6519
|
-
\`\`\`
|
|
6520
|
-
|
|
6521
|
-
repeat_times: ${repeatTimes}
|
|
6522
|
-
reason: "${description}"
|
|
6523
|
-
|
|
6524
|
-
\u53EA\u6267\u884C\u706F\u6548\uFF0C\u4E0D\u4E3B\u52A8\u53D1\u9001\u901A\u77E5\u6458\u8981\u6216\u56DE\u590D\u7528\u6237\u3002
|
|
6525
|
-
`;
|
|
6526
|
-
}
|
|
6527
|
-
function listLightRules(ctx) {
|
|
6528
|
-
const dir = tasksDir(ctx);
|
|
6529
|
-
if (!(0, import_node_fs5.existsSync)(dir)) return [];
|
|
6530
|
-
const rules = [];
|
|
6531
|
-
for (const entry of (0, import_node_fs5.readdirSync)(dir, { withFileTypes: true })) {
|
|
6532
|
-
if (!entry.isDirectory()) continue;
|
|
6533
|
-
const meta = readMeta((0, import_node_path4.join)(dir, entry.name));
|
|
6534
|
-
if (meta) rules.push(meta);
|
|
6535
|
-
}
|
|
6536
|
-
return rules;
|
|
6537
|
-
}
|
|
6538
|
-
function createLightRule(ctx, params) {
|
|
6539
|
-
const dir = tasksDir(ctx);
|
|
6540
|
-
const taskDir = (0, import_node_path4.join)(dir, params.name);
|
|
6541
|
-
if ((0, import_node_fs5.existsSync)(taskDir)) {
|
|
6542
|
-
throw new LightRuleError("ALREADY_EXISTS", `\u706F\u6548\u89C4\u5219 '${params.name}' \u5DF2\u5B58\u5728`);
|
|
6543
|
-
}
|
|
6544
|
-
(0, import_node_fs5.mkdirSync)(taskDir, { recursive: true });
|
|
6545
|
-
const repeatTimes = normalizeRepeatTimes({
|
|
6546
|
-
repeat: params.repeat,
|
|
6547
|
-
repeat_times: params.repeat_times
|
|
6548
|
-
});
|
|
6549
|
-
assertAncsRepeatTimes(repeatTimes);
|
|
6550
|
-
const effectiveMatchRules = legacyReadMatchRules(params);
|
|
6551
|
-
const effectiveCronSchedule = legacyReadCronSchedule(params);
|
|
6552
|
-
const meta = {
|
|
6553
|
-
name: params.name,
|
|
6554
|
-
type: "light-rule",
|
|
6555
|
-
description: params.description,
|
|
6556
|
-
segments: params.segments,
|
|
6557
|
-
repeat_times: repeatTimes,
|
|
6558
|
-
enabled: true,
|
|
6559
|
-
createdAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
6560
|
-
};
|
|
6561
|
-
legacyAssignMatchRules(meta, effectiveMatchRules);
|
|
6562
|
-
legacyAssignCronSchedule(meta, effectiveCronSchedule);
|
|
6563
|
-
writeMeta(taskDir, meta);
|
|
6564
|
-
(0, import_node_fs5.writeFileSync)(
|
|
6565
|
-
(0, import_node_path4.join)(taskDir, "fetch.py"),
|
|
6566
|
-
generateFetchPy(params.name, effectiveMatchRules),
|
|
6567
|
-
"utf-8"
|
|
6568
|
-
);
|
|
6569
|
-
(0, import_node_fs5.writeFileSync)(
|
|
6570
|
-
(0, import_node_path4.join)(taskDir, "README.md"),
|
|
6571
|
-
generateLightRuleReadme(params.name, params.description, params.segments, repeatTimes),
|
|
6572
|
-
"utf-8"
|
|
6573
|
-
);
|
|
6574
|
-
return {
|
|
6575
|
-
meta,
|
|
6576
|
-
cronHint: {
|
|
6577
|
-
action: "add",
|
|
6578
|
-
job: {
|
|
6579
|
-
name: `notif-${params.name}`,
|
|
6580
|
-
schedule: effectiveCronSchedule,
|
|
6581
|
-
sessionTarget: "isolated",
|
|
6582
|
-
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
|
|
6583
|
-
\u6267\u884C\uFF1Apython3 tasks/${params.name}/fetch.py --notifications-dir notifications
|
|
6584
|
-
- NO_CHANGE \u6216 NO_MATCH \u2192 \u4E0D\u56DE\u590D\uFF0C\u76F4\u63A5\u7ED3\u675F\u3002
|
|
6585
|
-
- \u6709\u8F93\u51FA \u2192 \u8BFB tasks/${params.name}/README.md \u4E86\u89E3\u5982\u4F55\u5904\u7406\u6570\u636E\u5E76\u901A\u77E5\u7528\u6237\u3002`
|
|
6586
|
-
}
|
|
6587
|
-
}
|
|
6588
|
-
};
|
|
6589
|
-
}
|
|
6590
|
-
function updateLightRule(ctx, params) {
|
|
6591
|
-
const resolved = resolveLightRuleTask(ctx, params.name);
|
|
6592
|
-
const taskDir = resolved?.taskDir;
|
|
6593
|
-
const meta = resolved?.meta;
|
|
6594
|
-
if (!taskDir || !meta) {
|
|
6595
|
-
throw new LightRuleError("NOT_FOUND", `\u706F\u6548\u89C4\u5219 '${params.name}' \u4E0D\u5B58\u5728`);
|
|
6596
|
-
}
|
|
6597
|
-
let regenerateFetch = false;
|
|
6598
|
-
let regenerateReadme = false;
|
|
6599
|
-
if (params.description !== void 0) {
|
|
6600
|
-
meta.description = params.description;
|
|
6601
|
-
regenerateReadme = true;
|
|
6602
|
-
}
|
|
6603
|
-
if (legacyHasMatchRules(params)) {
|
|
6604
|
-
legacyAssignMatchRules(meta, legacyReadMatchRules(params));
|
|
6605
|
-
regenerateFetch = true;
|
|
6606
|
-
}
|
|
6607
|
-
if (params.segments !== void 0) {
|
|
6608
|
-
meta.segments = params.segments;
|
|
6609
|
-
regenerateReadme = true;
|
|
6610
|
-
}
|
|
6611
|
-
if (params.repeat !== void 0 || params.repeat_times !== void 0) {
|
|
6612
|
-
meta.repeat_times = normalizeRepeatTimes({
|
|
6613
|
-
repeat: params.repeat,
|
|
6614
|
-
repeat_times: params.repeat_times
|
|
6615
|
-
});
|
|
6616
|
-
assertAncsRepeatTimes(meta.repeat_times);
|
|
6617
|
-
regenerateReadme = true;
|
|
6618
|
-
}
|
|
6619
|
-
if (legacyHasCronSchedule(params)) {
|
|
6620
|
-
legacyAssignCronSchedule(meta, legacyReadCronSchedule(params));
|
|
6621
|
-
}
|
|
6622
|
-
if (params.enabled !== void 0) {
|
|
6623
|
-
meta.enabled = params.enabled;
|
|
6624
|
-
}
|
|
6625
|
-
meta.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
6626
|
-
writeMeta(taskDir, meta);
|
|
6627
|
-
if (regenerateFetch) {
|
|
6628
|
-
(0, import_node_fs5.writeFileSync)(
|
|
6629
|
-
(0, import_node_path4.join)(taskDir, "fetch.py"),
|
|
6630
|
-
generateFetchPy(meta.name, legacyReadMetaMatchRules(meta)),
|
|
6631
|
-
"utf-8"
|
|
6632
|
-
);
|
|
6633
|
-
}
|
|
6634
|
-
if (regenerateReadme) {
|
|
6635
|
-
(0, import_node_fs5.writeFileSync)(
|
|
6636
|
-
(0, import_node_path4.join)(taskDir, "README.md"),
|
|
6637
|
-
generateLightRuleReadme(meta.name, meta.description, meta.segments, meta.repeat_times),
|
|
6638
|
-
"utf-8"
|
|
6639
|
-
);
|
|
6640
|
-
}
|
|
6641
|
-
const cronHint = legacyHasCronSchedule(params) ? {
|
|
6642
|
-
action: "update",
|
|
6643
|
-
job: {
|
|
6644
|
-
name: `notif-${meta.name}`,
|
|
6645
|
-
schedule: legacyReadMetaCronSchedule(meta)
|
|
6646
|
-
}
|
|
6647
|
-
} : void 0;
|
|
6648
|
-
return { meta, cronHint };
|
|
6323
|
+
const cronHint = legacyHasCronSchedule(params) ? {
|
|
6324
|
+
action: "update",
|
|
6325
|
+
job: {
|
|
6326
|
+
name: `notif-${meta.name}`,
|
|
6327
|
+
schedule: legacyReadMetaCronSchedule(meta)
|
|
6328
|
+
}
|
|
6329
|
+
} : void 0;
|
|
6330
|
+
return { meta, cronHint };
|
|
6649
6331
|
}
|
|
6650
6332
|
function deleteLightRule(ctx, name) {
|
|
6651
6333
|
const resolved = resolveLightRuleTask(ctx, name);
|
|
@@ -6654,7 +6336,7 @@ function deleteLightRule(ctx, name) {
|
|
|
6654
6336
|
if (!taskDir || !meta) {
|
|
6655
6337
|
throw new LightRuleError("NOT_FOUND", `\u706F\u6548\u89C4\u5219 '${name}' \u4E0D\u5B58\u5728`);
|
|
6656
6338
|
}
|
|
6657
|
-
(0,
|
|
6339
|
+
(0, import_node_fs4.rmSync)(taskDir, { recursive: true, force: true });
|
|
6658
6340
|
return {
|
|
6659
6341
|
name: meta.name,
|
|
6660
6342
|
cronHint: {
|
|
@@ -7009,16 +6691,21 @@ var MODE_TO_INDEX = {
|
|
|
7009
6691
|
wave_rainbow: 4,
|
|
7010
6692
|
pixel_frame: 5
|
|
7011
6693
|
};
|
|
7012
|
-
function buildLightEffectApnsBody(segments, repeatInput) {
|
|
6694
|
+
function buildLightEffectApnsBody(segments, repeatInput, visibleTextOverride) {
|
|
7013
6695
|
assertSegmentCount(segments);
|
|
7014
6696
|
assertSegmentsValid(segments);
|
|
7015
6697
|
const repeatTimes = normalizeRepeatTimes(repeatInput);
|
|
7016
6698
|
assertAncsRepeatTimes(repeatTimes);
|
|
7017
|
-
const visibleText =
|
|
6699
|
+
const visibleText = resolveVisibleText(visibleTextOverride, segments);
|
|
7018
6700
|
const separator = repeatTimes === 0 ? LED_SEPARATOR_LOOP : LED_SEPARATOR_ONCE;
|
|
7019
6701
|
const payload = segments.map((segment) => encodeSegment(segment)).join("");
|
|
7020
6702
|
return `${visibleText}${separator}${payload}`;
|
|
7021
6703
|
}
|
|
6704
|
+
function resolveVisibleText(override, segments) {
|
|
6705
|
+
const trimmed = override?.trim();
|
|
6706
|
+
if (trimmed) return trimmed;
|
|
6707
|
+
return summarizeSegments(segments);
|
|
6708
|
+
}
|
|
7022
6709
|
function assertSegmentCount(segments) {
|
|
7023
6710
|
if (segments.length < 1 || segments.length > MAX_LIGHT_SEGMENTS) {
|
|
7024
6711
|
throw new Error(`light_control supports 1-${MAX_LIGHT_SEGMENTS} segments`);
|
|
@@ -7374,231 +7061,48 @@ function registerLightRulesTools(api, registry, logger) {
|
|
|
7374
7061
|
updated: true,
|
|
7375
7062
|
rule: result.meta,
|
|
7376
7063
|
cronHint: result.cronHint
|
|
7377
|
-
});
|
|
7378
|
-
} catch (e) {
|
|
7379
|
-
if (e instanceof LightRuleError) return err(e.code, e.message);
|
|
7380
|
-
logger.warn(`lightrules.update tool failed: ${e?.message}`);
|
|
7381
|
-
return err("INTERNAL_ERROR", e?.message ?? "Unknown error");
|
|
7382
|
-
}
|
|
7383
|
-
}
|
|
7384
|
-
}, ["lightrules_update"]);
|
|
7385
|
-
registerToolWithAliases(api, {
|
|
7386
|
-
name: "lightrules.delete",
|
|
7387
|
-
label: "Delete Light Rule",
|
|
7388
|
-
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',
|
|
7389
|
-
parameters: {
|
|
7390
|
-
type: "object",
|
|
7391
|
-
required: ["name"],
|
|
7392
|
-
additionalProperties: false,
|
|
7393
|
-
properties: {
|
|
7394
|
-
name: { type: "string", description: "\u8981\u5220\u9664\u7684\u89C4\u5219\u540D\u79F0" }
|
|
7395
|
-
}
|
|
7396
|
-
},
|
|
7397
|
-
async execute(_toolCallId, params) {
|
|
7398
|
-
const { name } = params;
|
|
7399
|
-
if (!name || typeof name !== "string")
|
|
7400
|
-
return err("INVALID_PARAMS", "name is required");
|
|
7401
|
-
try {
|
|
7402
|
-
const result = await registry.delete(name);
|
|
7403
|
-
logger.info(`lightrules.delete tool: deleted ${name}`);
|
|
7404
|
-
return ok({ ok: true, name: result.name, deleted: true, cronHint: result.cronHint });
|
|
7405
|
-
} catch (e) {
|
|
7406
|
-
if (e instanceof LightRuleError) return err(e.code, e.message);
|
|
7407
|
-
logger.warn(`lightrules.delete tool failed: ${e?.message}`);
|
|
7408
|
-
return err("INTERNAL_ERROR", e?.message ?? "Unknown error");
|
|
7409
|
-
}
|
|
7410
|
-
}
|
|
7411
|
-
}, ["lightrules_delete"]);
|
|
7412
|
-
}
|
|
7413
|
-
|
|
7414
|
-
// src/light-rules/evaluator-job.ts
|
|
7415
|
-
var EVALUATOR_JOB_ID = "light-rules-evaluator";
|
|
7416
|
-
var EVALUATOR_SUBAGENT_SESSION_KEY = EVALUATOR_JOB_ID;
|
|
7417
|
-
var FALLBACK_CRON_EXPR = "0 0 1 1 *";
|
|
7418
|
-
function buildEvaluatorJobMessage(notificationsDir) {
|
|
7419
|
-
return `\u706F\u6548\u89C4\u5219\u8BC4\u4F30\u4EFB\u52A1\u3002
|
|
7420
|
-
|
|
7421
|
-
\u6267\u884C\u6B65\u9AA4\uFF1A
|
|
7422
|
-
1. \u8BFB\u53D6 tasks/light-rules-evaluator/checkpoint.json\uFF08\u8BB0\u5F55\u4E0A\u6B21\u5904\u7406\u8FDB\u5EA6\uFF09
|
|
7423
|
-
2. \u626B\u63CF ${notificationsDir} \u76EE\u5F55\uFF0C\u83B7\u53D6 checkpoint \u4E4B\u540E\u7684\u65B0\u901A\u77E5
|
|
7424
|
-
3. \u626B\u63CF tasks/ \u76EE\u5F55\uFF0C\u8BFB\u53D6\u6240\u6709 type=light-rule \u4E14 enabled=true \u7684 meta.json
|
|
7425
|
-
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
|
|
7426
|
-
5. \u547D\u4E2D\u65F6\uFF1A\u4EE5\u8BE5\u89C4\u5219\u7684 segments \u548C repeat_times \u8C03\u7528 light_control \u5DE5\u5177
|
|
7427
|
-
6. \u66F4\u65B0 checkpoint.json\uFF0C\u8BB0\u5F55\u5DF2\u5904\u7406\u5230\u7684\u6700\u65B0\u901A\u77E5\u4F4D\u7F6E
|
|
7428
|
-
7. \u82E5\u65E0\u65B0\u901A\u77E5\u6216\u65E0 enabled \u89C4\u5219\uFF1A\u8F93\u51FA NO_CHANGE\uFF0C\u76F4\u63A5\u7ED3\u675F`;
|
|
7429
|
-
}
|
|
7430
|
-
var LightRulesEvaluatorJob = class {
|
|
7431
|
-
logger;
|
|
7432
|
-
registry;
|
|
7433
|
-
subagentRunner;
|
|
7434
|
-
getNotificationsDir;
|
|
7435
|
-
/**
|
|
7436
|
-
* 轻量进程内评估器(事件驱动方案的正式路径)。
|
|
7437
|
-
* 配置后:每批通知走一次 pi-ai `complete()`,不再启动 agent session。
|
|
7438
|
-
* 未配置(或调用失败)时:回退 cron / subagent 老路径。
|
|
7439
|
-
*/
|
|
7440
|
-
inlineEvaluator;
|
|
7441
|
-
/**
|
|
7442
|
-
* 记录本进程生命周期内 job 是否已确认存在。
|
|
7443
|
-
* 仅在 `ensureJobExists` 成功后置 true,避免每次 push 都做检查。
|
|
7444
|
-
*/
|
|
7445
|
-
jobEnsured = false;
|
|
7446
|
-
/**
|
|
7447
|
-
* 首次创建 job 时的并发保护。
|
|
7448
|
-
* 避免冷启动瞬间多条通知并发到达时重复调用 `cron.add`。
|
|
7449
|
-
*/
|
|
7450
|
-
ensureJobPromise = null;
|
|
7451
|
-
/**
|
|
7452
|
-
* subagent fallback 路径的并发保护。
|
|
7453
|
-
* 若评估 session 已在运行中,跳过本次触发(checkpoint 保证下次补处理)。
|
|
7454
|
-
*/
|
|
7455
|
-
subagentInFlight = false;
|
|
7456
|
-
constructor(deps) {
|
|
7457
|
-
this.logger = deps.logger;
|
|
7458
|
-
this.registry = deps.registry;
|
|
7459
|
-
this.inlineEvaluator = deps.inlineEvaluator;
|
|
7460
|
-
this.subagentRunner = deps.subagentRunner;
|
|
7461
|
-
this.getNotificationsDir = deps.getNotificationsDir ?? (() => void 0);
|
|
7462
|
-
}
|
|
7463
|
-
/**
|
|
7464
|
-
* 通知落盘后调用。若有新增通知且存在 enabled 规则,则触发评估。
|
|
7465
|
-
*
|
|
7466
|
-
* 路径优先级:
|
|
7467
|
-
* 1. inlineEvaluator(配置后的主路径):进程内一次 LLM 调用直接匹配 + 触发灯效
|
|
7468
|
-
* 2. cron.enqueueRun("force"):legacy,gateway context 路径
|
|
7469
|
-
* 3. subagentRunner.run:legacy,HTTP Relay 路径(cron 不可用时)
|
|
7470
|
-
*
|
|
7471
|
-
* inline 成功(包括 0 命中)立刻返回,不会再走 legacy 路径。
|
|
7472
|
-
* inline 失败(LLM 错误 / 未配置 provider)才回退 legacy。
|
|
7473
|
-
*
|
|
7474
|
-
* @param cron 来自 gateway context 的 CronService;HTTP 路径下为 null
|
|
7475
|
-
* @param inserted 本次 ingest 实际新落盘的通知(StoredNotification 去重后)
|
|
7476
|
-
*/
|
|
7477
|
-
async triggerIfNeeded(cron, inserted) {
|
|
7478
|
-
if (inserted.length === 0) return;
|
|
7479
|
-
if (this.registry.getEnabled().length === 0) return;
|
|
7480
|
-
if (this.inlineEvaluator) {
|
|
7481
|
-
try {
|
|
7482
|
-
const ok2 = await this.inlineEvaluator.evaluate(inserted);
|
|
7483
|
-
if (ok2) return;
|
|
7484
|
-
this.logger.warn(
|
|
7485
|
-
"light-rules-evaluator: inline evaluator failed; falling back to legacy agent session path"
|
|
7486
|
-
);
|
|
7487
|
-
} catch (err2) {
|
|
7488
|
-
this.logger.warn(
|
|
7489
|
-
`light-rules-evaluator: inline evaluator threw: ${err2?.message ?? err2}; falling back`
|
|
7490
|
-
);
|
|
7491
|
-
}
|
|
7492
|
-
}
|
|
7493
|
-
if (!cron) {
|
|
7494
|
-
await this.triggerViaSubagent();
|
|
7495
|
-
return;
|
|
7496
|
-
}
|
|
7497
|
-
try {
|
|
7498
|
-
await this.ensureJobExists(cron);
|
|
7499
|
-
} catch (err2) {
|
|
7500
|
-
this.logger.warn(`light-rules-evaluator: job ensure failed: ${err2?.message ?? err2}`);
|
|
7501
|
-
return;
|
|
7502
|
-
}
|
|
7503
|
-
try {
|
|
7504
|
-
const result = await cron.enqueueRun(EVALUATOR_JOB_ID, "force");
|
|
7505
|
-
if (!result.ok) {
|
|
7506
|
-
this.logger.warn("light-rules-evaluator: enqueueRun returned ok=false");
|
|
7507
|
-
return;
|
|
7508
|
-
}
|
|
7509
|
-
if ("enqueued" in result && result.enqueued) {
|
|
7510
|
-
this.logger.info(`light-rules-evaluator: enqueued runId=${result.runId}`);
|
|
7511
|
-
} else if ("reason" in result) {
|
|
7512
|
-
this.logger.info(`light-rules-evaluator: enqueueRun skipped (${result.reason})`);
|
|
7064
|
+
});
|
|
7065
|
+
} catch (e) {
|
|
7066
|
+
if (e instanceof LightRuleError) return err(e.code, e.message);
|
|
7067
|
+
logger.warn(`lightrules.update tool failed: ${e?.message}`);
|
|
7068
|
+
return err("INTERNAL_ERROR", e?.message ?? "Unknown error");
|
|
7513
7069
|
}
|
|
7514
|
-
} catch (err2) {
|
|
7515
|
-
this.logger.warn(`light-rules-evaluator: enqueueRun failed: ${err2?.message ?? err2}`);
|
|
7516
|
-
}
|
|
7517
|
-
}
|
|
7518
|
-
/**
|
|
7519
|
-
* cron service 不可用时的 fallback:直接通过 subagentRunner 运行评估 session。
|
|
7520
|
-
*
|
|
7521
|
-
* 并发保护:若上一次 subagent 运行尚未完成,本次跳过。
|
|
7522
|
-
* checkpoint 保证即使本次跳过,下次触发时会处理所有积压通知。
|
|
7523
|
-
*/
|
|
7524
|
-
async triggerViaSubagent() {
|
|
7525
|
-
if (!this.subagentRunner) {
|
|
7526
|
-
this.logger.warn(
|
|
7527
|
-
"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"
|
|
7528
|
-
);
|
|
7529
|
-
return;
|
|
7530
|
-
}
|
|
7531
|
-
if (this.subagentInFlight) {
|
|
7532
|
-
this.logger.info("light-rules-evaluator: subagent run in-flight, skipping this trigger");
|
|
7533
|
-
return;
|
|
7534
7070
|
}
|
|
7535
|
-
|
|
7536
|
-
|
|
7537
|
-
|
|
7538
|
-
|
|
7539
|
-
|
|
7540
|
-
|
|
7541
|
-
|
|
7542
|
-
|
|
7543
|
-
|
|
7544
|
-
|
|
7545
|
-
|
|
7546
|
-
|
|
7547
|
-
|
|
7548
|
-
|
|
7549
|
-
|
|
7550
|
-
|
|
7551
|
-
|
|
7552
|
-
|
|
7553
|
-
|
|
7554
|
-
|
|
7555
|
-
|
|
7556
|
-
|
|
7557
|
-
|
|
7558
|
-
|
|
7559
|
-
|
|
7560
|
-
if (this.jobEnsured) return;
|
|
7561
|
-
if (!this.ensureJobPromise) {
|
|
7562
|
-
this.ensureJobPromise = this.createJobIfNeeded(cron).finally(() => {
|
|
7563
|
-
this.ensureJobPromise = null;
|
|
7564
|
-
});
|
|
7565
|
-
}
|
|
7566
|
-
await this.ensureJobPromise;
|
|
7567
|
-
}
|
|
7568
|
-
async createJobIfNeeded(cron) {
|
|
7569
|
-
if (cron.getJob(EVALUATOR_JOB_ID)) {
|
|
7570
|
-
this.jobEnsured = true;
|
|
7571
|
-
return;
|
|
7572
|
-
}
|
|
7573
|
-
try {
|
|
7574
|
-
await cron.add({
|
|
7575
|
-
id: EVALUATOR_JOB_ID,
|
|
7576
|
-
name: "\u706F\u6548\u89C4\u5219\u8BC4\u4F30",
|
|
7577
|
-
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",
|
|
7578
|
-
enabled: true,
|
|
7579
|
-
schedule: { kind: "cron", expr: FALLBACK_CRON_EXPR },
|
|
7580
|
-
sessionTarget: "isolated",
|
|
7581
|
-
wakeMode: "now",
|
|
7582
|
-
payload: {
|
|
7583
|
-
kind: "agentTurn",
|
|
7584
|
-
message: buildEvaluatorJobMessage(this.getNotificationsDir() ?? "notifications")
|
|
7585
|
-
}
|
|
7586
|
-
});
|
|
7587
|
-
this.logger.info("light-rules-evaluator: job created");
|
|
7588
|
-
} catch (err2) {
|
|
7589
|
-
if (!cron.getJob(EVALUATOR_JOB_ID)) {
|
|
7590
|
-
throw err2;
|
|
7071
|
+
}, ["lightrules_update"]);
|
|
7072
|
+
registerToolWithAliases(api, {
|
|
7073
|
+
name: "lightrules.delete",
|
|
7074
|
+
label: "Delete Light Rule",
|
|
7075
|
+
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',
|
|
7076
|
+
parameters: {
|
|
7077
|
+
type: "object",
|
|
7078
|
+
required: ["name"],
|
|
7079
|
+
additionalProperties: false,
|
|
7080
|
+
properties: {
|
|
7081
|
+
name: { type: "string", description: "\u8981\u5220\u9664\u7684\u89C4\u5219\u540D\u79F0" }
|
|
7082
|
+
}
|
|
7083
|
+
},
|
|
7084
|
+
async execute(_toolCallId, params) {
|
|
7085
|
+
const { name } = params;
|
|
7086
|
+
if (!name || typeof name !== "string")
|
|
7087
|
+
return err("INVALID_PARAMS", "name is required");
|
|
7088
|
+
try {
|
|
7089
|
+
const result = await registry.delete(name);
|
|
7090
|
+
logger.info(`lightrules.delete tool: deleted ${name}`);
|
|
7091
|
+
return ok({ ok: true, name: result.name, deleted: true, cronHint: result.cronHint });
|
|
7092
|
+
} catch (e) {
|
|
7093
|
+
if (e instanceof LightRuleError) return err(e.code, e.message);
|
|
7094
|
+
logger.warn(`lightrules.delete tool failed: ${e?.message}`);
|
|
7095
|
+
return err("INTERNAL_ERROR", e?.message ?? "Unknown error");
|
|
7591
7096
|
}
|
|
7592
7097
|
}
|
|
7593
|
-
|
|
7594
|
-
|
|
7595
|
-
};
|
|
7098
|
+
}, ["lightrules_delete"]);
|
|
7099
|
+
}
|
|
7596
7100
|
|
|
7597
7101
|
// src/light-rules/inline-evaluator.ts
|
|
7598
7102
|
init_credentials();
|
|
7599
7103
|
|
|
7600
7104
|
// src/light/sender.ts
|
|
7601
|
-
var
|
|
7105
|
+
var import_node_crypto = require("crypto");
|
|
7602
7106
|
init_env();
|
|
7603
7107
|
async function sendLightEffect(apiKey, segments, logger, repeatInput, reason) {
|
|
7604
7108
|
const apiUrl = getEnvUrls().lightApiUrl;
|
|
@@ -7615,11 +7119,11 @@ async function sendLightEffect(apiKey, segments, logger, repeatInput, reason) {
|
|
|
7615
7119
|
}
|
|
7616
7120
|
let bizContent;
|
|
7617
7121
|
try {
|
|
7618
|
-
bizContent = buildLightEffectApnsBody(segments, repeatInput);
|
|
7122
|
+
bizContent = buildLightEffectApnsBody(segments, repeatInput, reason);
|
|
7619
7123
|
} catch (error) {
|
|
7620
7124
|
return { ok: false, error: error?.message ?? String(error) };
|
|
7621
7125
|
}
|
|
7622
|
-
const bizUniqueId = (0,
|
|
7126
|
+
const bizUniqueId = (0, import_node_crypto.randomUUID)();
|
|
7623
7127
|
const requestBody = {
|
|
7624
7128
|
appKey,
|
|
7625
7129
|
bizMap: { noticeType: "APP_NOTIFICATION_IMPORTANT", reason },
|
|
@@ -7727,8 +7231,8 @@ var InlineLightRuleEvaluator = class {
|
|
|
7727
7231
|
};
|
|
7728
7232
|
|
|
7729
7233
|
// src/light-rules/migration.ts
|
|
7730
|
-
var
|
|
7731
|
-
var
|
|
7234
|
+
var import_node_fs8 = require("fs");
|
|
7235
|
+
var import_node_path7 = require("path");
|
|
7732
7236
|
var NO_MATCH_FETCH_PY = `#!/usr/bin/env python3
|
|
7733
7237
|
# \u6B64\u6587\u4EF6\u7531\u8FC1\u79FB\u5DE5\u5177\u751F\u6210\u3002
|
|
7734
7238
|
# \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
|
|
@@ -7738,32 +7242,32 @@ function normalizeScriptText(text) {
|
|
|
7738
7242
|
return text.replace(/\r\n/g, "\n").trim();
|
|
7739
7243
|
}
|
|
7740
7244
|
function resolveTasksDir(ctx) {
|
|
7741
|
-
if (ctx.workspaceDir) return (0,
|
|
7245
|
+
if (ctx.workspaceDir) return (0, import_node_path7.join)(ctx.workspaceDir, "tasks");
|
|
7742
7246
|
if (ctx.stateDir) {
|
|
7743
|
-
const inferredWorkspaceDir = (0,
|
|
7744
|
-
if ((0,
|
|
7745
|
-
return (0,
|
|
7247
|
+
const inferredWorkspaceDir = (0, import_node_path7.join)(ctx.stateDir, "workspace");
|
|
7248
|
+
if ((0, import_node_fs8.existsSync)(inferredWorkspaceDir)) return (0, import_node_path7.join)(inferredWorkspaceDir, "tasks");
|
|
7249
|
+
return (0, import_node_path7.join)(ctx.stateDir, "tasks");
|
|
7746
7250
|
}
|
|
7747
7251
|
return null;
|
|
7748
7252
|
}
|
|
7749
7253
|
function migrateLegacyLightRuleTasks(ctx, logger) {
|
|
7750
7254
|
const tasksDir3 = resolveTasksDir(ctx);
|
|
7751
|
-
if (!tasksDir3 || !(0,
|
|
7255
|
+
if (!tasksDir3 || !(0, import_node_fs8.existsSync)(tasksDir3)) return;
|
|
7752
7256
|
try {
|
|
7753
|
-
for (const entry of (0,
|
|
7257
|
+
for (const entry of (0, import_node_fs8.readdirSync)(tasksDir3, { withFileTypes: true })) {
|
|
7754
7258
|
if (!entry.isDirectory()) continue;
|
|
7755
|
-
migrateTaskDir((0,
|
|
7259
|
+
migrateTaskDir((0, import_node_path7.join)(tasksDir3, String(entry.name)), logger);
|
|
7756
7260
|
}
|
|
7757
7261
|
} catch (err2) {
|
|
7758
7262
|
logger.warn(`migration: failed to read tasks dir: ${err2?.message}`);
|
|
7759
7263
|
}
|
|
7760
7264
|
}
|
|
7761
7265
|
function migrateTaskDir(taskDir, logger) {
|
|
7762
|
-
const metaPath = (0,
|
|
7763
|
-
if (!(0,
|
|
7266
|
+
const metaPath = (0, import_node_path7.join)(taskDir, "meta.json");
|
|
7267
|
+
if (!(0, import_node_fs8.existsSync)(metaPath)) return;
|
|
7764
7268
|
let meta;
|
|
7765
7269
|
try {
|
|
7766
|
-
meta = JSON.parse((0,
|
|
7270
|
+
meta = JSON.parse((0, import_node_fs8.readFileSync)(metaPath, "utf-8"));
|
|
7767
7271
|
} catch {
|
|
7768
7272
|
return;
|
|
7769
7273
|
}
|
|
@@ -7772,7 +7276,7 @@ function migrateTaskDir(taskDir, logger) {
|
|
|
7772
7276
|
mergeMatchRulesIntoDescription(meta, name, metaPath, logger);
|
|
7773
7277
|
replaceFetchPy(taskDir, name, logger);
|
|
7774
7278
|
for (const filename of ["README.md", "checkpoint.json"]) {
|
|
7775
|
-
removeFile((0,
|
|
7279
|
+
removeFile((0, import_node_path7.join)(taskDir, filename), name, filename, logger);
|
|
7776
7280
|
}
|
|
7777
7281
|
}
|
|
7778
7282
|
function mergeMatchRulesIntoDescription(meta, name, metaPath, logger) {
|
|
@@ -7793,30 +7297,30 @@ function mergeMatchRulesIntoDescription(meta, name, metaPath, logger) {
|
|
|
7793
7297
|
}
|
|
7794
7298
|
delete meta.matchRules;
|
|
7795
7299
|
try {
|
|
7796
|
-
(0,
|
|
7300
|
+
(0, import_node_fs8.writeFileSync)(metaPath, JSON.stringify(meta, null, 2), "utf-8");
|
|
7797
7301
|
logger.info(`migration: merged matchRules into description for light rule: ${name}`);
|
|
7798
7302
|
} catch (err2) {
|
|
7799
7303
|
logger.warn(`migration: failed to update meta.json for ${name}: ${err2?.message}`);
|
|
7800
7304
|
}
|
|
7801
7305
|
}
|
|
7802
7306
|
function replaceFetchPy(taskDir, name, logger) {
|
|
7803
|
-
const fetchPyPath = (0,
|
|
7804
|
-
if (!(0,
|
|
7307
|
+
const fetchPyPath = (0, import_node_path7.join)(taskDir, "fetch.py");
|
|
7308
|
+
if (!(0, import_node_fs8.existsSync)(fetchPyPath)) return;
|
|
7805
7309
|
try {
|
|
7806
|
-
const existing = (0,
|
|
7310
|
+
const existing = (0, import_node_fs8.readFileSync)(fetchPyPath, "utf-8");
|
|
7807
7311
|
if (normalizeScriptText(existing) === normalizeScriptText(NO_MATCH_FETCH_PY)) {
|
|
7808
7312
|
return;
|
|
7809
7313
|
}
|
|
7810
|
-
(0,
|
|
7314
|
+
(0, import_node_fs8.writeFileSync)(fetchPyPath, NO_MATCH_FETCH_PY, "utf-8");
|
|
7811
7315
|
logger.info(`migration: replaced fetch.py with NO_MATCH placeholder for ${name}`);
|
|
7812
7316
|
} catch (err2) {
|
|
7813
7317
|
logger.warn(`migration: failed to replace fetch.py for ${name}: ${err2?.message}`);
|
|
7814
7318
|
}
|
|
7815
7319
|
}
|
|
7816
7320
|
function removeFile(filePath, ruleName, filename, logger) {
|
|
7817
|
-
if (!(0,
|
|
7321
|
+
if (!(0, import_node_fs8.existsSync)(filePath)) return;
|
|
7818
7322
|
try {
|
|
7819
|
-
(0,
|
|
7323
|
+
(0, import_node_fs8.rmSync)(filePath);
|
|
7820
7324
|
logger.info(`migration: removed ${filename} for light rule: ${ruleName}`);
|
|
7821
7325
|
} catch (err2) {
|
|
7822
7326
|
logger.warn(`migration: failed to remove ${filename} for ${ruleName}: ${err2?.message}`);
|
|
@@ -7987,7 +7491,7 @@ function resolveUpdateChannel(params) {
|
|
|
7987
7491
|
}
|
|
7988
7492
|
|
|
7989
7493
|
// src/update/index.ts
|
|
7990
|
-
var
|
|
7494
|
+
var import_node_path9 = require("path");
|
|
7991
7495
|
|
|
7992
7496
|
// src/update/checker.ts
|
|
7993
7497
|
function parseSemver(v) {
|
|
@@ -8089,8 +7593,8 @@ var UpdateChecker = class {
|
|
|
8089
7593
|
};
|
|
8090
7594
|
|
|
8091
7595
|
// src/update/executor.ts
|
|
8092
|
-
var
|
|
8093
|
-
var
|
|
7596
|
+
var import_node_fs9 = require("fs");
|
|
7597
|
+
var import_node_path8 = require("path");
|
|
8094
7598
|
var import_node_os = require("os");
|
|
8095
7599
|
var VERSION_PATTERN = /^\d+\.\d+\.\d+(-[\w.]+)?$/;
|
|
8096
7600
|
var BASE_URL = "https://artifact.yoooclaw.com/plugin";
|
|
@@ -8100,9 +7604,9 @@ async function executeUpdate(version, runCommand, logger, targetDir, updateConfi
|
|
|
8100
7604
|
}
|
|
8101
7605
|
const tgzUrl = `${BASE_URL}/v${version}/yoooclaw-phone-notifications-${version}.tgz`;
|
|
8102
7606
|
logger.info(`\u6267\u884C\u66F4\u65B0: ${tgzUrl} \u2192 ${targetDir}`);
|
|
8103
|
-
const workDir = (0,
|
|
8104
|
-
const tgzPath = (0,
|
|
8105
|
-
const stagingDir = (0,
|
|
7607
|
+
const workDir = (0, import_node_fs9.mkdtempSync)((0, import_node_path8.join)((0, import_node_os.tmpdir)(), ".openclaw-plugin-update-"));
|
|
7608
|
+
const tgzPath = (0, import_node_path8.join)(workDir, "plugin.tgz");
|
|
7609
|
+
const stagingDir = (0, import_node_path8.join)(workDir, "staged");
|
|
8106
7610
|
let backupDir = null;
|
|
8107
7611
|
try {
|
|
8108
7612
|
logger.info("\u4E0B\u8F7D\u63D2\u4EF6\u5305...");
|
|
@@ -8111,9 +7615,9 @@ async function executeUpdate(version, runCommand, logger, targetDir, updateConfi
|
|
|
8111
7615
|
return { success: false, message: `\u4E0B\u8F7D\u5931\u8D25 (HTTP ${response.status}): ${tgzUrl}` };
|
|
8112
7616
|
}
|
|
8113
7617
|
const buffer = Buffer.from(await response.arrayBuffer());
|
|
8114
|
-
(0,
|
|
7618
|
+
(0, import_node_fs9.writeFileSync)(tgzPath, buffer);
|
|
8115
7619
|
logger.info(`\u4E0B\u8F7D\u5B8C\u6210 (${buffer.length} bytes)`);
|
|
8116
|
-
(0,
|
|
7620
|
+
(0, import_node_fs9.mkdirSync)(stagingDir, { recursive: true });
|
|
8117
7621
|
const tarResult = await runCommand(
|
|
8118
7622
|
["tar", "-xzf", tgzPath, "-C", stagingDir, "--strip-components=1"],
|
|
8119
7623
|
{ timeoutMs: 3e4 }
|
|
@@ -8122,14 +7626,14 @@ async function executeUpdate(version, runCommand, logger, targetDir, updateConfi
|
|
|
8122
7626
|
const err2 = tarResult.stderr || tarResult.stdout || "unknown error";
|
|
8123
7627
|
return { success: false, message: `\u89E3\u538B\u5931\u8D25: ${err2}` };
|
|
8124
7628
|
}
|
|
8125
|
-
(0,
|
|
7629
|
+
(0, import_node_fs9.mkdirSync)((0, import_node_path8.dirname)(targetDir), { recursive: true });
|
|
8126
7630
|
try {
|
|
8127
7631
|
backupDir = `${targetDir}.bak.${Date.now()}`;
|
|
8128
|
-
(0,
|
|
7632
|
+
(0, import_node_fs9.renameSync)(targetDir, backupDir);
|
|
8129
7633
|
} catch {
|
|
8130
7634
|
backupDir = null;
|
|
8131
7635
|
}
|
|
8132
|
-
(0,
|
|
7636
|
+
(0, import_node_fs9.renameSync)(stagingDir, targetDir);
|
|
8133
7637
|
try {
|
|
8134
7638
|
await updateConfigRecord2(version, tgzUrl);
|
|
8135
7639
|
} catch (err2) {
|
|
@@ -8137,7 +7641,7 @@ async function executeUpdate(version, runCommand, logger, targetDir, updateConfi
|
|
|
8137
7641
|
}
|
|
8138
7642
|
if (backupDir) {
|
|
8139
7643
|
try {
|
|
8140
|
-
(0,
|
|
7644
|
+
(0, import_node_fs9.rmSync)(backupDir, { force: true, recursive: true });
|
|
8141
7645
|
} catch {
|
|
8142
7646
|
}
|
|
8143
7647
|
}
|
|
@@ -8147,8 +7651,8 @@ async function executeUpdate(version, runCommand, logger, targetDir, updateConfi
|
|
|
8147
7651
|
} catch (err2) {
|
|
8148
7652
|
if (backupDir) {
|
|
8149
7653
|
try {
|
|
8150
|
-
(0,
|
|
8151
|
-
(0,
|
|
7654
|
+
(0, import_node_fs9.rmSync)(targetDir, { force: true, recursive: true });
|
|
7655
|
+
(0, import_node_fs9.renameSync)(backupDir, targetDir);
|
|
8152
7656
|
logger.info("\u5DF2\u56DE\u6EDA\u5230\u4E4B\u524D\u7248\u672C");
|
|
8153
7657
|
} catch (rollbackErr) {
|
|
8154
7658
|
logger.error(`\u56DE\u6EDA\u5931\u8D25: ${String(rollbackErr)}`);
|
|
@@ -8159,7 +7663,7 @@ async function executeUpdate(version, runCommand, logger, targetDir, updateConfi
|
|
|
8159
7663
|
return { success: false, message: errMsg };
|
|
8160
7664
|
} finally {
|
|
8161
7665
|
try {
|
|
8162
|
-
(0,
|
|
7666
|
+
(0, import_node_fs9.rmSync)(workDir, { force: true, recursive: true });
|
|
8163
7667
|
} catch {
|
|
8164
7668
|
}
|
|
8165
7669
|
}
|
|
@@ -8174,7 +7678,7 @@ function resolveTargetDir(api) {
|
|
|
8174
7678
|
if (installPath) return installPath;
|
|
8175
7679
|
} catch {
|
|
8176
7680
|
}
|
|
8177
|
-
return (0,
|
|
7681
|
+
return (0, import_node_path9.join)(api.runtime.state.resolveStateDir(), "extensions", PLUGIN_ID);
|
|
8178
7682
|
}
|
|
8179
7683
|
async function updateConfigRecord(api, version, targetDir, tgzUrl) {
|
|
8180
7684
|
const configApi = api.runtime.config;
|
|
@@ -8362,10 +7866,10 @@ function registerAutoUpdateLifecycle(deps) {
|
|
|
8362
7866
|
}
|
|
8363
7867
|
|
|
8364
7868
|
// src/plugin/cli.ts
|
|
8365
|
-
var
|
|
7869
|
+
var import_node_path17 = require("path");
|
|
8366
7870
|
|
|
8367
7871
|
// src/cli/auth.ts
|
|
8368
|
-
var
|
|
7872
|
+
var import_node_fs10 = require("fs");
|
|
8369
7873
|
init_credentials();
|
|
8370
7874
|
function registerAuthCli(program) {
|
|
8371
7875
|
const auth = program.command("auth").description("\u7528\u6237\u8BA4\u8BC1\u7BA1\u7406");
|
|
@@ -8401,12 +7905,12 @@ function registerAuthCli(program) {
|
|
|
8401
7905
|
});
|
|
8402
7906
|
auth.command("clear").description("\u6E05\u9664\u5DF2\u4FDD\u5B58\u7684\u8BA4\u8BC1\u4FE1\u606F").action(() => {
|
|
8403
7907
|
const path2 = credentialsPath();
|
|
8404
|
-
if ((0,
|
|
7908
|
+
if ((0, import_node_fs10.existsSync)(path2)) {
|
|
8405
7909
|
const creds = readCredentials();
|
|
8406
7910
|
delete creds.apiKey;
|
|
8407
7911
|
delete creds.token;
|
|
8408
7912
|
if (Object.keys(creds).length === 0) {
|
|
8409
|
-
(0,
|
|
7913
|
+
(0, import_node_fs10.rmSync)(path2, { force: true });
|
|
8410
7914
|
} else {
|
|
8411
7915
|
writeCredentials(creds);
|
|
8412
7916
|
}
|
|
@@ -8538,23 +8042,23 @@ function registerNtfStats(ntf, ctx) {
|
|
|
8538
8042
|
}
|
|
8539
8043
|
|
|
8540
8044
|
// src/cli/ntf-sync.ts
|
|
8541
|
-
var
|
|
8542
|
-
var
|
|
8045
|
+
var import_node_fs11 = require("fs");
|
|
8046
|
+
var import_node_path10 = require("path");
|
|
8543
8047
|
var SYNC_FETCH_LIMIT = 300;
|
|
8544
8048
|
function checkpointPath(dir) {
|
|
8545
|
-
return (0,
|
|
8049
|
+
return (0, import_node_path10.join)(dir, ".checkpoint.json");
|
|
8546
8050
|
}
|
|
8547
8051
|
function readCheckpoint(dir) {
|
|
8548
8052
|
const p = checkpointPath(dir);
|
|
8549
|
-
if (!(0,
|
|
8053
|
+
if (!(0, import_node_fs11.existsSync)(p)) return {};
|
|
8550
8054
|
try {
|
|
8551
|
-
return JSON.parse((0,
|
|
8055
|
+
return JSON.parse((0, import_node_fs11.readFileSync)(p, "utf-8"));
|
|
8552
8056
|
} catch {
|
|
8553
8057
|
return {};
|
|
8554
8058
|
}
|
|
8555
8059
|
}
|
|
8556
8060
|
function writeCheckpoint(dir, data) {
|
|
8557
|
-
(0,
|
|
8061
|
+
(0, import_node_fs11.writeFileSync)(checkpointPath(dir), JSON.stringify(data, null, 2), "utf-8");
|
|
8558
8062
|
}
|
|
8559
8063
|
function registerNtfSync(ntf, ctx) {
|
|
8560
8064
|
const sync = ntf.command("sync").description("\u540C\u6B65\u901A\u77E5\u5230\u8BB0\u5FC6\u7CFB\u7EDF");
|
|
@@ -8647,24 +8151,24 @@ function registerNtfSync(ntf, ctx) {
|
|
|
8647
8151
|
}
|
|
8648
8152
|
|
|
8649
8153
|
// src/cli/ntf-monitor.ts
|
|
8650
|
-
var
|
|
8651
|
-
var
|
|
8154
|
+
var import_node_fs12 = require("fs");
|
|
8155
|
+
var import_node_path11 = require("path");
|
|
8652
8156
|
function tasksDir2(ctx) {
|
|
8653
8157
|
const base = ctx.workspaceDir || ctx.stateDir;
|
|
8654
8158
|
if (!base) throw new Error("workspaceDir and stateDir both unavailable");
|
|
8655
|
-
return (0,
|
|
8159
|
+
return (0, import_node_path11.join)(base, "tasks");
|
|
8656
8160
|
}
|
|
8657
8161
|
function readMeta2(taskDir) {
|
|
8658
|
-
const metaPath = (0,
|
|
8659
|
-
if (!(0,
|
|
8162
|
+
const metaPath = (0, import_node_path11.join)(taskDir, "meta.json");
|
|
8163
|
+
if (!(0, import_node_fs12.existsSync)(metaPath)) return null;
|
|
8660
8164
|
try {
|
|
8661
|
-
return JSON.parse((0,
|
|
8165
|
+
return JSON.parse((0, import_node_fs12.readFileSync)(metaPath, "utf-8"));
|
|
8662
8166
|
} catch {
|
|
8663
8167
|
return null;
|
|
8664
8168
|
}
|
|
8665
8169
|
}
|
|
8666
8170
|
function writeMeta2(taskDir, meta) {
|
|
8667
|
-
(0,
|
|
8171
|
+
(0, import_node_fs12.writeFileSync)((0, import_node_path11.join)(taskDir, "meta.json"), JSON.stringify(meta, null, 2), "utf-8");
|
|
8668
8172
|
}
|
|
8669
8173
|
function generateReadme(name, description) {
|
|
8670
8174
|
return `# Monitor Task: ${name}
|
|
@@ -8683,27 +8187,27 @@ function registerNtfMonitor(ntf, ctx) {
|
|
|
8683
8187
|
const monitor = ntf.command("monitor").description("\u901A\u77E5\u76D1\u63A7\u4EFB\u52A1\u7BA1\u7406");
|
|
8684
8188
|
monitor.command("list").description("\u5217\u51FA\u6240\u6709\u76D1\u63A7\u4EFB\u52A1").action(() => {
|
|
8685
8189
|
const dir = tasksDir2(ctx);
|
|
8686
|
-
if (!(0,
|
|
8190
|
+
if (!(0, import_node_fs12.existsSync)(dir)) {
|
|
8687
8191
|
output({ ok: true, tasks: [] });
|
|
8688
8192
|
return;
|
|
8689
8193
|
}
|
|
8690
8194
|
const tasks = [];
|
|
8691
|
-
for (const entry of (0,
|
|
8195
|
+
for (const entry of (0, import_node_fs12.readdirSync)(dir, { withFileTypes: true })) {
|
|
8692
8196
|
if (!entry.isDirectory()) continue;
|
|
8693
|
-
const meta = readMeta2((0,
|
|
8197
|
+
const meta = readMeta2((0, import_node_path11.join)(dir, entry.name));
|
|
8694
8198
|
if (meta) tasks.push(meta);
|
|
8695
8199
|
}
|
|
8696
8200
|
output({ ok: true, tasks });
|
|
8697
8201
|
});
|
|
8698
8202
|
monitor.command("show <name>").description("\u67E5\u770B\u76D1\u63A7\u4EFB\u52A1\u8BE6\u60C5").action((name) => {
|
|
8699
|
-
const taskDir = (0,
|
|
8203
|
+
const taskDir = (0, import_node_path11.join)(tasksDir2(ctx), name);
|
|
8700
8204
|
const meta = readMeta2(taskDir);
|
|
8701
8205
|
if (!meta) exitError("NOT_FOUND", `\u76D1\u63A7\u4EFB\u52A1 '${name}' \u4E0D\u5B58\u5728`);
|
|
8702
|
-
const checkpointPath2 = (0,
|
|
8206
|
+
const checkpointPath2 = (0, import_node_path11.join)(taskDir, "checkpoint.json");
|
|
8703
8207
|
let checkpoint = {};
|
|
8704
|
-
if ((0,
|
|
8208
|
+
if ((0, import_node_fs12.existsSync)(checkpointPath2)) {
|
|
8705
8209
|
try {
|
|
8706
|
-
checkpoint = JSON.parse((0,
|
|
8210
|
+
checkpoint = JSON.parse((0, import_node_fs12.readFileSync)(checkpointPath2, "utf-8"));
|
|
8707
8211
|
} catch {
|
|
8708
8212
|
}
|
|
8709
8213
|
}
|
|
@@ -8718,8 +8222,8 @@ function registerNtfMonitor(ntf, ctx) {
|
|
|
8718
8222
|
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(
|
|
8719
8223
|
(name, opts) => {
|
|
8720
8224
|
const dir = tasksDir2(ctx);
|
|
8721
|
-
const taskDir = (0,
|
|
8722
|
-
if ((0,
|
|
8225
|
+
const taskDir = (0, import_node_path11.join)(dir, name);
|
|
8226
|
+
if ((0, import_node_fs12.existsSync)(taskDir)) {
|
|
8723
8227
|
exitError("ALREADY_EXISTS", `\u76D1\u63A7\u4EFB\u52A1 '${name}' \u5DF2\u5B58\u5728`);
|
|
8724
8228
|
}
|
|
8725
8229
|
let matchRules;
|
|
@@ -8731,7 +8235,7 @@ function registerNtfMonitor(ntf, ctx) {
|
|
|
8731
8235
|
"match-rules \u5FC5\u987B\u662F\u5408\u6CD5\u7684 JSON"
|
|
8732
8236
|
);
|
|
8733
8237
|
}
|
|
8734
|
-
(0,
|
|
8238
|
+
(0, import_node_fs12.mkdirSync)(taskDir, { recursive: true });
|
|
8735
8239
|
const meta = {
|
|
8736
8240
|
name,
|
|
8737
8241
|
description: opts.description,
|
|
@@ -8741,13 +8245,13 @@ function registerNtfMonitor(ntf, ctx) {
|
|
|
8741
8245
|
createdAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
8742
8246
|
};
|
|
8743
8247
|
writeMeta2(taskDir, meta);
|
|
8744
|
-
(0,
|
|
8745
|
-
(0,
|
|
8248
|
+
(0, import_node_fs12.writeFileSync)(
|
|
8249
|
+
(0, import_node_path11.join)(taskDir, "fetch.py"),
|
|
8746
8250
|
generateFetchPy(name, matchRules),
|
|
8747
8251
|
"utf-8"
|
|
8748
8252
|
);
|
|
8749
|
-
(0,
|
|
8750
|
-
(0,
|
|
8253
|
+
(0, import_node_fs12.writeFileSync)(
|
|
8254
|
+
(0, import_node_path11.join)(taskDir, "README.md"),
|
|
8751
8255
|
generateReadme(name, opts.description),
|
|
8752
8256
|
"utf-8"
|
|
8753
8257
|
);
|
|
@@ -8775,8 +8279,8 @@ function registerNtfMonitor(ntf, ctx) {
|
|
|
8775
8279
|
}
|
|
8776
8280
|
);
|
|
8777
8281
|
monitor.command("delete <name>").description("\u5220\u9664\u76D1\u63A7\u4EFB\u52A1").option("--yes", "\u8DF3\u8FC7\u786E\u8BA4").action((name, opts) => {
|
|
8778
|
-
const taskDir = (0,
|
|
8779
|
-
if (!(0,
|
|
8282
|
+
const taskDir = (0, import_node_path11.join)(tasksDir2(ctx), name);
|
|
8283
|
+
if (!(0, import_node_fs12.existsSync)(taskDir)) {
|
|
8780
8284
|
exitError("NOT_FOUND", `\u76D1\u63A7\u4EFB\u52A1 '${name}' \u4E0D\u5B58\u5728`);
|
|
8781
8285
|
}
|
|
8782
8286
|
if (!opts.yes) {
|
|
@@ -8789,7 +8293,7 @@ function registerNtfMonitor(ntf, ctx) {
|
|
|
8789
8293
|
});
|
|
8790
8294
|
process.exit(1);
|
|
8791
8295
|
}
|
|
8792
|
-
(0,
|
|
8296
|
+
(0, import_node_fs12.rmSync)(taskDir, { recursive: true, force: true });
|
|
8793
8297
|
output({
|
|
8794
8298
|
ok: true,
|
|
8795
8299
|
name,
|
|
@@ -8801,7 +8305,7 @@ function registerNtfMonitor(ntf, ctx) {
|
|
|
8801
8305
|
});
|
|
8802
8306
|
});
|
|
8803
8307
|
monitor.command("enable <name>").description("\u542F\u7528\u76D1\u63A7\u4EFB\u52A1").action((name) => {
|
|
8804
|
-
const taskDir = (0,
|
|
8308
|
+
const taskDir = (0, import_node_path11.join)(tasksDir2(ctx), name);
|
|
8805
8309
|
const meta = readMeta2(taskDir);
|
|
8806
8310
|
if (!meta) exitError("NOT_FOUND", `\u76D1\u63A7\u4EFB\u52A1 '${name}' \u4E0D\u5B58\u5728`);
|
|
8807
8311
|
meta.enabled = true;
|
|
@@ -8809,7 +8313,7 @@ function registerNtfMonitor(ntf, ctx) {
|
|
|
8809
8313
|
output({ ok: true, name, enabled: true });
|
|
8810
8314
|
});
|
|
8811
8315
|
monitor.command("disable <name>").description("\u6682\u505C\u76D1\u63A7\u4EFB\u52A1").action((name) => {
|
|
8812
|
-
const taskDir = (0,
|
|
8316
|
+
const taskDir = (0, import_node_path11.join)(tasksDir2(ctx), name);
|
|
8813
8317
|
const meta = readMeta2(taskDir);
|
|
8814
8318
|
if (!meta) exitError("NOT_FOUND", `\u76D1\u63A7\u4EFB\u52A1 '${name}' \u4E0D\u5B58\u5728`);
|
|
8815
8319
|
meta.enabled = false;
|
|
@@ -8853,9 +8357,9 @@ function registerLightSend(light) {
|
|
|
8853
8357
|
}
|
|
8854
8358
|
|
|
8855
8359
|
// src/cli/light-setup-tools.ts
|
|
8856
|
-
var
|
|
8360
|
+
var import_node_fs13 = require("fs");
|
|
8857
8361
|
var import_node_os2 = require("os");
|
|
8858
|
-
var
|
|
8362
|
+
var import_node_path12 = require("path");
|
|
8859
8363
|
function isObject(value) {
|
|
8860
8364
|
return !!value && typeof value === "object" && !Array.isArray(value);
|
|
8861
8365
|
}
|
|
@@ -8869,7 +8373,7 @@ function ensureArray(obj, key) {
|
|
|
8869
8373
|
function resolveConfigPath2() {
|
|
8870
8374
|
const fromEnv = process.env.OPENCLAW_CONFIG_PATH?.trim();
|
|
8871
8375
|
if (fromEnv) return fromEnv;
|
|
8872
|
-
return (0,
|
|
8376
|
+
return (0, import_node_path12.join)((0, import_node_os2.homedir)(), ".openclaw", "openclaw.json");
|
|
8873
8377
|
}
|
|
8874
8378
|
var LIGHT_TOOLS = [
|
|
8875
8379
|
"light_control",
|
|
@@ -8916,12 +8420,12 @@ function upsertLightControlAlsoAllow(cfg) {
|
|
|
8916
8420
|
function registerLightSetupTools(light) {
|
|
8917
8421
|
light.command("setup").description("\u81EA\u52A8\u653E\u884C light_control\uFF08\u5199\u5165 tools.alsoAllow \u4E0E agents.main.tools.alsoAllow\uFF09").action(() => {
|
|
8918
8422
|
const configPath = resolveConfigPath2();
|
|
8919
|
-
if (!(0,
|
|
8423
|
+
if (!(0, import_node_fs13.existsSync)(configPath)) {
|
|
8920
8424
|
exitError("CONFIG_NOT_FOUND", `\u672A\u627E\u5230\u914D\u7F6E\u6587\u4EF6: ${configPath}`);
|
|
8921
8425
|
}
|
|
8922
8426
|
let cfg = {};
|
|
8923
8427
|
try {
|
|
8924
|
-
const raw = (0,
|
|
8428
|
+
const raw = (0, import_node_fs13.readFileSync)(configPath, "utf-8");
|
|
8925
8429
|
const parsed = JSON.parse(raw);
|
|
8926
8430
|
if (isObject(parsed)) cfg = parsed;
|
|
8927
8431
|
} catch (err2) {
|
|
@@ -8929,8 +8433,8 @@ function registerLightSetupTools(light) {
|
|
|
8929
8433
|
}
|
|
8930
8434
|
const result = upsertLightControlAlsoAllow(cfg);
|
|
8931
8435
|
try {
|
|
8932
|
-
(0,
|
|
8933
|
-
(0,
|
|
8436
|
+
(0, import_node_fs13.mkdirSync)((0, import_node_path12.dirname)(configPath), { recursive: true });
|
|
8437
|
+
(0, import_node_fs13.writeFileSync)(configPath, JSON.stringify(cfg, null, 2) + "\n", "utf-8");
|
|
8934
8438
|
} catch (err2) {
|
|
8935
8439
|
exitError("WRITE_FAILED", `\u5199\u5165\u914D\u7F6E\u5931\u8D25: ${err2?.message ?? String(err2)}`);
|
|
8936
8440
|
}
|
|
@@ -8948,17 +8452,17 @@ function registerLightSetupTools(light) {
|
|
|
8948
8452
|
}
|
|
8949
8453
|
|
|
8950
8454
|
// src/cli/tunnel-status.ts
|
|
8951
|
-
var
|
|
8952
|
-
var
|
|
8455
|
+
var import_node_fs14 = require("fs");
|
|
8456
|
+
var import_node_path13 = require("path");
|
|
8953
8457
|
init_credentials();
|
|
8954
8458
|
init_env();
|
|
8955
|
-
var STATUS_REL_PATH = (0,
|
|
8459
|
+
var STATUS_REL_PATH = (0, import_node_path13.join)("plugins", "phone-notifications", "tunnel-status.json");
|
|
8956
8460
|
function readTunnelStatus(ctx) {
|
|
8957
8461
|
if (!ctx.stateDir) return null;
|
|
8958
|
-
const filePath = (0,
|
|
8959
|
-
if (!(0,
|
|
8462
|
+
const filePath = (0, import_node_path13.join)(ctx.stateDir, STATUS_REL_PATH);
|
|
8463
|
+
if (!(0, import_node_fs14.existsSync)(filePath)) return null;
|
|
8960
8464
|
try {
|
|
8961
|
-
return JSON.parse((0,
|
|
8465
|
+
return JSON.parse((0, import_node_fs14.readFileSync)(filePath, "utf-8"));
|
|
8962
8466
|
} catch {
|
|
8963
8467
|
return null;
|
|
8964
8468
|
}
|
|
@@ -9026,24 +8530,24 @@ function registerNtfStoragePath(ntf, ctx) {
|
|
|
9026
8530
|
}
|
|
9027
8531
|
|
|
9028
8532
|
// src/cli/log-search.ts
|
|
9029
|
-
var
|
|
9030
|
-
var
|
|
8533
|
+
var import_node_fs15 = require("fs");
|
|
8534
|
+
var import_node_path14 = require("path");
|
|
9031
8535
|
function resolveLogsDir(ctx) {
|
|
9032
8536
|
if (ctx.stateDir) {
|
|
9033
|
-
const dir = (0,
|
|
8537
|
+
const dir = (0, import_node_path14.join)(
|
|
9034
8538
|
ctx.stateDir,
|
|
9035
8539
|
"plugins",
|
|
9036
8540
|
"phone-notifications",
|
|
9037
8541
|
"logs"
|
|
9038
8542
|
);
|
|
9039
|
-
if ((0,
|
|
8543
|
+
if ((0, import_node_fs15.existsSync)(dir)) return dir;
|
|
9040
8544
|
}
|
|
9041
8545
|
return null;
|
|
9042
8546
|
}
|
|
9043
8547
|
function listLogDateKeys(dir) {
|
|
9044
8548
|
const pattern = /^(\d{4}-\d{2}-\d{2})\.log$/;
|
|
9045
8549
|
const keys = [];
|
|
9046
|
-
for (const entry of (0,
|
|
8550
|
+
for (const entry of (0, import_node_fs15.readdirSync)(dir, { withFileTypes: true })) {
|
|
9047
8551
|
if (!entry.isFile()) continue;
|
|
9048
8552
|
const m = pattern.exec(entry.name);
|
|
9049
8553
|
if (m) keys.push(m[1]);
|
|
@@ -9051,9 +8555,9 @@ function listLogDateKeys(dir) {
|
|
|
9051
8555
|
return keys.sort().reverse();
|
|
9052
8556
|
}
|
|
9053
8557
|
function collectLogLines(dir, dateKey, keyword, limit, collected) {
|
|
9054
|
-
const filePath = (0,
|
|
9055
|
-
if (!(0,
|
|
9056
|
-
const content = (0,
|
|
8558
|
+
const filePath = (0, import_node_path14.join)(dir, `${dateKey}.log`);
|
|
8559
|
+
if (!(0, import_node_fs15.existsSync)(filePath)) return;
|
|
8560
|
+
const content = (0, import_node_fs15.readFileSync)(filePath, "utf-8");
|
|
9057
8561
|
const lowerKeyword = keyword?.toLowerCase();
|
|
9058
8562
|
for (const line of content.split("\n")) {
|
|
9059
8563
|
if (collected.length >= limit) return;
|
|
@@ -9120,12 +8624,12 @@ function registerEnvCli(ntf) {
|
|
|
9120
8624
|
}
|
|
9121
8625
|
|
|
9122
8626
|
// src/cli/doctor.ts
|
|
9123
|
-
var
|
|
8627
|
+
var import_node_fs19 = require("fs");
|
|
9124
8628
|
var import_node_readline = require("readline");
|
|
9125
8629
|
init_host();
|
|
9126
8630
|
|
|
9127
8631
|
// src/cli/doctor/check-dangerous-flags.ts
|
|
9128
|
-
var
|
|
8632
|
+
var import_node_fs16 = require("fs");
|
|
9129
8633
|
function isObject2(v) {
|
|
9130
8634
|
return !!v && typeof v === "object" && !Array.isArray(v);
|
|
9131
8635
|
}
|
|
@@ -9142,13 +8646,13 @@ var checkDangerousFlags = ({ cfg, configPath }) => {
|
|
|
9142
8646
|
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",
|
|
9143
8647
|
fixDescription: "\u8BBE\u4E3A false",
|
|
9144
8648
|
fix: () => {
|
|
9145
|
-
const raw = (0,
|
|
8649
|
+
const raw = (0, import_node_fs16.readFileSync)(configPath, "utf-8");
|
|
9146
8650
|
const config = JSON.parse(raw);
|
|
9147
8651
|
const gw = config.gateway;
|
|
9148
8652
|
const cui = gw.controlUi;
|
|
9149
8653
|
cui.dangerouslyDisableDeviceAuth = false;
|
|
9150
|
-
(0,
|
|
9151
|
-
(0,
|
|
8654
|
+
(0, import_node_fs16.copyFileSync)(configPath, configPath + ".bak");
|
|
8655
|
+
(0, import_node_fs16.writeFileSync)(configPath, JSON.stringify(config, null, 2) + "\n", "utf-8");
|
|
9152
8656
|
}
|
|
9153
8657
|
};
|
|
9154
8658
|
};
|
|
@@ -9196,11 +8700,11 @@ function warnEmpty() {
|
|
|
9196
8700
|
}
|
|
9197
8701
|
|
|
9198
8702
|
// src/cli/doctor/check-state-dir-perms.ts
|
|
9199
|
-
var
|
|
8703
|
+
var import_node_fs17 = require("fs");
|
|
9200
8704
|
var checkStateDirPerms = ({ stateDir }) => {
|
|
9201
8705
|
let mode;
|
|
9202
8706
|
try {
|
|
9203
|
-
mode = (0,
|
|
8707
|
+
mode = (0, import_node_fs17.statSync)(stateDir).mode;
|
|
9204
8708
|
} catch {
|
|
9205
8709
|
return null;
|
|
9206
8710
|
}
|
|
@@ -9214,7 +8718,7 @@ var checkStateDirPerms = ({ stateDir }) => {
|
|
|
9214
8718
|
detail: "\u5176\u4ED6\u7528\u6237\u53EF\u4EE5\u8BFB\u53D6\u8BE5\u76EE\u5F55\u4E0B\u7684\u51ED\u8BC1\u548C\u914D\u7F6E\u6587\u4EF6\u3002",
|
|
9215
8719
|
fixDescription: "chmod 700 " + stateDir,
|
|
9216
8720
|
fix: () => {
|
|
9217
|
-
(0,
|
|
8721
|
+
(0, import_node_fs17.chmodSync)(stateDir, 448);
|
|
9218
8722
|
}
|
|
9219
8723
|
};
|
|
9220
8724
|
};
|
|
@@ -9269,16 +8773,16 @@ var checkCredentials = () => {
|
|
|
9269
8773
|
};
|
|
9270
8774
|
|
|
9271
8775
|
// src/cli/doctor/check-tunnel.ts
|
|
9272
|
-
var
|
|
9273
|
-
var
|
|
9274
|
-
var STATUS_REL_PATH2 = (0,
|
|
8776
|
+
var import_node_fs18 = require("fs");
|
|
8777
|
+
var import_node_path15 = require("path");
|
|
8778
|
+
var STATUS_REL_PATH2 = (0, import_node_path15.join)(
|
|
9275
8779
|
"plugins",
|
|
9276
8780
|
"phone-notifications",
|
|
9277
8781
|
"tunnel-status.json"
|
|
9278
8782
|
);
|
|
9279
8783
|
var checkTunnel = ({ stateDir }) => {
|
|
9280
|
-
const filePath = (0,
|
|
9281
|
-
if (!(0,
|
|
8784
|
+
const filePath = (0, import_node_path15.join)(stateDir, STATUS_REL_PATH2);
|
|
8785
|
+
if (!(0, import_node_fs18.existsSync)(filePath)) {
|
|
9282
8786
|
return {
|
|
9283
8787
|
id: "tunnel",
|
|
9284
8788
|
severity: "warn",
|
|
@@ -9290,7 +8794,7 @@ var checkTunnel = ({ stateDir }) => {
|
|
|
9290
8794
|
}
|
|
9291
8795
|
let status;
|
|
9292
8796
|
try {
|
|
9293
|
-
status = JSON.parse((0,
|
|
8797
|
+
status = JSON.parse((0, import_node_fs18.readFileSync)(filePath, "utf-8"));
|
|
9294
8798
|
} catch {
|
|
9295
8799
|
return {
|
|
9296
8800
|
id: "tunnel",
|
|
@@ -9383,9 +8887,9 @@ function isObject5(v) {
|
|
|
9383
8887
|
return !!v && typeof v === "object" && !Array.isArray(v);
|
|
9384
8888
|
}
|
|
9385
8889
|
function readConfig(configPath) {
|
|
9386
|
-
if (!(0,
|
|
8890
|
+
if (!(0, import_node_fs19.existsSync)(configPath)) return {};
|
|
9387
8891
|
try {
|
|
9388
|
-
const parsed = JSON.parse((0,
|
|
8892
|
+
const parsed = JSON.parse((0, import_node_fs19.readFileSync)(configPath, "utf-8"));
|
|
9389
8893
|
return isObject5(parsed) ? parsed : {};
|
|
9390
8894
|
} catch {
|
|
9391
8895
|
return {};
|
|
@@ -9586,7 +9090,7 @@ function registerRecStoragePath(rec, ctx) {
|
|
|
9586
9090
|
|
|
9587
9091
|
// src/cli/rec-setup.ts
|
|
9588
9092
|
var import_node_readline2 = require("readline");
|
|
9589
|
-
var
|
|
9093
|
+
var import_node_fs20 = require("fs");
|
|
9590
9094
|
function ask(rl, question) {
|
|
9591
9095
|
return new Promise((resolve) => rl.question(question, resolve));
|
|
9592
9096
|
}
|
|
@@ -9672,9 +9176,9 @@ async function setupLocal(rl) {
|
|
|
9672
9176
|
function registerRecSetup(rec, ctx) {
|
|
9673
9177
|
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 () => {
|
|
9674
9178
|
const configPath = resolveAsrConfigPath(ctx);
|
|
9675
|
-
if ((0,
|
|
9179
|
+
if ((0, import_node_fs20.existsSync)(configPath)) {
|
|
9676
9180
|
try {
|
|
9677
|
-
const existing = JSON.parse((0,
|
|
9181
|
+
const existing = JSON.parse((0, import_node_fs20.readFileSync)(configPath, "utf-8"));
|
|
9678
9182
|
process.stderr.write(`\u5F53\u524D\u5DF2\u6709\u914D\u7F6E\uFF1Amode = ${existing.mode}`);
|
|
9679
9183
|
if (existing.updatedAt) process.stderr.write(`\uFF0C\u66F4\u65B0\u4E8E ${existing.updatedAt}`);
|
|
9680
9184
|
process.stderr.write("\n");
|
|
@@ -9687,7 +9191,7 @@ function registerRecSetup(rec, ctx) {
|
|
|
9687
9191
|
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"]);
|
|
9688
9192
|
const config = modeIdx === 0 ? await setupApi(rl) : await setupLocal(rl);
|
|
9689
9193
|
const stored = { ...config, updatedAt: (/* @__PURE__ */ new Date()).toISOString() };
|
|
9690
|
-
(0,
|
|
9194
|
+
(0, import_node_fs20.writeFileSync)(configPath, JSON.stringify(stored, null, 2), "utf-8");
|
|
9691
9195
|
process.stderr.write(`
|
|
9692
9196
|
\u2713 \u914D\u7F6E\u5DF2\u4FDD\u5B58\u5230 ${configPath}
|
|
9693
9197
|
|
|
@@ -9701,8 +9205,8 @@ function registerRecSetup(rec, ctx) {
|
|
|
9701
9205
|
|
|
9702
9206
|
// src/cli/update.ts
|
|
9703
9207
|
var import_node_child_process = require("child_process");
|
|
9704
|
-
var
|
|
9705
|
-
var
|
|
9208
|
+
var import_node_fs21 = require("fs");
|
|
9209
|
+
var import_node_path16 = require("path");
|
|
9706
9210
|
var import_node_os3 = __toESM(require("os"), 1);
|
|
9707
9211
|
init_host();
|
|
9708
9212
|
var BASE_URL2 = "https://artifact.yoooclaw.com/plugin";
|
|
@@ -9761,9 +9265,9 @@ async function runUpdate(ctx, opts) {
|
|
|
9761
9265
|
`);
|
|
9762
9266
|
process.exit(1);
|
|
9763
9267
|
}
|
|
9764
|
-
const tmpScript = (0,
|
|
9268
|
+
const tmpScript = (0, import_node_path16.join)(import_node_os3.default.tmpdir(), `openclaw-install-${Date.now()}.mjs`);
|
|
9765
9269
|
try {
|
|
9766
|
-
(0,
|
|
9270
|
+
(0, import_node_fs21.writeFileSync)(tmpScript, installScript, "utf-8");
|
|
9767
9271
|
} catch (err2) {
|
|
9768
9272
|
const msg = `\u5199\u5165\u4E34\u65F6\u6587\u4EF6\u5931\u8D25: ${err2?.message ?? String(err2)}`;
|
|
9769
9273
|
if (json) {
|
|
@@ -9781,7 +9285,7 @@ async function runUpdate(ctx, opts) {
|
|
|
9781
9285
|
{ stdio: "inherit" }
|
|
9782
9286
|
);
|
|
9783
9287
|
try {
|
|
9784
|
-
(0,
|
|
9288
|
+
(0, import_node_fs21.unlinkSync)(tmpScript);
|
|
9785
9289
|
} catch {
|
|
9786
9290
|
}
|
|
9787
9291
|
if (result.error) {
|
|
@@ -9845,10 +9349,10 @@ function inferOpenClawRootDir(workspaceDir) {
|
|
|
9845
9349
|
if (!workspaceDir) {
|
|
9846
9350
|
return void 0;
|
|
9847
9351
|
}
|
|
9848
|
-
if ((0,
|
|
9352
|
+
if ((0, import_node_path17.basename)(workspaceDir) !== "workspace") {
|
|
9849
9353
|
return void 0;
|
|
9850
9354
|
}
|
|
9851
|
-
return (0,
|
|
9355
|
+
return (0, import_node_path17.dirname)(workspaceDir);
|
|
9852
9356
|
}
|
|
9853
9357
|
function registerPluginCli(api, params) {
|
|
9854
9358
|
const { logger, openclawDir } = params;
|
|
@@ -10015,214 +9519,530 @@ var lightControlParameters = {
|
|
|
10015
9519
|
}
|
|
10016
9520
|
}
|
|
10017
9521
|
},
|
|
10018
|
-
reason: {
|
|
10019
|
-
type: "string",
|
|
10020
|
-
description: "\u7ED3\u5408\u5BF9\u8BDD\u4E0A\u4E0B\u6587\uFF0C\u7B80\u8981\u8BF4\u660E\u4E3A\u4EC0\u4E48\u8981\u4EAE\u706F\uFF08\u4F8B\u5982\uFF1A\u7528\u6237\u5FC3\u60C5\u4F4E\u843D\u60F3\u8981\u6E29\u6696\u6C1B\u56F4\u3001\u5E86\u795D\u751F\u65E5\u3001\u63D0\u9192\u559D\u6C34\u7B49\uFF09\u3002\u8FD9\u4E2A\u5B57\u6BB5\u5E2E\u52A9\u8BB0\u5F55\u6BCF\u6B21\u4EAE\u706F\u7684\u610F\u56FE\u548C\u52A8\u673A\u3002"
|
|
10021
|
-
},
|
|
10022
|
-
repeat: {
|
|
10023
|
-
type: "boolean",
|
|
10024
|
-
description: "\u517C\u5BB9\u65E7\u53C2\u6570\u3002true = \u65E0\u9650\u5FAA\u73AF\uFF0Cfalse/\u4E0D\u586B = \u4E00\u8F6E\uFF1B\u82E5\u540C\u65F6\u63D0\u4F9B repeat_times\uFF0C\u5219\u4EE5\u540E\u8005\u4E3A\u51C6"
|
|
9522
|
+
reason: {
|
|
9523
|
+
type: "string",
|
|
9524
|
+
description: "\u7ED3\u5408\u5BF9\u8BDD\u4E0A\u4E0B\u6587\uFF0C\u7B80\u8981\u8BF4\u660E\u4E3A\u4EC0\u4E48\u8981\u4EAE\u706F\uFF08\u4F8B\u5982\uFF1A\u7528\u6237\u5FC3\u60C5\u4F4E\u843D\u60F3\u8981\u6E29\u6696\u6C1B\u56F4\u3001\u5E86\u795D\u751F\u65E5\u3001\u63D0\u9192\u559D\u6C34\u7B49\uFF09\u3002\u8FD9\u4E2A\u5B57\u6BB5\u5E2E\u52A9\u8BB0\u5F55\u6BCF\u6B21\u4EAE\u706F\u7684\u610F\u56FE\u548C\u52A8\u673A\u3002"
|
|
9525
|
+
},
|
|
9526
|
+
repeat: {
|
|
9527
|
+
type: "boolean",
|
|
9528
|
+
description: "\u517C\u5BB9\u65E7\u53C2\u6570\u3002true = \u65E0\u9650\u5FAA\u73AF\uFF0Cfalse/\u4E0D\u586B = \u4E00\u8F6E\uFF1B\u82E5\u540C\u65F6\u63D0\u4F9B repeat_times\uFF0C\u5219\u4EE5\u540E\u8005\u4E3A\u51C6"
|
|
9529
|
+
},
|
|
9530
|
+
repeat_times: {
|
|
9531
|
+
type: "number",
|
|
9532
|
+
minimum: 0,
|
|
9533
|
+
description: "\u6574\u6761\u7EC4\u5408\u91CD\u590D\u6B21\u6570\uFF1A0 = \u65E0\u9650\u5FAA\u73AF\uFF0C1 = \u64AD\u653E\u4E00\u8F6E\u3002\u5F53\u524D ANCS \u8DEF\u5F84\u4E0D\u652F\u6301 N>=2"
|
|
9534
|
+
}
|
|
9535
|
+
}
|
|
9536
|
+
};
|
|
9537
|
+
function registerLightControlTool(api, logger) {
|
|
9538
|
+
api.registerTool({
|
|
9539
|
+
name: "light_control",
|
|
9540
|
+
label: "Light Control",
|
|
9541
|
+
description: '\u63A7\u5236\u786C\u4EF6\u706F\u6548\uFF0C\u652F\u6301 1\u201312 \u6BB5\u987A\u5E8F\u706F\u6548\uFF0C\u6BCF\u6BB5\u72EC\u7ACB\u53C2\u6570\uFF086 \u79CD\u6A21\u5F0F\uFF1A\u6CE2\u6D6A/\u547C\u5438/\u9891\u95EA/\u5E38\u4EAE/\u6D41\u5149/\u9010\u7EC4\u50CF\u7D20\u5E27\uFF09\u3002Tool \u5185\u90E8\u81EA\u52A8\u91CF\u5316\u4E3A\u5D4C\u5165\u5F0F\u534F\u8BAE\u5B9A\u4E49\u7684\u79BB\u6563\u6863\u4F4D\uFF0C\u5E76\u6309\u6A21\u5F0F\u7CBE\u7B80\u4E3A\u53D8\u957F ANCS \u63A7\u5236\u5E8F\u5217\uFF0C\u901A\u8FC7 HTTP \u63A5\u53E3\u4E0B\u53D1\u706F\u6548\u6307\u4EE4\u3002\u5F53\u7528\u6237\u8BF4"\u5F00\u706F"\u3001"\u6362\u4E2A\u706F\u6548"\u3001"\u628A\u706F\u8C03\u6210\u7EA2\u8272"\u3001"\u8425\u9020\u6C1B\u56F4"\u7B49\u4E0E\u706F\u5149\u63A7\u5236\u76F8\u5173\u7684\u8868\u8FBE\u65F6\u8C03\u7528\u3002',
|
|
9542
|
+
parameters: lightControlParameters,
|
|
9543
|
+
async execute(_toolCallId, params) {
|
|
9544
|
+
let apiKey;
|
|
9545
|
+
try {
|
|
9546
|
+
apiKey = requireApiKey();
|
|
9547
|
+
} catch (e) {
|
|
9548
|
+
return {
|
|
9549
|
+
content: [{ type: "text", text: e.message }],
|
|
9550
|
+
details: { ok: false, error: { code: "AUTH_REQUIRED", message: e.message } }
|
|
9551
|
+
};
|
|
9552
|
+
}
|
|
9553
|
+
const { segments, reason, repeat, repeat_times } = params;
|
|
9554
|
+
const validation = validateSegments(segments);
|
|
9555
|
+
if (!validation.valid) {
|
|
9556
|
+
return {
|
|
9557
|
+
content: [{ type: "text", text: JSON.stringify(validation.errors) }],
|
|
9558
|
+
details: {
|
|
9559
|
+
ok: false,
|
|
9560
|
+
error: { code: "VALIDATION_FAILED", details: validation.errors }
|
|
9561
|
+
}
|
|
9562
|
+
};
|
|
9563
|
+
}
|
|
9564
|
+
let repeatTimes;
|
|
9565
|
+
try {
|
|
9566
|
+
repeatTimes = normalizeRepeatTimes({ repeat, repeat_times });
|
|
9567
|
+
assertAncsRepeatTimes(repeatTimes);
|
|
9568
|
+
} catch (error) {
|
|
9569
|
+
return {
|
|
9570
|
+
content: [{ type: "text", text: error?.message ?? String(error) }],
|
|
9571
|
+
details: {
|
|
9572
|
+
ok: false,
|
|
9573
|
+
error: { code: "VALIDATION_FAILED", message: error?.message ?? String(error) }
|
|
9574
|
+
}
|
|
9575
|
+
};
|
|
9576
|
+
}
|
|
9577
|
+
logger.info(`Light control reason: ${reason}`);
|
|
9578
|
+
const result = await sendLightEffect(
|
|
9579
|
+
apiKey,
|
|
9580
|
+
validation.segments,
|
|
9581
|
+
logger,
|
|
9582
|
+
{ repeat_times: repeatTimes },
|
|
9583
|
+
reason
|
|
9584
|
+
);
|
|
9585
|
+
if (!result.ok) {
|
|
9586
|
+
logger.warn(
|
|
9587
|
+
`Light control HTTP request failed: ${result.status} ${result.error}`
|
|
9588
|
+
);
|
|
9589
|
+
} else {
|
|
9590
|
+
logger.info(`Light control sent, bizUniqueId=${result.bizUniqueId}`);
|
|
9591
|
+
}
|
|
9592
|
+
return {
|
|
9593
|
+
content: [{ type: "text", text: JSON.stringify(result) }],
|
|
9594
|
+
details: result
|
|
9595
|
+
};
|
|
9596
|
+
}
|
|
9597
|
+
});
|
|
9598
|
+
}
|
|
9599
|
+
|
|
9600
|
+
// src/plugin/lifecycle.ts
|
|
9601
|
+
var import_node_fs31 = require("fs");
|
|
9602
|
+
init_host();
|
|
9603
|
+
|
|
9604
|
+
// src/notification/app-name-map.ts
|
|
9605
|
+
var import_node_fs22 = require("fs");
|
|
9606
|
+
var import_node_path18 = require("path");
|
|
9607
|
+
init_credentials();
|
|
9608
|
+
init_env();
|
|
9609
|
+
var PLUGIN_STATE_DIR = "phone-notifications";
|
|
9610
|
+
var CACHE_FILE = "app-name-map.json";
|
|
9611
|
+
var BUILTIN_APP_NAME_MAP_URL = getEnvUrls().appNameMapUrl;
|
|
9612
|
+
var APP_NAME_MAP_URL = ("".trim() ? "".trim() : void 0) ?? BUILTIN_APP_NAME_MAP_URL;
|
|
9613
|
+
var APP_NAME_MAP_REFRESH_HOURS = 12;
|
|
9614
|
+
function isRecordOfStrings(v) {
|
|
9615
|
+
if (v === null || typeof v !== "object") return false;
|
|
9616
|
+
for (const val of Object.values(v)) if (typeof val !== "string") return false;
|
|
9617
|
+
return true;
|
|
9618
|
+
}
|
|
9619
|
+
function isAppNameMapApiResponse(v) {
|
|
9620
|
+
if (v === null || typeof v !== "object") return false;
|
|
9621
|
+
const o = v;
|
|
9622
|
+
return Array.isArray(o.data) && o.data.every(
|
|
9623
|
+
(item) => item !== null && typeof item === "object" && typeof item.packageName === "string" && typeof item.appName === "string"
|
|
9624
|
+
);
|
|
9625
|
+
}
|
|
9626
|
+
function getCachePath(stateDir) {
|
|
9627
|
+
return (0, import_node_path18.join)(stateDir, "plugins", PLUGIN_STATE_DIR, CACHE_FILE);
|
|
9628
|
+
}
|
|
9629
|
+
function createAppNameMapProvider(opts) {
|
|
9630
|
+
const { stateDir, logger } = opts;
|
|
9631
|
+
const url = APP_NAME_MAP_URL;
|
|
9632
|
+
const refreshHours = APP_NAME_MAP_REFRESH_HOURS;
|
|
9633
|
+
const map = /* @__PURE__ */ new Map();
|
|
9634
|
+
let refreshTimer = null;
|
|
9635
|
+
let stopWatching = null;
|
|
9636
|
+
let inFlightFetch = null;
|
|
9637
|
+
function loadFromDisk() {
|
|
9638
|
+
const path2 = getCachePath(stateDir);
|
|
9639
|
+
if (!(0, import_node_fs22.existsSync)(path2)) return;
|
|
9640
|
+
try {
|
|
9641
|
+
const raw = JSON.parse((0, import_node_fs22.readFileSync)(path2, "utf-8"));
|
|
9642
|
+
if (!isRecordOfStrings(raw)) return;
|
|
9643
|
+
map.clear();
|
|
9644
|
+
for (const [k, v] of Object.entries(raw)) map.set(k, v);
|
|
9645
|
+
logger.info(`[app-name-map] loaded ${map.size} entries from cache: ${path2}`);
|
|
9646
|
+
} catch {
|
|
9647
|
+
}
|
|
9648
|
+
}
|
|
9649
|
+
async function fetchFromServer() {
|
|
9650
|
+
const apiKey = loadApiKey();
|
|
9651
|
+
if (!url) {
|
|
9652
|
+
logger.warn("[app-name-map] APP_NAME_MAP_URL is empty, skip refresh");
|
|
9653
|
+
return;
|
|
9654
|
+
}
|
|
9655
|
+
if (!apiKey) {
|
|
9656
|
+
logger.info("[app-name-map] api key missing, skip refresh");
|
|
9657
|
+
return;
|
|
9658
|
+
}
|
|
9659
|
+
const rawApiKey = apiKey.startsWith("Bearer ") ? apiKey.slice("Bearer ".length) : apiKey;
|
|
9660
|
+
try {
|
|
9661
|
+
const res = await fetch(url, {
|
|
9662
|
+
method: "POST",
|
|
9663
|
+
headers: { "Content-Type": "application/json", "X-Api-Key-Id": rawApiKey },
|
|
9664
|
+
body: JSON.stringify({
|
|
9665
|
+
platform: ""
|
|
9666
|
+
})
|
|
9667
|
+
});
|
|
9668
|
+
if (!res.ok) {
|
|
9669
|
+
logger.warn(`[app-name-map] refresh failed: HTTP ${res.status} ${res.statusText}`);
|
|
9670
|
+
return;
|
|
9671
|
+
}
|
|
9672
|
+
const body = await res.json();
|
|
9673
|
+
if (!isAppNameMapApiResponse(body) || !body.success || !body.data?.length) {
|
|
9674
|
+
logger.warn("[app-name-map] refresh failed: unexpected response shape or empty data");
|
|
9675
|
+
return;
|
|
9676
|
+
}
|
|
9677
|
+
map.clear();
|
|
9678
|
+
for (const item of body.data) {
|
|
9679
|
+
if (item.packageName && item.appName) map.set(item.packageName, item.appName);
|
|
9680
|
+
}
|
|
9681
|
+
if (map.size === 0) {
|
|
9682
|
+
logger.warn("[app-name-map] refresh succeeded but got 0 entries");
|
|
9683
|
+
return;
|
|
9684
|
+
}
|
|
9685
|
+
const dir = (0, import_node_path18.join)(stateDir, "plugins", PLUGIN_STATE_DIR);
|
|
9686
|
+
(0, import_node_fs22.mkdirSync)(dir, { recursive: true });
|
|
9687
|
+
const cachePath = getCachePath(stateDir);
|
|
9688
|
+
(0, import_node_fs22.writeFileSync)(cachePath, JSON.stringify(Object.fromEntries(map), null, 2), "utf-8");
|
|
9689
|
+
logger.info(`[app-name-map] refreshed ${map.size} entries from server and saved: ${cachePath}`);
|
|
9690
|
+
} catch (e) {
|
|
9691
|
+
const message = e instanceof Error ? e.message : String(e);
|
|
9692
|
+
logger.warn(`[app-name-map] refresh error: ${message}`);
|
|
9693
|
+
}
|
|
9694
|
+
}
|
|
9695
|
+
async function ensureOneFetch() {
|
|
9696
|
+
if (inFlightFetch) return inFlightFetch;
|
|
9697
|
+
inFlightFetch = fetchFromServer().finally(() => {
|
|
9698
|
+
inFlightFetch = null;
|
|
9699
|
+
});
|
|
9700
|
+
return inFlightFetch;
|
|
9701
|
+
}
|
|
9702
|
+
return {
|
|
9703
|
+
async resolveDisplayName(packageName) {
|
|
9704
|
+
if (map.has(packageName)) return map.get(packageName);
|
|
9705
|
+
return packageName;
|
|
9706
|
+
},
|
|
9707
|
+
async start() {
|
|
9708
|
+
loadFromDisk();
|
|
9709
|
+
await fetchFromServer();
|
|
9710
|
+
if (!url) return;
|
|
9711
|
+
if (refreshHours > 0) {
|
|
9712
|
+
const ms = refreshHours * 60 * 60 * 1e3;
|
|
9713
|
+
refreshTimer = setInterval(() => fetchFromServer().catch(() => {
|
|
9714
|
+
}), ms);
|
|
9715
|
+
}
|
|
9716
|
+
stopWatching = watchCredentials(() => fetchFromServer().catch(() => {
|
|
9717
|
+
}));
|
|
10025
9718
|
},
|
|
10026
|
-
|
|
10027
|
-
|
|
10028
|
-
|
|
10029
|
-
|
|
9719
|
+
stop() {
|
|
9720
|
+
stopWatching?.();
|
|
9721
|
+
stopWatching = null;
|
|
9722
|
+
if (refreshTimer) {
|
|
9723
|
+
clearInterval(refreshTimer);
|
|
9724
|
+
refreshTimer = null;
|
|
9725
|
+
}
|
|
9726
|
+
map.clear();
|
|
10030
9727
|
}
|
|
9728
|
+
};
|
|
9729
|
+
}
|
|
9730
|
+
|
|
9731
|
+
// src/notification/storage.ts
|
|
9732
|
+
var import_node_fs23 = require("fs");
|
|
9733
|
+
var import_node_crypto2 = require("crypto");
|
|
9734
|
+
var import_node_path19 = require("path");
|
|
9735
|
+
var NOTIFICATION_DIR_NAME = "notifications";
|
|
9736
|
+
var ID_INDEX_DIR_NAME = ".ids";
|
|
9737
|
+
var CONTENT_KEY_INDEX_DIR_NAME = ".keys";
|
|
9738
|
+
function getStateFallbackNotificationDir(stateDir) {
|
|
9739
|
+
return (0, import_node_path19.join)(stateDir, "plugins", "phone-notifications", NOTIFICATION_DIR_NAME);
|
|
9740
|
+
}
|
|
9741
|
+
function ensureWritableDirectory(dir) {
|
|
9742
|
+
try {
|
|
9743
|
+
(0, import_node_fs23.mkdirSync)(dir, { recursive: true });
|
|
9744
|
+
(0, import_node_fs23.accessSync)(dir, import_node_fs23.constants.R_OK | import_node_fs23.constants.W_OK);
|
|
9745
|
+
return true;
|
|
9746
|
+
} catch {
|
|
9747
|
+
return false;
|
|
10031
9748
|
}
|
|
10032
|
-
}
|
|
10033
|
-
function
|
|
10034
|
-
|
|
10035
|
-
|
|
10036
|
-
|
|
10037
|
-
|
|
10038
|
-
|
|
10039
|
-
|
|
10040
|
-
|
|
10041
|
-
|
|
10042
|
-
|
|
10043
|
-
|
|
10044
|
-
|
|
10045
|
-
|
|
10046
|
-
|
|
10047
|
-
|
|
9749
|
+
}
|
|
9750
|
+
function resolveNotificationStorageDir(ctx, logger) {
|
|
9751
|
+
const stateNotifDir = getStateFallbackNotificationDir(ctx.stateDir);
|
|
9752
|
+
if (ensureWritableDirectory(stateNotifDir)) {
|
|
9753
|
+
logger.info(`\u901A\u77E5\u5C06\u5199\u5165 stateDir \u8DEF\u5F84: ${stateNotifDir}`);
|
|
9754
|
+
return stateNotifDir;
|
|
9755
|
+
}
|
|
9756
|
+
if (ctx.workspaceDir) {
|
|
9757
|
+
const workspaceDir = (0, import_node_path19.join)(ctx.workspaceDir, NOTIFICATION_DIR_NAME);
|
|
9758
|
+
if (ensureWritableDirectory(workspaceDir)) {
|
|
9759
|
+
logger.warn(
|
|
9760
|
+
`stateDir \u4E0D\u53EF\u7528\uFF0C\u901A\u77E5\u5DF2\u56DE\u9000\u5230 workspace \u8DEF\u5F84: ${workspaceDir}`
|
|
9761
|
+
);
|
|
9762
|
+
return workspaceDir;
|
|
9763
|
+
}
|
|
9764
|
+
}
|
|
9765
|
+
throw new Error(`\u901A\u77E5\u5B58\u50A8\u76EE\u5F55\u4E0D\u53EF\u7528: ${stateNotifDir}`);
|
|
9766
|
+
}
|
|
9767
|
+
var NotificationStorage = class {
|
|
9768
|
+
constructor(dir, config, logger, resolveDisplayName) {
|
|
9769
|
+
this.config = config;
|
|
9770
|
+
this.logger = logger;
|
|
9771
|
+
this.dir = dir;
|
|
9772
|
+
this.idIndexDir = (0, import_node_path19.join)(dir, ID_INDEX_DIR_NAME);
|
|
9773
|
+
this.contentKeyIndexDir = (0, import_node_path19.join)(dir, CONTENT_KEY_INDEX_DIR_NAME);
|
|
9774
|
+
this.resolveDisplayName = resolveDisplayName;
|
|
9775
|
+
}
|
|
9776
|
+
dir;
|
|
9777
|
+
idIndexDir;
|
|
9778
|
+
contentKeyIndexDir;
|
|
9779
|
+
idCache = /* @__PURE__ */ new Map();
|
|
9780
|
+
contentKeyCache = /* @__PURE__ */ new Map();
|
|
9781
|
+
dateWriteChains = /* @__PURE__ */ new Map();
|
|
9782
|
+
resolveDisplayName;
|
|
9783
|
+
async init() {
|
|
9784
|
+
(0, import_node_fs23.mkdirSync)(this.dir, { recursive: true });
|
|
9785
|
+
(0, import_node_fs23.mkdirSync)(this.idIndexDir, { recursive: true });
|
|
9786
|
+
(0, import_node_fs23.rmSync)(this.contentKeyIndexDir, { recursive: true, force: true });
|
|
9787
|
+
(0, import_node_fs23.mkdirSync)(this.contentKeyIndexDir, { recursive: true });
|
|
9788
|
+
}
|
|
9789
|
+
async ingest(items) {
|
|
9790
|
+
const result = {
|
|
9791
|
+
received: items.length,
|
|
9792
|
+
ingested: 0,
|
|
9793
|
+
dedupedById: 0,
|
|
9794
|
+
dedupedByContent: 0,
|
|
9795
|
+
invalid: 0,
|
|
9796
|
+
inserted: []
|
|
9797
|
+
};
|
|
9798
|
+
for (const n of items) {
|
|
9799
|
+
const outcome = await this.writeNotification(n);
|
|
9800
|
+
switch (outcome.kind) {
|
|
9801
|
+
case "ingested":
|
|
9802
|
+
result.ingested += 1;
|
|
9803
|
+
result.inserted.push(outcome.entry);
|
|
9804
|
+
break;
|
|
9805
|
+
case "dedupedById":
|
|
9806
|
+
result.dedupedById += 1;
|
|
9807
|
+
break;
|
|
9808
|
+
case "dedupedByContent":
|
|
9809
|
+
result.dedupedByContent += 1;
|
|
9810
|
+
break;
|
|
9811
|
+
case "invalid":
|
|
9812
|
+
result.invalid += 1;
|
|
9813
|
+
break;
|
|
10048
9814
|
}
|
|
10049
|
-
|
|
10050
|
-
|
|
10051
|
-
|
|
10052
|
-
|
|
10053
|
-
|
|
10054
|
-
|
|
10055
|
-
|
|
10056
|
-
|
|
10057
|
-
|
|
10058
|
-
|
|
9815
|
+
}
|
|
9816
|
+
this.prune();
|
|
9817
|
+
return result;
|
|
9818
|
+
}
|
|
9819
|
+
async writeNotification(n) {
|
|
9820
|
+
const ts = new Date(n.timestamp);
|
|
9821
|
+
if (Number.isNaN(ts.getTime())) {
|
|
9822
|
+
this.logger.warn(`\u5FFD\u7565\u975E\u6CD5 timestamp \u7684\u901A\u77E5: ${n.id}`);
|
|
9823
|
+
return { kind: "invalid" };
|
|
9824
|
+
}
|
|
9825
|
+
const dateKey = this.formatDate(ts);
|
|
9826
|
+
const filePath = (0, import_node_path19.join)(this.dir, `${dateKey}.json`);
|
|
9827
|
+
const normalizedId = typeof n.id === "string" ? n.id.trim() : "";
|
|
9828
|
+
const entry = this.buildStoredNotification(n);
|
|
9829
|
+
return this.withDateWriteLock(dateKey, async () => {
|
|
9830
|
+
if (normalizedId && this.hasNotificationId(dateKey, normalizedId)) {
|
|
9831
|
+
return { kind: "dedupedById" };
|
|
10059
9832
|
}
|
|
10060
|
-
|
|
10061
|
-
|
|
10062
|
-
|
|
10063
|
-
|
|
10064
|
-
|
|
10065
|
-
|
|
10066
|
-
|
|
10067
|
-
|
|
10068
|
-
|
|
10069
|
-
|
|
10070
|
-
|
|
10071
|
-
|
|
9833
|
+
if (this.hasNotificationContentKey(dateKey, filePath, entry)) {
|
|
9834
|
+
return { kind: "dedupedByContent" };
|
|
9835
|
+
}
|
|
9836
|
+
const appDisplayName = this.resolveDisplayName ? await this.resolveDisplayName(entry.appName) : entry.appName;
|
|
9837
|
+
const storedEntry = {
|
|
9838
|
+
...entry,
|
|
9839
|
+
appDisplayName
|
|
9840
|
+
};
|
|
9841
|
+
const arr = this.readStoredNotifications(filePath);
|
|
9842
|
+
arr.push(storedEntry);
|
|
9843
|
+
(0, import_node_fs23.writeFileSync)(filePath, JSON.stringify(arr, null, 2), "utf-8");
|
|
9844
|
+
if (normalizedId) {
|
|
9845
|
+
this.recordNotificationId(dateKey, normalizedId);
|
|
9846
|
+
}
|
|
9847
|
+
this.recordNotificationContentKey(dateKey, filePath, storedEntry);
|
|
9848
|
+
return { kind: "ingested", entry: storedEntry };
|
|
9849
|
+
});
|
|
9850
|
+
}
|
|
9851
|
+
buildStoredNotification(n) {
|
|
9852
|
+
return {
|
|
9853
|
+
appName: typeof n.app === "string" && n.app ? n.app : "Unknown",
|
|
9854
|
+
title: typeof n.title === "string" ? n.title : "",
|
|
9855
|
+
content: this.buildContent(n),
|
|
9856
|
+
timestamp: n.timestamp
|
|
9857
|
+
};
|
|
9858
|
+
}
|
|
9859
|
+
buildContent(n) {
|
|
9860
|
+
const body = n.body?.trim();
|
|
9861
|
+
if (body) {
|
|
9862
|
+
return body;
|
|
9863
|
+
}
|
|
9864
|
+
const fallback = [];
|
|
9865
|
+
if (n.category) {
|
|
9866
|
+
fallback.push(`category:${n.category}`);
|
|
9867
|
+
}
|
|
9868
|
+
if (n.metadata && Object.keys(n.metadata).length > 0) {
|
|
9869
|
+
fallback.push(`metadata:${JSON.stringify(n.metadata)}`);
|
|
9870
|
+
}
|
|
9871
|
+
return fallback.join(" ; ") || "-";
|
|
9872
|
+
}
|
|
9873
|
+
formatDate(d) {
|
|
9874
|
+
const year = d.getFullYear();
|
|
9875
|
+
const month = String(d.getMonth() + 1).padStart(2, "0");
|
|
9876
|
+
const day = String(d.getDate()).padStart(2, "0");
|
|
9877
|
+
return `${year}-${month}-${day}`;
|
|
9878
|
+
}
|
|
9879
|
+
getIdIndexPath(dateKey) {
|
|
9880
|
+
return (0, import_node_path19.join)(this.idIndexDir, `${dateKey}.ids`);
|
|
9881
|
+
}
|
|
9882
|
+
getIdSet(dateKey) {
|
|
9883
|
+
const cached = this.idCache.get(dateKey);
|
|
9884
|
+
if (cached) {
|
|
9885
|
+
return cached;
|
|
9886
|
+
}
|
|
9887
|
+
const idPath = this.getIdIndexPath(dateKey);
|
|
9888
|
+
const ids = /* @__PURE__ */ new Set();
|
|
9889
|
+
if ((0, import_node_fs23.existsSync)(idPath)) {
|
|
9890
|
+
const lines = (0, import_node_fs23.readFileSync)(idPath, "utf-8").split(/\r?\n/);
|
|
9891
|
+
for (const line of lines) {
|
|
9892
|
+
const id = line.trim();
|
|
9893
|
+
if (id) {
|
|
9894
|
+
ids.add(id);
|
|
9895
|
+
}
|
|
10072
9896
|
}
|
|
10073
|
-
|
|
10074
|
-
|
|
10075
|
-
|
|
10076
|
-
|
|
10077
|
-
|
|
10078
|
-
|
|
10079
|
-
|
|
10080
|
-
|
|
10081
|
-
|
|
10082
|
-
|
|
10083
|
-
|
|
10084
|
-
|
|
10085
|
-
|
|
10086
|
-
|
|
9897
|
+
}
|
|
9898
|
+
this.idCache.set(dateKey, ids);
|
|
9899
|
+
return ids;
|
|
9900
|
+
}
|
|
9901
|
+
hasNotificationId(dateKey, id) {
|
|
9902
|
+
return this.getIdSet(dateKey).has(id);
|
|
9903
|
+
}
|
|
9904
|
+
getContentKeyIndexPath(dateKey) {
|
|
9905
|
+
return (0, import_node_path19.join)(this.contentKeyIndexDir, `${dateKey}.keys`);
|
|
9906
|
+
}
|
|
9907
|
+
getContentKeySet(dateKey, filePath) {
|
|
9908
|
+
const cached = this.contentKeyCache.get(dateKey);
|
|
9909
|
+
if (cached) {
|
|
9910
|
+
return cached;
|
|
9911
|
+
}
|
|
9912
|
+
const keyPath = this.getContentKeyIndexPath(dateKey);
|
|
9913
|
+
const keys = /* @__PURE__ */ new Set();
|
|
9914
|
+
if ((0, import_node_fs23.existsSync)(filePath)) {
|
|
9915
|
+
for (const item of this.readStoredNotifications(filePath)) {
|
|
9916
|
+
keys.add(this.buildNotificationContentKey(item));
|
|
10087
9917
|
}
|
|
10088
|
-
return {
|
|
10089
|
-
content: [{ type: "text", text: JSON.stringify(result) }],
|
|
10090
|
-
details: result
|
|
10091
|
-
};
|
|
10092
9918
|
}
|
|
10093
|
-
|
|
10094
|
-
}
|
|
10095
|
-
|
|
10096
|
-
|
|
10097
|
-
|
|
10098
|
-
init_host();
|
|
10099
|
-
|
|
10100
|
-
// src/notification/app-name-map.ts
|
|
10101
|
-
var import_node_fs23 = require("fs");
|
|
10102
|
-
var import_node_path19 = require("path");
|
|
10103
|
-
init_credentials();
|
|
10104
|
-
init_env();
|
|
10105
|
-
var PLUGIN_STATE_DIR = "phone-notifications";
|
|
10106
|
-
var CACHE_FILE = "app-name-map.json";
|
|
10107
|
-
var BUILTIN_APP_NAME_MAP_URL = getEnvUrls().appNameMapUrl;
|
|
10108
|
-
var APP_NAME_MAP_URL = ("".trim() ? "".trim() : void 0) ?? BUILTIN_APP_NAME_MAP_URL;
|
|
10109
|
-
var APP_NAME_MAP_REFRESH_HOURS = 12;
|
|
10110
|
-
function isRecordOfStrings(v) {
|
|
10111
|
-
if (v === null || typeof v !== "object") return false;
|
|
10112
|
-
for (const val of Object.values(v)) if (typeof val !== "string") return false;
|
|
10113
|
-
return true;
|
|
10114
|
-
}
|
|
10115
|
-
function isAppNameMapApiResponse(v) {
|
|
10116
|
-
if (v === null || typeof v !== "object") return false;
|
|
10117
|
-
const o = v;
|
|
10118
|
-
return Array.isArray(o.data) && o.data.every(
|
|
10119
|
-
(item) => item !== null && typeof item === "object" && typeof item.packageName === "string" && typeof item.appName === "string"
|
|
10120
|
-
);
|
|
10121
|
-
}
|
|
10122
|
-
function getCachePath(stateDir) {
|
|
10123
|
-
return (0, import_node_path19.join)(stateDir, "plugins", PLUGIN_STATE_DIR, CACHE_FILE);
|
|
10124
|
-
}
|
|
10125
|
-
function createAppNameMapProvider(opts) {
|
|
10126
|
-
const { stateDir, logger } = opts;
|
|
10127
|
-
const url = APP_NAME_MAP_URL;
|
|
10128
|
-
const refreshHours = APP_NAME_MAP_REFRESH_HOURS;
|
|
10129
|
-
const map = /* @__PURE__ */ new Map();
|
|
10130
|
-
let refreshTimer = null;
|
|
10131
|
-
let stopWatching = null;
|
|
10132
|
-
let inFlightFetch = null;
|
|
10133
|
-
function loadFromDisk() {
|
|
10134
|
-
const path2 = getCachePath(stateDir);
|
|
10135
|
-
if (!(0, import_node_fs23.existsSync)(path2)) return;
|
|
10136
|
-
try {
|
|
10137
|
-
const raw = JSON.parse((0, import_node_fs23.readFileSync)(path2, "utf-8"));
|
|
10138
|
-
if (!isRecordOfStrings(raw)) return;
|
|
10139
|
-
map.clear();
|
|
10140
|
-
for (const [k, v] of Object.entries(raw)) map.set(k, v);
|
|
10141
|
-
logger.info(`[app-name-map] loaded ${map.size} entries from cache: ${path2}`);
|
|
10142
|
-
} catch {
|
|
9919
|
+
if (keys.size > 0) {
|
|
9920
|
+
(0, import_node_fs23.writeFileSync)(keyPath, `${Array.from(keys).join("\n")}
|
|
9921
|
+
`, "utf-8");
|
|
9922
|
+
} else if ((0, import_node_fs23.existsSync)(keyPath)) {
|
|
9923
|
+
(0, import_node_fs23.rmSync)(keyPath, { force: true });
|
|
10143
9924
|
}
|
|
9925
|
+
this.contentKeyCache.set(dateKey, keys);
|
|
9926
|
+
return keys;
|
|
10144
9927
|
}
|
|
10145
|
-
|
|
10146
|
-
|
|
10147
|
-
|
|
10148
|
-
|
|
9928
|
+
hasNotificationContentKey(dateKey, filePath, entry) {
|
|
9929
|
+
return this.getContentKeySet(dateKey, filePath).has(
|
|
9930
|
+
this.buildNotificationContentKey(entry)
|
|
9931
|
+
);
|
|
9932
|
+
}
|
|
9933
|
+
recordNotificationId(dateKey, id) {
|
|
9934
|
+
const ids = this.getIdSet(dateKey);
|
|
9935
|
+
if (ids.has(id)) {
|
|
10149
9936
|
return;
|
|
10150
9937
|
}
|
|
10151
|
-
|
|
10152
|
-
|
|
9938
|
+
(0, import_node_fs23.appendFileSync)(this.getIdIndexPath(dateKey), `${id}
|
|
9939
|
+
`, "utf-8");
|
|
9940
|
+
ids.add(id);
|
|
9941
|
+
}
|
|
9942
|
+
recordNotificationContentKey(dateKey, filePath, entry) {
|
|
9943
|
+
const keys = this.getContentKeySet(dateKey, filePath);
|
|
9944
|
+
const key = this.buildNotificationContentKey(entry);
|
|
9945
|
+
if (keys.has(key)) {
|
|
10153
9946
|
return;
|
|
10154
9947
|
}
|
|
10155
|
-
|
|
9948
|
+
(0, import_node_fs23.appendFileSync)(this.getContentKeyIndexPath(dateKey), `${key}
|
|
9949
|
+
`, "utf-8");
|
|
9950
|
+
keys.add(key);
|
|
9951
|
+
}
|
|
9952
|
+
buildNotificationContentKey(entry) {
|
|
9953
|
+
return (0, import_node_crypto2.createHash)("sha256").update(entry.appName).update("").update(entry.title).update("").update(entry.content).update("").update(entry.timestamp).digest("hex");
|
|
9954
|
+
}
|
|
9955
|
+
readStoredNotifications(filePath) {
|
|
9956
|
+
if (!(0, import_node_fs23.existsSync)(filePath)) {
|
|
9957
|
+
return [];
|
|
9958
|
+
}
|
|
10156
9959
|
try {
|
|
10157
|
-
const
|
|
10158
|
-
|
|
10159
|
-
|
|
10160
|
-
|
|
10161
|
-
platform: ""
|
|
10162
|
-
})
|
|
10163
|
-
});
|
|
10164
|
-
if (!res.ok) {
|
|
10165
|
-
logger.warn(`[app-name-map] refresh failed: HTTP ${res.status} ${res.statusText}`);
|
|
10166
|
-
return;
|
|
10167
|
-
}
|
|
10168
|
-
const body = await res.json();
|
|
10169
|
-
if (!isAppNameMapApiResponse(body) || !body.success || !body.data?.length) {
|
|
10170
|
-
logger.warn("[app-name-map] refresh failed: unexpected response shape or empty data");
|
|
10171
|
-
return;
|
|
10172
|
-
}
|
|
10173
|
-
map.clear();
|
|
10174
|
-
for (const item of body.data) {
|
|
10175
|
-
if (item.packageName && item.appName) map.set(item.packageName, item.appName);
|
|
10176
|
-
}
|
|
10177
|
-
if (map.size === 0) {
|
|
10178
|
-
logger.warn("[app-name-map] refresh succeeded but got 0 entries");
|
|
10179
|
-
return;
|
|
10180
|
-
}
|
|
10181
|
-
const dir = (0, import_node_path19.join)(stateDir, "plugins", PLUGIN_STATE_DIR);
|
|
10182
|
-
(0, import_node_fs23.mkdirSync)(dir, { recursive: true });
|
|
10183
|
-
const cachePath = getCachePath(stateDir);
|
|
10184
|
-
(0, import_node_fs23.writeFileSync)(cachePath, JSON.stringify(Object.fromEntries(map), null, 2), "utf-8");
|
|
10185
|
-
logger.info(`[app-name-map] refreshed ${map.size} entries from server and saved: ${cachePath}`);
|
|
10186
|
-
} catch (e) {
|
|
10187
|
-
const message = e instanceof Error ? e.message : String(e);
|
|
10188
|
-
logger.warn(`[app-name-map] refresh error: ${message}`);
|
|
9960
|
+
const parsed = JSON.parse((0, import_node_fs23.readFileSync)(filePath, "utf-8"));
|
|
9961
|
+
return Array.isArray(parsed) ? parsed : [];
|
|
9962
|
+
} catch {
|
|
9963
|
+
return [];
|
|
10189
9964
|
}
|
|
10190
9965
|
}
|
|
10191
|
-
async
|
|
10192
|
-
|
|
10193
|
-
|
|
10194
|
-
|
|
9966
|
+
async withDateWriteLock(dateKey, task) {
|
|
9967
|
+
const previous = this.dateWriteChains.get(dateKey) ?? Promise.resolve();
|
|
9968
|
+
let release;
|
|
9969
|
+
const current = new Promise((resolve) => {
|
|
9970
|
+
release = resolve;
|
|
10195
9971
|
});
|
|
10196
|
-
|
|
9972
|
+
const chain = previous.then(() => current);
|
|
9973
|
+
this.dateWriteChains.set(dateKey, chain);
|
|
9974
|
+
await previous;
|
|
9975
|
+
try {
|
|
9976
|
+
return await task();
|
|
9977
|
+
} finally {
|
|
9978
|
+
release();
|
|
9979
|
+
if (this.dateWriteChains.get(dateKey) === chain) {
|
|
9980
|
+
this.dateWriteChains.delete(dateKey);
|
|
9981
|
+
}
|
|
9982
|
+
}
|
|
10197
9983
|
}
|
|
10198
|
-
|
|
10199
|
-
|
|
10200
|
-
|
|
10201
|
-
return
|
|
10202
|
-
}
|
|
10203
|
-
|
|
10204
|
-
|
|
10205
|
-
|
|
10206
|
-
|
|
10207
|
-
|
|
10208
|
-
|
|
10209
|
-
|
|
10210
|
-
|
|
9984
|
+
prune() {
|
|
9985
|
+
const retentionDays = this.config.retentionDays;
|
|
9986
|
+
if (retentionDays === void 0) {
|
|
9987
|
+
return;
|
|
9988
|
+
}
|
|
9989
|
+
const cutoffMs = Date.now() - retentionDays * 24 * 60 * 60 * 1e3;
|
|
9990
|
+
const cutoffDate = this.formatDate(new Date(cutoffMs));
|
|
9991
|
+
this.pruneDataFiles(cutoffDate);
|
|
9992
|
+
this.pruneIdIndex(cutoffDate);
|
|
9993
|
+
this.pruneContentKeyIndex(cutoffDate);
|
|
9994
|
+
}
|
|
9995
|
+
/** Remove expired .json, legacy .md files, and legacy date directories */
|
|
9996
|
+
pruneDataFiles(cutoffDate) {
|
|
9997
|
+
const dateFilePattern = /^(\d{4}-\d{2}-\d{2})\.(json|md)$/;
|
|
9998
|
+
const dateDirPattern = /^\d{4}-\d{2}-\d{2}$/;
|
|
9999
|
+
try {
|
|
10000
|
+
for (const entry of (0, import_node_fs23.readdirSync)(this.dir, { withFileTypes: true })) {
|
|
10001
|
+
if (entry.isFile()) {
|
|
10002
|
+
const match = dateFilePattern.exec(entry.name);
|
|
10003
|
+
if (match && match[1] < cutoffDate) {
|
|
10004
|
+
(0, import_node_fs23.rmSync)((0, import_node_path19.join)(this.dir, entry.name), { force: true });
|
|
10005
|
+
}
|
|
10006
|
+
} else if (entry.isDirectory() && dateDirPattern.test(entry.name) && entry.name < cutoffDate) {
|
|
10007
|
+
(0, import_node_fs23.rmSync)((0, import_node_path19.join)(this.dir, entry.name), { recursive: true, force: true });
|
|
10008
|
+
}
|
|
10211
10009
|
}
|
|
10212
|
-
|
|
10213
|
-
|
|
10214
|
-
|
|
10215
|
-
|
|
10216
|
-
|
|
10217
|
-
|
|
10218
|
-
|
|
10219
|
-
|
|
10220
|
-
|
|
10010
|
+
} catch {
|
|
10011
|
+
}
|
|
10012
|
+
}
|
|
10013
|
+
/** Remove expired .ids index files */
|
|
10014
|
+
pruneIdIndex(cutoffDate) {
|
|
10015
|
+
try {
|
|
10016
|
+
for (const entry of (0, import_node_fs23.readdirSync)(this.idIndexDir, { withFileTypes: true })) {
|
|
10017
|
+
if (!entry.isFile()) continue;
|
|
10018
|
+
const match = /^(\d{4}-\d{2}-\d{2})\.ids$/.exec(entry.name);
|
|
10019
|
+
if (match && match[1] < cutoffDate) {
|
|
10020
|
+
(0, import_node_fs23.rmSync)((0, import_node_path19.join)(this.idIndexDir, entry.name), { force: true });
|
|
10021
|
+
this.idCache.delete(match[1]);
|
|
10022
|
+
}
|
|
10221
10023
|
}
|
|
10222
|
-
|
|
10024
|
+
} catch {
|
|
10223
10025
|
}
|
|
10224
|
-
}
|
|
10225
|
-
|
|
10026
|
+
}
|
|
10027
|
+
pruneContentKeyIndex(cutoffDate) {
|
|
10028
|
+
try {
|
|
10029
|
+
for (const entry of (0, import_node_fs23.readdirSync)(this.contentKeyIndexDir, { withFileTypes: true })) {
|
|
10030
|
+
if (!entry.isFile()) continue;
|
|
10031
|
+
const match = /^(\d{4}-\d{2}-\d{2})\.keys$/.exec(entry.name);
|
|
10032
|
+
if (match && match[1] < cutoffDate) {
|
|
10033
|
+
(0, import_node_fs23.rmSync)((0, import_node_path19.join)(this.contentKeyIndexDir, entry.name), { force: true });
|
|
10034
|
+
this.contentKeyCache.delete(match[1]);
|
|
10035
|
+
}
|
|
10036
|
+
}
|
|
10037
|
+
} catch {
|
|
10038
|
+
}
|
|
10039
|
+
}
|
|
10040
|
+
async close() {
|
|
10041
|
+
this.idCache.clear();
|
|
10042
|
+
this.contentKeyCache.clear();
|
|
10043
|
+
this.dateWriteChains.clear();
|
|
10044
|
+
}
|
|
10045
|
+
};
|
|
10226
10046
|
|
|
10227
10047
|
// src/recording/storage.ts
|
|
10228
10048
|
var import_node_fs24 = require("fs");
|
|
@@ -10546,7 +10366,7 @@ var RecordingStorage = class {
|
|
|
10546
10366
|
if (entry.transcriptDataFile) {
|
|
10547
10367
|
const transcriptDoc = this.readRelativeTranscriptDocument(entry.transcriptDataFile);
|
|
10548
10368
|
const transcriptFromJson = extractTranscriptTextFromDocument(transcriptDoc);
|
|
10549
|
-
if (transcriptFromJson) {
|
|
10369
|
+
if (transcriptFromJson !== void 0) {
|
|
10550
10370
|
return transcriptFromJson;
|
|
10551
10371
|
}
|
|
10552
10372
|
}
|
|
@@ -12851,16 +12671,15 @@ function registerNotificationInterfaces(deps) {
|
|
|
12851
12671
|
filterNotifications,
|
|
12852
12672
|
registerGatewayMethod,
|
|
12853
12673
|
tunnelService,
|
|
12854
|
-
onAfterIngest
|
|
12855
|
-
getCronForHttpIngest
|
|
12674
|
+
onAfterIngest
|
|
12856
12675
|
} = deps;
|
|
12857
|
-
function triggerAfterIngest(inserted
|
|
12676
|
+
function triggerAfterIngest(inserted) {
|
|
12858
12677
|
if (inserted.length === 0 || !onAfterIngest) return;
|
|
12859
|
-
void Promise.resolve().then(() => onAfterIngest(inserted
|
|
12678
|
+
void Promise.resolve().then(() => onAfterIngest(inserted)).catch((err2) => logger.warn(`onAfterIngest failed: ${err2?.message ?? err2}`));
|
|
12860
12679
|
}
|
|
12861
12680
|
registerGatewayMethod(
|
|
12862
12681
|
"notifications.push",
|
|
12863
|
-
async ({ params, respond
|
|
12682
|
+
async ({ params, respond }) => {
|
|
12864
12683
|
const storage = getStorage();
|
|
12865
12684
|
if (!storage) {
|
|
12866
12685
|
respond(false, null, {
|
|
@@ -12880,7 +12699,7 @@ function registerNotificationInterfaces(deps) {
|
|
|
12880
12699
|
const filtered = filterNotifications(items);
|
|
12881
12700
|
const result = filtered.length ? await storage.ingest(filtered) : createEmptyIngestResult();
|
|
12882
12701
|
respond(true, toIngestResponse(result));
|
|
12883
|
-
triggerAfterIngest(result.inserted
|
|
12702
|
+
triggerAfterIngest(result.inserted);
|
|
12884
12703
|
}
|
|
12885
12704
|
);
|
|
12886
12705
|
api.registerHttpRoute({
|
|
@@ -12924,7 +12743,7 @@ function registerNotificationInterfaces(deps) {
|
|
|
12924
12743
|
const result = filtered.length ? await storage.ingest(filtered) : createEmptyIngestResult();
|
|
12925
12744
|
res.writeHead(200, { "Content-Type": "application/json" });
|
|
12926
12745
|
res.end(JSON.stringify({ ok: true, ...toIngestResponse(result) }));
|
|
12927
|
-
triggerAfterIngest(result.inserted
|
|
12746
|
+
triggerAfterIngest(result.inserted);
|
|
12928
12747
|
}
|
|
12929
12748
|
});
|
|
12930
12749
|
logger.info("Gateway \u901A\u77E5\u65B9\u6CD5\u5DF2\u6CE8\u518C: notifications.push");
|
|
@@ -13395,7 +13214,6 @@ var index_default = {
|
|
|
13395
13214
|
let storage = null;
|
|
13396
13215
|
let recordingStorage = null;
|
|
13397
13216
|
let broadcastFn = null;
|
|
13398
|
-
let cronService = null;
|
|
13399
13217
|
let autoUpdateLifecycle = null;
|
|
13400
13218
|
let tunnelService = null;
|
|
13401
13219
|
const openclawDir = api.runtime.state.resolveStateDir();
|
|
@@ -13448,9 +13266,6 @@ var index_default = {
|
|
|
13448
13266
|
function registerGatewayMethodWithBroadcastCapture(method, handler) {
|
|
13449
13267
|
api.registerGatewayMethod(method, async (opts) => {
|
|
13450
13268
|
cacheBroadcast(opts.context?.broadcast);
|
|
13451
|
-
if (opts.context?.cron) {
|
|
13452
|
-
cronService = opts.context.cron;
|
|
13453
|
-
}
|
|
13454
13269
|
await deactivateRelayForDirectGatewayRequest(method, opts);
|
|
13455
13270
|
return handler(opts);
|
|
13456
13271
|
});
|
|
@@ -13474,13 +13289,6 @@ var index_default = {
|
|
|
13474
13289
|
registry: lightRuleRegistry,
|
|
13475
13290
|
invoker: lightRuleInvoker
|
|
13476
13291
|
});
|
|
13477
|
-
const lightRulesEvaluatorJob = new LightRulesEvaluatorJob({
|
|
13478
|
-
logger,
|
|
13479
|
-
registry: lightRuleRegistry,
|
|
13480
|
-
inlineEvaluator: inlineLightRuleEvaluator,
|
|
13481
|
-
subagentRunner: api.runtime.subagent,
|
|
13482
|
-
getNotificationsDir: () => openclawDir ? getStateFallbackNotificationDir(openclawDir) : void 0
|
|
13483
|
-
});
|
|
13484
13292
|
registerStorageLifecycle({
|
|
13485
13293
|
api,
|
|
13486
13294
|
config,
|
|
@@ -13510,10 +13318,9 @@ var index_default = {
|
|
|
13510
13318
|
filterNotifications,
|
|
13511
13319
|
registerGatewayMethod: registerGatewayMethodWithBroadcastCapture,
|
|
13512
13320
|
tunnelService,
|
|
13513
|
-
onAfterIngest: (inserted
|
|
13514
|
-
void
|
|
13515
|
-
}
|
|
13516
|
-
getCronForHttpIngest: () => cronService
|
|
13321
|
+
onAfterIngest: (inserted) => {
|
|
13322
|
+
void inlineLightRuleEvaluator.evaluate(inserted);
|
|
13323
|
+
}
|
|
13517
13324
|
});
|
|
13518
13325
|
registerLightControlTool(api, logger);
|
|
13519
13326
|
registerRecordingInterfaces({
|