@yoooclaw/phone-notifications 1.12.0 → 1.12.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +212 -29
- package/dist/bin/ntf.cjs +918 -113
- package/dist/bin/ntf.cjs.map +15 -10
- package/dist/cli/helpers.d.ts +27 -0
- package/dist/cli/helpers.d.ts.map +1 -1
- package/dist/cli/image-list.d.ts +3 -0
- package/dist/cli/image-list.d.ts.map +1 -0
- package/dist/cli/image-path.d.ts +3 -0
- package/dist/cli/image-path.d.ts.map +1 -0
- package/dist/cli/image-status.d.ts +3 -0
- package/dist/cli/image-status.d.ts.map +1 -0
- package/dist/cli/image-storage-path.d.ts +3 -0
- package/dist/cli/image-storage-path.d.ts.map +1 -0
- package/dist/cli/index.d.ts.map +1 -1
- package/dist/cli/ntf-query.d.ts +11 -0
- package/dist/cli/ntf-query.d.ts.map +1 -1
- package/dist/cli/ntf-summary-job.d.ts +3 -0
- package/dist/cli/ntf-summary-job.d.ts.map +1 -0
- package/dist/cli/ntf-summary.d.ts.map +1 -1
- package/dist/cli/ntf-sync.d.ts.map +1 -1
- package/dist/env.d.ts +3 -0
- package/dist/env.d.ts.map +1 -1
- package/dist/image/handler.d.ts +19 -0
- package/dist/image/handler.d.ts.map +1 -0
- package/dist/image/index.d.ts +3 -0
- package/dist/image/index.d.ts.map +1 -0
- package/dist/image/store.d.ts +76 -0
- package/dist/image/store.d.ts.map +1 -0
- package/dist/index.cjs +9089 -7137
- package/dist/index.cjs.map +40 -35
- package/dist/index.d.ts.map +1 -1
- package/dist/light-rules/client.d.ts +58 -0
- package/dist/light-rules/client.d.ts.map +1 -0
- package/dist/light-rules/gateway.d.ts.map +1 -1
- package/dist/light-rules/types.d.ts +13 -8
- package/dist/light-rules/types.d.ts.map +1 -1
- package/dist/notification/storage.d.ts +1 -0
- package/dist/notification/storage.d.ts.map +1 -1
- package/dist/notification/summary.d.ts +144 -0
- package/dist/notification/summary.d.ts.map +1 -0
- package/dist/plugin/images.d.ts +16 -0
- package/dist/plugin/images.d.ts.map +1 -0
- package/dist/plugin/lifecycle.d.ts +2 -0
- package/dist/plugin/lifecycle.d.ts.map +1 -1
- package/dist/plugin/light-rules-tools.d.ts +2 -2
- package/dist/plugin/light-rules-tools.d.ts.map +1 -1
- package/dist/plugin/notifications.d.ts +5 -3
- package/dist/plugin/notifications.d.ts.map +1 -1
- package/dist/plugin/recordings.d.ts.map +1 -1
- package/dist/recording/handler.d.ts +1 -0
- package/dist/recording/handler.d.ts.map +1 -1
- package/dist/recording/index.d.ts +1 -0
- package/dist/recording/index.d.ts.map +1 -1
- package/dist/recording/result-writer.d.ts +9 -0
- package/dist/recording/result-writer.d.ts.map +1 -0
- package/dist/recording/storage.d.ts +2 -0
- package/dist/recording/storage.d.ts.map +1 -1
- package/dist/recording/transcript-document.d.ts +1 -0
- package/dist/recording/transcript-document.d.ts.map +1 -1
- package/dist/tunnel/frame-slimmer.d.ts +37 -0
- package/dist/tunnel/frame-slimmer.d.ts.map +1 -0
- package/dist/tunnel/proxy.d.ts.map +1 -1
- package/dist/types.d.ts +120 -0
- package/dist/types.d.ts.map +1 -1
- package/dist/update/checker.d.ts +2 -1
- package/dist/update/checker.d.ts.map +1 -1
- package/dist/update/index.d.ts.map +1 -1
- package/openclaw.plugin.json +13 -1
- package/package.json +3 -2
- package/skills/notification-monitor/SKILL.md +14 -13
- package/skills/notification-query/SKILL.md +101 -7
- package/skills/notification-to-memory/SKILL.md +57 -22
- package/skills/notification-to-memory/references/step-0-preflight.md +0 -40
- package/skills/notification-to-memory/references/step-1-scan-pending.md +0 -137
- package/skills/notification-to-memory/references/step-2-process-dates.md +0 -98
- package/skills/notification-to-memory/references/step-3-check-cron-status.md +0 -38
- package/skills/notification-to-memory/references/step-4-final-reporting.md +0 -57
- package/skills/notification-to-memory/scripts/select-memory-prompt.sh +0 -18
package/dist/bin/ntf.cjs
CHANGED
|
@@ -2592,6 +2592,34 @@ function readRecordingIndex(dir) {
|
|
|
2592
2592
|
return [];
|
|
2593
2593
|
}
|
|
2594
2594
|
}
|
|
2595
|
+
function resolveImagesDir(ctx) {
|
|
2596
|
+
if (ctx.stateDir) {
|
|
2597
|
+
const dir = import_node_path4.join(ctx.stateDir, "plugins", "phone-notifications", "images");
|
|
2598
|
+
if (import_node_fs3.existsSync(dir))
|
|
2599
|
+
return dir;
|
|
2600
|
+
}
|
|
2601
|
+
if (ctx.workspaceDir) {
|
|
2602
|
+
const dir = import_node_path4.join(ctx.workspaceDir, "images");
|
|
2603
|
+
if (import_node_fs3.existsSync(dir))
|
|
2604
|
+
return dir;
|
|
2605
|
+
}
|
|
2606
|
+
return null;
|
|
2607
|
+
}
|
|
2608
|
+
function readImageIndex(dir) {
|
|
2609
|
+
const indexPath = import_node_path4.join(dir, "index.json");
|
|
2610
|
+
if (!import_node_fs3.existsSync(indexPath))
|
|
2611
|
+
return [];
|
|
2612
|
+
try {
|
|
2613
|
+
const raw = JSON.parse(import_node_fs3.readFileSync(indexPath, "utf-8"));
|
|
2614
|
+
const images = Array.isArray(raw?.images) ? raw.images : [];
|
|
2615
|
+
return images.sort((a, b) => b.metadata.created_at.localeCompare(a.metadata.created_at));
|
|
2616
|
+
} catch {
|
|
2617
|
+
return [];
|
|
2618
|
+
}
|
|
2619
|
+
}
|
|
2620
|
+
function resolveImageFile(dir, relative) {
|
|
2621
|
+
return import_node_path4.isAbsolute(relative) ? relative : import_node_path4.join(dir, relative);
|
|
2622
|
+
}
|
|
2595
2623
|
|
|
2596
2624
|
// src/auth/credentials.ts
|
|
2597
2625
|
var import_node_fs4 = require("node:fs");
|
|
@@ -2808,27 +2836,43 @@ function matchesNotificationQuery(item, opts) {
|
|
|
2808
2836
|
return true;
|
|
2809
2837
|
}
|
|
2810
2838
|
async function collectMatchingNotifications(dir, opts) {
|
|
2839
|
+
const result = await collectMatchingNotificationsWithStats(dir, opts);
|
|
2840
|
+
return result.notifications;
|
|
2841
|
+
}
|
|
2842
|
+
async function collectMatchingNotificationsWithStats(dir, opts) {
|
|
2811
2843
|
const keys = await listDateKeysAsync(dir);
|
|
2812
2844
|
const results = [];
|
|
2813
|
-
const
|
|
2845
|
+
const stats = {
|
|
2846
|
+
daysScanned: 0,
|
|
2847
|
+
itemsScanned: 0,
|
|
2848
|
+
matched: 0,
|
|
2849
|
+
stoppedAfterLimit: false
|
|
2850
|
+
};
|
|
2814
2851
|
for (const dateKey of keys) {
|
|
2815
2852
|
if (opts.fromDateKey && dateKey < opts.fromDateKey)
|
|
2816
2853
|
continue;
|
|
2817
2854
|
if (opts.toDateKey && dateKey > opts.toDateKey)
|
|
2818
2855
|
continue;
|
|
2856
|
+
stats.daysScanned += 1;
|
|
2819
2857
|
const items = sortNotificationsByTimestampDesc([
|
|
2820
2858
|
...await readDateFileAsync(dir, dateKey)
|
|
2821
2859
|
]);
|
|
2822
2860
|
for (const item of items) {
|
|
2861
|
+
stats.itemsScanned += 1;
|
|
2823
2862
|
if (!matchesNotificationQuery(item, opts))
|
|
2824
2863
|
continue;
|
|
2864
|
+
stats.matched += 1;
|
|
2825
2865
|
results.push(item);
|
|
2826
|
-
if (
|
|
2827
|
-
|
|
2866
|
+
if (results.length >= opts.limit) {
|
|
2867
|
+
stats.stoppedAfterLimit = true;
|
|
2868
|
+
return { notifications: results, stats };
|
|
2828
2869
|
}
|
|
2829
2870
|
}
|
|
2830
2871
|
}
|
|
2831
|
-
return
|
|
2872
|
+
return {
|
|
2873
|
+
notifications: sortNotificationsByTimestampDesc(results).slice(0, opts.limit),
|
|
2874
|
+
stats
|
|
2875
|
+
};
|
|
2832
2876
|
}
|
|
2833
2877
|
|
|
2834
2878
|
// src/cli/ntf-search.ts
|
|
@@ -2914,7 +2958,10 @@ function registerNtfSummary(ntf, ctx) {
|
|
|
2914
2958
|
const sampleLimit = parsePositiveIntegerOption(opts.sample, 30, "--sample", "INVALID_SAMPLE");
|
|
2915
2959
|
const topLimit = parsePositiveIntegerOption(opts.top, 10, "--top", "INVALID_TOP");
|
|
2916
2960
|
const maxContent = parsePositiveIntegerOption(opts.maxContent, 80, "--max-content", "INVALID_MAX_CONTENT");
|
|
2917
|
-
const
|
|
2961
|
+
const startedAtMs = Date.now();
|
|
2962
|
+
const queryResult = await collectMatchingNotificationsWithStats(dir, query);
|
|
2963
|
+
const elapsedMs = Date.now() - startedAtMs;
|
|
2964
|
+
const { notifications, stats } = queryResult;
|
|
2918
2965
|
const byApp = new Map;
|
|
2919
2966
|
const bySender = new Map;
|
|
2920
2967
|
const byConversation = new Map;
|
|
@@ -2946,6 +2993,13 @@ function registerNtfSummary(ntf, ctx) {
|
|
|
2946
2993
|
newest: notifications[0]?.timestamp ?? null,
|
|
2947
2994
|
oldest: notifications[notifications.length - 1]?.timestamp ?? null
|
|
2948
2995
|
},
|
|
2996
|
+
scan: {
|
|
2997
|
+
daysScanned: stats.daysScanned,
|
|
2998
|
+
itemsScanned: stats.itemsScanned,
|
|
2999
|
+
matched: stats.matched,
|
|
3000
|
+
stoppedAfterLimit: stats.stoppedAfterLimit,
|
|
3001
|
+
elapsedMs
|
|
3002
|
+
},
|
|
2949
3003
|
byApp: topRows(byApp, topLimit),
|
|
2950
3004
|
bySender: senderRows,
|
|
2951
3005
|
byConversation: topRows(byConversation, topLimit),
|
|
@@ -2956,6 +3010,555 @@ function registerNtfSummary(ntf, ctx) {
|
|
|
2956
3010
|
});
|
|
2957
3011
|
}
|
|
2958
3012
|
|
|
3013
|
+
// src/cli/ntf-summary-job.ts
|
|
3014
|
+
var import_node_crypto = require("node:crypto");
|
|
3015
|
+
var import_node_fs6 = require("node:fs");
|
|
3016
|
+
var import_node_path6 = require("node:path");
|
|
3017
|
+
var SUMMARY_ROOT = ".summaries";
|
|
3018
|
+
var JOBS_DIR = "jobs";
|
|
3019
|
+
var CHUNKS_DIR = "chunks";
|
|
3020
|
+
var SUMMARIES_DIR = "summaries";
|
|
3021
|
+
var RESULT_FILE = "result.md";
|
|
3022
|
+
var DEFAULT_JOB_LIMIT = 1000;
|
|
3023
|
+
var DEFAULT_CHUNK_SIZE = 150;
|
|
3024
|
+
var DEFAULT_MAX_CONTENT = 120;
|
|
3025
|
+
var DEFAULT_RUN_MAX_CHUNKS = 20;
|
|
3026
|
+
var ATTENTION_KEYWORDS = [
|
|
3027
|
+
"待办",
|
|
3028
|
+
"需要",
|
|
3029
|
+
"麻烦",
|
|
3030
|
+
"确认",
|
|
3031
|
+
"回复",
|
|
3032
|
+
"审批",
|
|
3033
|
+
"开会",
|
|
3034
|
+
"会议",
|
|
3035
|
+
"安排",
|
|
3036
|
+
"截止",
|
|
3037
|
+
"紧急",
|
|
3038
|
+
"异常",
|
|
3039
|
+
"失败",
|
|
3040
|
+
"风险",
|
|
3041
|
+
"todo",
|
|
3042
|
+
"deadline",
|
|
3043
|
+
"urgent",
|
|
3044
|
+
"error",
|
|
3045
|
+
"failed"
|
|
3046
|
+
];
|
|
3047
|
+
function truncate2(value, maxLength = 120) {
|
|
3048
|
+
if (value.length <= maxLength)
|
|
3049
|
+
return value;
|
|
3050
|
+
return `${value.slice(0, maxLength - 1)}…`;
|
|
3051
|
+
}
|
|
3052
|
+
function compactNotification2(item, maxContent) {
|
|
3053
|
+
const result = {
|
|
3054
|
+
appName: item.appName,
|
|
3055
|
+
title: truncate2(item.title, maxContent),
|
|
3056
|
+
content: truncate2(item.content, maxContent),
|
|
3057
|
+
timestamp: item.timestamp
|
|
3058
|
+
};
|
|
3059
|
+
if (item.appDisplayName)
|
|
3060
|
+
result.appDisplayName = item.appDisplayName;
|
|
3061
|
+
if (item.senderName)
|
|
3062
|
+
result.senderName = item.senderName;
|
|
3063
|
+
if (item.conversationType)
|
|
3064
|
+
result.conversationType = item.conversationType;
|
|
3065
|
+
if (item.conversationName)
|
|
3066
|
+
result.conversationName = item.conversationName;
|
|
3067
|
+
return result;
|
|
3068
|
+
}
|
|
3069
|
+
function notificationSender(item) {
|
|
3070
|
+
return item.senderName?.trim() || item.conversationName?.trim() || item.title.trim() || "(unknown)";
|
|
3071
|
+
}
|
|
3072
|
+
function notificationApp(item) {
|
|
3073
|
+
return item.appDisplayName?.trim() || item.appName.trim() || "(unknown)";
|
|
3074
|
+
}
|
|
3075
|
+
function increment2(map, key, seed) {
|
|
3076
|
+
const existing = map.get(key);
|
|
3077
|
+
if (existing) {
|
|
3078
|
+
existing.count += 1;
|
|
3079
|
+
return;
|
|
3080
|
+
}
|
|
3081
|
+
map.set(key, { ...seed, count: 1 });
|
|
3082
|
+
}
|
|
3083
|
+
function topRows2(map, limit) {
|
|
3084
|
+
return [...map.values()].sort((a, b) => b.count - a.count).slice(0, limit);
|
|
3085
|
+
}
|
|
3086
|
+
function formatTopRows(rows, labelKey) {
|
|
3087
|
+
if (rows.length === 0)
|
|
3088
|
+
return "无";
|
|
3089
|
+
return rows.map((row) => `${String(row[labelKey])} ${row.count}`).join(";");
|
|
3090
|
+
}
|
|
3091
|
+
function compactLine(item) {
|
|
3092
|
+
const app = notificationApp(item);
|
|
3093
|
+
const sender = notificationSender(item);
|
|
3094
|
+
return `[${item.timestamp}] ${app} · ${sender}: ${item.content}`;
|
|
3095
|
+
}
|
|
3096
|
+
function isAttentionCandidate(item) {
|
|
3097
|
+
const text = `${item.title}
|
|
3098
|
+
${item.content}`.toLowerCase();
|
|
3099
|
+
return ATTENTION_KEYWORDS.some((keyword) => text.includes(keyword.toLowerCase()));
|
|
3100
|
+
}
|
|
3101
|
+
function buildExtractiveChunkSummary(chunk, notifications) {
|
|
3102
|
+
const byApp = new Map;
|
|
3103
|
+
const bySender = new Map;
|
|
3104
|
+
for (const item of notifications) {
|
|
3105
|
+
const app = notificationApp(item);
|
|
3106
|
+
const sender = notificationSender(item);
|
|
3107
|
+
increment2(byApp, app, { app });
|
|
3108
|
+
increment2(bySender, sender, { sender });
|
|
3109
|
+
}
|
|
3110
|
+
const latest = notifications.slice(0, 5).map(compactLine);
|
|
3111
|
+
const attention = notifications.filter(isAttentionCandidate).slice(0, 5).map(compactLine);
|
|
3112
|
+
const lines = [
|
|
3113
|
+
`## 分片 ${chunk.id}`,
|
|
3114
|
+
"",
|
|
3115
|
+
`- 数量: ${chunk.count}`,
|
|
3116
|
+
`- 时间范围: ${chunk.range.oldest ?? "-"} 至 ${chunk.range.newest ?? "-"}`,
|
|
3117
|
+
`- 主要 App: ${formatTopRows(topRows2(byApp, 5), "app")}`,
|
|
3118
|
+
`- 主要发送人/会话: ${formatTopRows(topRows2(bySender, 8), "sender")}`,
|
|
3119
|
+
"",
|
|
3120
|
+
"### 可能需要关注",
|
|
3121
|
+
...attention.length > 0 ? attention.map((line) => `- ${line}`) : ["- 无明显待办、风险或异常样例"],
|
|
3122
|
+
"",
|
|
3123
|
+
"### 最近样例",
|
|
3124
|
+
...latest.length > 0 ? latest.map((line) => `- ${line}`) : ["- 无"],
|
|
3125
|
+
""
|
|
3126
|
+
];
|
|
3127
|
+
return lines.join(`
|
|
3128
|
+
`).trimEnd() + `
|
|
3129
|
+
`;
|
|
3130
|
+
}
|
|
3131
|
+
function nowIso() {
|
|
3132
|
+
return new Date().toISOString();
|
|
3133
|
+
}
|
|
3134
|
+
function createJobId() {
|
|
3135
|
+
const stamp = new Date().toISOString().replace(/[-:.TZ]/g, "").slice(0, 14);
|
|
3136
|
+
return `${stamp}-${import_node_crypto.randomUUID().slice(0, 8)}`;
|
|
3137
|
+
}
|
|
3138
|
+
function assertJobId(id) {
|
|
3139
|
+
if (!/^[A-Za-z0-9][A-Za-z0-9._-]{0,100}$/.test(id)) {
|
|
3140
|
+
exitError("INVALID_JOB_ID", "summary job id 不合法");
|
|
3141
|
+
}
|
|
3142
|
+
}
|
|
3143
|
+
function summaryJobsDir(notificationsDir) {
|
|
3144
|
+
return import_node_path6.join(notificationsDir, SUMMARY_ROOT, JOBS_DIR);
|
|
3145
|
+
}
|
|
3146
|
+
function summaryJobDir(notificationsDir, id) {
|
|
3147
|
+
assertJobId(id);
|
|
3148
|
+
return import_node_path6.join(summaryJobsDir(notificationsDir), id);
|
|
3149
|
+
}
|
|
3150
|
+
function metaPath(jobDir) {
|
|
3151
|
+
return import_node_path6.join(jobDir, "meta.json");
|
|
3152
|
+
}
|
|
3153
|
+
function chunkPath(jobDir, chunkId) {
|
|
3154
|
+
return import_node_path6.join(jobDir, CHUNKS_DIR, `${chunkId}.json`);
|
|
3155
|
+
}
|
|
3156
|
+
function writeJson(path, value) {
|
|
3157
|
+
import_node_fs6.writeFileSync(path, JSON.stringify(value, null, 2), "utf-8");
|
|
3158
|
+
}
|
|
3159
|
+
function readJson(path) {
|
|
3160
|
+
try {
|
|
3161
|
+
return JSON.parse(import_node_fs6.readFileSync(path, "utf-8"));
|
|
3162
|
+
} catch {
|
|
3163
|
+
exitError("READ_FAILED", `无法读取 JSON: ${path}`);
|
|
3164
|
+
}
|
|
3165
|
+
}
|
|
3166
|
+
function readMeta(notificationsDir, id) {
|
|
3167
|
+
const dir = summaryJobDir(notificationsDir, id);
|
|
3168
|
+
const path = metaPath(dir);
|
|
3169
|
+
if (!import_node_fs6.existsSync(path)) {
|
|
3170
|
+
exitError("JOB_NOT_FOUND", `summary job 不存在: ${id}`);
|
|
3171
|
+
}
|
|
3172
|
+
const meta = readJson(path);
|
|
3173
|
+
if (!isSummaryJobMeta(meta)) {
|
|
3174
|
+
exitError("INVALID_JOB", `summary job 元数据损坏: ${id}`);
|
|
3175
|
+
}
|
|
3176
|
+
return meta;
|
|
3177
|
+
}
|
|
3178
|
+
function saveMeta(notificationsDir, meta) {
|
|
3179
|
+
meta.updatedAt = nowIso();
|
|
3180
|
+
writeJson(metaPath(summaryJobDir(notificationsDir, meta.id)), meta);
|
|
3181
|
+
}
|
|
3182
|
+
function isSummaryJobMeta(value) {
|
|
3183
|
+
if (!value || typeof value !== "object")
|
|
3184
|
+
return false;
|
|
3185
|
+
const obj = value;
|
|
3186
|
+
return obj.schemaVersion === 1 && typeof obj.id === "string" && Array.isArray(obj.chunks) && typeof obj.total === "number";
|
|
3187
|
+
}
|
|
3188
|
+
function refreshStatus(meta) {
|
|
3189
|
+
if (meta.status === "cancelled" || meta.status === "complete") {
|
|
3190
|
+
return meta.status;
|
|
3191
|
+
}
|
|
3192
|
+
if (meta.chunks.every((chunk) => chunk.status === "done")) {
|
|
3193
|
+
return "ready";
|
|
3194
|
+
}
|
|
3195
|
+
if (meta.chunks.some((chunk) => chunk.status === "in_progress")) {
|
|
3196
|
+
return "in_progress";
|
|
3197
|
+
}
|
|
3198
|
+
return "pending";
|
|
3199
|
+
}
|
|
3200
|
+
function progressFor(meta) {
|
|
3201
|
+
const doneChunks = meta.chunks.filter((chunk) => chunk.status === "done");
|
|
3202
|
+
const doneNotifications = doneChunks.reduce((sum, chunk) => sum + chunk.count, 0);
|
|
3203
|
+
return {
|
|
3204
|
+
totalChunks: meta.chunks.length,
|
|
3205
|
+
doneChunks: doneChunks.length,
|
|
3206
|
+
totalNotifications: meta.total,
|
|
3207
|
+
doneNotifications,
|
|
3208
|
+
remainingChunks: meta.chunks.length - doneChunks.length
|
|
3209
|
+
};
|
|
3210
|
+
}
|
|
3211
|
+
function toPublicStatus(meta) {
|
|
3212
|
+
return {
|
|
3213
|
+
ok: true,
|
|
3214
|
+
id: meta.id,
|
|
3215
|
+
status: meta.status,
|
|
3216
|
+
createdAt: meta.createdAt,
|
|
3217
|
+
updatedAt: meta.updatedAt,
|
|
3218
|
+
query: meta.query,
|
|
3219
|
+
total: meta.total,
|
|
3220
|
+
chunkSize: meta.chunkSize,
|
|
3221
|
+
range: meta.range,
|
|
3222
|
+
scan: meta.scan,
|
|
3223
|
+
progress: progressFor(meta),
|
|
3224
|
+
resultFile: meta.resultFile ?? null,
|
|
3225
|
+
chunks: meta.chunks.map((chunk) => ({
|
|
3226
|
+
id: chunk.id,
|
|
3227
|
+
index: chunk.index,
|
|
3228
|
+
count: chunk.count,
|
|
3229
|
+
status: chunk.status,
|
|
3230
|
+
range: chunk.range,
|
|
3231
|
+
summaryFile: chunk.summaryFile ?? null
|
|
3232
|
+
}))
|
|
3233
|
+
};
|
|
3234
|
+
}
|
|
3235
|
+
function readChunkNotifications(jobDir, chunkId) {
|
|
3236
|
+
const chunk = readJson(chunkPath(jobDir, chunkId));
|
|
3237
|
+
if (!chunk || typeof chunk !== "object" || !Array.isArray(chunk.notifications)) {
|
|
3238
|
+
exitError("INVALID_CHUNK", `summary job 分片损坏: ${chunkId}`);
|
|
3239
|
+
}
|
|
3240
|
+
return chunk.notifications;
|
|
3241
|
+
}
|
|
3242
|
+
function buildCommitCommand(jobId, chunkId) {
|
|
3243
|
+
return `ntf summary-job commit ${jobId} --chunk-id ${chunkId} --summary-file <path>`;
|
|
3244
|
+
}
|
|
3245
|
+
function buildResultCommand(jobId) {
|
|
3246
|
+
return `ntf summary-job result ${jobId}`;
|
|
3247
|
+
}
|
|
3248
|
+
function resolveSummaryText(opts) {
|
|
3249
|
+
const hasSummary = typeof opts.summary === "string" && opts.summary.length > 0;
|
|
3250
|
+
const hasSummaryFile = typeof opts.summaryFile === "string" && opts.summaryFile.length > 0;
|
|
3251
|
+
if (hasSummary && hasSummaryFile) {
|
|
3252
|
+
exitError("INVALID_COMMIT", "--summary 和 --summary-file 只能二选一");
|
|
3253
|
+
}
|
|
3254
|
+
if (!hasSummary && !hasSummaryFile) {
|
|
3255
|
+
exitError("INVALID_COMMIT", "必须提供 --summary 或 --summary-file");
|
|
3256
|
+
}
|
|
3257
|
+
const text = hasSummary ? opts.summary : import_node_fs6.readFileSync(import_node_path6.resolve(opts.summaryFile), "utf-8");
|
|
3258
|
+
if (!text.trim()) {
|
|
3259
|
+
exitError("INVALID_COMMIT", "分片摘要不能为空");
|
|
3260
|
+
}
|
|
3261
|
+
return text.trimEnd() + `
|
|
3262
|
+
`;
|
|
3263
|
+
}
|
|
3264
|
+
function buildResultMarkdown(meta, jobDir) {
|
|
3265
|
+
const lines = [
|
|
3266
|
+
`# 通知总结任务 ${meta.id}`,
|
|
3267
|
+
"",
|
|
3268
|
+
`- 通知数: ${meta.total}`,
|
|
3269
|
+
`- 时间范围: ${meta.range.oldest ?? "-"} 至 ${meta.range.newest ?? "-"}`,
|
|
3270
|
+
`- 分片数: ${meta.chunks.length}`,
|
|
3271
|
+
"",
|
|
3272
|
+
"## 分片摘要",
|
|
3273
|
+
""
|
|
3274
|
+
];
|
|
3275
|
+
for (const chunk of meta.chunks) {
|
|
3276
|
+
lines.push(`### ${chunk.id} · ${chunk.count} 条 · ${chunk.range.oldest ?? "-"} 至 ${chunk.range.newest ?? "-"}`, "");
|
|
3277
|
+
if (!chunk.summaryFile) {
|
|
3278
|
+
lines.push("(未提交摘要)", "");
|
|
3279
|
+
continue;
|
|
3280
|
+
}
|
|
3281
|
+
lines.push(import_node_fs6.readFileSync(import_node_path6.join(jobDir, chunk.summaryFile), "utf-8").trim(), "");
|
|
3282
|
+
}
|
|
3283
|
+
return lines.join(`
|
|
3284
|
+
`).trimEnd() + `
|
|
3285
|
+
`;
|
|
3286
|
+
}
|
|
3287
|
+
function writeChunkSummary(notificationsDir, meta, chunk, text) {
|
|
3288
|
+
const jobDir = summaryJobDir(notificationsDir, meta.id);
|
|
3289
|
+
const relativeSummaryFile = import_node_path6.join(SUMMARIES_DIR, `${chunk.id}.md`);
|
|
3290
|
+
import_node_fs6.writeFileSync(import_node_path6.join(jobDir, relativeSummaryFile), text, "utf-8");
|
|
3291
|
+
chunk.status = "done";
|
|
3292
|
+
chunk.completedAt = nowIso();
|
|
3293
|
+
chunk.summaryFile = relativeSummaryFile;
|
|
3294
|
+
}
|
|
3295
|
+
function finalizeResultIfReady(notificationsDir, meta) {
|
|
3296
|
+
meta.status = refreshStatus(meta);
|
|
3297
|
+
if (meta.status !== "ready" && meta.status !== "complete") {
|
|
3298
|
+
return null;
|
|
3299
|
+
}
|
|
3300
|
+
const jobDir = summaryJobDir(notificationsDir, meta.id);
|
|
3301
|
+
const markdown = buildResultMarkdown(meta, jobDir);
|
|
3302
|
+
import_node_fs6.writeFileSync(import_node_path6.join(jobDir, RESULT_FILE), markdown, "utf-8");
|
|
3303
|
+
meta.status = "complete";
|
|
3304
|
+
meta.resultFile = RESULT_FILE;
|
|
3305
|
+
return markdown;
|
|
3306
|
+
}
|
|
3307
|
+
function registerNtfSummaryJob(ntf, ctx) {
|
|
3308
|
+
const summaryJob = ntf.command("summary-job").description("分片通知总结任务管理");
|
|
3309
|
+
summaryJob.command("create").description("创建分片通知总结任务").option("--from <time>", "开始时间 ISO 8601").option("--to <time>", "结束时间 ISO 8601").option("--app <name>", "按应用名过滤").option("--sender <name>", "按发送人过滤").option("--conversation-type <type>", "按会话类型过滤(group/private)").option("--keyword <text>", "在标题和内容中搜索关键词").option("--limit <n>", "纳入任务的最大通知条数", String(DEFAULT_JOB_LIMIT)).option("--chunk-size <n>", "每个分片的通知条数", String(DEFAULT_CHUNK_SIZE)).option("--max-content <n>", "分片中单条通知标题/正文最大字数", String(DEFAULT_MAX_CONTENT)).action(async (opts) => {
|
|
3310
|
+
const dir = resolveNotificationsDir(ctx);
|
|
3311
|
+
if (!dir)
|
|
3312
|
+
exitError("STORAGE_UNAVAILABLE", "通知存储目录不可用");
|
|
3313
|
+
progress("正在创建分片通知总结任务...");
|
|
3314
|
+
const query = parseNotificationQueryOptions(opts, DEFAULT_JOB_LIMIT);
|
|
3315
|
+
const chunkSize = parsePositiveIntegerOption(opts.chunkSize, DEFAULT_CHUNK_SIZE, "--chunk-size", "INVALID_CHUNK_SIZE");
|
|
3316
|
+
const maxContent = parsePositiveIntegerOption(opts.maxContent, DEFAULT_MAX_CONTENT, "--max-content", "INVALID_MAX_CONTENT");
|
|
3317
|
+
const startedAtMs = Date.now();
|
|
3318
|
+
const queryResult = await collectMatchingNotificationsWithStats(dir, query);
|
|
3319
|
+
const elapsedMs = Date.now() - startedAtMs;
|
|
3320
|
+
const notifications = queryResult.notifications;
|
|
3321
|
+
const id = createJobId();
|
|
3322
|
+
const createdAt = nowIso();
|
|
3323
|
+
const jobDir = summaryJobDir(dir, id);
|
|
3324
|
+
import_node_fs6.mkdirSync(import_node_path6.join(jobDir, CHUNKS_DIR), { recursive: true });
|
|
3325
|
+
import_node_fs6.mkdirSync(import_node_path6.join(jobDir, SUMMARIES_DIR), { recursive: true });
|
|
3326
|
+
const chunks = [];
|
|
3327
|
+
for (let start = 0;start < notifications.length; start += chunkSize) {
|
|
3328
|
+
const rawChunk = notifications.slice(start, start + chunkSize);
|
|
3329
|
+
const chunkNotifications = rawChunk.map((item) => compactNotification2(item, maxContent));
|
|
3330
|
+
const index = chunks.length;
|
|
3331
|
+
const chunkId = String(index + 1).padStart(4, "0");
|
|
3332
|
+
const end = start + chunkNotifications.length - 1;
|
|
3333
|
+
writeJson(chunkPath(jobDir, chunkId), {
|
|
3334
|
+
id: chunkId,
|
|
3335
|
+
index,
|
|
3336
|
+
start,
|
|
3337
|
+
end,
|
|
3338
|
+
notifications: chunkNotifications
|
|
3339
|
+
});
|
|
3340
|
+
chunks.push({
|
|
3341
|
+
id: chunkId,
|
|
3342
|
+
index,
|
|
3343
|
+
start,
|
|
3344
|
+
end,
|
|
3345
|
+
count: chunkNotifications.length,
|
|
3346
|
+
status: "pending",
|
|
3347
|
+
range: {
|
|
3348
|
+
newest: chunkNotifications[0]?.timestamp ?? null,
|
|
3349
|
+
oldest: chunkNotifications[chunkNotifications.length - 1]?.timestamp ?? null
|
|
3350
|
+
}
|
|
3351
|
+
});
|
|
3352
|
+
}
|
|
3353
|
+
const meta = {
|
|
3354
|
+
schemaVersion: 1,
|
|
3355
|
+
id,
|
|
3356
|
+
status: chunks.length === 0 ? "ready" : "pending",
|
|
3357
|
+
createdAt,
|
|
3358
|
+
updatedAt: createdAt,
|
|
3359
|
+
query: {
|
|
3360
|
+
...query.from ? { from: query.from } : {},
|
|
3361
|
+
...query.to ? { to: query.to } : {},
|
|
3362
|
+
...query.app ? { app: query.app } : {},
|
|
3363
|
+
...query.sender ? { sender: query.sender } : {},
|
|
3364
|
+
...query.conversationType ? { conversationType: query.conversationType } : {},
|
|
3365
|
+
...query.keyword ? { keyword: query.keyword } : {},
|
|
3366
|
+
limit: query.limit
|
|
3367
|
+
},
|
|
3368
|
+
chunkSize,
|
|
3369
|
+
maxContent,
|
|
3370
|
+
total: notifications.length,
|
|
3371
|
+
range: {
|
|
3372
|
+
newest: notifications[0]?.timestamp ?? null,
|
|
3373
|
+
oldest: notifications[notifications.length - 1]?.timestamp ?? null
|
|
3374
|
+
},
|
|
3375
|
+
scan: {
|
|
3376
|
+
daysScanned: queryResult.stats.daysScanned,
|
|
3377
|
+
itemsScanned: queryResult.stats.itemsScanned,
|
|
3378
|
+
matched: queryResult.stats.matched,
|
|
3379
|
+
stoppedAfterLimit: queryResult.stats.stoppedAfterLimit,
|
|
3380
|
+
elapsedMs
|
|
3381
|
+
},
|
|
3382
|
+
chunks
|
|
3383
|
+
};
|
|
3384
|
+
writeJson(metaPath(jobDir), meta);
|
|
3385
|
+
output({
|
|
3386
|
+
...toPublicStatus(meta),
|
|
3387
|
+
nextCommand: `ntf summary-job next ${id}`,
|
|
3388
|
+
resultCommand: buildResultCommand(id)
|
|
3389
|
+
});
|
|
3390
|
+
});
|
|
3391
|
+
summaryJob.command("status <id>").description("查看分片通知总结任务状态").action((id) => {
|
|
3392
|
+
const dir = resolveNotificationsDir(ctx);
|
|
3393
|
+
if (!dir)
|
|
3394
|
+
exitError("STORAGE_UNAVAILABLE", "通知存储目录不可用");
|
|
3395
|
+
const meta = readMeta(dir, id);
|
|
3396
|
+
meta.status = refreshStatus(meta);
|
|
3397
|
+
saveMeta(dir, meta);
|
|
3398
|
+
output(toPublicStatus(meta));
|
|
3399
|
+
});
|
|
3400
|
+
summaryJob.command("next <id>").description("领取或重试下一个待总结分片").action((id) => {
|
|
3401
|
+
const dir = resolveNotificationsDir(ctx);
|
|
3402
|
+
if (!dir)
|
|
3403
|
+
exitError("STORAGE_UNAVAILABLE", "通知存储目录不可用");
|
|
3404
|
+
const meta = readMeta(dir, id);
|
|
3405
|
+
if (meta.status === "cancelled") {
|
|
3406
|
+
exitError("JOB_CANCELLED", `summary job 已取消: ${id}`);
|
|
3407
|
+
}
|
|
3408
|
+
const jobDir = summaryJobDir(dir, id);
|
|
3409
|
+
let chunk = meta.chunks.find((item) => item.status === "in_progress");
|
|
3410
|
+
if (!chunk) {
|
|
3411
|
+
chunk = meta.chunks.find((item) => item.status === "pending");
|
|
3412
|
+
if (chunk) {
|
|
3413
|
+
chunk.status = "in_progress";
|
|
3414
|
+
chunk.claimedAt = nowIso();
|
|
3415
|
+
}
|
|
3416
|
+
}
|
|
3417
|
+
if (!chunk) {
|
|
3418
|
+
meta.status = refreshStatus(meta);
|
|
3419
|
+
saveMeta(dir, meta);
|
|
3420
|
+
output({
|
|
3421
|
+
ok: true,
|
|
3422
|
+
id,
|
|
3423
|
+
done: true,
|
|
3424
|
+
status: meta.status,
|
|
3425
|
+
progress: progressFor(meta),
|
|
3426
|
+
resultCommand: buildResultCommand(id)
|
|
3427
|
+
});
|
|
3428
|
+
return;
|
|
3429
|
+
}
|
|
3430
|
+
meta.status = refreshStatus(meta);
|
|
3431
|
+
saveMeta(dir, meta);
|
|
3432
|
+
output({
|
|
3433
|
+
ok: true,
|
|
3434
|
+
id,
|
|
3435
|
+
done: false,
|
|
3436
|
+
status: meta.status,
|
|
3437
|
+
chunk: {
|
|
3438
|
+
id: chunk.id,
|
|
3439
|
+
index: chunk.index,
|
|
3440
|
+
start: chunk.start,
|
|
3441
|
+
end: chunk.end,
|
|
3442
|
+
count: chunk.count,
|
|
3443
|
+
range: chunk.range
|
|
3444
|
+
},
|
|
3445
|
+
progress: progressFor(meta),
|
|
3446
|
+
commitCommand: buildCommitCommand(id, chunk.id),
|
|
3447
|
+
notifications: readChunkNotifications(jobDir, chunk.id)
|
|
3448
|
+
});
|
|
3449
|
+
});
|
|
3450
|
+
summaryJob.command("commit <id>").description("提交分片摘要并标记分片完成").requiredOption("--chunk-id <id>", "next 返回的分片 id").option("--summary <text>", "直接传入分片摘要文本").option("--summary-file <path>", "读取文件内容作为分片摘要").action((id, opts) => {
|
|
3451
|
+
const dir = resolveNotificationsDir(ctx);
|
|
3452
|
+
if (!dir)
|
|
3453
|
+
exitError("STORAGE_UNAVAILABLE", "通知存储目录不可用");
|
|
3454
|
+
const meta = readMeta(dir, id);
|
|
3455
|
+
if (meta.status === "cancelled") {
|
|
3456
|
+
exitError("JOB_CANCELLED", `summary job 已取消: ${id}`);
|
|
3457
|
+
}
|
|
3458
|
+
const chunk = meta.chunks.find((item) => item.id === opts.chunkId);
|
|
3459
|
+
if (!chunk) {
|
|
3460
|
+
exitError("CHUNK_NOT_FOUND", `summary job 分片不存在: ${opts.chunkId}`);
|
|
3461
|
+
}
|
|
3462
|
+
const text = resolveSummaryText(opts);
|
|
3463
|
+
writeChunkSummary(dir, meta, chunk, text);
|
|
3464
|
+
meta.status = refreshStatus(meta);
|
|
3465
|
+
saveMeta(dir, meta);
|
|
3466
|
+
output({
|
|
3467
|
+
ok: true,
|
|
3468
|
+
id,
|
|
3469
|
+
chunkId: chunk.id,
|
|
3470
|
+
status: meta.status,
|
|
3471
|
+
progress: progressFor(meta),
|
|
3472
|
+
nextCommand: meta.status === "ready" ? null : `ntf summary-job next ${id}`,
|
|
3473
|
+
resultCommand: meta.status === "ready" ? buildResultCommand(id) : null
|
|
3474
|
+
});
|
|
3475
|
+
});
|
|
3476
|
+
summaryJob.command("run <id>").description("自动处理待总结分片并在完成后生成结果").option("--max-chunks <n>", "本次最多自动处理的分片数", String(DEFAULT_RUN_MAX_CHUNKS)).option("--include-result", "完成时在输出中包含 markdown 结果").action((id, opts) => {
|
|
3477
|
+
const dir = resolveNotificationsDir(ctx);
|
|
3478
|
+
if (!dir)
|
|
3479
|
+
exitError("STORAGE_UNAVAILABLE", "通知存储目录不可用");
|
|
3480
|
+
const meta = readMeta(dir, id);
|
|
3481
|
+
if (meta.status === "cancelled") {
|
|
3482
|
+
exitError("JOB_CANCELLED", `summary job 已取消: ${id}`);
|
|
3483
|
+
}
|
|
3484
|
+
const maxChunks = parsePositiveIntegerOption(opts.maxChunks, DEFAULT_RUN_MAX_CHUNKS, "--max-chunks", "INVALID_MAX_CHUNKS");
|
|
3485
|
+
const jobDir = summaryJobDir(dir, id);
|
|
3486
|
+
const processed = [];
|
|
3487
|
+
for (const chunk of meta.chunks) {
|
|
3488
|
+
if (processed.length >= maxChunks)
|
|
3489
|
+
break;
|
|
3490
|
+
if (chunk.status === "done")
|
|
3491
|
+
continue;
|
|
3492
|
+
chunk.status = "in_progress";
|
|
3493
|
+
chunk.claimedAt = nowIso();
|
|
3494
|
+
const notifications = readChunkNotifications(jobDir, chunk.id);
|
|
3495
|
+
const summary = buildExtractiveChunkSummary(chunk, notifications);
|
|
3496
|
+
writeChunkSummary(dir, meta, chunk, summary);
|
|
3497
|
+
processed.push({ chunkId: chunk.id, count: chunk.count });
|
|
3498
|
+
}
|
|
3499
|
+
const markdown = finalizeResultIfReady(dir, meta);
|
|
3500
|
+
if (!markdown) {
|
|
3501
|
+
meta.status = refreshStatus(meta);
|
|
3502
|
+
}
|
|
3503
|
+
saveMeta(dir, meta);
|
|
3504
|
+
output({
|
|
3505
|
+
ok: true,
|
|
3506
|
+
id,
|
|
3507
|
+
status: meta.status,
|
|
3508
|
+
processed,
|
|
3509
|
+
progress: progressFor(meta),
|
|
3510
|
+
nextCommand: meta.status === "complete" ? null : `ntf summary-job run ${id}`,
|
|
3511
|
+
resultCommand: meta.status === "complete" ? buildResultCommand(id) : null,
|
|
3512
|
+
resultFile: meta.resultFile ?? null,
|
|
3513
|
+
...opts.includeResult && markdown ? { markdown } : {}
|
|
3514
|
+
});
|
|
3515
|
+
});
|
|
3516
|
+
summaryJob.command("result <id>").description("合并已提交的分片摘要并返回结果").action((id) => {
|
|
3517
|
+
const dir = resolveNotificationsDir(ctx);
|
|
3518
|
+
if (!dir)
|
|
3519
|
+
exitError("STORAGE_UNAVAILABLE", "通知存储目录不可用");
|
|
3520
|
+
const meta = readMeta(dir, id);
|
|
3521
|
+
meta.status = refreshStatus(meta);
|
|
3522
|
+
if (meta.status !== "ready" && meta.status !== "complete") {
|
|
3523
|
+
saveMeta(dir, meta);
|
|
3524
|
+
output({
|
|
3525
|
+
ok: true,
|
|
3526
|
+
id,
|
|
3527
|
+
done: false,
|
|
3528
|
+
status: meta.status,
|
|
3529
|
+
progress: progressFor(meta),
|
|
3530
|
+
nextCommand: `ntf summary-job next ${id}`
|
|
3531
|
+
});
|
|
3532
|
+
return;
|
|
3533
|
+
}
|
|
3534
|
+
const jobDir = summaryJobDir(dir, id);
|
|
3535
|
+
const markdown = finalizeResultIfReady(dir, meta) ?? buildResultMarkdown(meta, jobDir);
|
|
3536
|
+
saveMeta(dir, meta);
|
|
3537
|
+
output({
|
|
3538
|
+
ok: true,
|
|
3539
|
+
id,
|
|
3540
|
+
done: true,
|
|
3541
|
+
status: meta.status,
|
|
3542
|
+
resultFile: RESULT_FILE,
|
|
3543
|
+
markdown
|
|
3544
|
+
});
|
|
3545
|
+
});
|
|
3546
|
+
summaryJob.command("cancel <id>").description("取消分片通知总结任务").action((id) => {
|
|
3547
|
+
const dir = resolveNotificationsDir(ctx);
|
|
3548
|
+
if (!dir)
|
|
3549
|
+
exitError("STORAGE_UNAVAILABLE", "通知存储目录不可用");
|
|
3550
|
+
const meta = readMeta(dir, id);
|
|
3551
|
+
meta.status = "cancelled";
|
|
3552
|
+
saveMeta(dir, meta);
|
|
3553
|
+
output({
|
|
3554
|
+
ok: true,
|
|
3555
|
+
id,
|
|
3556
|
+
status: meta.status,
|
|
3557
|
+
progress: progressFor(meta)
|
|
3558
|
+
});
|
|
3559
|
+
});
|
|
3560
|
+
}
|
|
3561
|
+
|
|
2959
3562
|
// src/cli/ntf-stats.ts
|
|
2960
3563
|
function registerNtfStats(ntf, ctx) {
|
|
2961
3564
|
ntf.command("stats").description("通知统计分析(按日期/应用/发送人/时段聚合)").option("--from <date>", "开始日期 YYYY-MM-DD", daysAgo(7)).option("--to <date>", "结束日期 YYYY-MM-DD", today()).option("--app <name>", "只统计指定应用").option("--dim <dimension>", "统计维度:date/app/sender/hour/all", "all").action((opts) => {
|
|
@@ -3013,24 +3616,25 @@ function registerNtfStats(ntf, ctx) {
|
|
|
3013
3616
|
}
|
|
3014
3617
|
|
|
3015
3618
|
// src/cli/ntf-sync.ts
|
|
3016
|
-
var
|
|
3017
|
-
var
|
|
3619
|
+
var import_node_child_process = require("node:child_process");
|
|
3620
|
+
var import_node_fs7 = require("node:fs");
|
|
3621
|
+
var import_node_path7 = require("node:path");
|
|
3018
3622
|
var SYNC_FETCH_LIMIT = 100;
|
|
3019
3623
|
function checkpointPath(dir) {
|
|
3020
|
-
return
|
|
3624
|
+
return import_node_path7.join(dir, ".checkpoint.json");
|
|
3021
3625
|
}
|
|
3022
3626
|
function readCheckpoint(dir) {
|
|
3023
3627
|
const p = checkpointPath(dir);
|
|
3024
|
-
if (!
|
|
3628
|
+
if (!import_node_fs7.existsSync(p))
|
|
3025
3629
|
return {};
|
|
3026
3630
|
try {
|
|
3027
|
-
return JSON.parse(
|
|
3631
|
+
return JSON.parse(import_node_fs7.readFileSync(p, "utf-8"));
|
|
3028
3632
|
} catch {
|
|
3029
3633
|
return {};
|
|
3030
3634
|
}
|
|
3031
3635
|
}
|
|
3032
3636
|
function writeCheckpoint(dir, data) {
|
|
3033
|
-
|
|
3637
|
+
import_node_fs7.writeFileSync(checkpointPath(dir), JSON.stringify(data, null, 2), "utf-8");
|
|
3034
3638
|
}
|
|
3035
3639
|
function validateDateKey(value, optionName) {
|
|
3036
3640
|
const match = /^(\d{4})-(\d{2})-(\d{2})$/.exec(value);
|
|
@@ -3081,6 +3685,28 @@ function isDateInScope(dateKey, scope) {
|
|
|
3081
3685
|
return false;
|
|
3082
3686
|
return true;
|
|
3083
3687
|
}
|
|
3688
|
+
var MEMORY_PLUGIN_KEYWORD = "memory-lancedb-ultra";
|
|
3689
|
+
function detectMemoryBackend() {
|
|
3690
|
+
try {
|
|
3691
|
+
const out = import_node_child_process.execFileSync("openclaw", ["plugins", "list"], {
|
|
3692
|
+
encoding: "utf-8",
|
|
3693
|
+
timeout: 15000,
|
|
3694
|
+
stdio: ["ignore", "pipe", "ignore"]
|
|
3695
|
+
});
|
|
3696
|
+
const matched = out.split(`
|
|
3697
|
+
`).filter((line) => line.toLowerCase().includes(MEMORY_PLUGIN_KEYWORD));
|
|
3698
|
+
if (matched.length > 0 && !matched.some((line) => /disabled/i.test(line))) {
|
|
3699
|
+
return {
|
|
3700
|
+
backend: "lancedb-plugin",
|
|
3701
|
+
promptFile: "skills/notification-to-memory/references/write-memory-lancedb-plugin.md"
|
|
3702
|
+
};
|
|
3703
|
+
}
|
|
3704
|
+
} catch {}
|
|
3705
|
+
return {
|
|
3706
|
+
backend: "openclaw-native",
|
|
3707
|
+
promptFile: "skills/notification-to-memory/references/write-memory-openclaw-native.md"
|
|
3708
|
+
};
|
|
3709
|
+
}
|
|
3084
3710
|
function registerNtfSync(ntf, ctx) {
|
|
3085
3711
|
const sync = ntf.command("sync").description("同步通知到记忆系统");
|
|
3086
3712
|
sync.command("scan").description("扫描未处理的通知,默认只返回本地当天的待同步摘要").option("--all", "扫描 checkpoint 之后所有日期的未处理通知").option("--date <date>", "只扫描指定日期 YYYY-MM-DD").option("--from-date <date>", "只扫描该日期及之后的数据 YYYY-MM-DD").option("--to-date <date>", "只扫描该日期及之前的数据 YYYY-MM-DD").action((opts) => {
|
|
@@ -3117,6 +3743,67 @@ function registerNtfSync(ntf, ctx) {
|
|
|
3117
3743
|
allDatesTotalPending: totalPending + outsideScopePending
|
|
3118
3744
|
});
|
|
3119
3745
|
});
|
|
3746
|
+
sync.command("next").description("返回范围内下一批待同步通知(≤100 条)及记忆写入提示词路径;全部处理完时返回 done=true").option("--all", "处理 checkpoint 之后所有日期的未处理通知").option("--date <date>", "只处理指定日期 YYYY-MM-DD").option("--from-date <date>", "只处理该日期及之后的数据 YYYY-MM-DD").option("--to-date <date>", "只处理该日期及之前的数据 YYYY-MM-DD").action((opts) => {
|
|
3747
|
+
const dir = resolveNotificationsDir(ctx);
|
|
3748
|
+
if (!dir)
|
|
3749
|
+
exitError("STORAGE_UNAVAILABLE", "通知存储目录不可用");
|
|
3750
|
+
const scope = resolveScanScope(opts);
|
|
3751
|
+
const checkpoint = readCheckpoint(dir);
|
|
3752
|
+
const keys = listDateKeys(dir);
|
|
3753
|
+
let totalPending = 0;
|
|
3754
|
+
let outsideScopePending = 0;
|
|
3755
|
+
let nextDate = null;
|
|
3756
|
+
let nextItems = [];
|
|
3757
|
+
let nextStartIndex = 0;
|
|
3758
|
+
for (const dateKey of keys) {
|
|
3759
|
+
const items = readDateFile(dir, dateKey);
|
|
3760
|
+
const lastIndex = checkpoint[dateKey]?.lastIndex ?? -1;
|
|
3761
|
+
const unprocessed = items.length - (lastIndex + 1);
|
|
3762
|
+
if (unprocessed <= 0)
|
|
3763
|
+
continue;
|
|
3764
|
+
if (!isDateInScope(dateKey, scope)) {
|
|
3765
|
+
outsideScopePending += unprocessed;
|
|
3766
|
+
continue;
|
|
3767
|
+
}
|
|
3768
|
+
totalPending += unprocessed;
|
|
3769
|
+
if (!nextDate) {
|
|
3770
|
+
nextDate = dateKey;
|
|
3771
|
+
nextItems = items;
|
|
3772
|
+
nextStartIndex = lastIndex + 1;
|
|
3773
|
+
}
|
|
3774
|
+
}
|
|
3775
|
+
if (!nextDate) {
|
|
3776
|
+
output({
|
|
3777
|
+
ok: true,
|
|
3778
|
+
done: true,
|
|
3779
|
+
scope,
|
|
3780
|
+
totalPending: 0,
|
|
3781
|
+
outsideScopePending,
|
|
3782
|
+
allDatesTotalPending: outsideScopePending
|
|
3783
|
+
});
|
|
3784
|
+
return;
|
|
3785
|
+
}
|
|
3786
|
+
const notifications = nextItems.slice(nextStartIndex, nextStartIndex + SYNC_FETCH_LIMIT);
|
|
3787
|
+
const endIndex = nextStartIndex + notifications.length - 1;
|
|
3788
|
+
const unprocessedInDate = nextItems.length - nextStartIndex;
|
|
3789
|
+
const memory = detectMemoryBackend();
|
|
3790
|
+
output({
|
|
3791
|
+
ok: true,
|
|
3792
|
+
done: false,
|
|
3793
|
+
scope,
|
|
3794
|
+
date: nextDate,
|
|
3795
|
+
startIndex: nextStartIndex,
|
|
3796
|
+
endIndex,
|
|
3797
|
+
returned: notifications.length,
|
|
3798
|
+
hasMoreInDate: unprocessedInDate > notifications.length,
|
|
3799
|
+
remainingInScope: totalPending - notifications.length,
|
|
3800
|
+
outsideScopePending,
|
|
3801
|
+
memoryBackend: memory.backend,
|
|
3802
|
+
memoryPromptFile: memory.promptFile,
|
|
3803
|
+
commitCommand: `ntf sync commit --date ${nextDate} --end-index ${endIndex}`,
|
|
3804
|
+
notifications
|
|
3805
|
+
});
|
|
3806
|
+
});
|
|
3120
3807
|
sync.command("fetch").description("获取指定日期的未处理通知详情").requiredOption("--date <date>", "目标日期 YYYY-MM-DD").option("--max-end-index <index>", "本次同步快照允许读取的最大 endIndex").action((opts) => {
|
|
3121
3808
|
const dir = resolveNotificationsDir(ctx);
|
|
3122
3809
|
if (!dir)
|
|
@@ -3218,8 +3905,8 @@ function registerNtfSync(ntf, ctx) {
|
|
|
3218
3905
|
}
|
|
3219
3906
|
|
|
3220
3907
|
// src/cli/ntf-monitor.ts
|
|
3221
|
-
var
|
|
3222
|
-
var
|
|
3908
|
+
var import_node_fs8 = require("node:fs");
|
|
3909
|
+
var import_node_path8 = require("node:path");
|
|
3223
3910
|
|
|
3224
3911
|
// src/monitor/fetch-gen.ts
|
|
3225
3912
|
function generateFetchPy(name, matchRules) {
|
|
@@ -3306,20 +3993,20 @@ function tasksDir(ctx) {
|
|
|
3306
3993
|
const base = ctx.workspaceDir || ctx.stateDir;
|
|
3307
3994
|
if (!base)
|
|
3308
3995
|
throw new Error("workspaceDir and stateDir both unavailable");
|
|
3309
|
-
return
|
|
3996
|
+
return import_node_path8.join(base, "tasks");
|
|
3310
3997
|
}
|
|
3311
|
-
function
|
|
3312
|
-
const
|
|
3313
|
-
if (!
|
|
3998
|
+
function readMeta2(taskDir) {
|
|
3999
|
+
const metaPath2 = import_node_path8.join(taskDir, "meta.json");
|
|
4000
|
+
if (!import_node_fs8.existsSync(metaPath2))
|
|
3314
4001
|
return null;
|
|
3315
4002
|
try {
|
|
3316
|
-
return JSON.parse(
|
|
4003
|
+
return JSON.parse(import_node_fs8.readFileSync(metaPath2, "utf-8"));
|
|
3317
4004
|
} catch {
|
|
3318
4005
|
return null;
|
|
3319
4006
|
}
|
|
3320
4007
|
}
|
|
3321
4008
|
function writeMeta(taskDir, meta) {
|
|
3322
|
-
|
|
4009
|
+
import_node_fs8.writeFileSync(import_node_path8.join(taskDir, "meta.json"), JSON.stringify(meta, null, 2), "utf-8");
|
|
3323
4010
|
}
|
|
3324
4011
|
function generateReadme(name, description) {
|
|
3325
4012
|
return `# Monitor Task: ${name}
|
|
@@ -3338,30 +4025,30 @@ function registerNtfMonitor(ntf, ctx) {
|
|
|
3338
4025
|
const monitor = ntf.command("monitor").description("通知监控任务管理");
|
|
3339
4026
|
monitor.command("list").description("列出所有监控任务").action(() => {
|
|
3340
4027
|
const dir = tasksDir(ctx);
|
|
3341
|
-
if (!
|
|
4028
|
+
if (!import_node_fs8.existsSync(dir)) {
|
|
3342
4029
|
output({ ok: true, tasks: [] });
|
|
3343
4030
|
return;
|
|
3344
4031
|
}
|
|
3345
4032
|
const tasks = [];
|
|
3346
|
-
for (const entry of
|
|
4033
|
+
for (const entry of import_node_fs8.readdirSync(dir, { withFileTypes: true })) {
|
|
3347
4034
|
if (!entry.isDirectory())
|
|
3348
4035
|
continue;
|
|
3349
|
-
const meta =
|
|
4036
|
+
const meta = readMeta2(import_node_path8.join(dir, entry.name));
|
|
3350
4037
|
if (meta)
|
|
3351
4038
|
tasks.push(meta);
|
|
3352
4039
|
}
|
|
3353
4040
|
output({ ok: true, tasks });
|
|
3354
4041
|
});
|
|
3355
4042
|
monitor.command("show <name>").description("查看监控任务详情").action((name) => {
|
|
3356
|
-
const taskDir =
|
|
3357
|
-
const meta =
|
|
4043
|
+
const taskDir = import_node_path8.join(tasksDir(ctx), name);
|
|
4044
|
+
const meta = readMeta2(taskDir);
|
|
3358
4045
|
if (!meta)
|
|
3359
4046
|
exitError("NOT_FOUND", `监控任务 '${name}' 不存在`);
|
|
3360
|
-
const checkpointPath2 =
|
|
4047
|
+
const checkpointPath2 = import_node_path8.join(taskDir, "checkpoint.json");
|
|
3361
4048
|
let checkpoint = {};
|
|
3362
|
-
if (
|
|
4049
|
+
if (import_node_fs8.existsSync(checkpointPath2)) {
|
|
3363
4050
|
try {
|
|
3364
|
-
checkpoint = JSON.parse(
|
|
4051
|
+
checkpoint = JSON.parse(import_node_fs8.readFileSync(checkpointPath2, "utf-8"));
|
|
3365
4052
|
} catch {}
|
|
3366
4053
|
}
|
|
3367
4054
|
output({
|
|
@@ -3374,8 +4061,8 @@ function registerNtfMonitor(ntf, ctx) {
|
|
|
3374
4061
|
});
|
|
3375
4062
|
monitor.command("create <name>").description("创建监控任务").requiredOption("--description <text>", "任务描述").requiredOption("--match-rules <json>", "匹配规则 JSON").requiredOption("--schedule <cron>", "cron 表达式").action((name, opts) => {
|
|
3376
4063
|
const dir = tasksDir(ctx);
|
|
3377
|
-
const taskDir =
|
|
3378
|
-
if (
|
|
4064
|
+
const taskDir = import_node_path8.join(dir, name);
|
|
4065
|
+
if (import_node_fs8.existsSync(taskDir)) {
|
|
3379
4066
|
exitError("ALREADY_EXISTS", `监控任务 '${name}' 已存在`);
|
|
3380
4067
|
}
|
|
3381
4068
|
let matchRules;
|
|
@@ -3384,7 +4071,7 @@ function registerNtfMonitor(ntf, ctx) {
|
|
|
3384
4071
|
} catch {
|
|
3385
4072
|
exitError("VALIDATION_FAILED", "match-rules 必须是合法的 JSON");
|
|
3386
4073
|
}
|
|
3387
|
-
|
|
4074
|
+
import_node_fs8.mkdirSync(taskDir, { recursive: true });
|
|
3388
4075
|
const meta = {
|
|
3389
4076
|
name,
|
|
3390
4077
|
description: opts.description,
|
|
@@ -3394,8 +4081,8 @@ function registerNtfMonitor(ntf, ctx) {
|
|
|
3394
4081
|
createdAt: new Date().toISOString()
|
|
3395
4082
|
};
|
|
3396
4083
|
writeMeta(taskDir, meta);
|
|
3397
|
-
|
|
3398
|
-
|
|
4084
|
+
import_node_fs8.writeFileSync(import_node_path8.join(taskDir, "fetch.py"), generateFetchPy(name, matchRules), "utf-8");
|
|
4085
|
+
import_node_fs8.writeFileSync(import_node_path8.join(taskDir, "README.md"), generateReadme(name, opts.description), "utf-8");
|
|
3399
4086
|
output({
|
|
3400
4087
|
ok: true,
|
|
3401
4088
|
name,
|
|
@@ -3419,8 +4106,8 @@ function registerNtfMonitor(ntf, ctx) {
|
|
|
3419
4106
|
});
|
|
3420
4107
|
});
|
|
3421
4108
|
monitor.command("delete <name>").description("删除监控任务").option("--yes", "跳过确认").action((name, opts) => {
|
|
3422
|
-
const taskDir =
|
|
3423
|
-
if (!
|
|
4109
|
+
const taskDir = import_node_path8.join(tasksDir(ctx), name);
|
|
4110
|
+
if (!import_node_fs8.existsSync(taskDir)) {
|
|
3424
4111
|
exitError("NOT_FOUND", `监控任务 '${name}' 不存在`);
|
|
3425
4112
|
}
|
|
3426
4113
|
if (!opts.yes) {
|
|
@@ -3433,7 +4120,7 @@ function registerNtfMonitor(ntf, ctx) {
|
|
|
3433
4120
|
});
|
|
3434
4121
|
process.exit(1);
|
|
3435
4122
|
}
|
|
3436
|
-
|
|
4123
|
+
import_node_fs8.rmSync(taskDir, { recursive: true, force: true });
|
|
3437
4124
|
output({
|
|
3438
4125
|
ok: true,
|
|
3439
4126
|
name,
|
|
@@ -3445,8 +4132,8 @@ function registerNtfMonitor(ntf, ctx) {
|
|
|
3445
4132
|
});
|
|
3446
4133
|
});
|
|
3447
4134
|
monitor.command("enable <name>").description("启用监控任务").action((name) => {
|
|
3448
|
-
const taskDir =
|
|
3449
|
-
const meta =
|
|
4135
|
+
const taskDir = import_node_path8.join(tasksDir(ctx), name);
|
|
4136
|
+
const meta = readMeta2(taskDir);
|
|
3450
4137
|
if (!meta)
|
|
3451
4138
|
exitError("NOT_FOUND", `监控任务 '${name}' 不存在`);
|
|
3452
4139
|
meta.enabled = true;
|
|
@@ -3454,8 +4141,8 @@ function registerNtfMonitor(ntf, ctx) {
|
|
|
3454
4141
|
output({ ok: true, name, enabled: true });
|
|
3455
4142
|
});
|
|
3456
4143
|
monitor.command("disable <name>").description("暂停监控任务").action((name) => {
|
|
3457
|
-
const taskDir =
|
|
3458
|
-
const meta =
|
|
4144
|
+
const taskDir = import_node_path8.join(tasksDir(ctx), name);
|
|
4145
|
+
const meta = readMeta2(taskDir);
|
|
3459
4146
|
if (!meta)
|
|
3460
4147
|
exitError("NOT_FOUND", `监控任务 '${name}' 不存在`);
|
|
3461
4148
|
meta.enabled = false;
|
|
@@ -3465,7 +4152,7 @@ function registerNtfMonitor(ntf, ctx) {
|
|
|
3465
4152
|
}
|
|
3466
4153
|
|
|
3467
4154
|
// src/light/sender.ts
|
|
3468
|
-
var
|
|
4155
|
+
var import_node_crypto3 = require("node:crypto");
|
|
3469
4156
|
|
|
3470
4157
|
// src/light/repeat.ts
|
|
3471
4158
|
function normalizeRepeatTimes(input) {
|
|
@@ -3971,9 +4658,9 @@ function quantizeWindow(value) {
|
|
|
3971
4658
|
}
|
|
3972
4659
|
|
|
3973
4660
|
// src/env.ts
|
|
3974
|
-
var
|
|
3975
|
-
var
|
|
3976
|
-
var
|
|
4661
|
+
var import_node_crypto2 = require("node:crypto");
|
|
4662
|
+
var import_node_fs9 = require("node:fs");
|
|
4663
|
+
var import_node_path9 = require("node:path");
|
|
3977
4664
|
function parseEnvContent(content) {
|
|
3978
4665
|
const entries = new Map;
|
|
3979
4666
|
const tokens = content.split(/\r?\n/).flatMap((line) => line.split(/(?<![A-Z0-9_])(?=[A-Z_][A-Z0-9_]*=)/));
|
|
@@ -3993,20 +4680,20 @@ function parseEnvContent(content) {
|
|
|
3993
4680
|
}
|
|
3994
4681
|
function writeDotEnv(key, value) {
|
|
3995
4682
|
const path = resolveStateFile(".env");
|
|
3996
|
-
|
|
3997
|
-
const existing =
|
|
4683
|
+
import_node_fs9.mkdirSync(import_node_path9.dirname(path), { recursive: true });
|
|
4684
|
+
const existing = import_node_fs9.existsSync(path) ? import_node_fs9.readFileSync(path, "utf-8") : "";
|
|
3998
4685
|
const entries = parseEnvContent(existing);
|
|
3999
4686
|
entries.set(key, value);
|
|
4000
4687
|
const output2 = Array.from(entries, ([k, v]) => `${k}=${v}`).join(`
|
|
4001
4688
|
`) + `
|
|
4002
4689
|
`;
|
|
4003
|
-
const tmpPath = `${path}.tmp.${process.pid}.${
|
|
4690
|
+
const tmpPath = `${path}.tmp.${process.pid}.${import_node_crypto2.randomBytes(4).toString("hex")}`;
|
|
4004
4691
|
try {
|
|
4005
|
-
|
|
4006
|
-
|
|
4692
|
+
import_node_fs9.writeFileSync(tmpPath, output2, { encoding: "utf-8", mode: 384 });
|
|
4693
|
+
import_node_fs9.renameSync(tmpPath, path);
|
|
4007
4694
|
} catch (error) {
|
|
4008
4695
|
try {
|
|
4009
|
-
|
|
4696
|
+
import_node_fs9.rmSync(tmpPath, { force: true });
|
|
4010
4697
|
} catch {}
|
|
4011
4698
|
throw error;
|
|
4012
4699
|
}
|
|
@@ -4016,6 +4703,10 @@ var DEFAULT_ENV_HOSTS = {
|
|
|
4016
4703
|
test: "openclaw-service-test.yoooclaw.com",
|
|
4017
4704
|
production: "openclaw-service.yoooclaw.com"
|
|
4018
4705
|
};
|
|
4706
|
+
var NOTIFICATION_INTELLIGENCE_LIGHT_RULES_PLUGIN_PATH = "/api/plugin/notification-intelligence/light-rules";
|
|
4707
|
+
var NOTIFICATION_INTELLIGENCE_LIGHT_RULES_APP_PATH = "/api/notification-intelligence/light-rules";
|
|
4708
|
+
var NOTIFICATION_INTELLIGENCE_PLUGIN_BASE_PATH = "/api/plugin/notification-intelligence";
|
|
4709
|
+
var NOTIFICATION_INTELLIGENCE_APP_BASE_PATH = "/api/notification-intelligence";
|
|
4019
4710
|
function normalizeHost(host) {
|
|
4020
4711
|
const trimmed = host?.trim();
|
|
4021
4712
|
if (!trimmed)
|
|
@@ -4037,11 +4728,36 @@ function getEnvHost(env) {
|
|
|
4037
4728
|
}
|
|
4038
4729
|
return normalizeHost(host) ?? DEFAULT_ENV_HOSTS[env];
|
|
4039
4730
|
}
|
|
4731
|
+
function normalizeNotificationIntelligenceLightRulesPluginUrl(url) {
|
|
4732
|
+
const trimmed = url.trim().replace(/\/+$/, "");
|
|
4733
|
+
if (!trimmed)
|
|
4734
|
+
return trimmed;
|
|
4735
|
+
if (trimmed.endsWith(NOTIFICATION_INTELLIGENCE_LIGHT_RULES_PLUGIN_PATH)) {
|
|
4736
|
+
return trimmed;
|
|
4737
|
+
}
|
|
4738
|
+
if (trimmed.endsWith(NOTIFICATION_INTELLIGENCE_LIGHT_RULES_APP_PATH)) {
|
|
4739
|
+
const origin = trimmed.slice(0, -NOTIFICATION_INTELLIGENCE_LIGHT_RULES_APP_PATH.length);
|
|
4740
|
+
return `${origin}${NOTIFICATION_INTELLIGENCE_LIGHT_RULES_PLUGIN_PATH}`;
|
|
4741
|
+
}
|
|
4742
|
+
if (trimmed.endsWith(NOTIFICATION_INTELLIGENCE_PLUGIN_BASE_PATH)) {
|
|
4743
|
+
return `${trimmed}/light-rules`;
|
|
4744
|
+
}
|
|
4745
|
+
if (trimmed.endsWith(NOTIFICATION_INTELLIGENCE_APP_BASE_PATH)) {
|
|
4746
|
+
const origin = trimmed.slice(0, -NOTIFICATION_INTELLIGENCE_APP_BASE_PATH.length);
|
|
4747
|
+
return `${origin}${NOTIFICATION_INTELLIGENCE_LIGHT_RULES_PLUGIN_PATH}`;
|
|
4748
|
+
}
|
|
4749
|
+
if (/^https?:\/\/[^/]+$/i.test(trimmed)) {
|
|
4750
|
+
return `${trimmed}${NOTIFICATION_INTELLIGENCE_LIGHT_RULES_PLUGIN_PATH}`;
|
|
4751
|
+
}
|
|
4752
|
+
return trimmed;
|
|
4753
|
+
}
|
|
4040
4754
|
function buildEnvUrls(host) {
|
|
4041
4755
|
const https = `https://${host}`;
|
|
4042
4756
|
const wss = `wss://${host}`;
|
|
4757
|
+
const lightRulesUrl = normalizeNotificationIntelligenceLightRulesPluginUrl(process.env.NOTIFICATION_INTELLIGENCE_LIGHT_RULES_URL || `${https}${NOTIFICATION_INTELLIGENCE_LIGHT_RULES_PLUGIN_PATH}`);
|
|
4043
4758
|
return {
|
|
4044
4759
|
lightApiUrl: `${https}/api/message/tob/sendMessage`,
|
|
4760
|
+
notificationIntelligenceLightRulesPluginUrl: lightRulesUrl,
|
|
4045
4761
|
relayTunnelUrl: `${wss}/message/messages/ws/plugin`,
|
|
4046
4762
|
appNameMapUrl: `${https}/api/application-config/app-package/config-all`,
|
|
4047
4763
|
modelProxyLongRecordingSubmitTaskUrl: `${https}/api/model-proxy/long-recording/submit-task`,
|
|
@@ -4057,9 +4773,9 @@ var VALID_ENVS = new Set([
|
|
|
4057
4773
|
]);
|
|
4058
4774
|
function readDotEnv() {
|
|
4059
4775
|
const path = resolveStateFile(".env");
|
|
4060
|
-
if (!
|
|
4776
|
+
if (!import_node_fs9.existsSync(path))
|
|
4061
4777
|
return {};
|
|
4062
|
-
return Object.fromEntries(parseEnvContent(
|
|
4778
|
+
return Object.fromEntries(parseEnvContent(import_node_fs9.readFileSync(path, "utf-8")));
|
|
4063
4779
|
}
|
|
4064
4780
|
function readPersistedEnvName() {
|
|
4065
4781
|
const fromDotEnv = readDotEnv()["PHONE_NOTIFICATIONS_ENV"]?.trim();
|
|
@@ -4111,7 +4827,7 @@ async function sendLightEffect(apiKey, segments, logger, repeatInput, reason, ti
|
|
|
4111
4827
|
} catch (error) {
|
|
4112
4828
|
return { ok: false, error: error?.message ?? String(error) };
|
|
4113
4829
|
}
|
|
4114
|
-
const bizUniqueId =
|
|
4830
|
+
const bizUniqueId = import_node_crypto3.randomUUID();
|
|
4115
4831
|
const requestBody = {
|
|
4116
4832
|
appKey,
|
|
4117
4833
|
bizMap: { noticeType: "APP_NOTIFICATION_IMPORTANT", title: resolvedTitle, reason },
|
|
@@ -4177,8 +4893,8 @@ function registerLightSend(light) {
|
|
|
4177
4893
|
}
|
|
4178
4894
|
|
|
4179
4895
|
// src/cli/light-setup-tools.ts
|
|
4180
|
-
var
|
|
4181
|
-
var
|
|
4896
|
+
var import_node_fs10 = require("node:fs");
|
|
4897
|
+
var import_node_path10 = require("node:path");
|
|
4182
4898
|
|
|
4183
4899
|
// src/light-rules/names.ts
|
|
4184
4900
|
var LIGHT_RULE_GATEWAY_METHODS = {
|
|
@@ -4252,8 +4968,8 @@ function upsertToolsForPolicy(policy, tools) {
|
|
|
4252
4968
|
function resolveConfigPath2() {
|
|
4253
4969
|
return resolveConfigPath();
|
|
4254
4970
|
}
|
|
4255
|
-
var LIGHT_TOOLS = [
|
|
4256
|
-
function
|
|
4971
|
+
var LIGHT_TOOLS = [...LIGHT_RULE_TOOL_NAME_LIST];
|
|
4972
|
+
function upsertLightRuleToolsAlsoAllow(cfg) {
|
|
4257
4973
|
if (!isObject(cfg.tools))
|
|
4258
4974
|
cfg.tools = {};
|
|
4259
4975
|
const globalChanged = upsertToolsForPolicy(cfg.tools, LIGHT_TOOLS);
|
|
@@ -4272,24 +4988,24 @@ function upsertLightControlAlsoAllow(cfg) {
|
|
|
4272
4988
|
return { globalChanged, mainAgentChanged };
|
|
4273
4989
|
}
|
|
4274
4990
|
function registerLightSetupTools(light) {
|
|
4275
|
-
light.command("setup").description("
|
|
4991
|
+
light.command("setup").description("自动放行云端灯效规则工具(兼容 tools.allow / tools.alsoAllow)").action(() => {
|
|
4276
4992
|
const configPath = resolveConfigPath2();
|
|
4277
|
-
if (!
|
|
4993
|
+
if (!import_node_fs10.existsSync(configPath)) {
|
|
4278
4994
|
exitError("CONFIG_NOT_FOUND", `未找到配置文件: ${configPath}`);
|
|
4279
4995
|
}
|
|
4280
4996
|
let cfg = {};
|
|
4281
4997
|
try {
|
|
4282
|
-
const raw =
|
|
4998
|
+
const raw = import_node_fs10.readFileSync(configPath, "utf-8");
|
|
4283
4999
|
const parsed = JSON.parse(raw);
|
|
4284
5000
|
if (isObject(parsed))
|
|
4285
5001
|
cfg = parsed;
|
|
4286
5002
|
} catch (err) {
|
|
4287
5003
|
exitError("CONFIG_INVALID", `读取/解析配置失败: ${err?.message ?? String(err)}`);
|
|
4288
5004
|
}
|
|
4289
|
-
const result =
|
|
5005
|
+
const result = upsertLightRuleToolsAlsoAllow(cfg);
|
|
4290
5006
|
try {
|
|
4291
|
-
|
|
4292
|
-
|
|
5007
|
+
import_node_fs10.mkdirSync(import_node_path10.dirname(configPath), { recursive: true });
|
|
5008
|
+
import_node_fs10.writeFileSync(configPath, JSON.stringify(cfg, null, 2) + `
|
|
4293
5009
|
`, "utf-8");
|
|
4294
5010
|
} catch (err) {
|
|
4295
5011
|
exitError("WRITE_FAILED", `写入配置失败: ${err?.message ?? String(err)}`);
|
|
@@ -4308,10 +5024,10 @@ function registerLightSetupTools(light) {
|
|
|
4308
5024
|
}
|
|
4309
5025
|
|
|
4310
5026
|
// src/tunnel/status.ts
|
|
4311
|
-
var
|
|
4312
|
-
var
|
|
4313
|
-
var TUNNEL_STATUS_REL_PATH =
|
|
4314
|
-
var TUNNEL_LOCK_REL_PATH =
|
|
5027
|
+
var import_node_fs11 = require("node:fs");
|
|
5028
|
+
var import_node_path11 = require("node:path");
|
|
5029
|
+
var TUNNEL_STATUS_REL_PATH = import_node_path11.join("plugins", "phone-notifications", "tunnel-status.json");
|
|
5030
|
+
var TUNNEL_LOCK_REL_PATH = import_node_path11.join("plugins", "phone-notifications", "relay-tunnel.lock");
|
|
4315
5031
|
function isTunnelState(value) {
|
|
4316
5032
|
return value === "connected" || value === "connecting" || value === "disconnected" || value === "stopped";
|
|
4317
5033
|
}
|
|
@@ -4360,9 +5076,9 @@ function staleStatusMessage(status, lock) {
|
|
|
4360
5076
|
return `${prefix},但当前运行锁启动于 ${lock.startedAt},晚于状态时间,说明状态未随新进程刷新。请重启 openclaw 主进程。`;
|
|
4361
5077
|
}
|
|
4362
5078
|
function assessTunnelStatus(stateDir) {
|
|
4363
|
-
const statusFilePath =
|
|
4364
|
-
const lockFilePath =
|
|
4365
|
-
if (!
|
|
5079
|
+
const statusFilePath = import_node_path11.join(stateDir, TUNNEL_STATUS_REL_PATH);
|
|
5080
|
+
const lockFilePath = import_node_path11.join(stateDir, TUNNEL_LOCK_REL_PATH);
|
|
5081
|
+
if (!import_node_fs11.existsSync(statusFilePath)) {
|
|
4366
5082
|
return {
|
|
4367
5083
|
status: null,
|
|
4368
5084
|
issueCode: "STATUS_NOT_FOUND",
|
|
@@ -4370,7 +5086,7 @@ function assessTunnelStatus(stateDir) {
|
|
|
4370
5086
|
statusFilePath,
|
|
4371
5087
|
lockFilePath,
|
|
4372
5088
|
lock: {
|
|
4373
|
-
exists:
|
|
5089
|
+
exists: import_node_fs11.existsSync(lockFilePath),
|
|
4374
5090
|
pid: null,
|
|
4375
5091
|
startedAt: null,
|
|
4376
5092
|
active: null
|
|
@@ -4379,7 +5095,7 @@ function assessTunnelStatus(stateDir) {
|
|
|
4379
5095
|
}
|
|
4380
5096
|
let rawStatus;
|
|
4381
5097
|
try {
|
|
4382
|
-
rawStatus = JSON.parse(
|
|
5098
|
+
rawStatus = JSON.parse(import_node_fs11.readFileSync(statusFilePath, "utf-8"));
|
|
4383
5099
|
} catch {
|
|
4384
5100
|
return {
|
|
4385
5101
|
status: null,
|
|
@@ -4388,7 +5104,7 @@ function assessTunnelStatus(stateDir) {
|
|
|
4388
5104
|
statusFilePath,
|
|
4389
5105
|
lockFilePath,
|
|
4390
5106
|
lock: {
|
|
4391
|
-
exists:
|
|
5107
|
+
exists: import_node_fs11.existsSync(lockFilePath),
|
|
4392
5108
|
pid: null,
|
|
4393
5109
|
startedAt: null,
|
|
4394
5110
|
active: null
|
|
@@ -4403,7 +5119,7 @@ function assessTunnelStatus(stateDir) {
|
|
|
4403
5119
|
statusFilePath,
|
|
4404
5120
|
lockFilePath,
|
|
4405
5121
|
lock: {
|
|
4406
|
-
exists:
|
|
5122
|
+
exists: import_node_fs11.existsSync(lockFilePath),
|
|
4407
5123
|
pid: null,
|
|
4408
5124
|
startedAt: null,
|
|
4409
5125
|
active: null
|
|
@@ -4411,11 +5127,11 @@ function assessTunnelStatus(stateDir) {
|
|
|
4411
5127
|
};
|
|
4412
5128
|
}
|
|
4413
5129
|
const status = rawStatus;
|
|
4414
|
-
const lockExists =
|
|
5130
|
+
const lockExists = import_node_fs11.existsSync(lockFilePath);
|
|
4415
5131
|
let lockInfo = null;
|
|
4416
5132
|
if (lockExists) {
|
|
4417
5133
|
try {
|
|
4418
|
-
lockInfo = parseTunnelLockInfo(JSON.parse(
|
|
5134
|
+
lockInfo = parseTunnelLockInfo(JSON.parse(import_node_fs11.readFileSync(lockFilePath, "utf-8")));
|
|
4419
5135
|
} catch {
|
|
4420
5136
|
lockInfo = null;
|
|
4421
5137
|
}
|
|
@@ -4518,12 +5234,12 @@ function registerNtfStoragePath(ntf, ctx) {
|
|
|
4518
5234
|
}
|
|
4519
5235
|
|
|
4520
5236
|
// src/cli/log-search.ts
|
|
4521
|
-
var
|
|
4522
|
-
var
|
|
5237
|
+
var import_node_fs12 = require("node:fs");
|
|
5238
|
+
var import_node_path12 = require("node:path");
|
|
4523
5239
|
function resolveLogsDir(ctx) {
|
|
4524
5240
|
if (ctx.stateDir) {
|
|
4525
|
-
const dir =
|
|
4526
|
-
if (
|
|
5241
|
+
const dir = import_node_path12.join(ctx.stateDir, "plugins", "phone-notifications", "logs");
|
|
5242
|
+
if (import_node_fs12.existsSync(dir))
|
|
4527
5243
|
return dir;
|
|
4528
5244
|
}
|
|
4529
5245
|
return null;
|
|
@@ -4531,7 +5247,7 @@ function resolveLogsDir(ctx) {
|
|
|
4531
5247
|
function listLogDateKeys(dir) {
|
|
4532
5248
|
const pattern = /^(\d{4}-\d{2}-\d{2})\.log$/;
|
|
4533
5249
|
const keys = [];
|
|
4534
|
-
for (const entry of
|
|
5250
|
+
for (const entry of import_node_fs12.readdirSync(dir, { withFileTypes: true })) {
|
|
4535
5251
|
if (!entry.isFile())
|
|
4536
5252
|
continue;
|
|
4537
5253
|
const m = pattern.exec(entry.name);
|
|
@@ -4541,10 +5257,10 @@ function listLogDateKeys(dir) {
|
|
|
4541
5257
|
return keys.sort().reverse();
|
|
4542
5258
|
}
|
|
4543
5259
|
function collectLogLines(dir, dateKey, keyword, limit, collected) {
|
|
4544
|
-
const filePath =
|
|
4545
|
-
if (!
|
|
5260
|
+
const filePath = import_node_path12.join(dir, `${dateKey}.log`);
|
|
5261
|
+
if (!import_node_fs12.existsSync(filePath))
|
|
4546
5262
|
return;
|
|
4547
|
-
const content =
|
|
5263
|
+
const content = import_node_fs12.readFileSync(filePath, "utf-8");
|
|
4548
5264
|
const lowerKeyword = keyword?.toLowerCase();
|
|
4549
5265
|
for (const line of content.split(`
|
|
4550
5266
|
`)) {
|
|
@@ -4580,7 +5296,7 @@ function registerLogSearch(ntf, ctx) {
|
|
|
4580
5296
|
}
|
|
4581
5297
|
|
|
4582
5298
|
// src/cli/env.ts
|
|
4583
|
-
var
|
|
5299
|
+
var import_node_child_process2 = require("node:child_process");
|
|
4584
5300
|
|
|
4585
5301
|
// src/update/restart.ts
|
|
4586
5302
|
var DEFAULT_RESTART_GRACE_MS = 1500;
|
|
@@ -4704,7 +5420,7 @@ function triggerGatewayReloadAfterEnvSwitch(deps = {}) {
|
|
|
4704
5420
|
}
|
|
4705
5421
|
const hostCli = deps.hostCli?.trim() || process.env.HOST_CLI?.trim() || "openclaw";
|
|
4706
5422
|
const command = `${hostCli} gateway restart`;
|
|
4707
|
-
const run = deps.spawnSync ??
|
|
5423
|
+
const run = deps.spawnSync ?? import_node_child_process2.spawnSync;
|
|
4708
5424
|
const result = run(hostCli, ["gateway", "restart"], {
|
|
4709
5425
|
encoding: "utf-8",
|
|
4710
5426
|
stdio: "pipe",
|
|
@@ -4758,11 +5474,11 @@ function registerEnvCli(ntf, deps = {}) {
|
|
|
4758
5474
|
}
|
|
4759
5475
|
|
|
4760
5476
|
// src/cli/doctor.ts
|
|
4761
|
-
var
|
|
5477
|
+
var import_node_fs16 = require("node:fs");
|
|
4762
5478
|
var import_node_readline = require("node:readline");
|
|
4763
5479
|
|
|
4764
5480
|
// src/cli/doctor/check-dangerous-flags.ts
|
|
4765
|
-
var
|
|
5481
|
+
var import_node_fs13 = require("node:fs");
|
|
4766
5482
|
function isObject2(v) {
|
|
4767
5483
|
return !!v && typeof v === "object" && !Array.isArray(v);
|
|
4768
5484
|
}
|
|
@@ -4782,13 +5498,13 @@ var checkDangerousFlags = ({ cfg, configPath }) => {
|
|
|
4782
5498
|
detail: "这会关闭 Control UI 的设备身份验证,任何人都可以访问控制面板。",
|
|
4783
5499
|
fixDescription: "设为 false",
|
|
4784
5500
|
fix: () => {
|
|
4785
|
-
const raw =
|
|
5501
|
+
const raw = import_node_fs13.readFileSync(configPath, "utf-8");
|
|
4786
5502
|
const config = JSON.parse(raw);
|
|
4787
5503
|
const gw = config.gateway;
|
|
4788
5504
|
const cui = gw.controlUi;
|
|
4789
5505
|
cui.dangerouslyDisableDeviceAuth = false;
|
|
4790
|
-
|
|
4791
|
-
|
|
5506
|
+
import_node_fs13.copyFileSync(configPath, configPath + ".bak");
|
|
5507
|
+
import_node_fs13.writeFileSync(configPath, JSON.stringify(config, null, 2) + `
|
|
4792
5508
|
`, "utf-8");
|
|
4793
5509
|
}
|
|
4794
5510
|
};
|
|
@@ -4845,11 +5561,11 @@ function warnEmpty() {
|
|
|
4845
5561
|
}
|
|
4846
5562
|
|
|
4847
5563
|
// src/cli/doctor/check-state-dir-perms.ts
|
|
4848
|
-
var
|
|
5564
|
+
var import_node_fs14 = require("node:fs");
|
|
4849
5565
|
var checkStateDirPerms = ({ stateDir }) => {
|
|
4850
5566
|
let mode;
|
|
4851
5567
|
try {
|
|
4852
|
-
mode =
|
|
5568
|
+
mode = import_node_fs14.statSync(stateDir).mode;
|
|
4853
5569
|
} catch {
|
|
4854
5570
|
return null;
|
|
4855
5571
|
}
|
|
@@ -4864,7 +5580,7 @@ var checkStateDirPerms = ({ stateDir }) => {
|
|
|
4864
5580
|
detail: "其他用户可以读取该目录下的凭证和配置文件。",
|
|
4865
5581
|
fixDescription: "chmod 700 " + stateDir,
|
|
4866
5582
|
fix: () => {
|
|
4867
|
-
|
|
5583
|
+
import_node_fs14.chmodSync(stateDir, 448);
|
|
4868
5584
|
}
|
|
4869
5585
|
};
|
|
4870
5586
|
};
|
|
@@ -5073,16 +5789,16 @@ function pickNewestVersion(versions) {
|
|
|
5073
5789
|
}
|
|
5074
5790
|
|
|
5075
5791
|
// src/version.ts
|
|
5076
|
-
var
|
|
5792
|
+
var import_node_fs15 = require("node:fs");
|
|
5077
5793
|
function readBuildInjectedVersion() {
|
|
5078
5794
|
if (false) {}
|
|
5079
|
-
const version = "1.12.
|
|
5795
|
+
const version = "1.12.2".trim();
|
|
5080
5796
|
return version || undefined;
|
|
5081
5797
|
}
|
|
5082
5798
|
function readPluginVersionFromPackageJson() {
|
|
5083
5799
|
try {
|
|
5084
5800
|
const packageJsonUrl = new URL("../package.json", "file:///home/runner/work/openclaw-plugin/openclaw-plugin/src/version.ts");
|
|
5085
|
-
const packageJson = JSON.parse(
|
|
5801
|
+
const packageJson = JSON.parse(import_node_fs15.readFileSync(packageJsonUrl, "utf-8"));
|
|
5086
5802
|
const version = packageJson.version?.trim();
|
|
5087
5803
|
return version || undefined;
|
|
5088
5804
|
} catch {
|
|
@@ -5140,10 +5856,10 @@ function isObject5(v) {
|
|
|
5140
5856
|
return !!v && typeof v === "object" && !Array.isArray(v);
|
|
5141
5857
|
}
|
|
5142
5858
|
function readConfig(configPath) {
|
|
5143
|
-
if (!
|
|
5859
|
+
if (!import_node_fs16.existsSync(configPath))
|
|
5144
5860
|
return {};
|
|
5145
5861
|
try {
|
|
5146
|
-
const parsed = JSON.parse(
|
|
5862
|
+
const parsed = JSON.parse(import_node_fs16.readFileSync(configPath, "utf-8"));
|
|
5147
5863
|
return isObject5(parsed) ? parsed : {};
|
|
5148
5864
|
} catch {
|
|
5149
5865
|
return {};
|
|
@@ -5171,10 +5887,10 @@ function severityOrder(s) {
|
|
|
5171
5887
|
}
|
|
5172
5888
|
function confirm(question) {
|
|
5173
5889
|
const rl = import_node_readline.createInterface({ input: process.stdin, output: process.stderr });
|
|
5174
|
-
return new Promise((
|
|
5890
|
+
return new Promise((resolve2) => {
|
|
5175
5891
|
rl.question(question, (answer) => {
|
|
5176
5892
|
rl.close();
|
|
5177
|
-
|
|
5893
|
+
resolve2(answer.trim().toLowerCase() === "y");
|
|
5178
5894
|
});
|
|
5179
5895
|
});
|
|
5180
5896
|
}
|
|
@@ -5354,9 +6070,9 @@ function registerRecStoragePath(rec, ctx) {
|
|
|
5354
6070
|
|
|
5355
6071
|
// src/cli/rec-setup.ts
|
|
5356
6072
|
var import_node_readline2 = require("node:readline");
|
|
5357
|
-
var
|
|
6073
|
+
var import_node_fs17 = require("node:fs");
|
|
5358
6074
|
function ask(rl, question) {
|
|
5359
|
-
return new Promise((
|
|
6075
|
+
return new Promise((resolve2) => rl.question(question, resolve2));
|
|
5360
6076
|
}
|
|
5361
6077
|
async function askChoice(rl, prompt, choices, defaultIdx = 0) {
|
|
5362
6078
|
choices.forEach((c, i) => {
|
|
@@ -5455,9 +6171,9 @@ async function setupLocal(rl) {
|
|
|
5455
6171
|
function registerRecSetup(rec, ctx) {
|
|
5456
6172
|
rec.command("setup").description("交互式配置 ASR 转写参数,保存到本地配置文件").action(async () => {
|
|
5457
6173
|
const configPath = resolveAsrConfigPath(ctx);
|
|
5458
|
-
if (
|
|
6174
|
+
if (import_node_fs17.existsSync(configPath)) {
|
|
5459
6175
|
try {
|
|
5460
|
-
const existing = JSON.parse(
|
|
6176
|
+
const existing = JSON.parse(import_node_fs17.readFileSync(configPath, "utf-8"));
|
|
5461
6177
|
process.stderr.write(`当前已有配置:mode = ${existing.mode}`);
|
|
5462
6178
|
if (existing.updatedAt)
|
|
5463
6179
|
process.stderr.write(`,更新于 ${existing.updatedAt}`);
|
|
@@ -5473,7 +6189,7 @@ function registerRecSetup(rec, ctx) {
|
|
|
5473
6189
|
const modeIdx = await askChoice(rl, "选择模式", ["api(云端 model-proxy 长录音)", "local(本地 Whisper)"]);
|
|
5474
6190
|
const config = modeIdx === 0 ? await setupApi(rl) : await setupLocal(rl);
|
|
5475
6191
|
const stored = { ...config, updatedAt: new Date().toISOString() };
|
|
5476
|
-
|
|
6192
|
+
import_node_fs17.writeFileSync(configPath, JSON.stringify(stored, null, 2), "utf-8");
|
|
5477
6193
|
process.stderr.write(`
|
|
5478
6194
|
✓ 配置已保存到 ${configPath}
|
|
5479
6195
|
|
|
@@ -5485,10 +6201,93 @@ function registerRecSetup(rec, ctx) {
|
|
|
5485
6201
|
});
|
|
5486
6202
|
}
|
|
5487
6203
|
|
|
6204
|
+
// src/cli/image-list.ts
|
|
6205
|
+
function registerImageList(image, ctx) {
|
|
6206
|
+
image.command("list").description("列出所有图片(可按状态/来源应用过滤)").option("--status <status>", "按状态过滤(syncing|synced|sync_failed)").option("--app <app>", "按来源应用过滤").action((opts) => {
|
|
6207
|
+
const dir = resolveImagesDir(ctx);
|
|
6208
|
+
if (!dir)
|
|
6209
|
+
exitError("STORAGE_UNAVAILABLE", "图片存储目录不可用");
|
|
6210
|
+
let images = readImageIndex(dir);
|
|
6211
|
+
if (opts.status) {
|
|
6212
|
+
images = images.filter((e) => e.status === opts.status);
|
|
6213
|
+
}
|
|
6214
|
+
if (opts.app) {
|
|
6215
|
+
images = images.filter((e) => e.metadata.source_app === opts.app);
|
|
6216
|
+
}
|
|
6217
|
+
const items = images.map((e) => ({
|
|
6218
|
+
imageId: e.imageId,
|
|
6219
|
+
status: e.status,
|
|
6220
|
+
source_app: e.metadata.source_app ?? null,
|
|
6221
|
+
caption: e.metadata.caption ?? null,
|
|
6222
|
+
mime_type: e.metadata.mime_type ?? null,
|
|
6223
|
+
size_bytes: e.metadata.size_bytes ?? null,
|
|
6224
|
+
created_at: e.metadata.created_at,
|
|
6225
|
+
has_file: !!e.localFile,
|
|
6226
|
+
localFile: e.localFile ?? null,
|
|
6227
|
+
syncedAt: e.syncedAt ?? null,
|
|
6228
|
+
error: e.lastError ?? null
|
|
6229
|
+
}));
|
|
6230
|
+
output({ ok: true, total: items.length, images: items });
|
|
6231
|
+
});
|
|
6232
|
+
}
|
|
6233
|
+
|
|
6234
|
+
// src/cli/image-status.ts
|
|
6235
|
+
function registerImageStatus(image, ctx) {
|
|
6236
|
+
image.command("status <id>").description("查看单张图片详情").action((id) => {
|
|
6237
|
+
const dir = resolveImagesDir(ctx);
|
|
6238
|
+
if (!dir)
|
|
6239
|
+
exitError("STORAGE_UNAVAILABLE", "图片存储目录不可用");
|
|
6240
|
+
const entry = readImageIndex(dir).find((e) => e.imageId === id);
|
|
6241
|
+
if (!entry) {
|
|
6242
|
+
exitError("NOT_FOUND", `图片不存在: ${id}`);
|
|
6243
|
+
}
|
|
6244
|
+
output({
|
|
6245
|
+
ok: true,
|
|
6246
|
+
image: {
|
|
6247
|
+
imageId: entry.imageId,
|
|
6248
|
+
clientLabel: entry.clientLabel ?? null,
|
|
6249
|
+
status: entry.status,
|
|
6250
|
+
metadata: entry.metadata,
|
|
6251
|
+
localFile: entry.localFile ?? null,
|
|
6252
|
+
thumbnail: entry.thumbnail ?? null,
|
|
6253
|
+
syncedAt: entry.syncedAt ?? null,
|
|
6254
|
+
error: entry.lastError ?? null
|
|
6255
|
+
}
|
|
6256
|
+
});
|
|
6257
|
+
});
|
|
6258
|
+
}
|
|
6259
|
+
|
|
6260
|
+
// src/cli/image-path.ts
|
|
6261
|
+
function registerImagePath(image, ctx) {
|
|
6262
|
+
image.command("path <id>").description("打印图片本地文件绝对路径").action((id) => {
|
|
6263
|
+
const dir = resolveImagesDir(ctx);
|
|
6264
|
+
if (!dir)
|
|
6265
|
+
exitError("STORAGE_UNAVAILABLE", "图片存储目录不可用");
|
|
6266
|
+
const entry = readImageIndex(dir).find((e) => e.imageId === id);
|
|
6267
|
+
if (!entry) {
|
|
6268
|
+
exitError("NOT_FOUND", `图片不存在: ${id}`);
|
|
6269
|
+
}
|
|
6270
|
+
if (entry.status !== "synced" || !entry.localFile) {
|
|
6271
|
+
exitError("IMAGE_NOT_READY", `图片 ${id} 尚未下载完成(status=${entry.status}${entry.lastError ? `, error=${entry.lastError}` : ""})`);
|
|
6272
|
+
}
|
|
6273
|
+
output({ ok: true, path: resolveImageFile(dir, entry.localFile) });
|
|
6274
|
+
});
|
|
6275
|
+
}
|
|
6276
|
+
|
|
6277
|
+
// src/cli/image-storage-path.ts
|
|
6278
|
+
function registerImageStoragePath(image, ctx) {
|
|
6279
|
+
image.command("storage-path").description("查询图片存储路径").action(() => {
|
|
6280
|
+
const dir = resolveImagesDir(ctx);
|
|
6281
|
+
if (!dir)
|
|
6282
|
+
exitError("STORAGE_UNAVAILABLE", "图片存储目录不可用");
|
|
6283
|
+
output({ ok: true, path: dir });
|
|
6284
|
+
});
|
|
6285
|
+
}
|
|
6286
|
+
|
|
5488
6287
|
// src/cli/update.ts
|
|
5489
|
-
var
|
|
5490
|
-
var
|
|
5491
|
-
var
|
|
6288
|
+
var import_node_child_process3 = require("node:child_process");
|
|
6289
|
+
var import_node_fs18 = require("node:fs");
|
|
6290
|
+
var import_node_path13 = require("node:path");
|
|
5492
6291
|
var import_node_os = __toESM(require("node:os"));
|
|
5493
6292
|
async function fetchText(url) {
|
|
5494
6293
|
const res = await fetch(url, { signal: AbortSignal.timeout(30000) });
|
|
@@ -5561,9 +6360,9 @@ async function runUpdate(ctx, opts) {
|
|
|
5561
6360
|
`);
|
|
5562
6361
|
process.exit(1);
|
|
5563
6362
|
}
|
|
5564
|
-
const tmpScript =
|
|
6363
|
+
const tmpScript = import_node_path13.join(import_node_os.default.tmpdir(), `openclaw-install-${Date.now()}.mjs`);
|
|
5565
6364
|
try {
|
|
5566
|
-
|
|
6365
|
+
import_node_fs18.writeFileSync(tmpScript, installScript, "utf-8");
|
|
5567
6366
|
} catch (err) {
|
|
5568
6367
|
const msg = `写入临时文件失败: ${err?.message ?? String(err)}`;
|
|
5569
6368
|
if (json) {
|
|
@@ -5575,9 +6374,9 @@ async function runUpdate(ctx, opts) {
|
|
|
5575
6374
|
process.exit(1);
|
|
5576
6375
|
}
|
|
5577
6376
|
const stateDir = resolveStateDir(ctx.stateDir);
|
|
5578
|
-
const result =
|
|
6377
|
+
const result = import_node_child_process3.spawnSync(process.execPath, [tmpScript, "--version", latest, "--state-dir", stateDir], { stdio: "inherit" });
|
|
5579
6378
|
try {
|
|
5580
|
-
|
|
6379
|
+
import_node_fs18.unlinkSync(tmpScript);
|
|
5581
6380
|
} catch {}
|
|
5582
6381
|
if (result.error) {
|
|
5583
6382
|
const msg = `安装脚本执行失败: ${result.error.message}`;
|
|
@@ -5621,6 +6420,7 @@ function registerAllCli(program2, ctx, rootCommandName = "ntf") {
|
|
|
5621
6420
|
registerAuthCli(ntf);
|
|
5622
6421
|
registerNtfSearch(ntf, normalizedCtx);
|
|
5623
6422
|
registerNtfSummary(ntf, normalizedCtx);
|
|
6423
|
+
registerNtfSummaryJob(ntf, normalizedCtx);
|
|
5624
6424
|
registerNtfStats(ntf, normalizedCtx);
|
|
5625
6425
|
registerNtfSync(ntf, normalizedCtx);
|
|
5626
6426
|
registerNtfMonitor(ntf, normalizedCtx);
|
|
@@ -5637,6 +6437,11 @@ function registerAllCli(program2, ctx, rootCommandName = "ntf") {
|
|
|
5637
6437
|
registerRecStatus(rec, normalizedCtx);
|
|
5638
6438
|
registerRecStoragePath(rec, normalizedCtx);
|
|
5639
6439
|
registerRecSetup(rec, normalizedCtx);
|
|
6440
|
+
const image = ntf.command("image").description("图片管理");
|
|
6441
|
+
registerImageList(image, normalizedCtx);
|
|
6442
|
+
registerImageStatus(image, normalizedCtx);
|
|
6443
|
+
registerImagePath(image, normalizedCtx);
|
|
6444
|
+
registerImageStoragePath(image, normalizedCtx);
|
|
5640
6445
|
registerUpdate(ntf, normalizedCtx);
|
|
5641
6446
|
}
|
|
5642
6447
|
|
|
@@ -5651,5 +6456,5 @@ program2.parseAsync(process.argv).catch((err) => {
|
|
|
5651
6456
|
process.exit(1);
|
|
5652
6457
|
});
|
|
5653
6458
|
|
|
5654
|
-
//# debugId=
|
|
6459
|
+
//# debugId=5548672AE9D3231064756E2164756E21
|
|
5655
6460
|
//# sourceMappingURL=ntf.cjs.map
|