@yoooclaw/phone-notifications 1.10.7 → 1.11.0-beta.1
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 +1311 -1248
- package/dist/index.cjs.map +1 -1
- package/package.json +1 -1
- package/skills/notification-monitor/SKILL.md +83 -81
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 = normalizeOptionalText(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: normalizeOptionalText(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 normalizeOptionalText(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 normalizeOptionalText(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 (text
|
|
395
|
+
const text = normalizeOptionalText(normalized.text) ?? joinSegmentsText(segments);
|
|
396
|
+
if (!text) {
|
|
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: normalizeOptionalText(normalized.summary),
|
|
403
403
|
text,
|
|
404
404
|
segments
|
|
405
405
|
};
|
|
@@ -465,9 +465,6 @@ 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
|
-
}
|
|
471
468
|
function normalizeOptionalNumber(value) {
|
|
472
469
|
return typeof value === "number" && Number.isFinite(value) ? value : void 0;
|
|
473
470
|
}
|
|
@@ -1345,7 +1342,7 @@ async function runTranscriptionWorkflow(params) {
|
|
|
1345
1342
|
return { ok: false, error: result.error };
|
|
1346
1343
|
}
|
|
1347
1344
|
const title = normalizeOptionalText2(result.summary) ? normalizeOptionalText2(result.summary) : extractSummary(result.text ?? "");
|
|
1348
|
-
const summary = result.summaryText
|
|
1345
|
+
const summary = normalizeOptionalText2(result.summaryText);
|
|
1349
1346
|
result.summary = title;
|
|
1350
1347
|
const transcriptData = buildTranscriptDocument({
|
|
1351
1348
|
recordingId,
|
|
@@ -1606,11 +1603,16 @@ function buildLongRecordingSuccessResult(taskId, requestId, data, logger) {
|
|
|
1606
1603
|
);
|
|
1607
1604
|
const listResult = extractLongRecordingTextFromList(sourceTextList);
|
|
1608
1605
|
const sourceText = normalizeOptionalText2(data?.recordResult?.sourceText);
|
|
1609
|
-
const summaryText = normalizeOptionalText2(data?.recordResult?.summaryResult)
|
|
1606
|
+
const summaryText = normalizeOptionalText2(data?.recordResult?.summaryResult);
|
|
1610
1607
|
const title = normalizeOptionalText2(data?.recordResult?.title);
|
|
1611
1608
|
const category = normalizeOptionalText2(data?.recordResult?.category);
|
|
1612
1609
|
const text = listResult.text ?? sourceText ?? summaryText;
|
|
1613
|
-
|
|
1610
|
+
if (!text) {
|
|
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
|
+
}
|
|
1614
1616
|
if (!listResult.text && !sourceText && summaryText) {
|
|
1615
1617
|
logger.warn(
|
|
1616
1618
|
`[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}`
|
|
@@ -1630,7 +1632,7 @@ function buildLongRecordingSuccessResult(taskId, requestId, data, logger) {
|
|
|
1630
1632
|
provider: "model-proxy",
|
|
1631
1633
|
taskId,
|
|
1632
1634
|
requestId,
|
|
1633
|
-
status
|
|
1635
|
+
status: "SUCCEEDED"
|
|
1634
1636
|
},
|
|
1635
1637
|
rawResponse: data
|
|
1636
1638
|
};
|
|
@@ -5422,7 +5424,7 @@ function readBuildInjectedVersion() {
|
|
|
5422
5424
|
if (false) {
|
|
5423
5425
|
return void 0;
|
|
5424
5426
|
}
|
|
5425
|
-
const version = "1.
|
|
5427
|
+
const version = "1.11.0-beta.1".trim();
|
|
5426
5428
|
return version || void 0;
|
|
5427
5429
|
}
|
|
5428
5430
|
function readPluginVersionFromPackageJson() {
|
|
@@ -5547,764 +5549,448 @@ function formatLocalTimestamp(d) {
|
|
|
5547
5549
|
return `${y}-${m}-${day}T${hh}:${mm}:${ss}.${ms}${sign}${offsetHours}:${offsetMins}`;
|
|
5548
5550
|
}
|
|
5549
5551
|
|
|
5550
|
-
// src/
|
|
5552
|
+
// src/light/repeat.ts
|
|
5553
|
+
function normalizeRepeatTimes(input) {
|
|
5554
|
+
if (typeof input === "boolean") {
|
|
5555
|
+
return input ? 0 : 1;
|
|
5556
|
+
}
|
|
5557
|
+
if (typeof input === "number") {
|
|
5558
|
+
return validateRepeatTimes(input);
|
|
5559
|
+
}
|
|
5560
|
+
if (!input) {
|
|
5561
|
+
return 1;
|
|
5562
|
+
}
|
|
5563
|
+
if (input.repeat_times !== void 0) {
|
|
5564
|
+
return validateRepeatTimes(input.repeat_times);
|
|
5565
|
+
}
|
|
5566
|
+
if (input.repeat !== void 0) {
|
|
5567
|
+
return input.repeat ? 0 : 1;
|
|
5568
|
+
}
|
|
5569
|
+
return 1;
|
|
5570
|
+
}
|
|
5571
|
+
function assertAncsRepeatTimes(repeatTimes) {
|
|
5572
|
+
if (repeatTimes !== 0 && repeatTimes !== 1) {
|
|
5573
|
+
throw new Error(
|
|
5574
|
+
"\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"
|
|
5575
|
+
);
|
|
5576
|
+
}
|
|
5577
|
+
}
|
|
5578
|
+
function validateRepeatTimes(value) {
|
|
5579
|
+
if (!Number.isInteger(value) || value < 0) {
|
|
5580
|
+
throw new Error("repeat_times \u5FC5\u987B\u662F >=0 \u7684\u6574\u6570");
|
|
5581
|
+
}
|
|
5582
|
+
return value;
|
|
5583
|
+
}
|
|
5584
|
+
|
|
5585
|
+
// src/cli/helpers.ts
|
|
5551
5586
|
var import_node_fs3 = require("fs");
|
|
5552
|
-
var
|
|
5587
|
+
var import_promises = require("fs/promises");
|
|
5553
5588
|
var import_node_path2 = require("path");
|
|
5554
|
-
|
|
5555
|
-
|
|
5556
|
-
|
|
5557
|
-
|
|
5558
|
-
|
|
5589
|
+
function resolveNotificationsDir(ctx) {
|
|
5590
|
+
if (ctx.stateDir) {
|
|
5591
|
+
const dir = (0, import_node_path2.join)(
|
|
5592
|
+
ctx.stateDir,
|
|
5593
|
+
"plugins",
|
|
5594
|
+
"phone-notifications",
|
|
5595
|
+
"notifications"
|
|
5596
|
+
);
|
|
5597
|
+
if ((0, import_node_fs3.existsSync)(dir)) return dir;
|
|
5598
|
+
}
|
|
5599
|
+
if (ctx.workspaceDir) {
|
|
5600
|
+
const dir = (0, import_node_path2.join)(ctx.workspaceDir, "notifications");
|
|
5601
|
+
if ((0, import_node_fs3.existsSync)(dir)) return dir;
|
|
5602
|
+
}
|
|
5603
|
+
return null;
|
|
5559
5604
|
}
|
|
5560
|
-
function
|
|
5605
|
+
function listDateKeys(dir) {
|
|
5606
|
+
const pattern = /^(\d{4}-\d{2}-\d{2})\.json$/;
|
|
5607
|
+
const keys = [];
|
|
5608
|
+
for (const entry of (0, import_node_fs3.readdirSync)(dir, { withFileTypes: true })) {
|
|
5609
|
+
if (!entry.isFile()) continue;
|
|
5610
|
+
const m = pattern.exec(entry.name);
|
|
5611
|
+
if (m) keys.push(m[1]);
|
|
5612
|
+
}
|
|
5613
|
+
return keys.sort().reverse();
|
|
5614
|
+
}
|
|
5615
|
+
function readDateFile(dir, dateKey) {
|
|
5616
|
+
const filePath = (0, import_node_path2.join)(dir, `${dateKey}.json`);
|
|
5617
|
+
if (!(0, import_node_fs3.existsSync)(filePath)) return [];
|
|
5561
5618
|
try {
|
|
5562
|
-
(0, import_node_fs3.
|
|
5563
|
-
(0, import_node_fs3.accessSync)(dir, import_node_fs3.constants.R_OK | import_node_fs3.constants.W_OK);
|
|
5564
|
-
return true;
|
|
5619
|
+
return JSON.parse((0, import_node_fs3.readFileSync)(filePath, "utf-8"));
|
|
5565
5620
|
} catch {
|
|
5566
|
-
return
|
|
5621
|
+
return [];
|
|
5567
5622
|
}
|
|
5568
5623
|
}
|
|
5569
|
-
function
|
|
5570
|
-
|
|
5571
|
-
|
|
5572
|
-
|
|
5573
|
-
|
|
5624
|
+
function today() {
|
|
5625
|
+
return formatDate2(/* @__PURE__ */ new Date());
|
|
5626
|
+
}
|
|
5627
|
+
function formatDate2(d) {
|
|
5628
|
+
const y = d.getFullYear();
|
|
5629
|
+
const m = String(d.getMonth() + 1).padStart(2, "0");
|
|
5630
|
+
const day = String(d.getDate()).padStart(2, "0");
|
|
5631
|
+
return `${y}-${m}-${day}`;
|
|
5632
|
+
}
|
|
5633
|
+
function daysAgo(n) {
|
|
5634
|
+
const d = /* @__PURE__ */ new Date();
|
|
5635
|
+
d.setDate(d.getDate() - n);
|
|
5636
|
+
return formatDate2(d);
|
|
5637
|
+
}
|
|
5638
|
+
function filterDateRange(keys, from, to) {
|
|
5639
|
+
return keys.filter((k) => k >= from && k <= to);
|
|
5640
|
+
}
|
|
5641
|
+
function parseIsoTime(value, optionName) {
|
|
5642
|
+
const isoPattern = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}(?::\d{2}(?:\.\d{1,3})?)?(Z|[+-]\d{2}:\d{2})$/;
|
|
5643
|
+
if (!isoPattern.test(value)) {
|
|
5644
|
+
exitError(
|
|
5645
|
+
"INVALID_TIME",
|
|
5646
|
+
`${optionName} \u5FC5\u987B\u662F ISO 8601 \u65F6\u95F4\uFF0C\u4F8B\u5982 2026-03-01T09:00:00+08:00`
|
|
5647
|
+
);
|
|
5574
5648
|
}
|
|
5575
|
-
|
|
5576
|
-
|
|
5577
|
-
|
|
5578
|
-
|
|
5579
|
-
|
|
5580
|
-
|
|
5581
|
-
return workspaceDir;
|
|
5582
|
-
}
|
|
5649
|
+
const ts = Date.parse(value);
|
|
5650
|
+
if (Number.isNaN(ts)) {
|
|
5651
|
+
exitError(
|
|
5652
|
+
"INVALID_TIME",
|
|
5653
|
+
`${optionName} \u4E0D\u662F\u5408\u6CD5\u65F6\u95F4\uFF0C\u4F8B\u5982 2026-03-01T09:00:00+08:00`
|
|
5654
|
+
);
|
|
5583
5655
|
}
|
|
5584
|
-
|
|
5656
|
+
return ts;
|
|
5585
5657
|
}
|
|
5586
|
-
|
|
5587
|
-
|
|
5588
|
-
|
|
5589
|
-
|
|
5590
|
-
|
|
5591
|
-
|
|
5592
|
-
|
|
5593
|
-
|
|
5658
|
+
function sortNotificationsByTimestampDesc(items) {
|
|
5659
|
+
return items.sort((a, b) => Date.parse(b.timestamp) - Date.parse(a.timestamp));
|
|
5660
|
+
}
|
|
5661
|
+
async function listDateKeysAsync(dir) {
|
|
5662
|
+
const pattern = /^(\d{4}-\d{2}-\d{2})\.json$/;
|
|
5663
|
+
const keys = [];
|
|
5664
|
+
const entries = await (0, import_promises.readdir)(dir, { withFileTypes: true });
|
|
5665
|
+
for (const entry of entries) {
|
|
5666
|
+
if (!entry.isFile()) continue;
|
|
5667
|
+
const m = pattern.exec(entry.name);
|
|
5668
|
+
if (m) keys.push(m[1]);
|
|
5594
5669
|
}
|
|
5595
|
-
|
|
5596
|
-
|
|
5597
|
-
|
|
5598
|
-
|
|
5599
|
-
|
|
5600
|
-
|
|
5601
|
-
|
|
5602
|
-
|
|
5603
|
-
|
|
5604
|
-
|
|
5605
|
-
(0, import_node_fs3.rmSync)(this.contentKeyIndexDir, { recursive: true, force: true });
|
|
5606
|
-
(0, import_node_fs3.mkdirSync)(this.contentKeyIndexDir, { recursive: true });
|
|
5670
|
+
return keys.sort().reverse();
|
|
5671
|
+
}
|
|
5672
|
+
async function readDateFileAsync(dir, dateKey) {
|
|
5673
|
+
const filePath = (0, import_node_path2.join)(dir, `${dateKey}.json`);
|
|
5674
|
+
if (!(0, import_node_fs3.existsSync)(filePath)) return [];
|
|
5675
|
+
try {
|
|
5676
|
+
const content = await (0, import_promises.readFile)(filePath, "utf-8");
|
|
5677
|
+
return JSON.parse(content);
|
|
5678
|
+
} catch {
|
|
5679
|
+
return [];
|
|
5607
5680
|
}
|
|
5608
|
-
|
|
5609
|
-
|
|
5610
|
-
|
|
5611
|
-
|
|
5612
|
-
|
|
5613
|
-
|
|
5614
|
-
|
|
5615
|
-
|
|
5616
|
-
|
|
5617
|
-
|
|
5618
|
-
|
|
5619
|
-
|
|
5620
|
-
|
|
5621
|
-
|
|
5622
|
-
|
|
5623
|
-
|
|
5624
|
-
|
|
5625
|
-
|
|
5626
|
-
|
|
5627
|
-
|
|
5628
|
-
result.dedupedByContent += 1;
|
|
5629
|
-
break;
|
|
5630
|
-
case "invalid":
|
|
5631
|
-
result.invalid += 1;
|
|
5632
|
-
break;
|
|
5633
|
-
}
|
|
5634
|
-
}
|
|
5635
|
-
this.prune();
|
|
5636
|
-
return result;
|
|
5681
|
+
}
|
|
5682
|
+
function progress(message) {
|
|
5683
|
+
process.stderr.write(message + "\n");
|
|
5684
|
+
}
|
|
5685
|
+
function output(data) {
|
|
5686
|
+
process.stdout.write(JSON.stringify(data, null, 2) + "\n");
|
|
5687
|
+
}
|
|
5688
|
+
function exitError(code, message) {
|
|
5689
|
+
output({ ok: false, error: { code, message } });
|
|
5690
|
+
process.exit(1);
|
|
5691
|
+
}
|
|
5692
|
+
function resolveRecordingsDir(ctx) {
|
|
5693
|
+
if (ctx.stateDir) {
|
|
5694
|
+
const dir = (0, import_node_path2.join)(
|
|
5695
|
+
ctx.stateDir,
|
|
5696
|
+
"plugins",
|
|
5697
|
+
"phone-notifications",
|
|
5698
|
+
"recordings"
|
|
5699
|
+
);
|
|
5700
|
+
if ((0, import_node_fs3.existsSync)(dir)) return dir;
|
|
5637
5701
|
}
|
|
5638
|
-
|
|
5639
|
-
const
|
|
5640
|
-
if (
|
|
5641
|
-
this.logger.warn(`\u5FFD\u7565\u975E\u6CD5 timestamp \u7684\u901A\u77E5: ${n.id}`);
|
|
5642
|
-
return { kind: "invalid" };
|
|
5643
|
-
}
|
|
5644
|
-
const dateKey = this.formatDate(ts);
|
|
5645
|
-
const filePath = (0, import_node_path2.join)(this.dir, `${dateKey}.json`);
|
|
5646
|
-
const normalizedId = typeof n.id === "string" ? n.id.trim() : "";
|
|
5647
|
-
const entry = this.buildStoredNotification(n);
|
|
5648
|
-
return this.withDateWriteLock(dateKey, async () => {
|
|
5649
|
-
if (normalizedId && this.hasNotificationId(dateKey, normalizedId)) {
|
|
5650
|
-
return { kind: "dedupedById" };
|
|
5651
|
-
}
|
|
5652
|
-
if (this.hasNotificationContentKey(dateKey, filePath, entry)) {
|
|
5653
|
-
return { kind: "dedupedByContent" };
|
|
5654
|
-
}
|
|
5655
|
-
const appDisplayName = this.resolveDisplayName ? await this.resolveDisplayName(entry.appName) : entry.appName;
|
|
5656
|
-
const storedEntry = {
|
|
5657
|
-
...entry,
|
|
5658
|
-
appDisplayName
|
|
5659
|
-
};
|
|
5660
|
-
const arr = this.readStoredNotifications(filePath);
|
|
5661
|
-
arr.push(storedEntry);
|
|
5662
|
-
(0, import_node_fs3.writeFileSync)(filePath, JSON.stringify(arr, null, 2), "utf-8");
|
|
5663
|
-
if (normalizedId) {
|
|
5664
|
-
this.recordNotificationId(dateKey, normalizedId);
|
|
5665
|
-
}
|
|
5666
|
-
this.recordNotificationContentKey(dateKey, filePath, storedEntry);
|
|
5667
|
-
return { kind: "ingested", entry: storedEntry };
|
|
5668
|
-
});
|
|
5702
|
+
if (ctx.workspaceDir) {
|
|
5703
|
+
const dir = (0, import_node_path2.join)(ctx.workspaceDir, "recordings");
|
|
5704
|
+
if ((0, import_node_fs3.existsSync)(dir)) return dir;
|
|
5669
5705
|
}
|
|
5670
|
-
|
|
5671
|
-
|
|
5672
|
-
|
|
5673
|
-
|
|
5674
|
-
|
|
5675
|
-
|
|
5676
|
-
|
|
5706
|
+
return null;
|
|
5707
|
+
}
|
|
5708
|
+
function resolveAsrConfigPath(ctx) {
|
|
5709
|
+
let base;
|
|
5710
|
+
if (ctx.stateDir) {
|
|
5711
|
+
base = (0, import_node_path2.join)(ctx.stateDir, "plugins", "phone-notifications", "recordings");
|
|
5712
|
+
} else if (ctx.workspaceDir) {
|
|
5713
|
+
base = (0, import_node_path2.join)(ctx.workspaceDir, "recordings");
|
|
5714
|
+
} else {
|
|
5715
|
+
exitError(
|
|
5716
|
+
"STORAGE_UNAVAILABLE",
|
|
5717
|
+
"\u65E0\u6CD5\u786E\u5B9A\u5F55\u97F3\u5B58\u50A8\u76EE\u5F55\uFF1AstateDir \u548C workspaceDir \u5747\u672A\u8BBE\u7F6E"
|
|
5718
|
+
);
|
|
5677
5719
|
}
|
|
5678
|
-
|
|
5679
|
-
|
|
5680
|
-
|
|
5681
|
-
|
|
5682
|
-
|
|
5683
|
-
|
|
5684
|
-
|
|
5685
|
-
|
|
5686
|
-
|
|
5687
|
-
|
|
5688
|
-
|
|
5689
|
-
}
|
|
5690
|
-
return fallback.join(" ; ") || "-";
|
|
5720
|
+
(0, import_node_fs3.mkdirSync)(base, { recursive: true });
|
|
5721
|
+
return (0, import_node_path2.join)(base, "asr-config.json");
|
|
5722
|
+
}
|
|
5723
|
+
function readRecordingIndex(dir) {
|
|
5724
|
+
const indexPath = (0, import_node_path2.join)(dir, "index.json");
|
|
5725
|
+
if (!(0, import_node_fs3.existsSync)(indexPath)) return [];
|
|
5726
|
+
try {
|
|
5727
|
+
const raw = JSON.parse((0, import_node_fs3.readFileSync)(indexPath, "utf-8"));
|
|
5728
|
+
return Array.isArray(raw?.recordings) ? raw.recordings : [];
|
|
5729
|
+
} catch {
|
|
5730
|
+
return [];
|
|
5691
5731
|
}
|
|
5692
|
-
|
|
5693
|
-
|
|
5694
|
-
|
|
5695
|
-
|
|
5696
|
-
|
|
5732
|
+
}
|
|
5733
|
+
|
|
5734
|
+
// src/light/validators.ts
|
|
5735
|
+
var VALID_MODES = [
|
|
5736
|
+
"wave",
|
|
5737
|
+
"breath",
|
|
5738
|
+
"strobe",
|
|
5739
|
+
"steady",
|
|
5740
|
+
"wave_rainbow",
|
|
5741
|
+
"pixel_frame"
|
|
5742
|
+
];
|
|
5743
|
+
var MAX_SEGMENTS = 12;
|
|
5744
|
+
function validateSegments(segments) {
|
|
5745
|
+
if (!Array.isArray(segments)) {
|
|
5746
|
+
return { valid: false, errors: [{ field: "segments", message: "\u5FC5\u987B\u662F\u6570\u7EC4" }] };
|
|
5697
5747
|
}
|
|
5698
|
-
|
|
5699
|
-
return
|
|
5748
|
+
if (segments.length === 0) {
|
|
5749
|
+
return { valid: false, errors: [{ field: "segments", message: "\u4E0D\u80FD\u4E3A\u7A7A" }] };
|
|
5700
5750
|
}
|
|
5701
|
-
|
|
5702
|
-
|
|
5703
|
-
|
|
5704
|
-
|
|
5705
|
-
}
|
|
5706
|
-
const idPath = this.getIdIndexPath(dateKey);
|
|
5707
|
-
const ids = /* @__PURE__ */ new Set();
|
|
5708
|
-
if ((0, import_node_fs3.existsSync)(idPath)) {
|
|
5709
|
-
const lines = (0, import_node_fs3.readFileSync)(idPath, "utf-8").split(/\r?\n/);
|
|
5710
|
-
for (const line of lines) {
|
|
5711
|
-
const id = line.trim();
|
|
5712
|
-
if (id) {
|
|
5713
|
-
ids.add(id);
|
|
5714
|
-
}
|
|
5715
|
-
}
|
|
5716
|
-
}
|
|
5717
|
-
this.idCache.set(dateKey, ids);
|
|
5718
|
-
return ids;
|
|
5751
|
+
if (segments.length > MAX_SEGMENTS) {
|
|
5752
|
+
return {
|
|
5753
|
+
valid: false,
|
|
5754
|
+
errors: [{ field: "segments", message: `\u6700\u591A ${MAX_SEGMENTS} \u6BB5` }]
|
|
5755
|
+
};
|
|
5719
5756
|
}
|
|
5720
|
-
|
|
5721
|
-
|
|
5757
|
+
const errors = [];
|
|
5758
|
+
for (let i = 0; i < segments.length; i++) {
|
|
5759
|
+
validateSegment(segments[i], `segments[${i}]`, errors);
|
|
5722
5760
|
}
|
|
5723
|
-
|
|
5724
|
-
|
|
5761
|
+
if (errors.length > 0) return { valid: false, errors };
|
|
5762
|
+
return { valid: true, segments };
|
|
5763
|
+
}
|
|
5764
|
+
function parseAndValidateSegments(json) {
|
|
5765
|
+
let parsed;
|
|
5766
|
+
try {
|
|
5767
|
+
parsed = JSON.parse(json);
|
|
5768
|
+
} catch {
|
|
5769
|
+
exitError("VALIDATION_FAILED", "segments \u5FC5\u987B\u662F\u5408\u6CD5\u7684 JSON");
|
|
5725
5770
|
}
|
|
5726
|
-
|
|
5727
|
-
|
|
5728
|
-
|
|
5729
|
-
|
|
5730
|
-
}
|
|
5731
|
-
const keyPath = this.getContentKeyIndexPath(dateKey);
|
|
5732
|
-
const keys = /* @__PURE__ */ new Set();
|
|
5733
|
-
if ((0, import_node_fs3.existsSync)(filePath)) {
|
|
5734
|
-
for (const item of this.readStoredNotifications(filePath)) {
|
|
5735
|
-
keys.add(this.buildNotificationContentKey(item));
|
|
5736
|
-
}
|
|
5737
|
-
}
|
|
5738
|
-
if (keys.size > 0) {
|
|
5739
|
-
(0, import_node_fs3.writeFileSync)(keyPath, `${Array.from(keys).join("\n")}
|
|
5740
|
-
`, "utf-8");
|
|
5741
|
-
} else if ((0, import_node_fs3.existsSync)(keyPath)) {
|
|
5742
|
-
(0, import_node_fs3.rmSync)(keyPath, { force: true });
|
|
5743
|
-
}
|
|
5744
|
-
this.contentKeyCache.set(dateKey, keys);
|
|
5745
|
-
return keys;
|
|
5771
|
+
const result = validateSegments(parsed);
|
|
5772
|
+
if (!result.valid) {
|
|
5773
|
+
output({ ok: false, error: { code: "VALIDATION_FAILED", details: result.errors } });
|
|
5774
|
+
process.exit(1);
|
|
5746
5775
|
}
|
|
5747
|
-
|
|
5748
|
-
|
|
5749
|
-
|
|
5750
|
-
|
|
5776
|
+
return result.segments;
|
|
5777
|
+
}
|
|
5778
|
+
function validateSegment(seg, prefix, errors) {
|
|
5779
|
+
if (!isRecord(seg)) {
|
|
5780
|
+
errors.push({ field: prefix, message: "\u5FC5\u987B\u662F\u5BF9\u8C61" });
|
|
5781
|
+
return;
|
|
5751
5782
|
}
|
|
5752
|
-
|
|
5753
|
-
|
|
5754
|
-
|
|
5755
|
-
|
|
5756
|
-
|
|
5757
|
-
|
|
5758
|
-
`, "utf-8");
|
|
5759
|
-
ids.add(id);
|
|
5783
|
+
const mode = seg.mode;
|
|
5784
|
+
if (!VALID_MODES.includes(mode)) {
|
|
5785
|
+
errors.push({
|
|
5786
|
+
field: `${prefix}.mode`,
|
|
5787
|
+
message: `\u4E0D\u652F\u6301\u7684\u6A21\u5F0F '${String(mode)}'\uFF0C\u53EF\u9009\uFF1A${VALID_MODES.join("/")}`
|
|
5788
|
+
});
|
|
5760
5789
|
}
|
|
5761
|
-
|
|
5762
|
-
|
|
5763
|
-
|
|
5764
|
-
|
|
5765
|
-
|
|
5766
|
-
|
|
5767
|
-
|
|
5768
|
-
`,
|
|
5769
|
-
|
|
5790
|
+
validateNonNegativeNumber(seg.duration_s, `${prefix}.duration_s`, errors, "\u5FC5\u987B\u662F \u22650 \u7684\u6570\u5B57\uFF080 \u8868\u793A\u65E0\u9650\u65F6\u957F\uFF09");
|
|
5791
|
+
switch (mode) {
|
|
5792
|
+
case "wave":
|
|
5793
|
+
validateForegroundSegment(seg, prefix, errors);
|
|
5794
|
+
validateOptionalNonNegativeNumber(seg.interval_ms, `${prefix}.interval_ms`, errors);
|
|
5795
|
+
validateOptionalDirection(seg.direction, `${prefix}.direction`, errors);
|
|
5796
|
+
validateOptionalWindow(seg.window, `${prefix}.window`, errors);
|
|
5797
|
+
validateOptionalBackground(seg.background, `${prefix}.background`, errors);
|
|
5798
|
+
break;
|
|
5799
|
+
case "wave_rainbow":
|
|
5800
|
+
validateForegroundSegment(seg, prefix, errors);
|
|
5801
|
+
validateOptionalNonNegativeNumber(seg.interval_ms, `${prefix}.interval_ms`, errors);
|
|
5802
|
+
validateOptionalDirection(seg.direction, `${prefix}.direction`, errors);
|
|
5803
|
+
validateOptionalWindow(seg.window, `${prefix}.window`, errors);
|
|
5804
|
+
validateOptionalBackground(seg.background, `${prefix}.background`, errors);
|
|
5805
|
+
if (!hasNonZeroRgb(seg.color) && !hasNonZeroRgb(seg.background)) {
|
|
5806
|
+
errors.push({
|
|
5807
|
+
field: prefix,
|
|
5808
|
+
message: "wave_rainbow \u81F3\u5C11\u9700\u8981\u4E00\u7EC4\u975E\u96F6\u989C\u8272\u951A\u70B9\uFF08color \u6216 background\uFF09"
|
|
5809
|
+
});
|
|
5810
|
+
}
|
|
5811
|
+
break;
|
|
5812
|
+
case "breath":
|
|
5813
|
+
validateForegroundSegment(seg, prefix, errors);
|
|
5814
|
+
validateOptionalBreathTiming(seg.breath_timing, `${prefix}.breath_timing`, errors);
|
|
5815
|
+
break;
|
|
5816
|
+
case "strobe":
|
|
5817
|
+
validateForegroundSegment(seg, prefix, errors);
|
|
5818
|
+
validateOptionalNonNegativeNumber(seg.interval_ms, `${prefix}.interval_ms`, errors);
|
|
5819
|
+
break;
|
|
5820
|
+
case "steady":
|
|
5821
|
+
validateForegroundSegment(seg, prefix, errors);
|
|
5822
|
+
break;
|
|
5823
|
+
case "pixel_frame":
|
|
5824
|
+
validatePixelFrame(seg.pixels, `${prefix}.pixels`, errors);
|
|
5825
|
+
break;
|
|
5826
|
+
default:
|
|
5827
|
+
validateOptionalNonNegativeNumber(seg.brightness, `${prefix}.brightness`, errors);
|
|
5828
|
+
validateOptionalColor(seg.color, `${prefix}.color`, errors);
|
|
5829
|
+
validateOptionalNonNegativeNumber(seg.interval_ms, `${prefix}.interval_ms`, errors);
|
|
5830
|
+
validateOptionalDirection(seg.direction, `${prefix}.direction`, errors);
|
|
5831
|
+
validateOptionalWindow(seg.window, `${prefix}.window`, errors);
|
|
5832
|
+
validateOptionalBreathTiming(seg.breath_timing, `${prefix}.breath_timing`, errors);
|
|
5833
|
+
validateOptionalBackground(seg.background, `${prefix}.background`, errors);
|
|
5770
5834
|
}
|
|
5771
|
-
|
|
5772
|
-
|
|
5835
|
+
}
|
|
5836
|
+
function validateForegroundSegment(seg, prefix, errors) {
|
|
5837
|
+
validateNumberInRange(
|
|
5838
|
+
seg.brightness,
|
|
5839
|
+
`${prefix}.brightness`,
|
|
5840
|
+
errors,
|
|
5841
|
+
0,
|
|
5842
|
+
255,
|
|
5843
|
+
"\u5FC5\u987B\u662F 0\u2013255 \u7684\u6570\u5B57"
|
|
5844
|
+
);
|
|
5845
|
+
validateColor(seg.color, `${prefix}.color`, errors);
|
|
5846
|
+
if (seg.mode !== "steady" && seg.brightness === 0) {
|
|
5847
|
+
errors.push({
|
|
5848
|
+
field: `${prefix}.brightness`,
|
|
5849
|
+
message: "brightness=0 \u4EC5 steady \u6A21\u5F0F\u5141\u8BB8\uFF1B\u5176\u5B83\u6A21\u5F0F\u4F1A\u5728\u56FA\u4EF6\u4FA7\u88AB\u8FC7\u6EE4"
|
|
5850
|
+
});
|
|
5773
5851
|
}
|
|
5774
|
-
|
|
5775
|
-
|
|
5776
|
-
|
|
5777
|
-
}
|
|
5778
|
-
|
|
5779
|
-
const parsed = JSON.parse((0, import_node_fs3.readFileSync)(filePath, "utf-8"));
|
|
5780
|
-
return Array.isArray(parsed) ? parsed : [];
|
|
5781
|
-
} catch {
|
|
5782
|
-
return [];
|
|
5783
|
-
}
|
|
5852
|
+
}
|
|
5853
|
+
function validatePixelFrame(value, field, errors) {
|
|
5854
|
+
if (!Array.isArray(value)) {
|
|
5855
|
+
errors.push({ field, message: "pixel_frame \u5FC5\u987B\u63D0\u4F9B pixels \u6570\u7EC4\uFF081\u20137 \u9879\uFF09" });
|
|
5856
|
+
return;
|
|
5784
5857
|
}
|
|
5785
|
-
|
|
5786
|
-
|
|
5787
|
-
let release;
|
|
5788
|
-
const current = new Promise((resolve) => {
|
|
5789
|
-
release = resolve;
|
|
5790
|
-
});
|
|
5791
|
-
const chain = previous.then(() => current);
|
|
5792
|
-
this.dateWriteChains.set(dateKey, chain);
|
|
5793
|
-
await previous;
|
|
5794
|
-
try {
|
|
5795
|
-
return await task();
|
|
5796
|
-
} finally {
|
|
5797
|
-
release();
|
|
5798
|
-
if (this.dateWriteChains.get(dateKey) === chain) {
|
|
5799
|
-
this.dateWriteChains.delete(dateKey);
|
|
5800
|
-
}
|
|
5801
|
-
}
|
|
5858
|
+
if (value.length < 1 || value.length > 7) {
|
|
5859
|
+
errors.push({ field, message: "pixels \u5FC5\u987B\u4E3A 1\u20137 \u9879" });
|
|
5802
5860
|
}
|
|
5803
|
-
|
|
5804
|
-
|
|
5805
|
-
|
|
5806
|
-
|
|
5861
|
+
const seen = /* @__PURE__ */ new Set();
|
|
5862
|
+
for (let i = 0; i < value.length; i++) {
|
|
5863
|
+
const pixel = value[i];
|
|
5864
|
+
const prefix = `${field}[${i}]`;
|
|
5865
|
+
if (!isRecord(pixel)) {
|
|
5866
|
+
errors.push({ field: prefix, message: "\u5FC5\u987B\u662F\u5BF9\u8C61" });
|
|
5867
|
+
continue;
|
|
5807
5868
|
}
|
|
5808
|
-
const
|
|
5809
|
-
|
|
5810
|
-
|
|
5811
|
-
|
|
5812
|
-
|
|
5813
|
-
|
|
5814
|
-
|
|
5815
|
-
pruneDataFiles(cutoffDate) {
|
|
5816
|
-
const dateFilePattern = /^(\d{4}-\d{2}-\d{2})\.(json|md)$/;
|
|
5817
|
-
const dateDirPattern = /^\d{4}-\d{2}-\d{2}$/;
|
|
5818
|
-
try {
|
|
5819
|
-
for (const entry of (0, import_node_fs3.readdirSync)(this.dir, { withFileTypes: true })) {
|
|
5820
|
-
if (entry.isFile()) {
|
|
5821
|
-
const match = dateFilePattern.exec(entry.name);
|
|
5822
|
-
if (match && match[1] < cutoffDate) {
|
|
5823
|
-
(0, import_node_fs3.rmSync)((0, import_node_path2.join)(this.dir, entry.name), { force: true });
|
|
5824
|
-
}
|
|
5825
|
-
} else if (entry.isDirectory() && dateDirPattern.test(entry.name) && entry.name < cutoffDate) {
|
|
5826
|
-
(0, import_node_fs3.rmSync)((0, import_node_path2.join)(this.dir, entry.name), { recursive: true, force: true });
|
|
5827
|
-
}
|
|
5828
|
-
}
|
|
5829
|
-
} catch {
|
|
5869
|
+
const idx = pixel.index;
|
|
5870
|
+
if (!Number.isInteger(idx) || idx < 0 || idx > 6) {
|
|
5871
|
+
errors.push({ field: `${prefix}.index`, message: "index \u5FC5\u987B\u662F 0\u20136 \u7684\u6574\u6570" });
|
|
5872
|
+
} else if (seen.has(idx)) {
|
|
5873
|
+
errors.push({ field: `${prefix}.index`, message: `index=${idx} \u91CD\u590D` });
|
|
5874
|
+
} else {
|
|
5875
|
+
seen.add(idx);
|
|
5830
5876
|
}
|
|
5831
|
-
|
|
5832
|
-
|
|
5833
|
-
|
|
5834
|
-
|
|
5835
|
-
|
|
5836
|
-
|
|
5837
|
-
|
|
5838
|
-
if (match && match[1] < cutoffDate) {
|
|
5839
|
-
(0, import_node_fs3.rmSync)((0, import_node_path2.join)(this.idIndexDir, entry.name), { force: true });
|
|
5840
|
-
this.idCache.delete(match[1]);
|
|
5841
|
-
}
|
|
5842
|
-
}
|
|
5843
|
-
} catch {
|
|
5844
|
-
}
|
|
5845
|
-
}
|
|
5846
|
-
pruneContentKeyIndex(cutoffDate) {
|
|
5847
|
-
try {
|
|
5848
|
-
for (const entry of (0, import_node_fs3.readdirSync)(this.contentKeyIndexDir, { withFileTypes: true })) {
|
|
5849
|
-
if (!entry.isFile()) continue;
|
|
5850
|
-
const match = /^(\d{4}-\d{2}-\d{2})\.keys$/.exec(entry.name);
|
|
5851
|
-
if (match && match[1] < cutoffDate) {
|
|
5852
|
-
(0, import_node_fs3.rmSync)((0, import_node_path2.join)(this.contentKeyIndexDir, entry.name), { force: true });
|
|
5853
|
-
this.contentKeyCache.delete(match[1]);
|
|
5854
|
-
}
|
|
5855
|
-
}
|
|
5856
|
-
} catch {
|
|
5857
|
-
}
|
|
5858
|
-
}
|
|
5859
|
-
async close() {
|
|
5860
|
-
this.idCache.clear();
|
|
5861
|
-
this.contentKeyCache.clear();
|
|
5862
|
-
this.dateWriteChains.clear();
|
|
5863
|
-
}
|
|
5864
|
-
};
|
|
5865
|
-
|
|
5866
|
-
// src/light/repeat.ts
|
|
5867
|
-
function normalizeRepeatTimes(input) {
|
|
5868
|
-
if (typeof input === "boolean") {
|
|
5869
|
-
return input ? 0 : 1;
|
|
5870
|
-
}
|
|
5871
|
-
if (typeof input === "number") {
|
|
5872
|
-
return validateRepeatTimes(input);
|
|
5873
|
-
}
|
|
5874
|
-
if (!input) {
|
|
5875
|
-
return 1;
|
|
5876
|
-
}
|
|
5877
|
-
if (input.repeat_times !== void 0) {
|
|
5878
|
-
return validateRepeatTimes(input.repeat_times);
|
|
5879
|
-
}
|
|
5880
|
-
if (input.repeat !== void 0) {
|
|
5881
|
-
return input.repeat ? 0 : 1;
|
|
5882
|
-
}
|
|
5883
|
-
return 1;
|
|
5884
|
-
}
|
|
5885
|
-
function assertAncsRepeatTimes(repeatTimes) {
|
|
5886
|
-
if (repeatTimes !== 0 && repeatTimes !== 1) {
|
|
5887
|
-
throw new Error(
|
|
5888
|
-
"\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"
|
|
5877
|
+
validateNumberInRange(
|
|
5878
|
+
pixel.brightness,
|
|
5879
|
+
`${prefix}.brightness`,
|
|
5880
|
+
errors,
|
|
5881
|
+
0,
|
|
5882
|
+
255,
|
|
5883
|
+
"\u5FC5\u987B\u662F 0\u2013255 \u7684\u6570\u5B57"
|
|
5889
5884
|
);
|
|
5885
|
+
validateColor(pixel.color, `${prefix}.color`, errors);
|
|
5890
5886
|
}
|
|
5891
5887
|
}
|
|
5892
|
-
function
|
|
5893
|
-
if (
|
|
5894
|
-
|
|
5888
|
+
function validateOptionalBreathTiming(value, field, errors) {
|
|
5889
|
+
if (value === void 0) return;
|
|
5890
|
+
if (!isRecord(value)) {
|
|
5891
|
+
errors.push({ field, message: "\u5FC5\u987B\u662F\u5BF9\u8C61" });
|
|
5892
|
+
return;
|
|
5895
5893
|
}
|
|
5896
|
-
|
|
5894
|
+
validatePositiveNumber(
|
|
5895
|
+
value.rise_ms,
|
|
5896
|
+
`${field}.rise_ms`,
|
|
5897
|
+
errors,
|
|
5898
|
+
"rise_ms \u5FC5\u987B\u662F >0 \u7684\u6570\u5B57\uFF08\u4E0D\u652F\u6301 0ms\uFF09"
|
|
5899
|
+
);
|
|
5900
|
+
validateNonNegativeNumber(
|
|
5901
|
+
value.hold_ms,
|
|
5902
|
+
`${field}.hold_ms`,
|
|
5903
|
+
errors,
|
|
5904
|
+
"hold_ms \u5FC5\u987B\u662F \u22650 \u7684\u6570\u5B57"
|
|
5905
|
+
);
|
|
5906
|
+
validatePositiveNumber(
|
|
5907
|
+
value.fall_ms,
|
|
5908
|
+
`${field}.fall_ms`,
|
|
5909
|
+
errors,
|
|
5910
|
+
"fall_ms \u5FC5\u987B\u662F >0 \u7684\u6570\u5B57\uFF08\u4E0D\u652F\u6301 0ms\uFF09"
|
|
5911
|
+
);
|
|
5912
|
+
validateNonNegativeNumber(
|
|
5913
|
+
value.off_ms,
|
|
5914
|
+
`${field}.off_ms`,
|
|
5915
|
+
errors,
|
|
5916
|
+
"off_ms \u5FC5\u987B\u662F \u22650 \u7684\u6570\u5B57"
|
|
5917
|
+
);
|
|
5897
5918
|
}
|
|
5898
|
-
|
|
5899
|
-
|
|
5900
|
-
|
|
5901
|
-
|
|
5902
|
-
|
|
5903
|
-
function resolveNotificationsDir(ctx) {
|
|
5904
|
-
if (ctx.stateDir) {
|
|
5905
|
-
const dir = (0, import_node_path3.join)(
|
|
5906
|
-
ctx.stateDir,
|
|
5907
|
-
"plugins",
|
|
5908
|
-
"phone-notifications",
|
|
5909
|
-
"notifications"
|
|
5910
|
-
);
|
|
5911
|
-
if ((0, import_node_fs4.existsSync)(dir)) return dir;
|
|
5912
|
-
}
|
|
5913
|
-
if (ctx.workspaceDir) {
|
|
5914
|
-
const dir = (0, import_node_path3.join)(ctx.workspaceDir, "notifications");
|
|
5915
|
-
if ((0, import_node_fs4.existsSync)(dir)) return dir;
|
|
5919
|
+
function validateOptionalBackground(value, field, errors) {
|
|
5920
|
+
if (value === void 0) return;
|
|
5921
|
+
if (!isRecord(value)) {
|
|
5922
|
+
errors.push({ field, message: "\u5FC5\u987B\u5305\u542B r/g/b/brightness \u6570\u503C" });
|
|
5923
|
+
return;
|
|
5916
5924
|
}
|
|
5917
|
-
|
|
5925
|
+
validateColor(value, field, errors);
|
|
5926
|
+
validateNumberInRange(
|
|
5927
|
+
value.brightness,
|
|
5928
|
+
`${field}.brightness`,
|
|
5929
|
+
errors,
|
|
5930
|
+
0,
|
|
5931
|
+
255,
|
|
5932
|
+
"\u5FC5\u987B\u662F 0\u2013255 \u7684\u6570\u5B57"
|
|
5933
|
+
);
|
|
5918
5934
|
}
|
|
5919
|
-
function
|
|
5920
|
-
|
|
5921
|
-
|
|
5922
|
-
for (const entry of (0, import_node_fs4.readdirSync)(dir, { withFileTypes: true })) {
|
|
5923
|
-
if (!entry.isFile()) continue;
|
|
5924
|
-
const m = pattern.exec(entry.name);
|
|
5925
|
-
if (m) keys.push(m[1]);
|
|
5926
|
-
}
|
|
5927
|
-
return keys.sort().reverse();
|
|
5935
|
+
function validateOptionalColor(value, field, errors) {
|
|
5936
|
+
if (value === void 0) return;
|
|
5937
|
+
validateColor(value, field, errors);
|
|
5928
5938
|
}
|
|
5929
|
-
function
|
|
5930
|
-
|
|
5931
|
-
|
|
5932
|
-
|
|
5933
|
-
return JSON.parse((0, import_node_fs4.readFileSync)(filePath, "utf-8"));
|
|
5934
|
-
} catch {
|
|
5935
|
-
return [];
|
|
5939
|
+
function validateColor(value, field, errors) {
|
|
5940
|
+
if (!isRecord(value)) {
|
|
5941
|
+
errors.push({ field, message: "\u5FC5\u987B\u5305\u542B r/g/b \u6570\u503C" });
|
|
5942
|
+
return;
|
|
5936
5943
|
}
|
|
5944
|
+
validateNumberInRange(value.r, `${field}.r`, errors, 0, 255, "\u5FC5\u987B\u662F 0\u2013255 \u7684\u6570\u5B57");
|
|
5945
|
+
validateNumberInRange(value.g, `${field}.g`, errors, 0, 255, "\u5FC5\u987B\u662F 0\u2013255 \u7684\u6570\u5B57");
|
|
5946
|
+
validateNumberInRange(value.b, `${field}.b`, errors, 0, 255, "\u5FC5\u987B\u662F 0\u2013255 \u7684\u6570\u5B57");
|
|
5937
5947
|
}
|
|
5938
|
-
function
|
|
5939
|
-
|
|
5940
|
-
|
|
5941
|
-
|
|
5942
|
-
const y = d.getFullYear();
|
|
5943
|
-
const m = String(d.getMonth() + 1).padStart(2, "0");
|
|
5944
|
-
const day = String(d.getDate()).padStart(2, "0");
|
|
5945
|
-
return `${y}-${m}-${day}`;
|
|
5946
|
-
}
|
|
5947
|
-
function daysAgo(n) {
|
|
5948
|
-
const d = /* @__PURE__ */ new Date();
|
|
5949
|
-
d.setDate(d.getDate() - n);
|
|
5950
|
-
return formatDate2(d);
|
|
5951
|
-
}
|
|
5952
|
-
function filterDateRange(keys, from, to) {
|
|
5953
|
-
return keys.filter((k) => k >= from && k <= to);
|
|
5954
|
-
}
|
|
5955
|
-
function parseIsoTime(value, optionName) {
|
|
5956
|
-
const isoPattern = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}(?::\d{2}(?:\.\d{1,3})?)?(Z|[+-]\d{2}:\d{2})$/;
|
|
5957
|
-
if (!isoPattern.test(value)) {
|
|
5958
|
-
exitError(
|
|
5959
|
-
"INVALID_TIME",
|
|
5960
|
-
`${optionName} \u5FC5\u987B\u662F ISO 8601 \u65F6\u95F4\uFF0C\u4F8B\u5982 2026-03-01T09:00:00+08:00`
|
|
5961
|
-
);
|
|
5948
|
+
function validateOptionalDirection(value, field, errors) {
|
|
5949
|
+
if (value === void 0) return;
|
|
5950
|
+
if (value !== "ltr" && value !== "rtl") {
|
|
5951
|
+
errors.push({ field, message: "direction \u5FC5\u987B\u662F ltr \u6216 rtl" });
|
|
5962
5952
|
}
|
|
5963
|
-
|
|
5964
|
-
|
|
5965
|
-
|
|
5966
|
-
|
|
5967
|
-
|
|
5968
|
-
);
|
|
5953
|
+
}
|
|
5954
|
+
function validateOptionalWindow(value, field, errors) {
|
|
5955
|
+
if (value === void 0) return;
|
|
5956
|
+
if (value !== 1 && value !== 2 && value !== 3) {
|
|
5957
|
+
errors.push({ field, message: "window \u4EC5\u652F\u6301 1/2/3" });
|
|
5969
5958
|
}
|
|
5970
|
-
return ts;
|
|
5971
5959
|
}
|
|
5972
|
-
function
|
|
5973
|
-
|
|
5960
|
+
function validateOptionalNonNegativeNumber(value, field, errors) {
|
|
5961
|
+
if (value === void 0) return;
|
|
5962
|
+
validateNonNegativeNumber(value, field, errors, "\u5FC5\u987B\u662F \u22650 \u7684\u6570\u5B57");
|
|
5974
5963
|
}
|
|
5975
|
-
|
|
5976
|
-
|
|
5977
|
-
|
|
5978
|
-
|
|
5979
|
-
for (const entry of entries) {
|
|
5980
|
-
if (!entry.isFile()) continue;
|
|
5981
|
-
const m = pattern.exec(entry.name);
|
|
5982
|
-
if (m) keys.push(m[1]);
|
|
5964
|
+
function validatePositiveNumber(value, field, errors, message) {
|
|
5965
|
+
if (value === void 0) return;
|
|
5966
|
+
if (!isFiniteNumber(value) || value <= 0) {
|
|
5967
|
+
errors.push({ field, message });
|
|
5983
5968
|
}
|
|
5984
|
-
return keys.sort().reverse();
|
|
5985
5969
|
}
|
|
5986
|
-
|
|
5987
|
-
|
|
5988
|
-
|
|
5989
|
-
try {
|
|
5990
|
-
const content = await (0, import_promises.readFile)(filePath, "utf-8");
|
|
5991
|
-
return JSON.parse(content);
|
|
5992
|
-
} catch {
|
|
5993
|
-
return [];
|
|
5970
|
+
function validateNonNegativeNumber(value, field, errors, message) {
|
|
5971
|
+
if (!isFiniteNumber(value) || value < 0) {
|
|
5972
|
+
errors.push({ field, message });
|
|
5994
5973
|
}
|
|
5995
5974
|
}
|
|
5996
|
-
function
|
|
5997
|
-
|
|
5998
|
-
}
|
|
5999
|
-
function output(data) {
|
|
6000
|
-
process.stdout.write(JSON.stringify(data, null, 2) + "\n");
|
|
6001
|
-
}
|
|
6002
|
-
function exitError(code, message) {
|
|
6003
|
-
output({ ok: false, error: { code, message } });
|
|
6004
|
-
process.exit(1);
|
|
6005
|
-
}
|
|
6006
|
-
function resolveRecordingsDir(ctx) {
|
|
6007
|
-
if (ctx.stateDir) {
|
|
6008
|
-
const dir = (0, import_node_path3.join)(
|
|
6009
|
-
ctx.stateDir,
|
|
6010
|
-
"plugins",
|
|
6011
|
-
"phone-notifications",
|
|
6012
|
-
"recordings"
|
|
6013
|
-
);
|
|
6014
|
-
if ((0, import_node_fs4.existsSync)(dir)) return dir;
|
|
6015
|
-
}
|
|
6016
|
-
if (ctx.workspaceDir) {
|
|
6017
|
-
const dir = (0, import_node_path3.join)(ctx.workspaceDir, "recordings");
|
|
6018
|
-
if ((0, import_node_fs4.existsSync)(dir)) return dir;
|
|
5975
|
+
function validateNumberInRange(value, field, errors, min, max, message) {
|
|
5976
|
+
if (!isFiniteNumber(value) || value < min || value > max) {
|
|
5977
|
+
errors.push({ field, message });
|
|
6019
5978
|
}
|
|
6020
|
-
return null;
|
|
6021
5979
|
}
|
|
6022
|
-
function
|
|
6023
|
-
|
|
6024
|
-
|
|
6025
|
-
base = (0, import_node_path3.join)(ctx.stateDir, "plugins", "phone-notifications", "recordings");
|
|
6026
|
-
} else if (ctx.workspaceDir) {
|
|
6027
|
-
base = (0, import_node_path3.join)(ctx.workspaceDir, "recordings");
|
|
6028
|
-
} else {
|
|
6029
|
-
exitError(
|
|
6030
|
-
"STORAGE_UNAVAILABLE",
|
|
6031
|
-
"\u65E0\u6CD5\u786E\u5B9A\u5F55\u97F3\u5B58\u50A8\u76EE\u5F55\uFF1AstateDir \u548C workspaceDir \u5747\u672A\u8BBE\u7F6E"
|
|
6032
|
-
);
|
|
6033
|
-
}
|
|
6034
|
-
(0, import_node_fs4.mkdirSync)(base, { recursive: true });
|
|
6035
|
-
return (0, import_node_path3.join)(base, "asr-config.json");
|
|
5980
|
+
function hasNonZeroRgb(value) {
|
|
5981
|
+
if (!value) return false;
|
|
5982
|
+
return [value.r, value.g, value.b].some((channel) => isFiniteNumber(channel) && channel > 0);
|
|
6036
5983
|
}
|
|
6037
|
-
function
|
|
6038
|
-
|
|
6039
|
-
if (!(0, import_node_fs4.existsSync)(indexPath)) return [];
|
|
6040
|
-
try {
|
|
6041
|
-
const raw = JSON.parse((0, import_node_fs4.readFileSync)(indexPath, "utf-8"));
|
|
6042
|
-
return Array.isArray(raw?.recordings) ? raw.recordings : [];
|
|
6043
|
-
} catch {
|
|
6044
|
-
return [];
|
|
6045
|
-
}
|
|
6046
|
-
}
|
|
6047
|
-
|
|
6048
|
-
// src/light/validators.ts
|
|
6049
|
-
var VALID_MODES = [
|
|
6050
|
-
"wave",
|
|
6051
|
-
"breath",
|
|
6052
|
-
"strobe",
|
|
6053
|
-
"steady",
|
|
6054
|
-
"wave_rainbow",
|
|
6055
|
-
"pixel_frame"
|
|
6056
|
-
];
|
|
6057
|
-
var MAX_SEGMENTS = 12;
|
|
6058
|
-
function validateSegments(segments) {
|
|
6059
|
-
if (!Array.isArray(segments)) {
|
|
6060
|
-
return { valid: false, errors: [{ field: "segments", message: "\u5FC5\u987B\u662F\u6570\u7EC4" }] };
|
|
6061
|
-
}
|
|
6062
|
-
if (segments.length === 0) {
|
|
6063
|
-
return { valid: false, errors: [{ field: "segments", message: "\u4E0D\u80FD\u4E3A\u7A7A" }] };
|
|
6064
|
-
}
|
|
6065
|
-
if (segments.length > MAX_SEGMENTS) {
|
|
6066
|
-
return {
|
|
6067
|
-
valid: false,
|
|
6068
|
-
errors: [{ field: "segments", message: `\u6700\u591A ${MAX_SEGMENTS} \u6BB5` }]
|
|
6069
|
-
};
|
|
6070
|
-
}
|
|
6071
|
-
const errors = [];
|
|
6072
|
-
for (let i = 0; i < segments.length; i++) {
|
|
6073
|
-
validateSegment(segments[i], `segments[${i}]`, errors);
|
|
6074
|
-
}
|
|
6075
|
-
if (errors.length > 0) return { valid: false, errors };
|
|
6076
|
-
return { valid: true, segments };
|
|
6077
|
-
}
|
|
6078
|
-
function parseAndValidateSegments(json) {
|
|
6079
|
-
let parsed;
|
|
6080
|
-
try {
|
|
6081
|
-
parsed = JSON.parse(json);
|
|
6082
|
-
} catch {
|
|
6083
|
-
exitError("VALIDATION_FAILED", "segments \u5FC5\u987B\u662F\u5408\u6CD5\u7684 JSON");
|
|
6084
|
-
}
|
|
6085
|
-
const result = validateSegments(parsed);
|
|
6086
|
-
if (!result.valid) {
|
|
6087
|
-
output({ ok: false, error: { code: "VALIDATION_FAILED", details: result.errors } });
|
|
6088
|
-
process.exit(1);
|
|
6089
|
-
}
|
|
6090
|
-
return result.segments;
|
|
6091
|
-
}
|
|
6092
|
-
function validateSegment(seg, prefix, errors) {
|
|
6093
|
-
if (!isRecord(seg)) {
|
|
6094
|
-
errors.push({ field: prefix, message: "\u5FC5\u987B\u662F\u5BF9\u8C61" });
|
|
6095
|
-
return;
|
|
6096
|
-
}
|
|
6097
|
-
const mode = seg.mode;
|
|
6098
|
-
if (!VALID_MODES.includes(mode)) {
|
|
6099
|
-
errors.push({
|
|
6100
|
-
field: `${prefix}.mode`,
|
|
6101
|
-
message: `\u4E0D\u652F\u6301\u7684\u6A21\u5F0F '${String(mode)}'\uFF0C\u53EF\u9009\uFF1A${VALID_MODES.join("/")}`
|
|
6102
|
-
});
|
|
6103
|
-
}
|
|
6104
|
-
validateNonNegativeNumber(seg.duration_s, `${prefix}.duration_s`, errors, "\u5FC5\u987B\u662F \u22650 \u7684\u6570\u5B57\uFF080 \u8868\u793A\u65E0\u9650\u65F6\u957F\uFF09");
|
|
6105
|
-
switch (mode) {
|
|
6106
|
-
case "wave":
|
|
6107
|
-
validateForegroundSegment(seg, prefix, errors);
|
|
6108
|
-
validateOptionalNonNegativeNumber(seg.interval_ms, `${prefix}.interval_ms`, errors);
|
|
6109
|
-
validateOptionalDirection(seg.direction, `${prefix}.direction`, errors);
|
|
6110
|
-
validateOptionalWindow(seg.window, `${prefix}.window`, errors);
|
|
6111
|
-
validateOptionalBackground(seg.background, `${prefix}.background`, errors);
|
|
6112
|
-
break;
|
|
6113
|
-
case "wave_rainbow":
|
|
6114
|
-
validateForegroundSegment(seg, prefix, errors);
|
|
6115
|
-
validateOptionalNonNegativeNumber(seg.interval_ms, `${prefix}.interval_ms`, errors);
|
|
6116
|
-
validateOptionalDirection(seg.direction, `${prefix}.direction`, errors);
|
|
6117
|
-
validateOptionalWindow(seg.window, `${prefix}.window`, errors);
|
|
6118
|
-
validateOptionalBackground(seg.background, `${prefix}.background`, errors);
|
|
6119
|
-
if (!hasNonZeroRgb(seg.color) && !hasNonZeroRgb(seg.background)) {
|
|
6120
|
-
errors.push({
|
|
6121
|
-
field: prefix,
|
|
6122
|
-
message: "wave_rainbow \u81F3\u5C11\u9700\u8981\u4E00\u7EC4\u975E\u96F6\u989C\u8272\u951A\u70B9\uFF08color \u6216 background\uFF09"
|
|
6123
|
-
});
|
|
6124
|
-
}
|
|
6125
|
-
break;
|
|
6126
|
-
case "breath":
|
|
6127
|
-
validateForegroundSegment(seg, prefix, errors);
|
|
6128
|
-
validateOptionalBreathTiming(seg.breath_timing, `${prefix}.breath_timing`, errors);
|
|
6129
|
-
break;
|
|
6130
|
-
case "strobe":
|
|
6131
|
-
validateForegroundSegment(seg, prefix, errors);
|
|
6132
|
-
validateOptionalNonNegativeNumber(seg.interval_ms, `${prefix}.interval_ms`, errors);
|
|
6133
|
-
break;
|
|
6134
|
-
case "steady":
|
|
6135
|
-
validateForegroundSegment(seg, prefix, errors);
|
|
6136
|
-
break;
|
|
6137
|
-
case "pixel_frame":
|
|
6138
|
-
validatePixelFrame(seg.pixels, `${prefix}.pixels`, errors);
|
|
6139
|
-
break;
|
|
6140
|
-
default:
|
|
6141
|
-
validateOptionalNonNegativeNumber(seg.brightness, `${prefix}.brightness`, errors);
|
|
6142
|
-
validateOptionalColor(seg.color, `${prefix}.color`, errors);
|
|
6143
|
-
validateOptionalNonNegativeNumber(seg.interval_ms, `${prefix}.interval_ms`, errors);
|
|
6144
|
-
validateOptionalDirection(seg.direction, `${prefix}.direction`, errors);
|
|
6145
|
-
validateOptionalWindow(seg.window, `${prefix}.window`, errors);
|
|
6146
|
-
validateOptionalBreathTiming(seg.breath_timing, `${prefix}.breath_timing`, errors);
|
|
6147
|
-
validateOptionalBackground(seg.background, `${prefix}.background`, errors);
|
|
6148
|
-
}
|
|
6149
|
-
}
|
|
6150
|
-
function validateForegroundSegment(seg, prefix, errors) {
|
|
6151
|
-
validateNumberInRange(
|
|
6152
|
-
seg.brightness,
|
|
6153
|
-
`${prefix}.brightness`,
|
|
6154
|
-
errors,
|
|
6155
|
-
0,
|
|
6156
|
-
255,
|
|
6157
|
-
"\u5FC5\u987B\u662F 0\u2013255 \u7684\u6570\u5B57"
|
|
6158
|
-
);
|
|
6159
|
-
validateColor(seg.color, `${prefix}.color`, errors);
|
|
6160
|
-
if (seg.mode !== "steady" && seg.brightness === 0) {
|
|
6161
|
-
errors.push({
|
|
6162
|
-
field: `${prefix}.brightness`,
|
|
6163
|
-
message: "brightness=0 \u4EC5 steady \u6A21\u5F0F\u5141\u8BB8\uFF1B\u5176\u5B83\u6A21\u5F0F\u4F1A\u5728\u56FA\u4EF6\u4FA7\u88AB\u8FC7\u6EE4"
|
|
6164
|
-
});
|
|
6165
|
-
}
|
|
6166
|
-
}
|
|
6167
|
-
function validatePixelFrame(value, field, errors) {
|
|
6168
|
-
if (!Array.isArray(value)) {
|
|
6169
|
-
errors.push({ field, message: "pixel_frame \u5FC5\u987B\u63D0\u4F9B pixels \u6570\u7EC4\uFF081\u20137 \u9879\uFF09" });
|
|
6170
|
-
return;
|
|
6171
|
-
}
|
|
6172
|
-
if (value.length < 1 || value.length > 7) {
|
|
6173
|
-
errors.push({ field, message: "pixels \u5FC5\u987B\u4E3A 1\u20137 \u9879" });
|
|
6174
|
-
}
|
|
6175
|
-
const seen = /* @__PURE__ */ new Set();
|
|
6176
|
-
for (let i = 0; i < value.length; i++) {
|
|
6177
|
-
const pixel = value[i];
|
|
6178
|
-
const prefix = `${field}[${i}]`;
|
|
6179
|
-
if (!isRecord(pixel)) {
|
|
6180
|
-
errors.push({ field: prefix, message: "\u5FC5\u987B\u662F\u5BF9\u8C61" });
|
|
6181
|
-
continue;
|
|
6182
|
-
}
|
|
6183
|
-
const idx = pixel.index;
|
|
6184
|
-
if (!Number.isInteger(idx) || idx < 0 || idx > 6) {
|
|
6185
|
-
errors.push({ field: `${prefix}.index`, message: "index \u5FC5\u987B\u662F 0\u20136 \u7684\u6574\u6570" });
|
|
6186
|
-
} else if (seen.has(idx)) {
|
|
6187
|
-
errors.push({ field: `${prefix}.index`, message: `index=${idx} \u91CD\u590D` });
|
|
6188
|
-
} else {
|
|
6189
|
-
seen.add(idx);
|
|
6190
|
-
}
|
|
6191
|
-
validateNumberInRange(
|
|
6192
|
-
pixel.brightness,
|
|
6193
|
-
`${prefix}.brightness`,
|
|
6194
|
-
errors,
|
|
6195
|
-
0,
|
|
6196
|
-
255,
|
|
6197
|
-
"\u5FC5\u987B\u662F 0\u2013255 \u7684\u6570\u5B57"
|
|
6198
|
-
);
|
|
6199
|
-
validateColor(pixel.color, `${prefix}.color`, errors);
|
|
6200
|
-
}
|
|
6201
|
-
}
|
|
6202
|
-
function validateOptionalBreathTiming(value, field, errors) {
|
|
6203
|
-
if (value === void 0) return;
|
|
6204
|
-
if (!isRecord(value)) {
|
|
6205
|
-
errors.push({ field, message: "\u5FC5\u987B\u662F\u5BF9\u8C61" });
|
|
6206
|
-
return;
|
|
6207
|
-
}
|
|
6208
|
-
validatePositiveNumber(
|
|
6209
|
-
value.rise_ms,
|
|
6210
|
-
`${field}.rise_ms`,
|
|
6211
|
-
errors,
|
|
6212
|
-
"rise_ms \u5FC5\u987B\u662F >0 \u7684\u6570\u5B57\uFF08\u4E0D\u652F\u6301 0ms\uFF09"
|
|
6213
|
-
);
|
|
6214
|
-
validateNonNegativeNumber(
|
|
6215
|
-
value.hold_ms,
|
|
6216
|
-
`${field}.hold_ms`,
|
|
6217
|
-
errors,
|
|
6218
|
-
"hold_ms \u5FC5\u987B\u662F \u22650 \u7684\u6570\u5B57"
|
|
6219
|
-
);
|
|
6220
|
-
validatePositiveNumber(
|
|
6221
|
-
value.fall_ms,
|
|
6222
|
-
`${field}.fall_ms`,
|
|
6223
|
-
errors,
|
|
6224
|
-
"fall_ms \u5FC5\u987B\u662F >0 \u7684\u6570\u5B57\uFF08\u4E0D\u652F\u6301 0ms\uFF09"
|
|
6225
|
-
);
|
|
6226
|
-
validateNonNegativeNumber(
|
|
6227
|
-
value.off_ms,
|
|
6228
|
-
`${field}.off_ms`,
|
|
6229
|
-
errors,
|
|
6230
|
-
"off_ms \u5FC5\u987B\u662F \u22650 \u7684\u6570\u5B57"
|
|
6231
|
-
);
|
|
6232
|
-
}
|
|
6233
|
-
function validateOptionalBackground(value, field, errors) {
|
|
6234
|
-
if (value === void 0) return;
|
|
6235
|
-
if (!isRecord(value)) {
|
|
6236
|
-
errors.push({ field, message: "\u5FC5\u987B\u5305\u542B r/g/b/brightness \u6570\u503C" });
|
|
6237
|
-
return;
|
|
6238
|
-
}
|
|
6239
|
-
validateColor(value, field, errors);
|
|
6240
|
-
validateNumberInRange(
|
|
6241
|
-
value.brightness,
|
|
6242
|
-
`${field}.brightness`,
|
|
6243
|
-
errors,
|
|
6244
|
-
0,
|
|
6245
|
-
255,
|
|
6246
|
-
"\u5FC5\u987B\u662F 0\u2013255 \u7684\u6570\u5B57"
|
|
6247
|
-
);
|
|
6248
|
-
}
|
|
6249
|
-
function validateOptionalColor(value, field, errors) {
|
|
6250
|
-
if (value === void 0) return;
|
|
6251
|
-
validateColor(value, field, errors);
|
|
6252
|
-
}
|
|
6253
|
-
function validateColor(value, field, errors) {
|
|
6254
|
-
if (!isRecord(value)) {
|
|
6255
|
-
errors.push({ field, message: "\u5FC5\u987B\u5305\u542B r/g/b \u6570\u503C" });
|
|
6256
|
-
return;
|
|
6257
|
-
}
|
|
6258
|
-
validateNumberInRange(value.r, `${field}.r`, errors, 0, 255, "\u5FC5\u987B\u662F 0\u2013255 \u7684\u6570\u5B57");
|
|
6259
|
-
validateNumberInRange(value.g, `${field}.g`, errors, 0, 255, "\u5FC5\u987B\u662F 0\u2013255 \u7684\u6570\u5B57");
|
|
6260
|
-
validateNumberInRange(value.b, `${field}.b`, errors, 0, 255, "\u5FC5\u987B\u662F 0\u2013255 \u7684\u6570\u5B57");
|
|
6261
|
-
}
|
|
6262
|
-
function validateOptionalDirection(value, field, errors) {
|
|
6263
|
-
if (value === void 0) return;
|
|
6264
|
-
if (value !== "ltr" && value !== "rtl") {
|
|
6265
|
-
errors.push({ field, message: "direction \u5FC5\u987B\u662F ltr \u6216 rtl" });
|
|
6266
|
-
}
|
|
6267
|
-
}
|
|
6268
|
-
function validateOptionalWindow(value, field, errors) {
|
|
6269
|
-
if (value === void 0) return;
|
|
6270
|
-
if (value !== 1 && value !== 2 && value !== 3) {
|
|
6271
|
-
errors.push({ field, message: "window \u4EC5\u652F\u6301 1/2/3" });
|
|
6272
|
-
}
|
|
6273
|
-
}
|
|
6274
|
-
function validateOptionalNonNegativeNumber(value, field, errors) {
|
|
6275
|
-
if (value === void 0) return;
|
|
6276
|
-
validateNonNegativeNumber(value, field, errors, "\u5FC5\u987B\u662F \u22650 \u7684\u6570\u5B57");
|
|
6277
|
-
}
|
|
6278
|
-
function validatePositiveNumber(value, field, errors, message) {
|
|
6279
|
-
if (value === void 0) return;
|
|
6280
|
-
if (!isFiniteNumber(value) || value <= 0) {
|
|
6281
|
-
errors.push({ field, message });
|
|
6282
|
-
}
|
|
6283
|
-
}
|
|
6284
|
-
function validateNonNegativeNumber(value, field, errors, message) {
|
|
6285
|
-
if (!isFiniteNumber(value) || value < 0) {
|
|
6286
|
-
errors.push({ field, message });
|
|
6287
|
-
}
|
|
6288
|
-
}
|
|
6289
|
-
function validateNumberInRange(value, field, errors, min, max, message) {
|
|
6290
|
-
if (!isFiniteNumber(value) || value < min || value > max) {
|
|
6291
|
-
errors.push({ field, message });
|
|
6292
|
-
}
|
|
6293
|
-
}
|
|
6294
|
-
function hasNonZeroRgb(value) {
|
|
6295
|
-
if (!value) return false;
|
|
6296
|
-
return [value.r, value.g, value.b].some((channel) => isFiniteNumber(channel) && channel > 0);
|
|
6297
|
-
}
|
|
6298
|
-
function isFiniteNumber(value) {
|
|
6299
|
-
return typeof value === "number" && Number.isFinite(value);
|
|
5984
|
+
function isFiniteNumber(value) {
|
|
5985
|
+
return typeof value === "number" && Number.isFinite(value);
|
|
6300
5986
|
}
|
|
6301
5987
|
function isRecord(value) {
|
|
6302
5988
|
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
6303
5989
|
}
|
|
6304
5990
|
|
|
6305
5991
|
// src/light-rules/storage.ts
|
|
6306
|
-
var
|
|
6307
|
-
var
|
|
5992
|
+
var import_node_fs4 = require("fs");
|
|
5993
|
+
var import_node_path3 = require("path");
|
|
6308
5994
|
|
|
6309
5995
|
// src/monitor/fetch-gen.ts
|
|
6310
5996
|
function generateFetchPy(name, matchRules) {
|
|
@@ -6414,14 +6100,14 @@ function legacyReadMetaCronSchedule(meta) {
|
|
|
6414
6100
|
function resolveBaseDir(ctx) {
|
|
6415
6101
|
if (ctx.workspaceDir) return ctx.workspaceDir;
|
|
6416
6102
|
if (ctx.stateDir) {
|
|
6417
|
-
const inferredWorkspaceDir = (0,
|
|
6418
|
-
if ((0,
|
|
6103
|
+
const inferredWorkspaceDir = (0, import_node_path3.join)(ctx.stateDir, "workspace");
|
|
6104
|
+
if ((0, import_node_fs4.existsSync)(inferredWorkspaceDir)) return inferredWorkspaceDir;
|
|
6419
6105
|
return ctx.stateDir;
|
|
6420
6106
|
}
|
|
6421
6107
|
throw new Error("workspaceDir and stateDir both unavailable");
|
|
6422
6108
|
}
|
|
6423
6109
|
function tasksDir(ctx) {
|
|
6424
|
-
return (0,
|
|
6110
|
+
return (0, import_node_path3.join)(resolveBaseDir(ctx), "tasks");
|
|
6425
6111
|
}
|
|
6426
6112
|
function normalizeLightRuleLookupName(name) {
|
|
6427
6113
|
return name.trim().replace(/\.json$/i, "");
|
|
@@ -6430,7 +6116,7 @@ function resolveLightRuleTask(ctx, name) {
|
|
|
6430
6116
|
const dir = tasksDir(ctx);
|
|
6431
6117
|
const normalizedName = normalizeLightRuleLookupName(name);
|
|
6432
6118
|
if (!normalizedName) return null;
|
|
6433
|
-
const directTaskDir = (0,
|
|
6119
|
+
const directTaskDir = (0, import_node_path3.join)(dir, normalizedName);
|
|
6434
6120
|
const directMeta = readMeta(directTaskDir);
|
|
6435
6121
|
if (directMeta) {
|
|
6436
6122
|
return {
|
|
@@ -6438,10 +6124,10 @@ function resolveLightRuleTask(ctx, name) {
|
|
|
6438
6124
|
meta: directMeta
|
|
6439
6125
|
};
|
|
6440
6126
|
}
|
|
6441
|
-
if (!(0,
|
|
6442
|
-
for (const entry of (0,
|
|
6127
|
+
if (!(0, import_node_fs4.existsSync)(dir)) return null;
|
|
6128
|
+
for (const entry of (0, import_node_fs4.readdirSync)(dir, { withFileTypes: true })) {
|
|
6443
6129
|
if (!entry.isDirectory()) continue;
|
|
6444
|
-
const taskDir = (0,
|
|
6130
|
+
const taskDir = (0, import_node_path3.join)(dir, entry.name);
|
|
6445
6131
|
const meta = readMeta(taskDir);
|
|
6446
6132
|
if (meta?.name === normalizedName) {
|
|
6447
6133
|
return {
|
|
@@ -6461,16 +6147,16 @@ function isLegacyLightRuleWithoutType(raw) {
|
|
|
6461
6147
|
return raw.type === void 0 && readOptionalString(raw.name) !== void 0 && readOptionalString(raw.description) !== void 0 && Array.isArray(raw.segments);
|
|
6462
6148
|
}
|
|
6463
6149
|
function readMeta(taskDir) {
|
|
6464
|
-
const metaPath = (0,
|
|
6465
|
-
if (!(0,
|
|
6150
|
+
const metaPath = (0, import_node_path3.join)(taskDir, "meta.json");
|
|
6151
|
+
if (!(0, import_node_fs4.existsSync)(metaPath)) return null;
|
|
6466
6152
|
try {
|
|
6467
|
-
const raw = JSON.parse((0,
|
|
6153
|
+
const raw = JSON.parse((0, import_node_fs4.readFileSync)(metaPath, "utf-8"));
|
|
6468
6154
|
if (!raw || typeof raw !== "object" || Array.isArray(raw)) return null;
|
|
6469
6155
|
if (raw.type !== "light-rule" && !isLegacyLightRuleWithoutType(raw)) return null;
|
|
6470
6156
|
if (!Array.isArray(raw.segments)) return null;
|
|
6471
|
-
const name = readOptionalString(raw.name) ?? (0,
|
|
6157
|
+
const name = readOptionalString(raw.name) ?? (0, import_node_path3.basename)(taskDir);
|
|
6472
6158
|
const description = readOptionalString(raw.description) ?? readOptionalString(raw.reason) ?? name;
|
|
6473
|
-
const createdAt = readOptionalString(raw.createdAt) ?? (0,
|
|
6159
|
+
const createdAt = readOptionalString(raw.createdAt) ?? (0, import_node_fs4.statSync)(metaPath).birthtime.toISOString();
|
|
6474
6160
|
const enabled = typeof raw.enabled === "boolean" ? raw.enabled : true;
|
|
6475
6161
|
const repeatTimes = normalizeRepeatTimes({
|
|
6476
6162
|
repeat: raw.repeat,
|
|
@@ -6492,7 +6178,7 @@ function readMeta(taskDir) {
|
|
|
6492
6178
|
}
|
|
6493
6179
|
}
|
|
6494
6180
|
function writeMeta(taskDir, meta) {
|
|
6495
|
-
(0,
|
|
6181
|
+
(0, import_node_fs4.writeFileSync)((0, import_node_path3.join)(taskDir, "meta.json"), JSON.stringify(meta, null, 2), "utf-8");
|
|
6496
6182
|
}
|
|
6497
6183
|
function generateLightRuleReadme(name, description, segments, repeatTimes) {
|
|
6498
6184
|
const segmentsJson = JSON.stringify(segments, null, 2);
|
|
@@ -6524,22 +6210,22 @@ reason: "${description}"
|
|
|
6524
6210
|
}
|
|
6525
6211
|
function listLightRules(ctx) {
|
|
6526
6212
|
const dir = tasksDir(ctx);
|
|
6527
|
-
if (!(0,
|
|
6213
|
+
if (!(0, import_node_fs4.existsSync)(dir)) return [];
|
|
6528
6214
|
const rules = [];
|
|
6529
|
-
for (const entry of (0,
|
|
6215
|
+
for (const entry of (0, import_node_fs4.readdirSync)(dir, { withFileTypes: true })) {
|
|
6530
6216
|
if (!entry.isDirectory()) continue;
|
|
6531
|
-
const meta = readMeta((0,
|
|
6217
|
+
const meta = readMeta((0, import_node_path3.join)(dir, entry.name));
|
|
6532
6218
|
if (meta) rules.push(meta);
|
|
6533
6219
|
}
|
|
6534
6220
|
return rules;
|
|
6535
6221
|
}
|
|
6536
6222
|
function createLightRule(ctx, params) {
|
|
6537
6223
|
const dir = tasksDir(ctx);
|
|
6538
|
-
const taskDir = (0,
|
|
6539
|
-
if ((0,
|
|
6224
|
+
const taskDir = (0, import_node_path3.join)(dir, params.name);
|
|
6225
|
+
if ((0, import_node_fs4.existsSync)(taskDir)) {
|
|
6540
6226
|
throw new LightRuleError("ALREADY_EXISTS", `\u706F\u6548\u89C4\u5219 '${params.name}' \u5DF2\u5B58\u5728`);
|
|
6541
6227
|
}
|
|
6542
|
-
(0,
|
|
6228
|
+
(0, import_node_fs4.mkdirSync)(taskDir, { recursive: true });
|
|
6543
6229
|
const repeatTimes = normalizeRepeatTimes({
|
|
6544
6230
|
repeat: params.repeat,
|
|
6545
6231
|
repeat_times: params.repeat_times
|
|
@@ -6559,13 +6245,13 @@ function createLightRule(ctx, params) {
|
|
|
6559
6245
|
legacyAssignMatchRules(meta, effectiveMatchRules);
|
|
6560
6246
|
legacyAssignCronSchedule(meta, effectiveCronSchedule);
|
|
6561
6247
|
writeMeta(taskDir, meta);
|
|
6562
|
-
(0,
|
|
6563
|
-
(0,
|
|
6248
|
+
(0, import_node_fs4.writeFileSync)(
|
|
6249
|
+
(0, import_node_path3.join)(taskDir, "fetch.py"),
|
|
6564
6250
|
generateFetchPy(params.name, effectiveMatchRules),
|
|
6565
6251
|
"utf-8"
|
|
6566
6252
|
);
|
|
6567
|
-
(0,
|
|
6568
|
-
(0,
|
|
6253
|
+
(0, import_node_fs4.writeFileSync)(
|
|
6254
|
+
(0, import_node_path3.join)(taskDir, "README.md"),
|
|
6569
6255
|
generateLightRuleReadme(params.name, params.description, params.segments, repeatTimes),
|
|
6570
6256
|
"utf-8"
|
|
6571
6257
|
);
|
|
@@ -6623,15 +6309,15 @@ function updateLightRule(ctx, params) {
|
|
|
6623
6309
|
meta.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
6624
6310
|
writeMeta(taskDir, meta);
|
|
6625
6311
|
if (regenerateFetch) {
|
|
6626
|
-
(0,
|
|
6627
|
-
(0,
|
|
6312
|
+
(0, import_node_fs4.writeFileSync)(
|
|
6313
|
+
(0, import_node_path3.join)(taskDir, "fetch.py"),
|
|
6628
6314
|
generateFetchPy(meta.name, legacyReadMetaMatchRules(meta)),
|
|
6629
6315
|
"utf-8"
|
|
6630
6316
|
);
|
|
6631
6317
|
}
|
|
6632
6318
|
if (regenerateReadme) {
|
|
6633
|
-
(0,
|
|
6634
|
-
(0,
|
|
6319
|
+
(0, import_node_fs4.writeFileSync)(
|
|
6320
|
+
(0, import_node_path3.join)(taskDir, "README.md"),
|
|
6635
6321
|
generateLightRuleReadme(meta.name, meta.description, meta.segments, meta.repeat_times),
|
|
6636
6322
|
"utf-8"
|
|
6637
6323
|
);
|
|
@@ -6652,7 +6338,7 @@ function deleteLightRule(ctx, name) {
|
|
|
6652
6338
|
if (!taskDir || !meta) {
|
|
6653
6339
|
throw new LightRuleError("NOT_FOUND", `\u706F\u6548\u89C4\u5219 '${name}' \u4E0D\u5B58\u5728`);
|
|
6654
6340
|
}
|
|
6655
|
-
(0,
|
|
6341
|
+
(0, import_node_fs4.rmSync)(taskDir, { recursive: true, force: true });
|
|
6656
6342
|
return {
|
|
6657
6343
|
name: meta.name,
|
|
6658
6344
|
cronHint: {
|
|
@@ -7409,168 +7095,141 @@ function registerLightRulesTools(api, registry, logger) {
|
|
|
7409
7095
|
}, ["lightrules_delete"]);
|
|
7410
7096
|
}
|
|
7411
7097
|
|
|
7412
|
-
// src/light-rules/evaluator
|
|
7413
|
-
|
|
7414
|
-
var EVALUATOR_SUBAGENT_SESSION_KEY = EVALUATOR_JOB_ID;
|
|
7415
|
-
var FALLBACK_CRON_EXPR = "0 0 1 1 *";
|
|
7416
|
-
function buildEvaluatorJobMessage(notificationsDir) {
|
|
7417
|
-
return `\u706F\u6548\u89C4\u5219\u8BC4\u4F30\u4EFB\u52A1\u3002
|
|
7098
|
+
// src/light-rules/inline-evaluator.ts
|
|
7099
|
+
init_credentials();
|
|
7418
7100
|
|
|
7419
|
-
|
|
7420
|
-
|
|
7421
|
-
|
|
7422
|
-
|
|
7423
|
-
|
|
7424
|
-
|
|
7425
|
-
|
|
7426
|
-
|
|
7101
|
+
// src/light/sender.ts
|
|
7102
|
+
var import_node_crypto = require("crypto");
|
|
7103
|
+
init_env();
|
|
7104
|
+
async function sendLightEffect(apiKey, segments, logger, repeatInput, reason) {
|
|
7105
|
+
const apiUrl = getEnvUrls().lightApiUrl;
|
|
7106
|
+
const appKey = "7Q617S1G5WD274JI";
|
|
7107
|
+
const templateId = "1990771146010017788";
|
|
7108
|
+
logger?.info(
|
|
7109
|
+
`Light sender: apiUrl=${apiUrl ?? "UNSET"}, appKey=${appKey ? appKey.substring(0, 8) + "\u2026" : "UNSET"}, templateId=${templateId ?? "UNSET"}, apiKey=${apiKey ? apiKey.substring(0, 20) + "\u2026" : "EMPTY"}, segments=${JSON.stringify(segments)}`
|
|
7110
|
+
);
|
|
7111
|
+
if (!apiUrl || !appKey || !templateId) {
|
|
7112
|
+
return {
|
|
7113
|
+
ok: false,
|
|
7114
|
+
error: "\u706F\u6548 API \u672A\u914D\u7F6E\uFF0C\u8BF7\u8BBE\u7F6E\u73AF\u5883\u53D8\u91CF LIGHT_API_URL / LIGHT_APP_KEY / LIGHT_TEMPLATE_ID"
|
|
7115
|
+
};
|
|
7116
|
+
}
|
|
7117
|
+
let bizContent;
|
|
7118
|
+
try {
|
|
7119
|
+
bizContent = buildLightEffectApnsBody(segments, repeatInput);
|
|
7120
|
+
} catch (error) {
|
|
7121
|
+
return { ok: false, error: error?.message ?? String(error) };
|
|
7122
|
+
}
|
|
7123
|
+
const bizUniqueId = (0, import_node_crypto.randomUUID)();
|
|
7124
|
+
const requestBody = {
|
|
7125
|
+
appKey,
|
|
7126
|
+
bizMap: { noticeType: "APP_NOTIFICATION_IMPORTANT", reason },
|
|
7127
|
+
bizUniqueId,
|
|
7128
|
+
paramsMap: { bizContent },
|
|
7129
|
+
pushType: "SPECIFY_PUSH",
|
|
7130
|
+
templateId
|
|
7131
|
+
};
|
|
7132
|
+
logger?.info(
|
|
7133
|
+
`Light sender: POST ${apiUrl}, bizUniqueId=${bizUniqueId}, body=${JSON.stringify(requestBody).substring(0, 500)}`
|
|
7134
|
+
);
|
|
7135
|
+
const res = await fetch(apiUrl, {
|
|
7136
|
+
method: "POST",
|
|
7137
|
+
headers: {
|
|
7138
|
+
"Content-Type": "application/json",
|
|
7139
|
+
"X-Api-Key-Id": apiKey.startsWith("Bearer ") ? apiKey.slice("Bearer ".length) : apiKey
|
|
7140
|
+
},
|
|
7141
|
+
body: JSON.stringify(requestBody)
|
|
7142
|
+
});
|
|
7143
|
+
const resBody = await res.text();
|
|
7144
|
+
if (!res.ok) {
|
|
7145
|
+
logger?.warn(
|
|
7146
|
+
`Light sender: FAILED ${res.status}, url=${apiUrl}, resBody=${resBody.substring(0, 500)}`
|
|
7147
|
+
);
|
|
7148
|
+
return { ok: false, status: res.status, error: resBody };
|
|
7149
|
+
}
|
|
7150
|
+
logger?.info(`Light sender: OK bizUniqueId=${bizUniqueId}, resBody=${resBody.substring(0, 200)}`);
|
|
7151
|
+
return { ok: true, bizUniqueId, response: JSON.parse(resBody) };
|
|
7427
7152
|
}
|
|
7428
|
-
|
|
7153
|
+
|
|
7154
|
+
// src/light-rules/inline-evaluator.ts
|
|
7155
|
+
var InlineLightRuleEvaluator = class {
|
|
7429
7156
|
logger;
|
|
7430
7157
|
registry;
|
|
7431
|
-
|
|
7432
|
-
getNotificationsDir;
|
|
7433
|
-
/**
|
|
7434
|
-
* 记录本进程生命周期内 job 是否已确认存在。
|
|
7435
|
-
* 仅在 `ensureJobExists` 成功后置 true,避免每次 push 都做检查。
|
|
7436
|
-
*/
|
|
7437
|
-
jobEnsured = false;
|
|
7438
|
-
/**
|
|
7439
|
-
* 首次创建 job 时的并发保护。
|
|
7440
|
-
* 避免冷启动瞬间多条通知并发到达时重复调用 `cron.add`。
|
|
7441
|
-
*/
|
|
7442
|
-
ensureJobPromise = null;
|
|
7443
|
-
/**
|
|
7444
|
-
* subagent fallback 路径的并发保护。
|
|
7445
|
-
* 若评估 session 已在运行中,跳过本次触发(checkpoint 保证下次补处理)。
|
|
7446
|
-
*/
|
|
7447
|
-
subagentInFlight = false;
|
|
7158
|
+
invoker;
|
|
7448
7159
|
constructor(deps) {
|
|
7449
7160
|
this.logger = deps.logger;
|
|
7450
7161
|
this.registry = deps.registry;
|
|
7451
|
-
this.
|
|
7452
|
-
this.getNotificationsDir = deps.getNotificationsDir ?? (() => void 0);
|
|
7162
|
+
this.invoker = deps.invoker;
|
|
7453
7163
|
}
|
|
7454
7164
|
/**
|
|
7455
|
-
*
|
|
7456
|
-
*
|
|
7457
|
-
* 两条路径:
|
|
7458
|
-
* - cron 不为 null:enqueueRun("force") 入队(gateway context 路径,正常路径)
|
|
7459
|
-
* - cron 为 null:通过 subagentRunner 直接运行(HTTP Relay 路径,fallback)
|
|
7165
|
+
* 评估一批新通知。
|
|
7460
7166
|
*
|
|
7461
|
-
* @
|
|
7462
|
-
* @param insertedCount 本次 ingest 新落盘的通知条数(StoredNotification 去重后)
|
|
7167
|
+
* @returns true 表示评估流程正常结束;false 表示 invoker 失败,调用方可选择回退。
|
|
7463
7168
|
*/
|
|
7464
|
-
async
|
|
7465
|
-
if (
|
|
7466
|
-
|
|
7467
|
-
if (
|
|
7468
|
-
|
|
7469
|
-
|
|
7169
|
+
async evaluate(notifications) {
|
|
7170
|
+
if (notifications.length === 0) return true;
|
|
7171
|
+
const rules = this.registry.getEnabled();
|
|
7172
|
+
if (rules.length === 0) return true;
|
|
7173
|
+
const matches = await this.invoker.matchNotifications(notifications, rules);
|
|
7174
|
+
if (matches === null) return false;
|
|
7175
|
+
if (matches.length === 0) {
|
|
7176
|
+
this.logger.info(
|
|
7177
|
+
`lightrules: 0 matches (notifications=${notifications.length}, rules=${rules.length})`
|
|
7178
|
+
);
|
|
7179
|
+
return true;
|
|
7180
|
+
}
|
|
7181
|
+
const firedRules = /* @__PURE__ */ new Set();
|
|
7182
|
+
for (const match of matches) {
|
|
7183
|
+
if (firedRules.has(match.ruleName)) continue;
|
|
7184
|
+
const rule = this.registry.get(match.ruleName);
|
|
7185
|
+
if (!rule) {
|
|
7186
|
+
this.logger.warn(
|
|
7187
|
+
`lightrules: matched rule '${match.ruleName}' not found in registry`
|
|
7188
|
+
);
|
|
7189
|
+
continue;
|
|
7190
|
+
}
|
|
7191
|
+
firedRules.add(match.ruleName);
|
|
7192
|
+
await this.triggerLight(rule);
|
|
7470
7193
|
}
|
|
7194
|
+
this.logger.info(
|
|
7195
|
+
`lightrules: fired ${firedRules.size} rule(s): ${[...firedRules].join(", ")} (from ${matches.length} match(es) across ${notifications.length} notification(s))`
|
|
7196
|
+
);
|
|
7197
|
+
return true;
|
|
7198
|
+
}
|
|
7199
|
+
async triggerLight(rule) {
|
|
7200
|
+
let apiKey;
|
|
7471
7201
|
try {
|
|
7472
|
-
|
|
7202
|
+
apiKey = requireApiKey();
|
|
7473
7203
|
} catch (err2) {
|
|
7474
|
-
this.logger.warn(
|
|
7204
|
+
this.logger.warn(
|
|
7205
|
+
`lightrules: trigger '${rule.name}' skipped - no API key: ${err2?.message ?? err2}`
|
|
7206
|
+
);
|
|
7475
7207
|
return;
|
|
7476
7208
|
}
|
|
7477
7209
|
try {
|
|
7478
|
-
const result = await
|
|
7210
|
+
const result = await sendLightEffect(
|
|
7211
|
+
apiKey,
|
|
7212
|
+
rule.segments,
|
|
7213
|
+
this.logger,
|
|
7214
|
+
{ repeat_times: rule.repeat_times },
|
|
7215
|
+
rule.description
|
|
7216
|
+
);
|
|
7479
7217
|
if (!result.ok) {
|
|
7480
|
-
this.logger.warn(
|
|
7481
|
-
|
|
7482
|
-
|
|
7483
|
-
if ("enqueued" in result && result.enqueued) {
|
|
7484
|
-
this.logger.info(`light-rules-evaluator: enqueued runId=${result.runId}`);
|
|
7485
|
-
} else if ("reason" in result) {
|
|
7486
|
-
this.logger.info(`light-rules-evaluator: enqueueRun skipped (${result.reason})`);
|
|
7218
|
+
this.logger.warn(
|
|
7219
|
+
`lightrules: trigger '${rule.name}' http-failed (${result.status ?? "?"}): ${result.error}`
|
|
7220
|
+
);
|
|
7487
7221
|
}
|
|
7488
7222
|
} catch (err2) {
|
|
7489
|
-
this.logger.warn(`light-rules-evaluator: enqueueRun failed: ${err2?.message ?? err2}`);
|
|
7490
|
-
}
|
|
7491
|
-
}
|
|
7492
|
-
/**
|
|
7493
|
-
* cron service 不可用时的 fallback:直接通过 subagentRunner 运行评估 session。
|
|
7494
|
-
*
|
|
7495
|
-
* 并发保护:若上一次 subagent 运行尚未完成,本次跳过。
|
|
7496
|
-
* checkpoint 保证即使本次跳过,下次触发时会处理所有积压通知。
|
|
7497
|
-
*/
|
|
7498
|
-
async triggerViaSubagent() {
|
|
7499
|
-
if (!this.subagentRunner) {
|
|
7500
7223
|
this.logger.warn(
|
|
7501
|
-
|
|
7224
|
+
`lightrules: trigger '${rule.name}' exception: ${err2?.message ?? err2}`
|
|
7502
7225
|
);
|
|
7503
|
-
return;
|
|
7504
|
-
}
|
|
7505
|
-
if (this.subagentInFlight) {
|
|
7506
|
-
this.logger.info("light-rules-evaluator: subagent run in-flight, skipping this trigger");
|
|
7507
|
-
return;
|
|
7508
|
-
}
|
|
7509
|
-
const notificationsDir = this.getNotificationsDir();
|
|
7510
|
-
if (!notificationsDir) {
|
|
7511
|
-
this.logger.warn("light-rules-evaluator: notifications dir not ready, skipping subagent trigger");
|
|
7512
|
-
return;
|
|
7513
|
-
}
|
|
7514
|
-
this.subagentInFlight = true;
|
|
7515
|
-
try {
|
|
7516
|
-
const result = await this.subagentRunner.run({
|
|
7517
|
-
sessionKey: EVALUATOR_SUBAGENT_SESSION_KEY,
|
|
7518
|
-
message: buildEvaluatorJobMessage(notificationsDir),
|
|
7519
|
-
deliver: false,
|
|
7520
|
-
idempotencyKey: `${EVALUATOR_SUBAGENT_SESSION_KEY}-${Date.now()}`
|
|
7521
|
-
});
|
|
7522
|
-
this.logger.info(`light-rules-evaluator: subagent triggered runId=${result.runId}`);
|
|
7523
|
-
} catch (err2) {
|
|
7524
|
-
this.logger.warn(`light-rules-evaluator: subagent trigger failed: ${err2?.message ?? err2}`);
|
|
7525
|
-
} finally {
|
|
7526
|
-
this.subagentInFlight = false;
|
|
7527
7226
|
}
|
|
7528
7227
|
}
|
|
7529
|
-
/**
|
|
7530
|
-
* 按需创建 `light-rules-evaluator` job。
|
|
7531
|
-
* 若 job 已存在(内存缓存或 cron store),直接返回;否则调用 `cron.add`。
|
|
7532
|
-
*/
|
|
7533
|
-
async ensureJobExists(cron) {
|
|
7534
|
-
if (this.jobEnsured) return;
|
|
7535
|
-
if (!this.ensureJobPromise) {
|
|
7536
|
-
this.ensureJobPromise = this.createJobIfNeeded(cron).finally(() => {
|
|
7537
|
-
this.ensureJobPromise = null;
|
|
7538
|
-
});
|
|
7539
|
-
}
|
|
7540
|
-
await this.ensureJobPromise;
|
|
7541
|
-
}
|
|
7542
|
-
async createJobIfNeeded(cron) {
|
|
7543
|
-
if (cron.getJob(EVALUATOR_JOB_ID)) {
|
|
7544
|
-
this.jobEnsured = true;
|
|
7545
|
-
return;
|
|
7546
|
-
}
|
|
7547
|
-
try {
|
|
7548
|
-
await cron.add({
|
|
7549
|
-
id: EVALUATOR_JOB_ID,
|
|
7550
|
-
name: "\u706F\u6548\u89C4\u5219\u8BC4\u4F30",
|
|
7551
|
-
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",
|
|
7552
|
-
enabled: true,
|
|
7553
|
-
schedule: { kind: "cron", expr: FALLBACK_CRON_EXPR },
|
|
7554
|
-
sessionTarget: "isolated",
|
|
7555
|
-
wakeMode: "now",
|
|
7556
|
-
payload: {
|
|
7557
|
-
kind: "agentTurn",
|
|
7558
|
-
message: buildEvaluatorJobMessage(this.getNotificationsDir() ?? "notifications")
|
|
7559
|
-
}
|
|
7560
|
-
});
|
|
7561
|
-
this.logger.info("light-rules-evaluator: job created");
|
|
7562
|
-
} catch (err2) {
|
|
7563
|
-
if (!cron.getJob(EVALUATOR_JOB_ID)) {
|
|
7564
|
-
throw err2;
|
|
7565
|
-
}
|
|
7566
|
-
}
|
|
7567
|
-
this.jobEnsured = true;
|
|
7568
|
-
}
|
|
7569
7228
|
};
|
|
7570
7229
|
|
|
7571
7230
|
// src/light-rules/migration.ts
|
|
7572
|
-
var
|
|
7573
|
-
var
|
|
7231
|
+
var import_node_fs8 = require("fs");
|
|
7232
|
+
var import_node_path7 = require("path");
|
|
7574
7233
|
var NO_MATCH_FETCH_PY = `#!/usr/bin/env python3
|
|
7575
7234
|
# \u6B64\u6587\u4EF6\u7531\u8FC1\u79FB\u5DE5\u5177\u751F\u6210\u3002
|
|
7576
7235
|
# \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
|
|
@@ -7580,32 +7239,32 @@ function normalizeScriptText(text) {
|
|
|
7580
7239
|
return text.replace(/\r\n/g, "\n").trim();
|
|
7581
7240
|
}
|
|
7582
7241
|
function resolveTasksDir(ctx) {
|
|
7583
|
-
if (ctx.workspaceDir) return (0,
|
|
7242
|
+
if (ctx.workspaceDir) return (0, import_node_path7.join)(ctx.workspaceDir, "tasks");
|
|
7584
7243
|
if (ctx.stateDir) {
|
|
7585
|
-
const inferredWorkspaceDir = (0,
|
|
7586
|
-
if ((0,
|
|
7587
|
-
return (0,
|
|
7244
|
+
const inferredWorkspaceDir = (0, import_node_path7.join)(ctx.stateDir, "workspace");
|
|
7245
|
+
if ((0, import_node_fs8.existsSync)(inferredWorkspaceDir)) return (0, import_node_path7.join)(inferredWorkspaceDir, "tasks");
|
|
7246
|
+
return (0, import_node_path7.join)(ctx.stateDir, "tasks");
|
|
7588
7247
|
}
|
|
7589
7248
|
return null;
|
|
7590
7249
|
}
|
|
7591
7250
|
function migrateLegacyLightRuleTasks(ctx, logger) {
|
|
7592
7251
|
const tasksDir3 = resolveTasksDir(ctx);
|
|
7593
|
-
if (!tasksDir3 || !(0,
|
|
7252
|
+
if (!tasksDir3 || !(0, import_node_fs8.existsSync)(tasksDir3)) return;
|
|
7594
7253
|
try {
|
|
7595
|
-
for (const entry of (0,
|
|
7254
|
+
for (const entry of (0, import_node_fs8.readdirSync)(tasksDir3, { withFileTypes: true })) {
|
|
7596
7255
|
if (!entry.isDirectory()) continue;
|
|
7597
|
-
migrateTaskDir((0,
|
|
7256
|
+
migrateTaskDir((0, import_node_path7.join)(tasksDir3, String(entry.name)), logger);
|
|
7598
7257
|
}
|
|
7599
7258
|
} catch (err2) {
|
|
7600
7259
|
logger.warn(`migration: failed to read tasks dir: ${err2?.message}`);
|
|
7601
7260
|
}
|
|
7602
7261
|
}
|
|
7603
7262
|
function migrateTaskDir(taskDir, logger) {
|
|
7604
|
-
const metaPath = (0,
|
|
7605
|
-
if (!(0,
|
|
7263
|
+
const metaPath = (0, import_node_path7.join)(taskDir, "meta.json");
|
|
7264
|
+
if (!(0, import_node_fs8.existsSync)(metaPath)) return;
|
|
7606
7265
|
let meta;
|
|
7607
7266
|
try {
|
|
7608
|
-
meta = JSON.parse((0,
|
|
7267
|
+
meta = JSON.parse((0, import_node_fs8.readFileSync)(metaPath, "utf-8"));
|
|
7609
7268
|
} catch {
|
|
7610
7269
|
return;
|
|
7611
7270
|
}
|
|
@@ -7614,7 +7273,7 @@ function migrateTaskDir(taskDir, logger) {
|
|
|
7614
7273
|
mergeMatchRulesIntoDescription(meta, name, metaPath, logger);
|
|
7615
7274
|
replaceFetchPy(taskDir, name, logger);
|
|
7616
7275
|
for (const filename of ["README.md", "checkpoint.json"]) {
|
|
7617
|
-
removeFile((0,
|
|
7276
|
+
removeFile((0, import_node_path7.join)(taskDir, filename), name, filename, logger);
|
|
7618
7277
|
}
|
|
7619
7278
|
}
|
|
7620
7279
|
function mergeMatchRulesIntoDescription(meta, name, metaPath, logger) {
|
|
@@ -7635,36 +7294,185 @@ function mergeMatchRulesIntoDescription(meta, name, metaPath, logger) {
|
|
|
7635
7294
|
}
|
|
7636
7295
|
delete meta.matchRules;
|
|
7637
7296
|
try {
|
|
7638
|
-
(0,
|
|
7297
|
+
(0, import_node_fs8.writeFileSync)(metaPath, JSON.stringify(meta, null, 2), "utf-8");
|
|
7639
7298
|
logger.info(`migration: merged matchRules into description for light rule: ${name}`);
|
|
7640
7299
|
} catch (err2) {
|
|
7641
7300
|
logger.warn(`migration: failed to update meta.json for ${name}: ${err2?.message}`);
|
|
7642
7301
|
}
|
|
7643
7302
|
}
|
|
7644
7303
|
function replaceFetchPy(taskDir, name, logger) {
|
|
7645
|
-
const fetchPyPath = (0,
|
|
7646
|
-
if (!(0,
|
|
7304
|
+
const fetchPyPath = (0, import_node_path7.join)(taskDir, "fetch.py");
|
|
7305
|
+
if (!(0, import_node_fs8.existsSync)(fetchPyPath)) return;
|
|
7647
7306
|
try {
|
|
7648
|
-
const existing = (0,
|
|
7307
|
+
const existing = (0, import_node_fs8.readFileSync)(fetchPyPath, "utf-8");
|
|
7649
7308
|
if (normalizeScriptText(existing) === normalizeScriptText(NO_MATCH_FETCH_PY)) {
|
|
7650
7309
|
return;
|
|
7651
7310
|
}
|
|
7652
|
-
(0,
|
|
7311
|
+
(0, import_node_fs8.writeFileSync)(fetchPyPath, NO_MATCH_FETCH_PY, "utf-8");
|
|
7653
7312
|
logger.info(`migration: replaced fetch.py with NO_MATCH placeholder for ${name}`);
|
|
7654
7313
|
} catch (err2) {
|
|
7655
7314
|
logger.warn(`migration: failed to replace fetch.py for ${name}: ${err2?.message}`);
|
|
7656
7315
|
}
|
|
7657
7316
|
}
|
|
7658
7317
|
function removeFile(filePath, ruleName, filename, logger) {
|
|
7659
|
-
if (!(0,
|
|
7318
|
+
if (!(0, import_node_fs8.existsSync)(filePath)) return;
|
|
7660
7319
|
try {
|
|
7661
|
-
(0,
|
|
7320
|
+
(0, import_node_fs8.rmSync)(filePath);
|
|
7662
7321
|
logger.info(`migration: removed ${filename} for light rule: ${ruleName}`);
|
|
7663
7322
|
} catch (err2) {
|
|
7664
7323
|
logger.warn(`migration: failed to remove ${filename} for ${ruleName}: ${err2?.message}`);
|
|
7665
7324
|
}
|
|
7666
7325
|
}
|
|
7667
7326
|
|
|
7327
|
+
// src/light-rules/pi-invoker.ts
|
|
7328
|
+
var import_agent_runtime = require("openclaw/plugin-sdk/agent-runtime");
|
|
7329
|
+
var DEFAULT_PROVIDER = "anthropic";
|
|
7330
|
+
var DEFAULT_MODEL_ID = "claude-haiku-4-5-20251001";
|
|
7331
|
+
var DEFAULT_TIMEOUT_MS = 1e4;
|
|
7332
|
+
var MAX_OUTPUT_TOKENS = 512;
|
|
7333
|
+
var PiAiInvoker = class {
|
|
7334
|
+
constructor(api, logger, options = {}) {
|
|
7335
|
+
this.api = api;
|
|
7336
|
+
this.logger = logger;
|
|
7337
|
+
this.options = options;
|
|
7338
|
+
}
|
|
7339
|
+
/**
|
|
7340
|
+
* 对一批通知和当前启用的规则做一次 LLM 匹配。
|
|
7341
|
+
*
|
|
7342
|
+
* 返回:
|
|
7343
|
+
* - `LlmMatchResult[]`:匹配成功(可能为空数组,即 0 命中)
|
|
7344
|
+
* - `null`:调用失败(模型准备失败 / 网络 / 解析)。调用方据此决定是否回退。
|
|
7345
|
+
*/
|
|
7346
|
+
async matchNotifications(notifications, rules) {
|
|
7347
|
+
if (notifications.length === 0 || rules.length === 0) return [];
|
|
7348
|
+
const provider = this.options.provider ?? DEFAULT_PROVIDER;
|
|
7349
|
+
const modelId = this.options.modelId ?? DEFAULT_MODEL_ID;
|
|
7350
|
+
const prepared = await (0, import_agent_runtime.prepareSimpleCompletionModel)({
|
|
7351
|
+
cfg: this.api.config,
|
|
7352
|
+
provider,
|
|
7353
|
+
modelId
|
|
7354
|
+
});
|
|
7355
|
+
if ("error" in prepared) {
|
|
7356
|
+
this.logger.warn(
|
|
7357
|
+
`PiAiInvoker: prepare ${provider}/${modelId} failed: ${prepared.error}`
|
|
7358
|
+
);
|
|
7359
|
+
return null;
|
|
7360
|
+
}
|
|
7361
|
+
const systemPrompt = buildSystemPrompt(rules);
|
|
7362
|
+
const userMessage = buildUserMessage(notifications);
|
|
7363
|
+
try {
|
|
7364
|
+
const resp = await (0, import_agent_runtime.completeWithPreparedSimpleCompletionModel)({
|
|
7365
|
+
model: prepared.model,
|
|
7366
|
+
auth: prepared.auth,
|
|
7367
|
+
context: {
|
|
7368
|
+
systemPrompt,
|
|
7369
|
+
messages: [
|
|
7370
|
+
{ role: "user", content: userMessage, timestamp: Date.now() }
|
|
7371
|
+
]
|
|
7372
|
+
},
|
|
7373
|
+
options: {
|
|
7374
|
+
temperature: 0,
|
|
7375
|
+
maxTokens: MAX_OUTPUT_TOKENS,
|
|
7376
|
+
signal: AbortSignal.timeout(this.options.timeoutMs ?? DEFAULT_TIMEOUT_MS),
|
|
7377
|
+
cacheRetention: "short"
|
|
7378
|
+
}
|
|
7379
|
+
});
|
|
7380
|
+
if (resp.stopReason === "error" || resp.stopReason === "aborted") {
|
|
7381
|
+
this.logger.warn(
|
|
7382
|
+
`PiAiInvoker: complete stopped with ${resp.stopReason}: ${resp.errorMessage ?? "n/a"}`
|
|
7383
|
+
);
|
|
7384
|
+
return null;
|
|
7385
|
+
}
|
|
7386
|
+
const text = extractText(resp.content);
|
|
7387
|
+
return parseMatchResult(text, notifications.length, rules);
|
|
7388
|
+
} catch (err2) {
|
|
7389
|
+
this.logger.warn(`PiAiInvoker: complete failed: ${err2?.message ?? err2}`);
|
|
7390
|
+
return null;
|
|
7391
|
+
}
|
|
7392
|
+
}
|
|
7393
|
+
};
|
|
7394
|
+
function extractText(content) {
|
|
7395
|
+
if (!Array.isArray(content)) return "";
|
|
7396
|
+
const parts = [];
|
|
7397
|
+
for (const c of content) {
|
|
7398
|
+
if (c && typeof c === "object" && c.type === "text") {
|
|
7399
|
+
const t = c.text;
|
|
7400
|
+
if (typeof t === "string") parts.push(t);
|
|
7401
|
+
}
|
|
7402
|
+
}
|
|
7403
|
+
return parts.join("");
|
|
7404
|
+
}
|
|
7405
|
+
function buildSystemPrompt(rules) {
|
|
7406
|
+
const lines = [
|
|
7407
|
+
'\u4F60\u662F"\u624B\u673A\u901A\u77E5 \u2192 \u706F\u6548\u89C4\u5219"\u547D\u4E2D\u5339\u914D\u52A9\u624B\u3002',
|
|
7408
|
+
"",
|
|
7409
|
+
"\u5F53\u524D\u542F\u7528\u7684\u706F\u6548\u89C4\u5219\uFF08\u6309\u521B\u5EFA\u65F6\u95F4\u5012\u5E8F\uFF09\uFF1A",
|
|
7410
|
+
""
|
|
7411
|
+
];
|
|
7412
|
+
const sorted = [...rules].sort(
|
|
7413
|
+
(a, b) => (b.createdAt ?? "").localeCompare(a.createdAt ?? "")
|
|
7414
|
+
);
|
|
7415
|
+
sorted.forEach((rule, idx) => {
|
|
7416
|
+
lines.push(`[${idx + 1}] name: ${rule.name}`);
|
|
7417
|
+
lines.push(` description: ${rule.description}`);
|
|
7418
|
+
lines.push("");
|
|
7419
|
+
});
|
|
7420
|
+
lines.push(
|
|
7421
|
+
"\u4EFB\u52A1\uFF1A\u5BF9\u7528\u6237\u63A5\u4E0B\u6765\u53D1\u6765\u7684\u6BCF\u6761\u901A\u77E5\uFF0C\u5224\u65AD\u5B83\u547D\u4E2D\u4E86\u54EA\u4E9B\u89C4\u5219\u3002",
|
|
7422
|
+
"",
|
|
7423
|
+
"\u8F93\u51FA\u5FC5\u987B\u662F\u7EAF JSON\uFF08\u4E0D\u8981 Markdown \u4EE3\u7801\u5757\u3001\u4E0D\u8981\u4EFB\u4F55\u89E3\u91CA\u6587\u5B57\uFF09\uFF1A",
|
|
7424
|
+
'{"matches":[{"i":<\u901A\u77E5\u4E0B\u6807>,"rule":"<\u89C4\u5219name>"}]}',
|
|
7425
|
+
"",
|
|
7426
|
+
'- \u547D\u4E2D 0 \u6761 \u2192 \u8F93\u51FA {"matches":[]}',
|
|
7427
|
+
"- \u4E00\u6761\u901A\u77E5\u53EF\u80FD\u547D\u4E2D\u591A\u6761\u89C4\u5219 \u2192 \u6BCF\u4E2A\u7EC4\u5408\u4E00\u4E2A\u6761\u76EE",
|
|
7428
|
+
'- \u4E25\u683C\u6309\u89C4\u5219 description \u5224\u65AD\uFF0C\u62FF\u4E0D\u51C6\u65F6\u503E\u5411\u4E8E"\u4E0D\u89E6\u53D1"',
|
|
7429
|
+
"- rule \u5FC5\u987B\u7CBE\u786E\u7B49\u4E8E\u4E0A\u9762\u5217\u51FA\u7684 name \u5B57\u7B26\u4E32"
|
|
7430
|
+
);
|
|
7431
|
+
return lines.join("\n");
|
|
7432
|
+
}
|
|
7433
|
+
function buildUserMessage(notifications) {
|
|
7434
|
+
const lines = [
|
|
7435
|
+
`\u4EE5\u4E0B ${notifications.length} \u6761\u65B0\u624B\u673A\u901A\u77E5\uFF0C\u6309\u987A\u5E8F\u7F16\u53F7\uFF1A`,
|
|
7436
|
+
""
|
|
7437
|
+
];
|
|
7438
|
+
notifications.forEach((n, i) => {
|
|
7439
|
+
const app = n.appDisplayName ?? n.appName;
|
|
7440
|
+
lines.push(
|
|
7441
|
+
`[${i}] app=${app} time=${n.timestamp}`,
|
|
7442
|
+
` title: ${n.title}`,
|
|
7443
|
+
` content: ${n.content}`,
|
|
7444
|
+
""
|
|
7445
|
+
);
|
|
7446
|
+
});
|
|
7447
|
+
lines.push("\u8BF7\u5224\u65AD\u6BCF\u6761\u901A\u77E5\u547D\u4E2D\u4E86\u54EA\u4E9B\u89C4\u5219\uFF0C\u6309\u7EA6\u5B9A\u683C\u5F0F\u8F93\u51FA JSON\u3002");
|
|
7448
|
+
return lines.join("\n");
|
|
7449
|
+
}
|
|
7450
|
+
function parseMatchResult(rawText, notificationCount, rules) {
|
|
7451
|
+
if (!rawText) return [];
|
|
7452
|
+
const text = rawText.trim().replace(/^```(?:json)?\s*/i, "").replace(/\s*```$/i, "").trim();
|
|
7453
|
+
let parsed;
|
|
7454
|
+
try {
|
|
7455
|
+
parsed = JSON.parse(text);
|
|
7456
|
+
} catch {
|
|
7457
|
+
return [];
|
|
7458
|
+
}
|
|
7459
|
+
if (!parsed || typeof parsed !== "object") return [];
|
|
7460
|
+
const raw = parsed.matches;
|
|
7461
|
+
if (!Array.isArray(raw)) return [];
|
|
7462
|
+
const validRuleNames = new Set(rules.map((r) => r.name));
|
|
7463
|
+
const results = [];
|
|
7464
|
+
for (const m of raw) {
|
|
7465
|
+
if (!m || typeof m !== "object") continue;
|
|
7466
|
+
const entry = m;
|
|
7467
|
+
const i = typeof entry.i === "number" ? entry.i : typeof entry.index === "number" ? entry.index : -1;
|
|
7468
|
+
const ruleName = typeof entry.rule === "string" ? entry.rule : null;
|
|
7469
|
+
if (!Number.isInteger(i) || i < 0 || i >= notificationCount) continue;
|
|
7470
|
+
if (!ruleName || !validRuleNames.has(ruleName)) continue;
|
|
7471
|
+
results.push({ notificationIndex: i, ruleName });
|
|
7472
|
+
}
|
|
7473
|
+
return results;
|
|
7474
|
+
}
|
|
7475
|
+
|
|
7668
7476
|
// src/plugin/auto-update.ts
|
|
7669
7477
|
init_env();
|
|
7670
7478
|
|
|
@@ -7680,7 +7488,7 @@ function resolveUpdateChannel(params) {
|
|
|
7680
7488
|
}
|
|
7681
7489
|
|
|
7682
7490
|
// src/update/index.ts
|
|
7683
|
-
var
|
|
7491
|
+
var import_node_path9 = require("path");
|
|
7684
7492
|
|
|
7685
7493
|
// src/update/checker.ts
|
|
7686
7494
|
function parseSemver(v) {
|
|
@@ -7782,8 +7590,8 @@ var UpdateChecker = class {
|
|
|
7782
7590
|
};
|
|
7783
7591
|
|
|
7784
7592
|
// src/update/executor.ts
|
|
7785
|
-
var
|
|
7786
|
-
var
|
|
7593
|
+
var import_node_fs9 = require("fs");
|
|
7594
|
+
var import_node_path8 = require("path");
|
|
7787
7595
|
var import_node_os = require("os");
|
|
7788
7596
|
var VERSION_PATTERN = /^\d+\.\d+\.\d+(-[\w.]+)?$/;
|
|
7789
7597
|
var BASE_URL = "https://artifact.yoooclaw.com/plugin";
|
|
@@ -7793,9 +7601,9 @@ async function executeUpdate(version, runCommand, logger, targetDir, updateConfi
|
|
|
7793
7601
|
}
|
|
7794
7602
|
const tgzUrl = `${BASE_URL}/v${version}/yoooclaw-phone-notifications-${version}.tgz`;
|
|
7795
7603
|
logger.info(`\u6267\u884C\u66F4\u65B0: ${tgzUrl} \u2192 ${targetDir}`);
|
|
7796
|
-
const workDir = (0,
|
|
7797
|
-
const tgzPath = (0,
|
|
7798
|
-
const stagingDir = (0,
|
|
7604
|
+
const workDir = (0, import_node_fs9.mkdtempSync)((0, import_node_path8.join)((0, import_node_os.tmpdir)(), ".openclaw-plugin-update-"));
|
|
7605
|
+
const tgzPath = (0, import_node_path8.join)(workDir, "plugin.tgz");
|
|
7606
|
+
const stagingDir = (0, import_node_path8.join)(workDir, "staged");
|
|
7799
7607
|
let backupDir = null;
|
|
7800
7608
|
try {
|
|
7801
7609
|
logger.info("\u4E0B\u8F7D\u63D2\u4EF6\u5305...");
|
|
@@ -7804,9 +7612,9 @@ async function executeUpdate(version, runCommand, logger, targetDir, updateConfi
|
|
|
7804
7612
|
return { success: false, message: `\u4E0B\u8F7D\u5931\u8D25 (HTTP ${response.status}): ${tgzUrl}` };
|
|
7805
7613
|
}
|
|
7806
7614
|
const buffer = Buffer.from(await response.arrayBuffer());
|
|
7807
|
-
(0,
|
|
7615
|
+
(0, import_node_fs9.writeFileSync)(tgzPath, buffer);
|
|
7808
7616
|
logger.info(`\u4E0B\u8F7D\u5B8C\u6210 (${buffer.length} bytes)`);
|
|
7809
|
-
(0,
|
|
7617
|
+
(0, import_node_fs9.mkdirSync)(stagingDir, { recursive: true });
|
|
7810
7618
|
const tarResult = await runCommand(
|
|
7811
7619
|
["tar", "-xzf", tgzPath, "-C", stagingDir, "--strip-components=1"],
|
|
7812
7620
|
{ timeoutMs: 3e4 }
|
|
@@ -7815,14 +7623,14 @@ async function executeUpdate(version, runCommand, logger, targetDir, updateConfi
|
|
|
7815
7623
|
const err2 = tarResult.stderr || tarResult.stdout || "unknown error";
|
|
7816
7624
|
return { success: false, message: `\u89E3\u538B\u5931\u8D25: ${err2}` };
|
|
7817
7625
|
}
|
|
7818
|
-
(0,
|
|
7626
|
+
(0, import_node_fs9.mkdirSync)((0, import_node_path8.dirname)(targetDir), { recursive: true });
|
|
7819
7627
|
try {
|
|
7820
7628
|
backupDir = `${targetDir}.bak.${Date.now()}`;
|
|
7821
|
-
(0,
|
|
7629
|
+
(0, import_node_fs9.renameSync)(targetDir, backupDir);
|
|
7822
7630
|
} catch {
|
|
7823
7631
|
backupDir = null;
|
|
7824
7632
|
}
|
|
7825
|
-
(0,
|
|
7633
|
+
(0, import_node_fs9.renameSync)(stagingDir, targetDir);
|
|
7826
7634
|
try {
|
|
7827
7635
|
await updateConfigRecord2(version, tgzUrl);
|
|
7828
7636
|
} catch (err2) {
|
|
@@ -7830,7 +7638,7 @@ async function executeUpdate(version, runCommand, logger, targetDir, updateConfi
|
|
|
7830
7638
|
}
|
|
7831
7639
|
if (backupDir) {
|
|
7832
7640
|
try {
|
|
7833
|
-
(0,
|
|
7641
|
+
(0, import_node_fs9.rmSync)(backupDir, { force: true, recursive: true });
|
|
7834
7642
|
} catch {
|
|
7835
7643
|
}
|
|
7836
7644
|
}
|
|
@@ -7840,8 +7648,8 @@ async function executeUpdate(version, runCommand, logger, targetDir, updateConfi
|
|
|
7840
7648
|
} catch (err2) {
|
|
7841
7649
|
if (backupDir) {
|
|
7842
7650
|
try {
|
|
7843
|
-
(0,
|
|
7844
|
-
(0,
|
|
7651
|
+
(0, import_node_fs9.rmSync)(targetDir, { force: true, recursive: true });
|
|
7652
|
+
(0, import_node_fs9.renameSync)(backupDir, targetDir);
|
|
7845
7653
|
logger.info("\u5DF2\u56DE\u6EDA\u5230\u4E4B\u524D\u7248\u672C");
|
|
7846
7654
|
} catch (rollbackErr) {
|
|
7847
7655
|
logger.error(`\u56DE\u6EDA\u5931\u8D25: ${String(rollbackErr)}`);
|
|
@@ -7852,7 +7660,7 @@ async function executeUpdate(version, runCommand, logger, targetDir, updateConfi
|
|
|
7852
7660
|
return { success: false, message: errMsg };
|
|
7853
7661
|
} finally {
|
|
7854
7662
|
try {
|
|
7855
|
-
(0,
|
|
7663
|
+
(0, import_node_fs9.rmSync)(workDir, { force: true, recursive: true });
|
|
7856
7664
|
} catch {
|
|
7857
7665
|
}
|
|
7858
7666
|
}
|
|
@@ -7867,7 +7675,7 @@ function resolveTargetDir(api) {
|
|
|
7867
7675
|
if (installPath) return installPath;
|
|
7868
7676
|
} catch {
|
|
7869
7677
|
}
|
|
7870
|
-
return (0,
|
|
7678
|
+
return (0, import_node_path9.join)(api.runtime.state.resolveStateDir(), "extensions", PLUGIN_ID);
|
|
7871
7679
|
}
|
|
7872
7680
|
async function updateConfigRecord(api, version, targetDir, tgzUrl) {
|
|
7873
7681
|
const configApi = api.runtime.config;
|
|
@@ -8055,10 +7863,10 @@ function registerAutoUpdateLifecycle(deps) {
|
|
|
8055
7863
|
}
|
|
8056
7864
|
|
|
8057
7865
|
// src/plugin/cli.ts
|
|
8058
|
-
var
|
|
7866
|
+
var import_node_path17 = require("path");
|
|
8059
7867
|
|
|
8060
7868
|
// src/cli/auth.ts
|
|
8061
|
-
var
|
|
7869
|
+
var import_node_fs10 = require("fs");
|
|
8062
7870
|
init_credentials();
|
|
8063
7871
|
function registerAuthCli(program) {
|
|
8064
7872
|
const auth = program.command("auth").description("\u7528\u6237\u8BA4\u8BC1\u7BA1\u7406");
|
|
@@ -8094,12 +7902,12 @@ function registerAuthCli(program) {
|
|
|
8094
7902
|
});
|
|
8095
7903
|
auth.command("clear").description("\u6E05\u9664\u5DF2\u4FDD\u5B58\u7684\u8BA4\u8BC1\u4FE1\u606F").action(() => {
|
|
8096
7904
|
const path2 = credentialsPath();
|
|
8097
|
-
if ((0,
|
|
7905
|
+
if ((0, import_node_fs10.existsSync)(path2)) {
|
|
8098
7906
|
const creds = readCredentials();
|
|
8099
7907
|
delete creds.apiKey;
|
|
8100
7908
|
delete creds.token;
|
|
8101
7909
|
if (Object.keys(creds).length === 0) {
|
|
8102
|
-
(0,
|
|
7910
|
+
(0, import_node_fs10.rmSync)(path2, { force: true });
|
|
8103
7911
|
} else {
|
|
8104
7912
|
writeCredentials(creds);
|
|
8105
7913
|
}
|
|
@@ -8231,23 +8039,23 @@ function registerNtfStats(ntf, ctx) {
|
|
|
8231
8039
|
}
|
|
8232
8040
|
|
|
8233
8041
|
// src/cli/ntf-sync.ts
|
|
8234
|
-
var
|
|
8235
|
-
var
|
|
8042
|
+
var import_node_fs11 = require("fs");
|
|
8043
|
+
var import_node_path10 = require("path");
|
|
8236
8044
|
var SYNC_FETCH_LIMIT = 300;
|
|
8237
8045
|
function checkpointPath(dir) {
|
|
8238
|
-
return (0,
|
|
8046
|
+
return (0, import_node_path10.join)(dir, ".checkpoint.json");
|
|
8239
8047
|
}
|
|
8240
8048
|
function readCheckpoint(dir) {
|
|
8241
8049
|
const p = checkpointPath(dir);
|
|
8242
|
-
if (!(0,
|
|
8050
|
+
if (!(0, import_node_fs11.existsSync)(p)) return {};
|
|
8243
8051
|
try {
|
|
8244
|
-
return JSON.parse((0,
|
|
8052
|
+
return JSON.parse((0, import_node_fs11.readFileSync)(p, "utf-8"));
|
|
8245
8053
|
} catch {
|
|
8246
8054
|
return {};
|
|
8247
8055
|
}
|
|
8248
8056
|
}
|
|
8249
8057
|
function writeCheckpoint(dir, data) {
|
|
8250
|
-
(0,
|
|
8058
|
+
(0, import_node_fs11.writeFileSync)(checkpointPath(dir), JSON.stringify(data, null, 2), "utf-8");
|
|
8251
8059
|
}
|
|
8252
8060
|
function registerNtfSync(ntf, ctx) {
|
|
8253
8061
|
const sync = ntf.command("sync").description("\u540C\u6B65\u901A\u77E5\u5230\u8BB0\u5FC6\u7CFB\u7EDF");
|
|
@@ -8340,24 +8148,24 @@ function registerNtfSync(ntf, ctx) {
|
|
|
8340
8148
|
}
|
|
8341
8149
|
|
|
8342
8150
|
// src/cli/ntf-monitor.ts
|
|
8343
|
-
var
|
|
8344
|
-
var
|
|
8151
|
+
var import_node_fs12 = require("fs");
|
|
8152
|
+
var import_node_path11 = require("path");
|
|
8345
8153
|
function tasksDir2(ctx) {
|
|
8346
8154
|
const base = ctx.workspaceDir || ctx.stateDir;
|
|
8347
8155
|
if (!base) throw new Error("workspaceDir and stateDir both unavailable");
|
|
8348
|
-
return (0,
|
|
8156
|
+
return (0, import_node_path11.join)(base, "tasks");
|
|
8349
8157
|
}
|
|
8350
8158
|
function readMeta2(taskDir) {
|
|
8351
|
-
const metaPath = (0,
|
|
8352
|
-
if (!(0,
|
|
8159
|
+
const metaPath = (0, import_node_path11.join)(taskDir, "meta.json");
|
|
8160
|
+
if (!(0, import_node_fs12.existsSync)(metaPath)) return null;
|
|
8353
8161
|
try {
|
|
8354
|
-
return JSON.parse((0,
|
|
8162
|
+
return JSON.parse((0, import_node_fs12.readFileSync)(metaPath, "utf-8"));
|
|
8355
8163
|
} catch {
|
|
8356
8164
|
return null;
|
|
8357
8165
|
}
|
|
8358
8166
|
}
|
|
8359
8167
|
function writeMeta2(taskDir, meta) {
|
|
8360
|
-
(0,
|
|
8168
|
+
(0, import_node_fs12.writeFileSync)((0, import_node_path11.join)(taskDir, "meta.json"), JSON.stringify(meta, null, 2), "utf-8");
|
|
8361
8169
|
}
|
|
8362
8170
|
function generateReadme(name, description) {
|
|
8363
8171
|
return `# Monitor Task: ${name}
|
|
@@ -8376,27 +8184,27 @@ function registerNtfMonitor(ntf, ctx) {
|
|
|
8376
8184
|
const monitor = ntf.command("monitor").description("\u901A\u77E5\u76D1\u63A7\u4EFB\u52A1\u7BA1\u7406");
|
|
8377
8185
|
monitor.command("list").description("\u5217\u51FA\u6240\u6709\u76D1\u63A7\u4EFB\u52A1").action(() => {
|
|
8378
8186
|
const dir = tasksDir2(ctx);
|
|
8379
|
-
if (!(0,
|
|
8187
|
+
if (!(0, import_node_fs12.existsSync)(dir)) {
|
|
8380
8188
|
output({ ok: true, tasks: [] });
|
|
8381
8189
|
return;
|
|
8382
8190
|
}
|
|
8383
8191
|
const tasks = [];
|
|
8384
|
-
for (const entry of (0,
|
|
8192
|
+
for (const entry of (0, import_node_fs12.readdirSync)(dir, { withFileTypes: true })) {
|
|
8385
8193
|
if (!entry.isDirectory()) continue;
|
|
8386
|
-
const meta = readMeta2((0,
|
|
8194
|
+
const meta = readMeta2((0, import_node_path11.join)(dir, entry.name));
|
|
8387
8195
|
if (meta) tasks.push(meta);
|
|
8388
8196
|
}
|
|
8389
8197
|
output({ ok: true, tasks });
|
|
8390
8198
|
});
|
|
8391
8199
|
monitor.command("show <name>").description("\u67E5\u770B\u76D1\u63A7\u4EFB\u52A1\u8BE6\u60C5").action((name) => {
|
|
8392
|
-
const taskDir = (0,
|
|
8200
|
+
const taskDir = (0, import_node_path11.join)(tasksDir2(ctx), name);
|
|
8393
8201
|
const meta = readMeta2(taskDir);
|
|
8394
8202
|
if (!meta) exitError("NOT_FOUND", `\u76D1\u63A7\u4EFB\u52A1 '${name}' \u4E0D\u5B58\u5728`);
|
|
8395
|
-
const checkpointPath2 = (0,
|
|
8203
|
+
const checkpointPath2 = (0, import_node_path11.join)(taskDir, "checkpoint.json");
|
|
8396
8204
|
let checkpoint = {};
|
|
8397
|
-
if ((0,
|
|
8205
|
+
if ((0, import_node_fs12.existsSync)(checkpointPath2)) {
|
|
8398
8206
|
try {
|
|
8399
|
-
checkpoint = JSON.parse((0,
|
|
8207
|
+
checkpoint = JSON.parse((0, import_node_fs12.readFileSync)(checkpointPath2, "utf-8"));
|
|
8400
8208
|
} catch {
|
|
8401
8209
|
}
|
|
8402
8210
|
}
|
|
@@ -8411,8 +8219,8 @@ function registerNtfMonitor(ntf, ctx) {
|
|
|
8411
8219
|
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(
|
|
8412
8220
|
(name, opts) => {
|
|
8413
8221
|
const dir = tasksDir2(ctx);
|
|
8414
|
-
const taskDir = (0,
|
|
8415
|
-
if ((0,
|
|
8222
|
+
const taskDir = (0, import_node_path11.join)(dir, name);
|
|
8223
|
+
if ((0, import_node_fs12.existsSync)(taskDir)) {
|
|
8416
8224
|
exitError("ALREADY_EXISTS", `\u76D1\u63A7\u4EFB\u52A1 '${name}' \u5DF2\u5B58\u5728`);
|
|
8417
8225
|
}
|
|
8418
8226
|
let matchRules;
|
|
@@ -8424,7 +8232,7 @@ function registerNtfMonitor(ntf, ctx) {
|
|
|
8424
8232
|
"match-rules \u5FC5\u987B\u662F\u5408\u6CD5\u7684 JSON"
|
|
8425
8233
|
);
|
|
8426
8234
|
}
|
|
8427
|
-
(0,
|
|
8235
|
+
(0, import_node_fs12.mkdirSync)(taskDir, { recursive: true });
|
|
8428
8236
|
const meta = {
|
|
8429
8237
|
name,
|
|
8430
8238
|
description: opts.description,
|
|
@@ -8434,13 +8242,13 @@ function registerNtfMonitor(ntf, ctx) {
|
|
|
8434
8242
|
createdAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
8435
8243
|
};
|
|
8436
8244
|
writeMeta2(taskDir, meta);
|
|
8437
|
-
(0,
|
|
8438
|
-
(0,
|
|
8245
|
+
(0, import_node_fs12.writeFileSync)(
|
|
8246
|
+
(0, import_node_path11.join)(taskDir, "fetch.py"),
|
|
8439
8247
|
generateFetchPy(name, matchRules),
|
|
8440
8248
|
"utf-8"
|
|
8441
8249
|
);
|
|
8442
|
-
(0,
|
|
8443
|
-
(0,
|
|
8250
|
+
(0, import_node_fs12.writeFileSync)(
|
|
8251
|
+
(0, import_node_path11.join)(taskDir, "README.md"),
|
|
8444
8252
|
generateReadme(name, opts.description),
|
|
8445
8253
|
"utf-8"
|
|
8446
8254
|
);
|
|
@@ -8468,8 +8276,8 @@ function registerNtfMonitor(ntf, ctx) {
|
|
|
8468
8276
|
}
|
|
8469
8277
|
);
|
|
8470
8278
|
monitor.command("delete <name>").description("\u5220\u9664\u76D1\u63A7\u4EFB\u52A1").option("--yes", "\u8DF3\u8FC7\u786E\u8BA4").action((name, opts) => {
|
|
8471
|
-
const taskDir = (0,
|
|
8472
|
-
if (!(0,
|
|
8279
|
+
const taskDir = (0, import_node_path11.join)(tasksDir2(ctx), name);
|
|
8280
|
+
if (!(0, import_node_fs12.existsSync)(taskDir)) {
|
|
8473
8281
|
exitError("NOT_FOUND", `\u76D1\u63A7\u4EFB\u52A1 '${name}' \u4E0D\u5B58\u5728`);
|
|
8474
8282
|
}
|
|
8475
8283
|
if (!opts.yes) {
|
|
@@ -8482,7 +8290,7 @@ function registerNtfMonitor(ntf, ctx) {
|
|
|
8482
8290
|
});
|
|
8483
8291
|
process.exit(1);
|
|
8484
8292
|
}
|
|
8485
|
-
(0,
|
|
8293
|
+
(0, import_node_fs12.rmSync)(taskDir, { recursive: true, force: true });
|
|
8486
8294
|
output({
|
|
8487
8295
|
ok: true,
|
|
8488
8296
|
name,
|
|
@@ -8494,7 +8302,7 @@ function registerNtfMonitor(ntf, ctx) {
|
|
|
8494
8302
|
});
|
|
8495
8303
|
});
|
|
8496
8304
|
monitor.command("enable <name>").description("\u542F\u7528\u76D1\u63A7\u4EFB\u52A1").action((name) => {
|
|
8497
|
-
const taskDir = (0,
|
|
8305
|
+
const taskDir = (0, import_node_path11.join)(tasksDir2(ctx), name);
|
|
8498
8306
|
const meta = readMeta2(taskDir);
|
|
8499
8307
|
if (!meta) exitError("NOT_FOUND", `\u76D1\u63A7\u4EFB\u52A1 '${name}' \u4E0D\u5B58\u5728`);
|
|
8500
8308
|
meta.enabled = true;
|
|
@@ -8502,7 +8310,7 @@ function registerNtfMonitor(ntf, ctx) {
|
|
|
8502
8310
|
output({ ok: true, name, enabled: true });
|
|
8503
8311
|
});
|
|
8504
8312
|
monitor.command("disable <name>").description("\u6682\u505C\u76D1\u63A7\u4EFB\u52A1").action((name) => {
|
|
8505
|
-
const taskDir = (0,
|
|
8313
|
+
const taskDir = (0, import_node_path11.join)(tasksDir2(ctx), name);
|
|
8506
8314
|
const meta = readMeta2(taskDir);
|
|
8507
8315
|
if (!meta) exitError("NOT_FOUND", `\u76D1\u63A7\u4EFB\u52A1 '${name}' \u4E0D\u5B58\u5728`);
|
|
8508
8316
|
meta.enabled = false;
|
|
@@ -8513,61 +8321,6 @@ function registerNtfMonitor(ntf, ctx) {
|
|
|
8513
8321
|
|
|
8514
8322
|
// src/cli/light-send.ts
|
|
8515
8323
|
init_credentials();
|
|
8516
|
-
|
|
8517
|
-
// src/light/sender.ts
|
|
8518
|
-
var import_node_crypto2 = require("crypto");
|
|
8519
|
-
init_env();
|
|
8520
|
-
async function sendLightEffect(apiKey, segments, logger, repeatInput, reason) {
|
|
8521
|
-
const apiUrl = getEnvUrls().lightApiUrl;
|
|
8522
|
-
const appKey = "7Q617S1G5WD274JI";
|
|
8523
|
-
const templateId = "1990771146010017788";
|
|
8524
|
-
logger?.info(
|
|
8525
|
-
`Light sender: apiUrl=${apiUrl ?? "UNSET"}, appKey=${appKey ? appKey.substring(0, 8) + "\u2026" : "UNSET"}, templateId=${templateId ?? "UNSET"}, apiKey=${apiKey ? apiKey.substring(0, 20) + "\u2026" : "EMPTY"}, segments=${JSON.stringify(segments)}`
|
|
8526
|
-
);
|
|
8527
|
-
if (!apiUrl || !appKey || !templateId) {
|
|
8528
|
-
return {
|
|
8529
|
-
ok: false,
|
|
8530
|
-
error: "\u706F\u6548 API \u672A\u914D\u7F6E\uFF0C\u8BF7\u8BBE\u7F6E\u73AF\u5883\u53D8\u91CF LIGHT_API_URL / LIGHT_APP_KEY / LIGHT_TEMPLATE_ID"
|
|
8531
|
-
};
|
|
8532
|
-
}
|
|
8533
|
-
let bizContent;
|
|
8534
|
-
try {
|
|
8535
|
-
bizContent = buildLightEffectApnsBody(segments, repeatInput);
|
|
8536
|
-
} catch (error) {
|
|
8537
|
-
return { ok: false, error: error?.message ?? String(error) };
|
|
8538
|
-
}
|
|
8539
|
-
const bizUniqueId = (0, import_node_crypto2.randomUUID)();
|
|
8540
|
-
const requestBody = {
|
|
8541
|
-
appKey,
|
|
8542
|
-
bizMap: { noticeType: "APP_NOTIFICATION_IMPORTANT", reason },
|
|
8543
|
-
bizUniqueId,
|
|
8544
|
-
paramsMap: { bizContent },
|
|
8545
|
-
pushType: "SPECIFY_PUSH",
|
|
8546
|
-
templateId
|
|
8547
|
-
};
|
|
8548
|
-
logger?.info(
|
|
8549
|
-
`Light sender: POST ${apiUrl}, bizUniqueId=${bizUniqueId}, body=${JSON.stringify(requestBody).substring(0, 500)}`
|
|
8550
|
-
);
|
|
8551
|
-
const res = await fetch(apiUrl, {
|
|
8552
|
-
method: "POST",
|
|
8553
|
-
headers: {
|
|
8554
|
-
"Content-Type": "application/json",
|
|
8555
|
-
"X-Api-Key-Id": apiKey.startsWith("Bearer ") ? apiKey.slice("Bearer ".length) : apiKey
|
|
8556
|
-
},
|
|
8557
|
-
body: JSON.stringify(requestBody)
|
|
8558
|
-
});
|
|
8559
|
-
const resBody = await res.text();
|
|
8560
|
-
if (!res.ok) {
|
|
8561
|
-
logger?.warn(
|
|
8562
|
-
`Light sender: FAILED ${res.status}, url=${apiUrl}, resBody=${resBody.substring(0, 500)}`
|
|
8563
|
-
);
|
|
8564
|
-
return { ok: false, status: res.status, error: resBody };
|
|
8565
|
-
}
|
|
8566
|
-
logger?.info(`Light sender: OK bizUniqueId=${bizUniqueId}, resBody=${resBody.substring(0, 200)}`);
|
|
8567
|
-
return { ok: true, bizUniqueId, response: JSON.parse(resBody) };
|
|
8568
|
-
}
|
|
8569
|
-
|
|
8570
|
-
// src/cli/light-send.ts
|
|
8571
8324
|
function registerLightSend(light) {
|
|
8572
8325
|
light.command("send").description("\u53D1\u9001\u706F\u6548\u6307\u4EE4\u5230\u786C\u4EF6\u8BBE\u5907").requiredOption("--segments <json>", "\u706F\u6548\u53C2\u6570 JSON").option("--repeat", "\u65E0\u9650\u5FAA\u73AF\u64AD\u653E\uFF08\u9ED8\u8BA4\u4EC5\u64AD\u653E\u4E00\u8F6E\uFF09").option("--repeat-times <n>", "\u6574\u6761\u7EC4\u5408\u91CD\u590D\u6B21\u6570\uFF1A0=\u65E0\u9650\uFF0C1=\u4E00\u8F6E\uFF1B\u5F53\u524D ANCS \u8DEF\u5F84\u4E0D\u652F\u6301 >=2").action(async (opts) => {
|
|
8573
8326
|
let apiKey;
|
|
@@ -8601,9 +8354,9 @@ function registerLightSend(light) {
|
|
|
8601
8354
|
}
|
|
8602
8355
|
|
|
8603
8356
|
// src/cli/light-setup-tools.ts
|
|
8604
|
-
var
|
|
8357
|
+
var import_node_fs13 = require("fs");
|
|
8605
8358
|
var import_node_os2 = require("os");
|
|
8606
|
-
var
|
|
8359
|
+
var import_node_path12 = require("path");
|
|
8607
8360
|
function isObject(value) {
|
|
8608
8361
|
return !!value && typeof value === "object" && !Array.isArray(value);
|
|
8609
8362
|
}
|
|
@@ -8617,7 +8370,7 @@ function ensureArray(obj, key) {
|
|
|
8617
8370
|
function resolveConfigPath2() {
|
|
8618
8371
|
const fromEnv = process.env.OPENCLAW_CONFIG_PATH?.trim();
|
|
8619
8372
|
if (fromEnv) return fromEnv;
|
|
8620
|
-
return (0,
|
|
8373
|
+
return (0, import_node_path12.join)((0, import_node_os2.homedir)(), ".openclaw", "openclaw.json");
|
|
8621
8374
|
}
|
|
8622
8375
|
var LIGHT_TOOLS = [
|
|
8623
8376
|
"light_control",
|
|
@@ -8664,12 +8417,12 @@ function upsertLightControlAlsoAllow(cfg) {
|
|
|
8664
8417
|
function registerLightSetupTools(light) {
|
|
8665
8418
|
light.command("setup").description("\u81EA\u52A8\u653E\u884C light_control\uFF08\u5199\u5165 tools.alsoAllow \u4E0E agents.main.tools.alsoAllow\uFF09").action(() => {
|
|
8666
8419
|
const configPath = resolveConfigPath2();
|
|
8667
|
-
if (!(0,
|
|
8420
|
+
if (!(0, import_node_fs13.existsSync)(configPath)) {
|
|
8668
8421
|
exitError("CONFIG_NOT_FOUND", `\u672A\u627E\u5230\u914D\u7F6E\u6587\u4EF6: ${configPath}`);
|
|
8669
8422
|
}
|
|
8670
8423
|
let cfg = {};
|
|
8671
8424
|
try {
|
|
8672
|
-
const raw = (0,
|
|
8425
|
+
const raw = (0, import_node_fs13.readFileSync)(configPath, "utf-8");
|
|
8673
8426
|
const parsed = JSON.parse(raw);
|
|
8674
8427
|
if (isObject(parsed)) cfg = parsed;
|
|
8675
8428
|
} catch (err2) {
|
|
@@ -8677,8 +8430,8 @@ function registerLightSetupTools(light) {
|
|
|
8677
8430
|
}
|
|
8678
8431
|
const result = upsertLightControlAlsoAllow(cfg);
|
|
8679
8432
|
try {
|
|
8680
|
-
(0,
|
|
8681
|
-
(0,
|
|
8433
|
+
(0, import_node_fs13.mkdirSync)((0, import_node_path12.dirname)(configPath), { recursive: true });
|
|
8434
|
+
(0, import_node_fs13.writeFileSync)(configPath, JSON.stringify(cfg, null, 2) + "\n", "utf-8");
|
|
8682
8435
|
} catch (err2) {
|
|
8683
8436
|
exitError("WRITE_FAILED", `\u5199\u5165\u914D\u7F6E\u5931\u8D25: ${err2?.message ?? String(err2)}`);
|
|
8684
8437
|
}
|
|
@@ -8696,17 +8449,17 @@ function registerLightSetupTools(light) {
|
|
|
8696
8449
|
}
|
|
8697
8450
|
|
|
8698
8451
|
// src/cli/tunnel-status.ts
|
|
8699
|
-
var
|
|
8700
|
-
var
|
|
8452
|
+
var import_node_fs14 = require("fs");
|
|
8453
|
+
var import_node_path13 = require("path");
|
|
8701
8454
|
init_credentials();
|
|
8702
8455
|
init_env();
|
|
8703
|
-
var STATUS_REL_PATH = (0,
|
|
8456
|
+
var STATUS_REL_PATH = (0, import_node_path13.join)("plugins", "phone-notifications", "tunnel-status.json");
|
|
8704
8457
|
function readTunnelStatus(ctx) {
|
|
8705
8458
|
if (!ctx.stateDir) return null;
|
|
8706
|
-
const filePath = (0,
|
|
8707
|
-
if (!(0,
|
|
8459
|
+
const filePath = (0, import_node_path13.join)(ctx.stateDir, STATUS_REL_PATH);
|
|
8460
|
+
if (!(0, import_node_fs14.existsSync)(filePath)) return null;
|
|
8708
8461
|
try {
|
|
8709
|
-
return JSON.parse((0,
|
|
8462
|
+
return JSON.parse((0, import_node_fs14.readFileSync)(filePath, "utf-8"));
|
|
8710
8463
|
} catch {
|
|
8711
8464
|
return null;
|
|
8712
8465
|
}
|
|
@@ -8774,24 +8527,24 @@ function registerNtfStoragePath(ntf, ctx) {
|
|
|
8774
8527
|
}
|
|
8775
8528
|
|
|
8776
8529
|
// src/cli/log-search.ts
|
|
8777
|
-
var
|
|
8778
|
-
var
|
|
8530
|
+
var import_node_fs15 = require("fs");
|
|
8531
|
+
var import_node_path14 = require("path");
|
|
8779
8532
|
function resolveLogsDir(ctx) {
|
|
8780
8533
|
if (ctx.stateDir) {
|
|
8781
|
-
const dir = (0,
|
|
8534
|
+
const dir = (0, import_node_path14.join)(
|
|
8782
8535
|
ctx.stateDir,
|
|
8783
8536
|
"plugins",
|
|
8784
8537
|
"phone-notifications",
|
|
8785
8538
|
"logs"
|
|
8786
8539
|
);
|
|
8787
|
-
if ((0,
|
|
8540
|
+
if ((0, import_node_fs15.existsSync)(dir)) return dir;
|
|
8788
8541
|
}
|
|
8789
8542
|
return null;
|
|
8790
8543
|
}
|
|
8791
8544
|
function listLogDateKeys(dir) {
|
|
8792
8545
|
const pattern = /^(\d{4}-\d{2}-\d{2})\.log$/;
|
|
8793
8546
|
const keys = [];
|
|
8794
|
-
for (const entry of (0,
|
|
8547
|
+
for (const entry of (0, import_node_fs15.readdirSync)(dir, { withFileTypes: true })) {
|
|
8795
8548
|
if (!entry.isFile()) continue;
|
|
8796
8549
|
const m = pattern.exec(entry.name);
|
|
8797
8550
|
if (m) keys.push(m[1]);
|
|
@@ -8799,9 +8552,9 @@ function listLogDateKeys(dir) {
|
|
|
8799
8552
|
return keys.sort().reverse();
|
|
8800
8553
|
}
|
|
8801
8554
|
function collectLogLines(dir, dateKey, keyword, limit, collected) {
|
|
8802
|
-
const filePath = (0,
|
|
8803
|
-
if (!(0,
|
|
8804
|
-
const content = (0,
|
|
8555
|
+
const filePath = (0, import_node_path14.join)(dir, `${dateKey}.log`);
|
|
8556
|
+
if (!(0, import_node_fs15.existsSync)(filePath)) return;
|
|
8557
|
+
const content = (0, import_node_fs15.readFileSync)(filePath, "utf-8");
|
|
8805
8558
|
const lowerKeyword = keyword?.toLowerCase();
|
|
8806
8559
|
for (const line of content.split("\n")) {
|
|
8807
8560
|
if (collected.length >= limit) return;
|
|
@@ -8868,12 +8621,12 @@ function registerEnvCli(ntf) {
|
|
|
8868
8621
|
}
|
|
8869
8622
|
|
|
8870
8623
|
// src/cli/doctor.ts
|
|
8871
|
-
var
|
|
8624
|
+
var import_node_fs19 = require("fs");
|
|
8872
8625
|
var import_node_readline = require("readline");
|
|
8873
8626
|
init_host();
|
|
8874
8627
|
|
|
8875
8628
|
// src/cli/doctor/check-dangerous-flags.ts
|
|
8876
|
-
var
|
|
8629
|
+
var import_node_fs16 = require("fs");
|
|
8877
8630
|
function isObject2(v) {
|
|
8878
8631
|
return !!v && typeof v === "object" && !Array.isArray(v);
|
|
8879
8632
|
}
|
|
@@ -8890,13 +8643,13 @@ var checkDangerousFlags = ({ cfg, configPath }) => {
|
|
|
8890
8643
|
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",
|
|
8891
8644
|
fixDescription: "\u8BBE\u4E3A false",
|
|
8892
8645
|
fix: () => {
|
|
8893
|
-
const raw = (0,
|
|
8646
|
+
const raw = (0, import_node_fs16.readFileSync)(configPath, "utf-8");
|
|
8894
8647
|
const config = JSON.parse(raw);
|
|
8895
8648
|
const gw = config.gateway;
|
|
8896
8649
|
const cui = gw.controlUi;
|
|
8897
8650
|
cui.dangerouslyDisableDeviceAuth = false;
|
|
8898
|
-
(0,
|
|
8899
|
-
(0,
|
|
8651
|
+
(0, import_node_fs16.copyFileSync)(configPath, configPath + ".bak");
|
|
8652
|
+
(0, import_node_fs16.writeFileSync)(configPath, JSON.stringify(config, null, 2) + "\n", "utf-8");
|
|
8900
8653
|
}
|
|
8901
8654
|
};
|
|
8902
8655
|
};
|
|
@@ -8944,11 +8697,11 @@ function warnEmpty() {
|
|
|
8944
8697
|
}
|
|
8945
8698
|
|
|
8946
8699
|
// src/cli/doctor/check-state-dir-perms.ts
|
|
8947
|
-
var
|
|
8700
|
+
var import_node_fs17 = require("fs");
|
|
8948
8701
|
var checkStateDirPerms = ({ stateDir }) => {
|
|
8949
8702
|
let mode;
|
|
8950
8703
|
try {
|
|
8951
|
-
mode = (0,
|
|
8704
|
+
mode = (0, import_node_fs17.statSync)(stateDir).mode;
|
|
8952
8705
|
} catch {
|
|
8953
8706
|
return null;
|
|
8954
8707
|
}
|
|
@@ -8962,7 +8715,7 @@ var checkStateDirPerms = ({ stateDir }) => {
|
|
|
8962
8715
|
detail: "\u5176\u4ED6\u7528\u6237\u53EF\u4EE5\u8BFB\u53D6\u8BE5\u76EE\u5F55\u4E0B\u7684\u51ED\u8BC1\u548C\u914D\u7F6E\u6587\u4EF6\u3002",
|
|
8963
8716
|
fixDescription: "chmod 700 " + stateDir,
|
|
8964
8717
|
fix: () => {
|
|
8965
|
-
(0,
|
|
8718
|
+
(0, import_node_fs17.chmodSync)(stateDir, 448);
|
|
8966
8719
|
}
|
|
8967
8720
|
};
|
|
8968
8721
|
};
|
|
@@ -9017,16 +8770,16 @@ var checkCredentials = () => {
|
|
|
9017
8770
|
};
|
|
9018
8771
|
|
|
9019
8772
|
// src/cli/doctor/check-tunnel.ts
|
|
9020
|
-
var
|
|
9021
|
-
var
|
|
9022
|
-
var STATUS_REL_PATH2 = (0,
|
|
8773
|
+
var import_node_fs18 = require("fs");
|
|
8774
|
+
var import_node_path15 = require("path");
|
|
8775
|
+
var STATUS_REL_PATH2 = (0, import_node_path15.join)(
|
|
9023
8776
|
"plugins",
|
|
9024
8777
|
"phone-notifications",
|
|
9025
8778
|
"tunnel-status.json"
|
|
9026
8779
|
);
|
|
9027
8780
|
var checkTunnel = ({ stateDir }) => {
|
|
9028
|
-
const filePath = (0,
|
|
9029
|
-
if (!(0,
|
|
8781
|
+
const filePath = (0, import_node_path15.join)(stateDir, STATUS_REL_PATH2);
|
|
8782
|
+
if (!(0, import_node_fs18.existsSync)(filePath)) {
|
|
9030
8783
|
return {
|
|
9031
8784
|
id: "tunnel",
|
|
9032
8785
|
severity: "warn",
|
|
@@ -9038,7 +8791,7 @@ var checkTunnel = ({ stateDir }) => {
|
|
|
9038
8791
|
}
|
|
9039
8792
|
let status;
|
|
9040
8793
|
try {
|
|
9041
|
-
status = JSON.parse((0,
|
|
8794
|
+
status = JSON.parse((0, import_node_fs18.readFileSync)(filePath, "utf-8"));
|
|
9042
8795
|
} catch {
|
|
9043
8796
|
return {
|
|
9044
8797
|
id: "tunnel",
|
|
@@ -9131,9 +8884,9 @@ function isObject5(v) {
|
|
|
9131
8884
|
return !!v && typeof v === "object" && !Array.isArray(v);
|
|
9132
8885
|
}
|
|
9133
8886
|
function readConfig(configPath) {
|
|
9134
|
-
if (!(0,
|
|
8887
|
+
if (!(0, import_node_fs19.existsSync)(configPath)) return {};
|
|
9135
8888
|
try {
|
|
9136
|
-
const parsed = JSON.parse((0,
|
|
8889
|
+
const parsed = JSON.parse((0, import_node_fs19.readFileSync)(configPath, "utf-8"));
|
|
9137
8890
|
return isObject5(parsed) ? parsed : {};
|
|
9138
8891
|
} catch {
|
|
9139
8892
|
return {};
|
|
@@ -9334,7 +9087,7 @@ function registerRecStoragePath(rec, ctx) {
|
|
|
9334
9087
|
|
|
9335
9088
|
// src/cli/rec-setup.ts
|
|
9336
9089
|
var import_node_readline2 = require("readline");
|
|
9337
|
-
var
|
|
9090
|
+
var import_node_fs20 = require("fs");
|
|
9338
9091
|
function ask(rl, question) {
|
|
9339
9092
|
return new Promise((resolve) => rl.question(question, resolve));
|
|
9340
9093
|
}
|
|
@@ -9420,9 +9173,9 @@ async function setupLocal(rl) {
|
|
|
9420
9173
|
function registerRecSetup(rec, ctx) {
|
|
9421
9174
|
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 () => {
|
|
9422
9175
|
const configPath = resolveAsrConfigPath(ctx);
|
|
9423
|
-
if ((0,
|
|
9176
|
+
if ((0, import_node_fs20.existsSync)(configPath)) {
|
|
9424
9177
|
try {
|
|
9425
|
-
const existing = JSON.parse((0,
|
|
9178
|
+
const existing = JSON.parse((0, import_node_fs20.readFileSync)(configPath, "utf-8"));
|
|
9426
9179
|
process.stderr.write(`\u5F53\u524D\u5DF2\u6709\u914D\u7F6E\uFF1Amode = ${existing.mode}`);
|
|
9427
9180
|
if (existing.updatedAt) process.stderr.write(`\uFF0C\u66F4\u65B0\u4E8E ${existing.updatedAt}`);
|
|
9428
9181
|
process.stderr.write("\n");
|
|
@@ -9435,7 +9188,7 @@ function registerRecSetup(rec, ctx) {
|
|
|
9435
9188
|
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"]);
|
|
9436
9189
|
const config = modeIdx === 0 ? await setupApi(rl) : await setupLocal(rl);
|
|
9437
9190
|
const stored = { ...config, updatedAt: (/* @__PURE__ */ new Date()).toISOString() };
|
|
9438
|
-
(0,
|
|
9191
|
+
(0, import_node_fs20.writeFileSync)(configPath, JSON.stringify(stored, null, 2), "utf-8");
|
|
9439
9192
|
process.stderr.write(`
|
|
9440
9193
|
\u2713 \u914D\u7F6E\u5DF2\u4FDD\u5B58\u5230 ${configPath}
|
|
9441
9194
|
|
|
@@ -9449,8 +9202,8 @@ function registerRecSetup(rec, ctx) {
|
|
|
9449
9202
|
|
|
9450
9203
|
// src/cli/update.ts
|
|
9451
9204
|
var import_node_child_process = require("child_process");
|
|
9452
|
-
var
|
|
9453
|
-
var
|
|
9205
|
+
var import_node_fs21 = require("fs");
|
|
9206
|
+
var import_node_path16 = require("path");
|
|
9454
9207
|
var import_node_os3 = __toESM(require("os"), 1);
|
|
9455
9208
|
init_host();
|
|
9456
9209
|
var BASE_URL2 = "https://artifact.yoooclaw.com/plugin";
|
|
@@ -9509,9 +9262,9 @@ async function runUpdate(ctx, opts) {
|
|
|
9509
9262
|
`);
|
|
9510
9263
|
process.exit(1);
|
|
9511
9264
|
}
|
|
9512
|
-
const tmpScript = (0,
|
|
9265
|
+
const tmpScript = (0, import_node_path16.join)(import_node_os3.default.tmpdir(), `openclaw-install-${Date.now()}.mjs`);
|
|
9513
9266
|
try {
|
|
9514
|
-
(0,
|
|
9267
|
+
(0, import_node_fs21.writeFileSync)(tmpScript, installScript, "utf-8");
|
|
9515
9268
|
} catch (err2) {
|
|
9516
9269
|
const msg = `\u5199\u5165\u4E34\u65F6\u6587\u4EF6\u5931\u8D25: ${err2?.message ?? String(err2)}`;
|
|
9517
9270
|
if (json) {
|
|
@@ -9529,7 +9282,7 @@ async function runUpdate(ctx, opts) {
|
|
|
9529
9282
|
{ stdio: "inherit" }
|
|
9530
9283
|
);
|
|
9531
9284
|
try {
|
|
9532
|
-
(0,
|
|
9285
|
+
(0, import_node_fs21.unlinkSync)(tmpScript);
|
|
9533
9286
|
} catch {
|
|
9534
9287
|
}
|
|
9535
9288
|
if (result.error) {
|
|
@@ -9593,10 +9346,10 @@ function inferOpenClawRootDir(workspaceDir) {
|
|
|
9593
9346
|
if (!workspaceDir) {
|
|
9594
9347
|
return void 0;
|
|
9595
9348
|
}
|
|
9596
|
-
if ((0,
|
|
9349
|
+
if ((0, import_node_path17.basename)(workspaceDir) !== "workspace") {
|
|
9597
9350
|
return void 0;
|
|
9598
9351
|
}
|
|
9599
|
-
return (0,
|
|
9352
|
+
return (0, import_node_path17.dirname)(workspaceDir);
|
|
9600
9353
|
}
|
|
9601
9354
|
function registerPluginCli(api, params) {
|
|
9602
9355
|
const { logger, openclawDir } = params;
|
|
@@ -9818,159 +9571,475 @@ function registerLightControlTool(api, logger) {
|
|
|
9818
9571
|
}
|
|
9819
9572
|
};
|
|
9820
9573
|
}
|
|
9821
|
-
logger.info(`Light control reason: ${reason}`);
|
|
9822
|
-
const result = await sendLightEffect(
|
|
9823
|
-
apiKey,
|
|
9824
|
-
validation.segments,
|
|
9825
|
-
logger,
|
|
9826
|
-
{ repeat_times: repeatTimes },
|
|
9827
|
-
reason
|
|
9828
|
-
);
|
|
9829
|
-
if (!result.ok) {
|
|
9830
|
-
logger.warn(
|
|
9831
|
-
`Light control HTTP request failed: ${result.status} ${result.error}`
|
|
9832
|
-
);
|
|
9833
|
-
} else {
|
|
9834
|
-
logger.info(`Light control sent, bizUniqueId=${result.bizUniqueId}`);
|
|
9574
|
+
logger.info(`Light control reason: ${reason}`);
|
|
9575
|
+
const result = await sendLightEffect(
|
|
9576
|
+
apiKey,
|
|
9577
|
+
validation.segments,
|
|
9578
|
+
logger,
|
|
9579
|
+
{ repeat_times: repeatTimes },
|
|
9580
|
+
reason
|
|
9581
|
+
);
|
|
9582
|
+
if (!result.ok) {
|
|
9583
|
+
logger.warn(
|
|
9584
|
+
`Light control HTTP request failed: ${result.status} ${result.error}`
|
|
9585
|
+
);
|
|
9586
|
+
} else {
|
|
9587
|
+
logger.info(`Light control sent, bizUniqueId=${result.bizUniqueId}`);
|
|
9588
|
+
}
|
|
9589
|
+
return {
|
|
9590
|
+
content: [{ type: "text", text: JSON.stringify(result) }],
|
|
9591
|
+
details: result
|
|
9592
|
+
};
|
|
9593
|
+
}
|
|
9594
|
+
});
|
|
9595
|
+
}
|
|
9596
|
+
|
|
9597
|
+
// src/plugin/lifecycle.ts
|
|
9598
|
+
var import_node_fs31 = require("fs");
|
|
9599
|
+
init_host();
|
|
9600
|
+
|
|
9601
|
+
// src/notification/app-name-map.ts
|
|
9602
|
+
var import_node_fs22 = require("fs");
|
|
9603
|
+
var import_node_path18 = require("path");
|
|
9604
|
+
init_credentials();
|
|
9605
|
+
init_env();
|
|
9606
|
+
var PLUGIN_STATE_DIR = "phone-notifications";
|
|
9607
|
+
var CACHE_FILE = "app-name-map.json";
|
|
9608
|
+
var BUILTIN_APP_NAME_MAP_URL = getEnvUrls().appNameMapUrl;
|
|
9609
|
+
var APP_NAME_MAP_URL = ("".trim() ? "".trim() : void 0) ?? BUILTIN_APP_NAME_MAP_URL;
|
|
9610
|
+
var APP_NAME_MAP_REFRESH_HOURS = 12;
|
|
9611
|
+
function isRecordOfStrings(v) {
|
|
9612
|
+
if (v === null || typeof v !== "object") return false;
|
|
9613
|
+
for (const val of Object.values(v)) if (typeof val !== "string") return false;
|
|
9614
|
+
return true;
|
|
9615
|
+
}
|
|
9616
|
+
function isAppNameMapApiResponse(v) {
|
|
9617
|
+
if (v === null || typeof v !== "object") return false;
|
|
9618
|
+
const o = v;
|
|
9619
|
+
return Array.isArray(o.data) && o.data.every(
|
|
9620
|
+
(item) => item !== null && typeof item === "object" && typeof item.packageName === "string" && typeof item.appName === "string"
|
|
9621
|
+
);
|
|
9622
|
+
}
|
|
9623
|
+
function getCachePath(stateDir) {
|
|
9624
|
+
return (0, import_node_path18.join)(stateDir, "plugins", PLUGIN_STATE_DIR, CACHE_FILE);
|
|
9625
|
+
}
|
|
9626
|
+
function createAppNameMapProvider(opts) {
|
|
9627
|
+
const { stateDir, logger } = opts;
|
|
9628
|
+
const url = APP_NAME_MAP_URL;
|
|
9629
|
+
const refreshHours = APP_NAME_MAP_REFRESH_HOURS;
|
|
9630
|
+
const map = /* @__PURE__ */ new Map();
|
|
9631
|
+
let refreshTimer = null;
|
|
9632
|
+
let stopWatching = null;
|
|
9633
|
+
let inFlightFetch = null;
|
|
9634
|
+
function loadFromDisk() {
|
|
9635
|
+
const path2 = getCachePath(stateDir);
|
|
9636
|
+
if (!(0, import_node_fs22.existsSync)(path2)) return;
|
|
9637
|
+
try {
|
|
9638
|
+
const raw = JSON.parse((0, import_node_fs22.readFileSync)(path2, "utf-8"));
|
|
9639
|
+
if (!isRecordOfStrings(raw)) return;
|
|
9640
|
+
map.clear();
|
|
9641
|
+
for (const [k, v] of Object.entries(raw)) map.set(k, v);
|
|
9642
|
+
logger.info(`[app-name-map] loaded ${map.size} entries from cache: ${path2}`);
|
|
9643
|
+
} catch {
|
|
9644
|
+
}
|
|
9645
|
+
}
|
|
9646
|
+
async function fetchFromServer() {
|
|
9647
|
+
const apiKey = loadApiKey();
|
|
9648
|
+
if (!url) {
|
|
9649
|
+
logger.warn("[app-name-map] APP_NAME_MAP_URL is empty, skip refresh");
|
|
9650
|
+
return;
|
|
9651
|
+
}
|
|
9652
|
+
if (!apiKey) {
|
|
9653
|
+
logger.info("[app-name-map] api key missing, skip refresh");
|
|
9654
|
+
return;
|
|
9655
|
+
}
|
|
9656
|
+
const rawApiKey = apiKey.startsWith("Bearer ") ? apiKey.slice("Bearer ".length) : apiKey;
|
|
9657
|
+
try {
|
|
9658
|
+
const res = await fetch(url, {
|
|
9659
|
+
method: "POST",
|
|
9660
|
+
headers: { "Content-Type": "application/json", "X-Api-Key-Id": rawApiKey },
|
|
9661
|
+
body: JSON.stringify({
|
|
9662
|
+
platform: ""
|
|
9663
|
+
})
|
|
9664
|
+
});
|
|
9665
|
+
if (!res.ok) {
|
|
9666
|
+
logger.warn(`[app-name-map] refresh failed: HTTP ${res.status} ${res.statusText}`);
|
|
9667
|
+
return;
|
|
9668
|
+
}
|
|
9669
|
+
const body = await res.json();
|
|
9670
|
+
if (!isAppNameMapApiResponse(body) || !body.success || !body.data?.length) {
|
|
9671
|
+
logger.warn("[app-name-map] refresh failed: unexpected response shape or empty data");
|
|
9672
|
+
return;
|
|
9673
|
+
}
|
|
9674
|
+
map.clear();
|
|
9675
|
+
for (const item of body.data) {
|
|
9676
|
+
if (item.packageName && item.appName) map.set(item.packageName, item.appName);
|
|
9677
|
+
}
|
|
9678
|
+
if (map.size === 0) {
|
|
9679
|
+
logger.warn("[app-name-map] refresh succeeded but got 0 entries");
|
|
9680
|
+
return;
|
|
9681
|
+
}
|
|
9682
|
+
const dir = (0, import_node_path18.join)(stateDir, "plugins", PLUGIN_STATE_DIR);
|
|
9683
|
+
(0, import_node_fs22.mkdirSync)(dir, { recursive: true });
|
|
9684
|
+
const cachePath = getCachePath(stateDir);
|
|
9685
|
+
(0, import_node_fs22.writeFileSync)(cachePath, JSON.stringify(Object.fromEntries(map), null, 2), "utf-8");
|
|
9686
|
+
logger.info(`[app-name-map] refreshed ${map.size} entries from server and saved: ${cachePath}`);
|
|
9687
|
+
} catch (e) {
|
|
9688
|
+
const message = e instanceof Error ? e.message : String(e);
|
|
9689
|
+
logger.warn(`[app-name-map] refresh error: ${message}`);
|
|
9690
|
+
}
|
|
9691
|
+
}
|
|
9692
|
+
async function ensureOneFetch() {
|
|
9693
|
+
if (inFlightFetch) return inFlightFetch;
|
|
9694
|
+
inFlightFetch = fetchFromServer().finally(() => {
|
|
9695
|
+
inFlightFetch = null;
|
|
9696
|
+
});
|
|
9697
|
+
return inFlightFetch;
|
|
9698
|
+
}
|
|
9699
|
+
return {
|
|
9700
|
+
async resolveDisplayName(packageName) {
|
|
9701
|
+
if (map.has(packageName)) return map.get(packageName);
|
|
9702
|
+
return packageName;
|
|
9703
|
+
},
|
|
9704
|
+
async start() {
|
|
9705
|
+
loadFromDisk();
|
|
9706
|
+
await fetchFromServer();
|
|
9707
|
+
if (!url) return;
|
|
9708
|
+
if (refreshHours > 0) {
|
|
9709
|
+
const ms = refreshHours * 60 * 60 * 1e3;
|
|
9710
|
+
refreshTimer = setInterval(() => fetchFromServer().catch(() => {
|
|
9711
|
+
}), ms);
|
|
9712
|
+
}
|
|
9713
|
+
stopWatching = watchCredentials(() => fetchFromServer().catch(() => {
|
|
9714
|
+
}));
|
|
9715
|
+
},
|
|
9716
|
+
stop() {
|
|
9717
|
+
stopWatching?.();
|
|
9718
|
+
stopWatching = null;
|
|
9719
|
+
if (refreshTimer) {
|
|
9720
|
+
clearInterval(refreshTimer);
|
|
9721
|
+
refreshTimer = null;
|
|
9722
|
+
}
|
|
9723
|
+
map.clear();
|
|
9724
|
+
}
|
|
9725
|
+
};
|
|
9726
|
+
}
|
|
9727
|
+
|
|
9728
|
+
// src/notification/storage.ts
|
|
9729
|
+
var import_node_fs23 = require("fs");
|
|
9730
|
+
var import_node_crypto2 = require("crypto");
|
|
9731
|
+
var import_node_path19 = require("path");
|
|
9732
|
+
var NOTIFICATION_DIR_NAME = "notifications";
|
|
9733
|
+
var ID_INDEX_DIR_NAME = ".ids";
|
|
9734
|
+
var CONTENT_KEY_INDEX_DIR_NAME = ".keys";
|
|
9735
|
+
function getStateFallbackNotificationDir(stateDir) {
|
|
9736
|
+
return (0, import_node_path19.join)(stateDir, "plugins", "phone-notifications", NOTIFICATION_DIR_NAME);
|
|
9737
|
+
}
|
|
9738
|
+
function ensureWritableDirectory(dir) {
|
|
9739
|
+
try {
|
|
9740
|
+
(0, import_node_fs23.mkdirSync)(dir, { recursive: true });
|
|
9741
|
+
(0, import_node_fs23.accessSync)(dir, import_node_fs23.constants.R_OK | import_node_fs23.constants.W_OK);
|
|
9742
|
+
return true;
|
|
9743
|
+
} catch {
|
|
9744
|
+
return false;
|
|
9745
|
+
}
|
|
9746
|
+
}
|
|
9747
|
+
function resolveNotificationStorageDir(ctx, logger) {
|
|
9748
|
+
const stateNotifDir = getStateFallbackNotificationDir(ctx.stateDir);
|
|
9749
|
+
if (ensureWritableDirectory(stateNotifDir)) {
|
|
9750
|
+
logger.info(`\u901A\u77E5\u5C06\u5199\u5165 stateDir \u8DEF\u5F84: ${stateNotifDir}`);
|
|
9751
|
+
return stateNotifDir;
|
|
9752
|
+
}
|
|
9753
|
+
if (ctx.workspaceDir) {
|
|
9754
|
+
const workspaceDir = (0, import_node_path19.join)(ctx.workspaceDir, NOTIFICATION_DIR_NAME);
|
|
9755
|
+
if (ensureWritableDirectory(workspaceDir)) {
|
|
9756
|
+
logger.warn(
|
|
9757
|
+
`stateDir \u4E0D\u53EF\u7528\uFF0C\u901A\u77E5\u5DF2\u56DE\u9000\u5230 workspace \u8DEF\u5F84: ${workspaceDir}`
|
|
9758
|
+
);
|
|
9759
|
+
return workspaceDir;
|
|
9760
|
+
}
|
|
9761
|
+
}
|
|
9762
|
+
throw new Error(`\u901A\u77E5\u5B58\u50A8\u76EE\u5F55\u4E0D\u53EF\u7528: ${stateNotifDir}`);
|
|
9763
|
+
}
|
|
9764
|
+
var NotificationStorage = class {
|
|
9765
|
+
constructor(dir, config, logger, resolveDisplayName) {
|
|
9766
|
+
this.config = config;
|
|
9767
|
+
this.logger = logger;
|
|
9768
|
+
this.dir = dir;
|
|
9769
|
+
this.idIndexDir = (0, import_node_path19.join)(dir, ID_INDEX_DIR_NAME);
|
|
9770
|
+
this.contentKeyIndexDir = (0, import_node_path19.join)(dir, CONTENT_KEY_INDEX_DIR_NAME);
|
|
9771
|
+
this.resolveDisplayName = resolveDisplayName;
|
|
9772
|
+
}
|
|
9773
|
+
dir;
|
|
9774
|
+
idIndexDir;
|
|
9775
|
+
contentKeyIndexDir;
|
|
9776
|
+
idCache = /* @__PURE__ */ new Map();
|
|
9777
|
+
contentKeyCache = /* @__PURE__ */ new Map();
|
|
9778
|
+
dateWriteChains = /* @__PURE__ */ new Map();
|
|
9779
|
+
resolveDisplayName;
|
|
9780
|
+
async init() {
|
|
9781
|
+
(0, import_node_fs23.mkdirSync)(this.dir, { recursive: true });
|
|
9782
|
+
(0, import_node_fs23.mkdirSync)(this.idIndexDir, { recursive: true });
|
|
9783
|
+
(0, import_node_fs23.rmSync)(this.contentKeyIndexDir, { recursive: true, force: true });
|
|
9784
|
+
(0, import_node_fs23.mkdirSync)(this.contentKeyIndexDir, { recursive: true });
|
|
9785
|
+
}
|
|
9786
|
+
async ingest(items) {
|
|
9787
|
+
const result = {
|
|
9788
|
+
received: items.length,
|
|
9789
|
+
ingested: 0,
|
|
9790
|
+
dedupedById: 0,
|
|
9791
|
+
dedupedByContent: 0,
|
|
9792
|
+
invalid: 0,
|
|
9793
|
+
inserted: []
|
|
9794
|
+
};
|
|
9795
|
+
for (const n of items) {
|
|
9796
|
+
const outcome = await this.writeNotification(n);
|
|
9797
|
+
switch (outcome.kind) {
|
|
9798
|
+
case "ingested":
|
|
9799
|
+
result.ingested += 1;
|
|
9800
|
+
result.inserted.push(outcome.entry);
|
|
9801
|
+
break;
|
|
9802
|
+
case "dedupedById":
|
|
9803
|
+
result.dedupedById += 1;
|
|
9804
|
+
break;
|
|
9805
|
+
case "dedupedByContent":
|
|
9806
|
+
result.dedupedByContent += 1;
|
|
9807
|
+
break;
|
|
9808
|
+
case "invalid":
|
|
9809
|
+
result.invalid += 1;
|
|
9810
|
+
break;
|
|
9811
|
+
}
|
|
9812
|
+
}
|
|
9813
|
+
this.prune();
|
|
9814
|
+
return result;
|
|
9815
|
+
}
|
|
9816
|
+
async writeNotification(n) {
|
|
9817
|
+
const ts = new Date(n.timestamp);
|
|
9818
|
+
if (Number.isNaN(ts.getTime())) {
|
|
9819
|
+
this.logger.warn(`\u5FFD\u7565\u975E\u6CD5 timestamp \u7684\u901A\u77E5: ${n.id}`);
|
|
9820
|
+
return { kind: "invalid" };
|
|
9821
|
+
}
|
|
9822
|
+
const dateKey = this.formatDate(ts);
|
|
9823
|
+
const filePath = (0, import_node_path19.join)(this.dir, `${dateKey}.json`);
|
|
9824
|
+
const normalizedId = typeof n.id === "string" ? n.id.trim() : "";
|
|
9825
|
+
const entry = this.buildStoredNotification(n);
|
|
9826
|
+
return this.withDateWriteLock(dateKey, async () => {
|
|
9827
|
+
if (normalizedId && this.hasNotificationId(dateKey, normalizedId)) {
|
|
9828
|
+
return { kind: "dedupedById" };
|
|
9829
|
+
}
|
|
9830
|
+
if (this.hasNotificationContentKey(dateKey, filePath, entry)) {
|
|
9831
|
+
return { kind: "dedupedByContent" };
|
|
9832
|
+
}
|
|
9833
|
+
const appDisplayName = this.resolveDisplayName ? await this.resolveDisplayName(entry.appName) : entry.appName;
|
|
9834
|
+
const storedEntry = {
|
|
9835
|
+
...entry,
|
|
9836
|
+
appDisplayName
|
|
9837
|
+
};
|
|
9838
|
+
const arr = this.readStoredNotifications(filePath);
|
|
9839
|
+
arr.push(storedEntry);
|
|
9840
|
+
(0, import_node_fs23.writeFileSync)(filePath, JSON.stringify(arr, null, 2), "utf-8");
|
|
9841
|
+
if (normalizedId) {
|
|
9842
|
+
this.recordNotificationId(dateKey, normalizedId);
|
|
9843
|
+
}
|
|
9844
|
+
this.recordNotificationContentKey(dateKey, filePath, storedEntry);
|
|
9845
|
+
return { kind: "ingested", entry: storedEntry };
|
|
9846
|
+
});
|
|
9847
|
+
}
|
|
9848
|
+
buildStoredNotification(n) {
|
|
9849
|
+
return {
|
|
9850
|
+
appName: typeof n.app === "string" && n.app ? n.app : "Unknown",
|
|
9851
|
+
title: typeof n.title === "string" ? n.title : "",
|
|
9852
|
+
content: this.buildContent(n),
|
|
9853
|
+
timestamp: n.timestamp
|
|
9854
|
+
};
|
|
9855
|
+
}
|
|
9856
|
+
buildContent(n) {
|
|
9857
|
+
const body = n.body?.trim();
|
|
9858
|
+
if (body) {
|
|
9859
|
+
return body;
|
|
9860
|
+
}
|
|
9861
|
+
const fallback = [];
|
|
9862
|
+
if (n.category) {
|
|
9863
|
+
fallback.push(`category:${n.category}`);
|
|
9864
|
+
}
|
|
9865
|
+
if (n.metadata && Object.keys(n.metadata).length > 0) {
|
|
9866
|
+
fallback.push(`metadata:${JSON.stringify(n.metadata)}`);
|
|
9867
|
+
}
|
|
9868
|
+
return fallback.join(" ; ") || "-";
|
|
9869
|
+
}
|
|
9870
|
+
formatDate(d) {
|
|
9871
|
+
const year = d.getFullYear();
|
|
9872
|
+
const month = String(d.getMonth() + 1).padStart(2, "0");
|
|
9873
|
+
const day = String(d.getDate()).padStart(2, "0");
|
|
9874
|
+
return `${year}-${month}-${day}`;
|
|
9875
|
+
}
|
|
9876
|
+
getIdIndexPath(dateKey) {
|
|
9877
|
+
return (0, import_node_path19.join)(this.idIndexDir, `${dateKey}.ids`);
|
|
9878
|
+
}
|
|
9879
|
+
getIdSet(dateKey) {
|
|
9880
|
+
const cached = this.idCache.get(dateKey);
|
|
9881
|
+
if (cached) {
|
|
9882
|
+
return cached;
|
|
9883
|
+
}
|
|
9884
|
+
const idPath = this.getIdIndexPath(dateKey);
|
|
9885
|
+
const ids = /* @__PURE__ */ new Set();
|
|
9886
|
+
if ((0, import_node_fs23.existsSync)(idPath)) {
|
|
9887
|
+
const lines = (0, import_node_fs23.readFileSync)(idPath, "utf-8").split(/\r?\n/);
|
|
9888
|
+
for (const line of lines) {
|
|
9889
|
+
const id = line.trim();
|
|
9890
|
+
if (id) {
|
|
9891
|
+
ids.add(id);
|
|
9892
|
+
}
|
|
9893
|
+
}
|
|
9894
|
+
}
|
|
9895
|
+
this.idCache.set(dateKey, ids);
|
|
9896
|
+
return ids;
|
|
9897
|
+
}
|
|
9898
|
+
hasNotificationId(dateKey, id) {
|
|
9899
|
+
return this.getIdSet(dateKey).has(id);
|
|
9900
|
+
}
|
|
9901
|
+
getContentKeyIndexPath(dateKey) {
|
|
9902
|
+
return (0, import_node_path19.join)(this.contentKeyIndexDir, `${dateKey}.keys`);
|
|
9903
|
+
}
|
|
9904
|
+
getContentKeySet(dateKey, filePath) {
|
|
9905
|
+
const cached = this.contentKeyCache.get(dateKey);
|
|
9906
|
+
if (cached) {
|
|
9907
|
+
return cached;
|
|
9908
|
+
}
|
|
9909
|
+
const keyPath = this.getContentKeyIndexPath(dateKey);
|
|
9910
|
+
const keys = /* @__PURE__ */ new Set();
|
|
9911
|
+
if ((0, import_node_fs23.existsSync)(filePath)) {
|
|
9912
|
+
for (const item of this.readStoredNotifications(filePath)) {
|
|
9913
|
+
keys.add(this.buildNotificationContentKey(item));
|
|
9835
9914
|
}
|
|
9836
|
-
return {
|
|
9837
|
-
content: [{ type: "text", text: JSON.stringify(result) }],
|
|
9838
|
-
details: result
|
|
9839
|
-
};
|
|
9840
9915
|
}
|
|
9841
|
-
|
|
9842
|
-
}
|
|
9843
|
-
|
|
9844
|
-
|
|
9845
|
-
|
|
9846
|
-
init_host();
|
|
9847
|
-
|
|
9848
|
-
// src/notification/app-name-map.ts
|
|
9849
|
-
var import_node_fs23 = require("fs");
|
|
9850
|
-
var import_node_path19 = require("path");
|
|
9851
|
-
init_credentials();
|
|
9852
|
-
init_env();
|
|
9853
|
-
var PLUGIN_STATE_DIR = "phone-notifications";
|
|
9854
|
-
var CACHE_FILE = "app-name-map.json";
|
|
9855
|
-
var BUILTIN_APP_NAME_MAP_URL = getEnvUrls().appNameMapUrl;
|
|
9856
|
-
var APP_NAME_MAP_URL = ("".trim() ? "".trim() : void 0) ?? BUILTIN_APP_NAME_MAP_URL;
|
|
9857
|
-
var APP_NAME_MAP_REFRESH_HOURS = 12;
|
|
9858
|
-
function isRecordOfStrings(v) {
|
|
9859
|
-
if (v === null || typeof v !== "object") return false;
|
|
9860
|
-
for (const val of Object.values(v)) if (typeof val !== "string") return false;
|
|
9861
|
-
return true;
|
|
9862
|
-
}
|
|
9863
|
-
function isAppNameMapApiResponse(v) {
|
|
9864
|
-
if (v === null || typeof v !== "object") return false;
|
|
9865
|
-
const o = v;
|
|
9866
|
-
return Array.isArray(o.data) && o.data.every(
|
|
9867
|
-
(item) => item !== null && typeof item === "object" && typeof item.packageName === "string" && typeof item.appName === "string"
|
|
9868
|
-
);
|
|
9869
|
-
}
|
|
9870
|
-
function getCachePath(stateDir) {
|
|
9871
|
-
return (0, import_node_path19.join)(stateDir, "plugins", PLUGIN_STATE_DIR, CACHE_FILE);
|
|
9872
|
-
}
|
|
9873
|
-
function createAppNameMapProvider(opts) {
|
|
9874
|
-
const { stateDir, logger } = opts;
|
|
9875
|
-
const url = APP_NAME_MAP_URL;
|
|
9876
|
-
const refreshHours = APP_NAME_MAP_REFRESH_HOURS;
|
|
9877
|
-
const map = /* @__PURE__ */ new Map();
|
|
9878
|
-
let refreshTimer = null;
|
|
9879
|
-
let stopWatching = null;
|
|
9880
|
-
let inFlightFetch = null;
|
|
9881
|
-
function loadFromDisk() {
|
|
9882
|
-
const path2 = getCachePath(stateDir);
|
|
9883
|
-
if (!(0, import_node_fs23.existsSync)(path2)) return;
|
|
9884
|
-
try {
|
|
9885
|
-
const raw = JSON.parse((0, import_node_fs23.readFileSync)(path2, "utf-8"));
|
|
9886
|
-
if (!isRecordOfStrings(raw)) return;
|
|
9887
|
-
map.clear();
|
|
9888
|
-
for (const [k, v] of Object.entries(raw)) map.set(k, v);
|
|
9889
|
-
logger.info(`[app-name-map] loaded ${map.size} entries from cache: ${path2}`);
|
|
9890
|
-
} catch {
|
|
9916
|
+
if (keys.size > 0) {
|
|
9917
|
+
(0, import_node_fs23.writeFileSync)(keyPath, `${Array.from(keys).join("\n")}
|
|
9918
|
+
`, "utf-8");
|
|
9919
|
+
} else if ((0, import_node_fs23.existsSync)(keyPath)) {
|
|
9920
|
+
(0, import_node_fs23.rmSync)(keyPath, { force: true });
|
|
9891
9921
|
}
|
|
9922
|
+
this.contentKeyCache.set(dateKey, keys);
|
|
9923
|
+
return keys;
|
|
9892
9924
|
}
|
|
9893
|
-
|
|
9894
|
-
|
|
9895
|
-
|
|
9896
|
-
|
|
9925
|
+
hasNotificationContentKey(dateKey, filePath, entry) {
|
|
9926
|
+
return this.getContentKeySet(dateKey, filePath).has(
|
|
9927
|
+
this.buildNotificationContentKey(entry)
|
|
9928
|
+
);
|
|
9929
|
+
}
|
|
9930
|
+
recordNotificationId(dateKey, id) {
|
|
9931
|
+
const ids = this.getIdSet(dateKey);
|
|
9932
|
+
if (ids.has(id)) {
|
|
9897
9933
|
return;
|
|
9898
9934
|
}
|
|
9899
|
-
|
|
9900
|
-
|
|
9935
|
+
(0, import_node_fs23.appendFileSync)(this.getIdIndexPath(dateKey), `${id}
|
|
9936
|
+
`, "utf-8");
|
|
9937
|
+
ids.add(id);
|
|
9938
|
+
}
|
|
9939
|
+
recordNotificationContentKey(dateKey, filePath, entry) {
|
|
9940
|
+
const keys = this.getContentKeySet(dateKey, filePath);
|
|
9941
|
+
const key = this.buildNotificationContentKey(entry);
|
|
9942
|
+
if (keys.has(key)) {
|
|
9901
9943
|
return;
|
|
9902
9944
|
}
|
|
9903
|
-
|
|
9945
|
+
(0, import_node_fs23.appendFileSync)(this.getContentKeyIndexPath(dateKey), `${key}
|
|
9946
|
+
`, "utf-8");
|
|
9947
|
+
keys.add(key);
|
|
9948
|
+
}
|
|
9949
|
+
buildNotificationContentKey(entry) {
|
|
9950
|
+
return (0, import_node_crypto2.createHash)("sha256").update(entry.appName).update("").update(entry.title).update("").update(entry.content).update("").update(entry.timestamp).digest("hex");
|
|
9951
|
+
}
|
|
9952
|
+
readStoredNotifications(filePath) {
|
|
9953
|
+
if (!(0, import_node_fs23.existsSync)(filePath)) {
|
|
9954
|
+
return [];
|
|
9955
|
+
}
|
|
9904
9956
|
try {
|
|
9905
|
-
const
|
|
9906
|
-
|
|
9907
|
-
|
|
9908
|
-
|
|
9909
|
-
platform: ""
|
|
9910
|
-
})
|
|
9911
|
-
});
|
|
9912
|
-
if (!res.ok) {
|
|
9913
|
-
logger.warn(`[app-name-map] refresh failed: HTTP ${res.status} ${res.statusText}`);
|
|
9914
|
-
return;
|
|
9915
|
-
}
|
|
9916
|
-
const body = await res.json();
|
|
9917
|
-
if (!isAppNameMapApiResponse(body) || !body.success || !body.data?.length) {
|
|
9918
|
-
logger.warn("[app-name-map] refresh failed: unexpected response shape or empty data");
|
|
9919
|
-
return;
|
|
9920
|
-
}
|
|
9921
|
-
map.clear();
|
|
9922
|
-
for (const item of body.data) {
|
|
9923
|
-
if (item.packageName && item.appName) map.set(item.packageName, item.appName);
|
|
9924
|
-
}
|
|
9925
|
-
if (map.size === 0) {
|
|
9926
|
-
logger.warn("[app-name-map] refresh succeeded but got 0 entries");
|
|
9927
|
-
return;
|
|
9928
|
-
}
|
|
9929
|
-
const dir = (0, import_node_path19.join)(stateDir, "plugins", PLUGIN_STATE_DIR);
|
|
9930
|
-
(0, import_node_fs23.mkdirSync)(dir, { recursive: true });
|
|
9931
|
-
const cachePath = getCachePath(stateDir);
|
|
9932
|
-
(0, import_node_fs23.writeFileSync)(cachePath, JSON.stringify(Object.fromEntries(map), null, 2), "utf-8");
|
|
9933
|
-
logger.info(`[app-name-map] refreshed ${map.size} entries from server and saved: ${cachePath}`);
|
|
9934
|
-
} catch (e) {
|
|
9935
|
-
const message = e instanceof Error ? e.message : String(e);
|
|
9936
|
-
logger.warn(`[app-name-map] refresh error: ${message}`);
|
|
9957
|
+
const parsed = JSON.parse((0, import_node_fs23.readFileSync)(filePath, "utf-8"));
|
|
9958
|
+
return Array.isArray(parsed) ? parsed : [];
|
|
9959
|
+
} catch {
|
|
9960
|
+
return [];
|
|
9937
9961
|
}
|
|
9938
9962
|
}
|
|
9939
|
-
async
|
|
9940
|
-
|
|
9941
|
-
|
|
9942
|
-
|
|
9963
|
+
async withDateWriteLock(dateKey, task) {
|
|
9964
|
+
const previous = this.dateWriteChains.get(dateKey) ?? Promise.resolve();
|
|
9965
|
+
let release;
|
|
9966
|
+
const current = new Promise((resolve) => {
|
|
9967
|
+
release = resolve;
|
|
9943
9968
|
});
|
|
9944
|
-
|
|
9969
|
+
const chain = previous.then(() => current);
|
|
9970
|
+
this.dateWriteChains.set(dateKey, chain);
|
|
9971
|
+
await previous;
|
|
9972
|
+
try {
|
|
9973
|
+
return await task();
|
|
9974
|
+
} finally {
|
|
9975
|
+
release();
|
|
9976
|
+
if (this.dateWriteChains.get(dateKey) === chain) {
|
|
9977
|
+
this.dateWriteChains.delete(dateKey);
|
|
9978
|
+
}
|
|
9979
|
+
}
|
|
9945
9980
|
}
|
|
9946
|
-
|
|
9947
|
-
|
|
9948
|
-
|
|
9949
|
-
return
|
|
9950
|
-
}
|
|
9951
|
-
|
|
9952
|
-
|
|
9953
|
-
|
|
9954
|
-
|
|
9955
|
-
|
|
9956
|
-
|
|
9957
|
-
|
|
9958
|
-
|
|
9981
|
+
prune() {
|
|
9982
|
+
const retentionDays = this.config.retentionDays;
|
|
9983
|
+
if (retentionDays === void 0) {
|
|
9984
|
+
return;
|
|
9985
|
+
}
|
|
9986
|
+
const cutoffMs = Date.now() - retentionDays * 24 * 60 * 60 * 1e3;
|
|
9987
|
+
const cutoffDate = this.formatDate(new Date(cutoffMs));
|
|
9988
|
+
this.pruneDataFiles(cutoffDate);
|
|
9989
|
+
this.pruneIdIndex(cutoffDate);
|
|
9990
|
+
this.pruneContentKeyIndex(cutoffDate);
|
|
9991
|
+
}
|
|
9992
|
+
/** Remove expired .json, legacy .md files, and legacy date directories */
|
|
9993
|
+
pruneDataFiles(cutoffDate) {
|
|
9994
|
+
const dateFilePattern = /^(\d{4}-\d{2}-\d{2})\.(json|md)$/;
|
|
9995
|
+
const dateDirPattern = /^\d{4}-\d{2}-\d{2}$/;
|
|
9996
|
+
try {
|
|
9997
|
+
for (const entry of (0, import_node_fs23.readdirSync)(this.dir, { withFileTypes: true })) {
|
|
9998
|
+
if (entry.isFile()) {
|
|
9999
|
+
const match = dateFilePattern.exec(entry.name);
|
|
10000
|
+
if (match && match[1] < cutoffDate) {
|
|
10001
|
+
(0, import_node_fs23.rmSync)((0, import_node_path19.join)(this.dir, entry.name), { force: true });
|
|
10002
|
+
}
|
|
10003
|
+
} else if (entry.isDirectory() && dateDirPattern.test(entry.name) && entry.name < cutoffDate) {
|
|
10004
|
+
(0, import_node_fs23.rmSync)((0, import_node_path19.join)(this.dir, entry.name), { recursive: true, force: true });
|
|
10005
|
+
}
|
|
9959
10006
|
}
|
|
9960
|
-
|
|
9961
|
-
|
|
9962
|
-
|
|
9963
|
-
|
|
9964
|
-
|
|
9965
|
-
|
|
9966
|
-
|
|
9967
|
-
|
|
9968
|
-
|
|
10007
|
+
} catch {
|
|
10008
|
+
}
|
|
10009
|
+
}
|
|
10010
|
+
/** Remove expired .ids index files */
|
|
10011
|
+
pruneIdIndex(cutoffDate) {
|
|
10012
|
+
try {
|
|
10013
|
+
for (const entry of (0, import_node_fs23.readdirSync)(this.idIndexDir, { withFileTypes: true })) {
|
|
10014
|
+
if (!entry.isFile()) continue;
|
|
10015
|
+
const match = /^(\d{4}-\d{2}-\d{2})\.ids$/.exec(entry.name);
|
|
10016
|
+
if (match && match[1] < cutoffDate) {
|
|
10017
|
+
(0, import_node_fs23.rmSync)((0, import_node_path19.join)(this.idIndexDir, entry.name), { force: true });
|
|
10018
|
+
this.idCache.delete(match[1]);
|
|
10019
|
+
}
|
|
9969
10020
|
}
|
|
9970
|
-
|
|
10021
|
+
} catch {
|
|
9971
10022
|
}
|
|
9972
|
-
}
|
|
9973
|
-
|
|
10023
|
+
}
|
|
10024
|
+
pruneContentKeyIndex(cutoffDate) {
|
|
10025
|
+
try {
|
|
10026
|
+
for (const entry of (0, import_node_fs23.readdirSync)(this.contentKeyIndexDir, { withFileTypes: true })) {
|
|
10027
|
+
if (!entry.isFile()) continue;
|
|
10028
|
+
const match = /^(\d{4}-\d{2}-\d{2})\.keys$/.exec(entry.name);
|
|
10029
|
+
if (match && match[1] < cutoffDate) {
|
|
10030
|
+
(0, import_node_fs23.rmSync)((0, import_node_path19.join)(this.contentKeyIndexDir, entry.name), { force: true });
|
|
10031
|
+
this.contentKeyCache.delete(match[1]);
|
|
10032
|
+
}
|
|
10033
|
+
}
|
|
10034
|
+
} catch {
|
|
10035
|
+
}
|
|
10036
|
+
}
|
|
10037
|
+
async close() {
|
|
10038
|
+
this.idCache.clear();
|
|
10039
|
+
this.contentKeyCache.clear();
|
|
10040
|
+
this.dateWriteChains.clear();
|
|
10041
|
+
}
|
|
10042
|
+
};
|
|
9974
10043
|
|
|
9975
10044
|
// src/recording/storage.ts
|
|
9976
10045
|
var import_node_fs24 = require("fs");
|
|
@@ -10294,7 +10363,7 @@ var RecordingStorage = class {
|
|
|
10294
10363
|
if (entry.transcriptDataFile) {
|
|
10295
10364
|
const transcriptDoc = this.readRelativeTranscriptDocument(entry.transcriptDataFile);
|
|
10296
10365
|
const transcriptFromJson = extractTranscriptTextFromDocument(transcriptDoc);
|
|
10297
|
-
if (transcriptFromJson
|
|
10366
|
+
if (transcriptFromJson) {
|
|
10298
10367
|
return transcriptFromJson;
|
|
10299
10368
|
}
|
|
10300
10369
|
}
|
|
@@ -10588,11 +10657,11 @@ var import_node_fs25 = require("fs");
|
|
|
10588
10657
|
var import_node_path21 = require("path");
|
|
10589
10658
|
var import_promises2 = require("stream/promises");
|
|
10590
10659
|
var import_node_stream = require("stream");
|
|
10591
|
-
var
|
|
10660
|
+
var DEFAULT_TIMEOUT_MS2 = 5 * 60 * 1e3;
|
|
10592
10661
|
var DEFAULT_MAX_RETRIES = 3;
|
|
10593
10662
|
var DEFAULT_RETRY_BACKOFF_MS = 2e3;
|
|
10594
10663
|
async function downloadFile(url, destPath, logger, options) {
|
|
10595
|
-
const timeoutMs = options?.timeoutMs ??
|
|
10664
|
+
const timeoutMs = options?.timeoutMs ?? DEFAULT_TIMEOUT_MS2;
|
|
10596
10665
|
const maxRetries = options?.maxRetries ?? DEFAULT_MAX_RETRIES;
|
|
10597
10666
|
const retryBackoffMs = options?.retryBackoffMs ?? DEFAULT_RETRY_BACKOFF_MS;
|
|
10598
10667
|
(0, import_node_fs25.mkdirSync)((0, import_node_path21.dirname)(destPath), { recursive: true });
|
|
@@ -10877,7 +10946,7 @@ var import_node_path24 = require("path");
|
|
|
10877
10946
|
// node_modules/.pnpm/ws@8.19.0/node_modules/ws/wrapper.mjs
|
|
10878
10947
|
var import_stream = __toESM(require_stream(), 1);
|
|
10879
10948
|
var import_receiver = __toESM(require_receiver(), 1);
|
|
10880
|
-
var
|
|
10949
|
+
var import_sender4 = __toESM(require_sender(), 1);
|
|
10881
10950
|
var import_websocket = __toESM(require_websocket(), 1);
|
|
10882
10951
|
var import_websocket_server = __toESM(require_websocket_server(), 1);
|
|
10883
10952
|
var wrapper_default = import_websocket.default;
|
|
@@ -12599,16 +12668,15 @@ function registerNotificationInterfaces(deps) {
|
|
|
12599
12668
|
filterNotifications,
|
|
12600
12669
|
registerGatewayMethod,
|
|
12601
12670
|
tunnelService,
|
|
12602
|
-
onAfterIngest
|
|
12603
|
-
getCronForHttpIngest
|
|
12671
|
+
onAfterIngest
|
|
12604
12672
|
} = deps;
|
|
12605
|
-
function triggerAfterIngest(
|
|
12606
|
-
if (
|
|
12607
|
-
void Promise.resolve().then(() => onAfterIngest(
|
|
12673
|
+
function triggerAfterIngest(inserted) {
|
|
12674
|
+
if (inserted.length === 0 || !onAfterIngest) return;
|
|
12675
|
+
void Promise.resolve().then(() => onAfterIngest(inserted)).catch((err2) => logger.warn(`onAfterIngest failed: ${err2?.message ?? err2}`));
|
|
12608
12676
|
}
|
|
12609
12677
|
registerGatewayMethod(
|
|
12610
12678
|
"notifications.push",
|
|
12611
|
-
async ({ params, respond
|
|
12679
|
+
async ({ params, respond }) => {
|
|
12612
12680
|
const storage = getStorage();
|
|
12613
12681
|
if (!storage) {
|
|
12614
12682
|
respond(false, null, {
|
|
@@ -12628,7 +12696,7 @@ function registerNotificationInterfaces(deps) {
|
|
|
12628
12696
|
const filtered = filterNotifications(items);
|
|
12629
12697
|
const result = filtered.length ? await storage.ingest(filtered) : createEmptyIngestResult();
|
|
12630
12698
|
respond(true, toIngestResponse(result));
|
|
12631
|
-
triggerAfterIngest(result.inserted
|
|
12699
|
+
triggerAfterIngest(result.inserted);
|
|
12632
12700
|
}
|
|
12633
12701
|
);
|
|
12634
12702
|
api.registerHttpRoute({
|
|
@@ -12672,7 +12740,7 @@ function registerNotificationInterfaces(deps) {
|
|
|
12672
12740
|
const result = filtered.length ? await storage.ingest(filtered) : createEmptyIngestResult();
|
|
12673
12741
|
res.writeHead(200, { "Content-Type": "application/json" });
|
|
12674
12742
|
res.end(JSON.stringify({ ok: true, ...toIngestResponse(result) }));
|
|
12675
|
-
triggerAfterIngest(result.inserted
|
|
12743
|
+
triggerAfterIngest(result.inserted);
|
|
12676
12744
|
}
|
|
12677
12745
|
});
|
|
12678
12746
|
logger.info("Gateway \u901A\u77E5\u65B9\u6CD5\u5DF2\u6CE8\u518C: notifications.push");
|
|
@@ -13143,7 +13211,6 @@ var index_default = {
|
|
|
13143
13211
|
let storage = null;
|
|
13144
13212
|
let recordingStorage = null;
|
|
13145
13213
|
let broadcastFn = null;
|
|
13146
|
-
let cronService = null;
|
|
13147
13214
|
let autoUpdateLifecycle = null;
|
|
13148
13215
|
let tunnelService = null;
|
|
13149
13216
|
const openclawDir = api.runtime.state.resolveStateDir();
|
|
@@ -13196,9 +13263,6 @@ var index_default = {
|
|
|
13196
13263
|
function registerGatewayMethodWithBroadcastCapture(method, handler) {
|
|
13197
13264
|
api.registerGatewayMethod(method, async (opts) => {
|
|
13198
13265
|
cacheBroadcast(opts.context?.broadcast);
|
|
13199
|
-
if (opts.context?.cron) {
|
|
13200
|
-
cronService = opts.context.cron;
|
|
13201
|
-
}
|
|
13202
13266
|
await deactivateRelayForDirectGatewayRequest(method, opts);
|
|
13203
13267
|
return handler(opts);
|
|
13204
13268
|
});
|
|
@@ -13216,11 +13280,11 @@ var index_default = {
|
|
|
13216
13280
|
broadcastFn("recording.status", event);
|
|
13217
13281
|
}
|
|
13218
13282
|
const lightRuleRegistry = new LightRuleRegistry(lightRuleCtx);
|
|
13219
|
-
const
|
|
13283
|
+
const lightRuleInvoker = new PiAiInvoker(api, logger);
|
|
13284
|
+
const inlineLightRuleEvaluator = new InlineLightRuleEvaluator({
|
|
13220
13285
|
logger,
|
|
13221
13286
|
registry: lightRuleRegistry,
|
|
13222
|
-
|
|
13223
|
-
getNotificationsDir: () => openclawDir ? getStateFallbackNotificationDir(openclawDir) : void 0
|
|
13287
|
+
invoker: lightRuleInvoker
|
|
13224
13288
|
});
|
|
13225
13289
|
registerStorageLifecycle({
|
|
13226
13290
|
api,
|
|
@@ -13251,10 +13315,9 @@ var index_default = {
|
|
|
13251
13315
|
filterNotifications,
|
|
13252
13316
|
registerGatewayMethod: registerGatewayMethodWithBroadcastCapture,
|
|
13253
13317
|
tunnelService,
|
|
13254
|
-
onAfterIngest: (
|
|
13255
|
-
void
|
|
13256
|
-
}
|
|
13257
|
-
getCronForHttpIngest: () => cronService
|
|
13318
|
+
onAfterIngest: (inserted) => {
|
|
13319
|
+
void inlineLightRuleEvaluator.evaluate(inserted);
|
|
13320
|
+
}
|
|
13258
13321
|
});
|
|
13259
13322
|
registerLightControlTool(api, logger);
|
|
13260
13323
|
registerRecordingInterfaces({
|