openclaw-telegram-manager 2.6.3 → 2.8.0
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 +1 -1
- package/dist/commands/archive.d.ts.map +1 -1
- package/dist/commands/archive.js +33 -0
- package/dist/commands/archive.js.map +1 -1
- package/dist/commands/daily-report.d.ts +0 -3
- package/dist/commands/daily-report.d.ts.map +1 -1
- package/dist/commands/daily-report.js +8 -49
- package/dist/commands/daily-report.js.map +1 -1
- package/dist/commands/doctor-all.d.ts +0 -3
- package/dist/commands/doctor-all.d.ts.map +1 -1
- package/dist/commands/doctor-all.js +4 -81
- package/dist/commands/doctor-all.js.map +1 -1
- package/dist/commands/init.d.ts.map +1 -1
- package/dist/commands/init.js +23 -0
- package/dist/commands/init.js.map +1 -1
- package/dist/commands/status.d.ts +9 -1
- package/dist/commands/status.d.ts.map +1 -1
- package/dist/commands/status.js +45 -25
- package/dist/commands/status.js.map +1 -1
- package/dist/lib/capsule.js +7 -7
- package/dist/lib/capsule.js.map +1 -1
- package/dist/lib/cron.d.ts +32 -0
- package/dist/lib/cron.d.ts.map +1 -0
- package/dist/lib/cron.js +84 -0
- package/dist/lib/cron.js.map +1 -0
- package/dist/lib/doctor-checks.d.ts +4 -0
- package/dist/lib/doctor-checks.d.ts.map +1 -1
- package/dist/lib/doctor-checks.js +13 -0
- package/dist/lib/doctor-checks.js.map +1 -1
- package/dist/lib/registry.d.ts.map +1 -1
- package/dist/lib/registry.js +11 -0
- package/dist/lib/registry.js.map +1 -1
- package/dist/lib/telegram.d.ts +1 -3
- package/dist/lib/telegram.d.ts.map +1 -1
- package/dist/lib/telegram.js +6 -19
- package/dist/lib/telegram.js.map +1 -1
- package/dist/lib/types.d.ts +3 -1
- package/dist/lib/types.d.ts.map +1 -1
- package/dist/lib/types.js +2 -1
- package/dist/lib/types.js.map +1 -1
- package/dist/plugin.js +262 -161
- package/dist/setup.js +1 -1
- package/dist/tool.js +1 -1
- package/dist/tool.js.map +1 -1
- package/package.json +1 -1
- package/skills/tm/SKILL.md +20 -3
package/dist/plugin.js
CHANGED
|
@@ -8815,7 +8815,7 @@ __export(value_exports2, {
|
|
|
8815
8815
|
});
|
|
8816
8816
|
|
|
8817
8817
|
// src/lib/types.ts
|
|
8818
|
-
var CURRENT_REGISTRY_VERSION =
|
|
8818
|
+
var CURRENT_REGISTRY_VERSION = 6;
|
|
8819
8819
|
var CAPSULE_VERSION = 3;
|
|
8820
8820
|
var MAX_POST_ERROR_LENGTH = 500;
|
|
8821
8821
|
var MAX_NAME_LENGTH = 100;
|
|
@@ -8855,6 +8855,7 @@ var TopicEntrySchema = Type.Object({
|
|
|
8855
8855
|
snoozeUntil: Type.Union([Type.String(), Type.Null()]),
|
|
8856
8856
|
consecutiveSilentDoctors: Type.Integer({ minimum: 0 }),
|
|
8857
8857
|
lastPostError: Type.Union([Type.String({ maxLength: MAX_POST_ERROR_LENGTH }), Type.Null()]),
|
|
8858
|
+
cronJobId: Type.Union([Type.String(), Type.Null()]),
|
|
8858
8859
|
extras: Type.Record(Type.String(), Type.Unknown())
|
|
8859
8860
|
});
|
|
8860
8861
|
var RegistrySchema = Type.Object({
|
|
@@ -8942,6 +8943,17 @@ var migrations = {
|
|
|
8942
8943
|
}
|
|
8943
8944
|
}
|
|
8944
8945
|
return data;
|
|
8946
|
+
},
|
|
8947
|
+
"5_to_6": (data) => {
|
|
8948
|
+
const topics = data["topics"];
|
|
8949
|
+
if (topics && typeof topics === "object" && !Array.isArray(topics)) {
|
|
8950
|
+
for (const entry of Object.values(topics)) {
|
|
8951
|
+
if (entry["cronJobId"] === void 0) {
|
|
8952
|
+
entry["cronJobId"] = null;
|
|
8953
|
+
}
|
|
8954
|
+
}
|
|
8955
|
+
}
|
|
8956
|
+
return data;
|
|
8945
8957
|
}
|
|
8946
8958
|
};
|
|
8947
8959
|
function migrateRegistry(data) {
|
|
@@ -9174,7 +9186,9 @@ _Build, deploy, test, and other commands for this topic. Kept here so they're no
|
|
|
9174
9186
|
`,
|
|
9175
9187
|
"LINKS.md": (name) => `# Links: ${name}
|
|
9176
9188
|
|
|
9177
|
-
|
|
9189
|
+
## Key paths & URLs
|
|
9190
|
+
|
|
9191
|
+
_Not set yet \u2014 add repository paths, dashboards, service URLs, or anything the agent needs after a reset._
|
|
9178
9192
|
`,
|
|
9179
9193
|
"CRON.md": (name) => `# Cron: ${name}
|
|
9180
9194
|
|
|
@@ -9193,27 +9207,91 @@ _Agent prepends here automatically. Most recent entries first._
|
|
|
9193
9207
|
var OVERLAY_TEMPLATES = {
|
|
9194
9208
|
"ARCHITECTURE.md": (name) => `# Architecture: ${name}
|
|
9195
9209
|
|
|
9196
|
-
|
|
9210
|
+
## Components
|
|
9211
|
+
|
|
9212
|
+
_None yet._
|
|
9213
|
+
|
|
9214
|
+
## Data flow
|
|
9215
|
+
|
|
9216
|
+
_None yet._
|
|
9217
|
+
|
|
9218
|
+
## Key decisions
|
|
9219
|
+
|
|
9220
|
+
_None yet._
|
|
9197
9221
|
`,
|
|
9198
9222
|
"DEPLOY.md": (name) => `# Deployment: ${name}
|
|
9199
9223
|
|
|
9200
|
-
|
|
9224
|
+
## Paths
|
|
9225
|
+
|
|
9226
|
+
_Repository and runtime/data paths go here._
|
|
9227
|
+
|
|
9228
|
+
## Environments
|
|
9229
|
+
|
|
9230
|
+
_None yet._
|
|
9231
|
+
|
|
9232
|
+
## Deploy steps
|
|
9233
|
+
|
|
9234
|
+
_None yet._
|
|
9235
|
+
|
|
9236
|
+
## Rollback
|
|
9237
|
+
|
|
9238
|
+
_None yet._
|
|
9201
9239
|
`,
|
|
9202
9240
|
"SOURCES.md": (name) => `# Sources: ${name}
|
|
9203
9241
|
|
|
9204
|
-
|
|
9242
|
+
## Papers & articles
|
|
9243
|
+
|
|
9244
|
+
_None yet._
|
|
9245
|
+
|
|
9246
|
+
## Datasets & APIs
|
|
9247
|
+
|
|
9248
|
+
_None yet._
|
|
9249
|
+
|
|
9250
|
+
## Other references
|
|
9251
|
+
|
|
9252
|
+
_None yet._
|
|
9205
9253
|
`,
|
|
9206
9254
|
"FINDINGS.md": (name) => `# Findings: ${name}
|
|
9207
9255
|
|
|
9208
|
-
|
|
9256
|
+
## Key findings
|
|
9257
|
+
|
|
9258
|
+
_None yet._
|
|
9259
|
+
|
|
9260
|
+
## Evidence & data
|
|
9261
|
+
|
|
9262
|
+
_None yet._
|
|
9263
|
+
|
|
9264
|
+
## Recommendations
|
|
9265
|
+
|
|
9266
|
+
_None yet._
|
|
9209
9267
|
`,
|
|
9210
9268
|
"CAMPAIGNS.md": (name) => `# Campaigns: ${name}
|
|
9211
9269
|
|
|
9212
|
-
|
|
9270
|
+
## Active campaigns
|
|
9271
|
+
|
|
9272
|
+
_None yet._
|
|
9273
|
+
|
|
9274
|
+
## Audiences & channels
|
|
9275
|
+
|
|
9276
|
+
_None yet._
|
|
9277
|
+
|
|
9278
|
+
## Timeline & budget
|
|
9279
|
+
|
|
9280
|
+
_None yet._
|
|
9213
9281
|
`,
|
|
9214
9282
|
"METRICS.md": (name) => `# Metrics: ${name}
|
|
9215
9283
|
|
|
9216
|
-
|
|
9284
|
+
## KPIs & targets
|
|
9285
|
+
|
|
9286
|
+
_None yet._
|
|
9287
|
+
|
|
9288
|
+
## Tracking & dashboards
|
|
9289
|
+
|
|
9290
|
+
_None yet._
|
|
9291
|
+
|
|
9292
|
+
## Performance data
|
|
9293
|
+
|
|
9294
|
+
_None yet._
|
|
9217
9295
|
`
|
|
9218
9296
|
};
|
|
9219
9297
|
var CAPSULE_FILE_MODE = 416;
|
|
@@ -9341,39 +9419,22 @@ function checkAuthorization(userId, command, registry, topicAllowFrom) {
|
|
|
9341
9419
|
|
|
9342
9420
|
// src/lib/telegram.ts
|
|
9343
9421
|
var TELEGRAM_MSG_LIMIT = 4096;
|
|
9344
|
-
var HEALTH_LABELS = {
|
|
9345
|
-
fresh: "\u2705 Active",
|
|
9346
|
-
// green check
|
|
9347
|
-
stale: "\u23F3 Inactive",
|
|
9348
|
-
// hourglass
|
|
9349
|
-
blocked: "\u26A0\uFE0F Blocked"
|
|
9350
|
-
// warning
|
|
9351
|
-
};
|
|
9352
9422
|
function buildDailyReport(data, format = "html") {
|
|
9353
9423
|
const isHtml = format === "html";
|
|
9354
9424
|
const esc = (s) => isHtml ? htmlEscape(s) : s;
|
|
9355
9425
|
const bold = (s) => isHtml ? `<b>${s}</b>` : `**${s}**`;
|
|
9356
9426
|
const n = esc(data.name);
|
|
9357
|
-
const healthLabel = HEALTH_LABELS[data.health] ?? data.health;
|
|
9358
9427
|
const lines = [
|
|
9359
9428
|
bold(`Daily Report: ${n}`),
|
|
9360
9429
|
"",
|
|
9361
|
-
bold("Done
|
|
9430
|
+
bold("Done"),
|
|
9362
9431
|
esc(data.doneContent),
|
|
9363
9432
|
"",
|
|
9364
|
-
bold("
|
|
9365
|
-
esc(data.learningsContent),
|
|
9366
|
-
"",
|
|
9367
|
-
bold("Blockers/Risks"),
|
|
9368
|
-
esc(data.blockersContent),
|
|
9369
|
-
"",
|
|
9370
|
-
bold("Next actions (now)"),
|
|
9433
|
+
bold("Next"),
|
|
9371
9434
|
esc(data.nextContent),
|
|
9372
9435
|
"",
|
|
9373
|
-
bold("
|
|
9374
|
-
esc(data.
|
|
9375
|
-
"",
|
|
9376
|
-
`${bold("Health:")} ${healthLabel}`
|
|
9436
|
+
bold("Blockers"),
|
|
9437
|
+
esc(data.blockersContent)
|
|
9377
9438
|
];
|
|
9378
9439
|
return truncateMessage(lines.join("\n"));
|
|
9379
9440
|
}
|
|
@@ -9522,7 +9583,7 @@ function buildHelpCard() {
|
|
|
9522
9583
|
"",
|
|
9523
9584
|
"**Basics**",
|
|
9524
9585
|
"/tm init \u2014 set up this topic",
|
|
9525
|
-
"/tm status \u2014 see current progress",
|
|
9586
|
+
"/tm status \u2014 see current progress (add --expanded for more detail)",
|
|
9526
9587
|
"/tm list \u2014 all topics",
|
|
9527
9588
|
"/tm help \u2014 this message",
|
|
9528
9589
|
"",
|
|
@@ -9881,6 +9942,67 @@ async function getConfigWrites(rpc) {
|
|
|
9881
9942
|
}
|
|
9882
9943
|
}
|
|
9883
9944
|
|
|
9945
|
+
// src/lib/cron.ts
|
|
9946
|
+
var DEFAULT_CRON_SCHEDULE = "0 9 * * *";
|
|
9947
|
+
var DEFAULT_CRON_TZ = "UTC";
|
|
9948
|
+
async function registerDailyReportCron(rpc, params, logger) {
|
|
9949
|
+
if (!rpc) {
|
|
9950
|
+
return { jobId: null, error: "RPC not available" };
|
|
9951
|
+
}
|
|
9952
|
+
const { topicName, slug, groupId, threadId, schedule, tz } = params;
|
|
9953
|
+
const cronExpr = schedule ?? DEFAULT_CRON_SCHEDULE;
|
|
9954
|
+
const timezone = tz ?? DEFAULT_CRON_TZ;
|
|
9955
|
+
const telegramTarget = `${groupId}:topic:${threadId}`;
|
|
9956
|
+
try {
|
|
9957
|
+
const result = await rpc.call("cron.add", {
|
|
9958
|
+
name: `tm-daily-${slug}`,
|
|
9959
|
+
schedule: {
|
|
9960
|
+
kind: "cron",
|
|
9961
|
+
expr: cronExpr,
|
|
9962
|
+
tz: timezone
|
|
9963
|
+
},
|
|
9964
|
+
sessionTarget: "isolated",
|
|
9965
|
+
wakeMode: "now",
|
|
9966
|
+
payload: {
|
|
9967
|
+
kind: "agentTurn",
|
|
9968
|
+
message: `Run topic_manager with command "daily-report" for the topic "${topicName}" (slug: ${slug}, group: ${groupId}, thread: ${threadId}). Post the report to this topic.`
|
|
9969
|
+
},
|
|
9970
|
+
delivery: {
|
|
9971
|
+
mode: "announce",
|
|
9972
|
+
channels: {
|
|
9973
|
+
telegram: {
|
|
9974
|
+
to: telegramTarget
|
|
9975
|
+
}
|
|
9976
|
+
}
|
|
9977
|
+
}
|
|
9978
|
+
});
|
|
9979
|
+
const jobId = result["jobId"] ?? result["id"] ?? null;
|
|
9980
|
+
if (jobId) {
|
|
9981
|
+
logger.info(`[cron] Created daily report job "${jobId}" for ${slug} (${cronExpr} ${timezone})`);
|
|
9982
|
+
return { jobId };
|
|
9983
|
+
}
|
|
9984
|
+
return { jobId: null, error: "Gateway did not return a job ID" };
|
|
9985
|
+
} catch (err) {
|
|
9986
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
9987
|
+
logger.error(`[cron] Failed to create daily report job for ${slug}: ${msg}`);
|
|
9988
|
+
return { jobId: null, error: msg };
|
|
9989
|
+
}
|
|
9990
|
+
}
|
|
9991
|
+
async function removeCronJob(rpc, jobId, logger) {
|
|
9992
|
+
if (!rpc) {
|
|
9993
|
+
return false;
|
|
9994
|
+
}
|
|
9995
|
+
try {
|
|
9996
|
+
await rpc.call("cron.remove", { jobId });
|
|
9997
|
+
logger.info(`[cron] Removed cron job "${jobId}"`);
|
|
9998
|
+
return true;
|
|
9999
|
+
} catch (err) {
|
|
10000
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
10001
|
+
logger.error(`[cron] Failed to remove cron job "${jobId}": ${msg}`);
|
|
10002
|
+
return false;
|
|
10003
|
+
}
|
|
10004
|
+
}
|
|
10005
|
+
|
|
9884
10006
|
// src/commands/autopilot.ts
|
|
9885
10007
|
import * as fs6 from "node:fs";
|
|
9886
10008
|
import * as path6 from "node:path";
|
|
@@ -10085,6 +10207,7 @@ async function handleInit(ctx, args) {
|
|
|
10085
10207
|
snoozeUntil: null,
|
|
10086
10208
|
consecutiveSilentDoctors: 0,
|
|
10087
10209
|
lastPostError: null,
|
|
10210
|
+
cronJobId: null,
|
|
10088
10211
|
extras: {}
|
|
10089
10212
|
};
|
|
10090
10213
|
data.topics[key] = newEntry;
|
|
@@ -10133,6 +10256,25 @@ async function handleInit(ctx, args) {
|
|
|
10133
10256
|
restartMsg = `
|
|
10134
10257
|
Warning: config sync failed: ${msg}`;
|
|
10135
10258
|
}
|
|
10259
|
+
try {
|
|
10260
|
+
const cronResult = await registerDailyReportCron(rpc, {
|
|
10261
|
+
topicName: name,
|
|
10262
|
+
slug: finalSlug,
|
|
10263
|
+
groupId,
|
|
10264
|
+
threadId
|
|
10265
|
+
}, logger);
|
|
10266
|
+
if (cronResult.jobId) {
|
|
10267
|
+
await withRegistry(workspaceDir, (data) => {
|
|
10268
|
+
const entry = data.topics[key];
|
|
10269
|
+
if (entry) {
|
|
10270
|
+
entry.cronJobId = cronResult.jobId;
|
|
10271
|
+
}
|
|
10272
|
+
});
|
|
10273
|
+
}
|
|
10274
|
+
} catch (err) {
|
|
10275
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
10276
|
+
logger.error(`[init] Cron registration failed: ${msg}`);
|
|
10277
|
+
}
|
|
10136
10278
|
appendAudit(
|
|
10137
10279
|
workspaceDir,
|
|
10138
10280
|
buildAuditEntry(userId, "init", finalSlug, `Initialized topic name="${name}" type=${topicType} group=${groupId} thread=${threadId}`)
|
|
@@ -10543,10 +10685,26 @@ function runSpamControlCheck(entry) {
|
|
|
10543
10685
|
}
|
|
10544
10686
|
return results;
|
|
10545
10687
|
}
|
|
10688
|
+
function runPostErrorCheck(entry) {
|
|
10689
|
+
const results = [];
|
|
10690
|
+
if (entry.lastPostError) {
|
|
10691
|
+
results.push(
|
|
10692
|
+
check(
|
|
10693
|
+
Severity.WARN,
|
|
10694
|
+
"lastPostFailed",
|
|
10695
|
+
`Last report delivery failed: ${entry.lastPostError}`,
|
|
10696
|
+
false,
|
|
10697
|
+
"This will be retried on the next health check cycle"
|
|
10698
|
+
)
|
|
10699
|
+
);
|
|
10700
|
+
}
|
|
10701
|
+
return results;
|
|
10702
|
+
}
|
|
10546
10703
|
function runAllChecksForTopic(entry, projectsBase, includeContent, registry, cronJobsPath) {
|
|
10547
10704
|
const results = [];
|
|
10548
10705
|
const capsuleDir = path8.join(projectsBase, entry.slug);
|
|
10549
10706
|
results.push(...runRegistryChecks(entry, projectsBase));
|
|
10707
|
+
results.push(...runPostErrorCheck(entry));
|
|
10550
10708
|
if (!fs8.existsSync(capsuleDir)) return results;
|
|
10551
10709
|
results.push(...runCapsuleChecks(entry, projectsBase));
|
|
10552
10710
|
const capsuleFiles = readCapsuleFiles(capsuleDir);
|
|
@@ -10712,21 +10870,14 @@ async function handleDailyReport(ctx) {
|
|
|
10712
10870
|
}
|
|
10713
10871
|
const statusContent = readFileOrNull(path10.join(capsuleDir, "STATUS.md"));
|
|
10714
10872
|
const todoContent = readFileOrNull(path10.join(capsuleDir, "TODO.md"));
|
|
10715
|
-
const learningsContent = readFileOrNull(path10.join(capsuleDir, "LEARNINGS.md"));
|
|
10716
10873
|
const doneContent = extractDoneSection(statusContent);
|
|
10717
|
-
const newLearnings = extractTodayLearnings(learningsContent);
|
|
10718
10874
|
const blockers = extractBlockers(todoContent);
|
|
10719
10875
|
const nextContent = extractNextActions(statusContent);
|
|
10720
|
-
const upcomingContent = extractUpcoming(statusContent);
|
|
10721
|
-
const health = computeHealth(entry.lastMessageAt, statusContent, blockers);
|
|
10722
10876
|
const reportData = {
|
|
10723
10877
|
name: entry.name,
|
|
10724
10878
|
doneContent,
|
|
10725
|
-
learningsContent: newLearnings,
|
|
10726
10879
|
blockersContent: blockers,
|
|
10727
|
-
nextContent
|
|
10728
|
-
upcomingContent,
|
|
10729
|
-
health
|
|
10880
|
+
nextContent
|
|
10730
10881
|
};
|
|
10731
10882
|
if (ctx.postFn) {
|
|
10732
10883
|
try {
|
|
@@ -10736,11 +10887,18 @@ async function handleDailyReport(ctx) {
|
|
|
10736
10887
|
const e = data.topics[key];
|
|
10737
10888
|
if (e) {
|
|
10738
10889
|
e.lastDailyReportAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
10890
|
+
e.lastPostError = null;
|
|
10739
10891
|
}
|
|
10740
10892
|
});
|
|
10741
10893
|
} catch (err) {
|
|
10742
10894
|
const msg = err instanceof Error ? err.message : String(err);
|
|
10743
10895
|
logger.error(`[daily-report] Post failed: ${msg}`);
|
|
10896
|
+
await withRegistry(workspaceDir, (data) => {
|
|
10897
|
+
const e = data.topics[key];
|
|
10898
|
+
if (e) {
|
|
10899
|
+
e.lastPostError = msg.slice(0, MAX_POST_ERROR_LENGTH);
|
|
10900
|
+
}
|
|
10901
|
+
});
|
|
10744
10902
|
return { text: `Daily report generated but post failed: ${msg}` };
|
|
10745
10903
|
}
|
|
10746
10904
|
} else {
|
|
@@ -10767,26 +10925,6 @@ function extractDoneSection(statusContent) {
|
|
|
10767
10925
|
const text = match[1]?.trim();
|
|
10768
10926
|
return text || "Empty.";
|
|
10769
10927
|
}
|
|
10770
|
-
function extractTodayLearnings(learningsContent) {
|
|
10771
|
-
if (!learningsContent) return "No learnings recorded yet.";
|
|
10772
|
-
const today = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
|
|
10773
|
-
const lines = learningsContent.split("\n");
|
|
10774
|
-
const todayLines = [];
|
|
10775
|
-
let inTodaySection = false;
|
|
10776
|
-
for (const line of lines) {
|
|
10777
|
-
if (line.startsWith("## ") && line.includes(today)) {
|
|
10778
|
-
inTodaySection = true;
|
|
10779
|
-
continue;
|
|
10780
|
-
}
|
|
10781
|
-
if (inTodaySection && line.startsWith("## ")) {
|
|
10782
|
-
break;
|
|
10783
|
-
}
|
|
10784
|
-
if (inTodaySection && line.trim()) {
|
|
10785
|
-
todayLines.push(line);
|
|
10786
|
-
}
|
|
10787
|
-
}
|
|
10788
|
-
return todayLines.length > 0 ? todayLines.join("\n") : "None today.";
|
|
10789
|
-
}
|
|
10790
10928
|
function extractBlockers(todoContent) {
|
|
10791
10929
|
if (!todoContent) return "No tasks recorded yet.";
|
|
10792
10930
|
const lines = todoContent.split("\n");
|
|
@@ -10802,22 +10940,6 @@ function extractNextActions(statusContent) {
|
|
|
10802
10940
|
const text = match[1]?.trim();
|
|
10803
10941
|
return text || "None yet.";
|
|
10804
10942
|
}
|
|
10805
|
-
function extractUpcoming(statusContent) {
|
|
10806
|
-
if (!statusContent) return "No status available yet.";
|
|
10807
|
-
const match = statusContent.match(/^##\s*Upcoming actions\s*\n([\s\S]*?)(?=\n##\s|\n*$)/im);
|
|
10808
|
-
if (!match) return "None yet.";
|
|
10809
|
-
const text = match[1]?.trim();
|
|
10810
|
-
return text || "None yet.";
|
|
10811
|
-
}
|
|
10812
|
-
function computeHealth(lastMessageAt, statusContent, blockers) {
|
|
10813
|
-
if (blockers && blockers !== "None." && blockers !== "No tasks recorded yet.") {
|
|
10814
|
-
return "blocked";
|
|
10815
|
-
}
|
|
10816
|
-
if (!lastMessageAt) return "stale";
|
|
10817
|
-
const hoursSinceActivity = (Date.now() - new Date(lastMessageAt).getTime()) / 36e5;
|
|
10818
|
-
if (hoursSinceActivity > 72) return "stale";
|
|
10819
|
-
return "fresh";
|
|
10820
|
-
}
|
|
10821
10943
|
|
|
10822
10944
|
// src/commands/doctor-all.ts
|
|
10823
10945
|
function getEligibility(entry, now, statusTimestamp) {
|
|
@@ -10863,9 +10985,6 @@ function buildDoctorAllSummary(data) {
|
|
|
10863
10985
|
checkedTopics,
|
|
10864
10986
|
skippedTopics,
|
|
10865
10987
|
postFailures,
|
|
10866
|
-
dailyReportsSent,
|
|
10867
|
-
dailyReportsSkipped,
|
|
10868
|
-
hasPostFn,
|
|
10869
10988
|
migrationGroups,
|
|
10870
10989
|
errors
|
|
10871
10990
|
} = data;
|
|
@@ -10921,15 +11040,6 @@ function buildDoctorAllSummary(data) {
|
|
|
10921
11040
|
lines.push("");
|
|
10922
11041
|
lines.push(`\u26A0\uFE0F ${postFailures} topic(s) failed to post`);
|
|
10923
11042
|
}
|
|
10924
|
-
if (hasPostFn) {
|
|
10925
|
-
const parts = [];
|
|
10926
|
-
if (dailyReportsSent > 0) parts.push(`${dailyReportsSent} sent`);
|
|
10927
|
-
if (dailyReportsSkipped > 0) parts.push(`${dailyReportsSkipped} skipped`);
|
|
10928
|
-
if (parts.length > 0) {
|
|
10929
|
-
lines.push("");
|
|
10930
|
-
lines.push(`Daily reports: ${parts.join(", ")}`);
|
|
10931
|
-
}
|
|
10932
|
-
}
|
|
10933
11043
|
if (migrationGroups > 0) {
|
|
10934
11044
|
lines.push("");
|
|
10935
11045
|
lines.push(`**Warning:** ${migrationGroups} group(s) had all topics fail. The group may have been migrated or deleted.`);
|
|
@@ -11082,54 +11192,6 @@ async function handleDoctorAll(ctx) {
|
|
|
11082
11192
|
}
|
|
11083
11193
|
}
|
|
11084
11194
|
}
|
|
11085
|
-
let dailyReportSuccesses = 0;
|
|
11086
|
-
let dailyReportSkipped = 0;
|
|
11087
|
-
const dailyReportKeys = /* @__PURE__ */ new Set();
|
|
11088
|
-
if (ctx.postFn && reports.length > 0) {
|
|
11089
|
-
const rateLimitedPost = createRateLimitedPoster(ctx.postFn);
|
|
11090
|
-
const nowDate = now.toISOString().slice(0, 10);
|
|
11091
|
-
for (const report of reports) {
|
|
11092
|
-
const key = `${report.groupId}:${report.threadId}`;
|
|
11093
|
-
const entry = registry.topics[key];
|
|
11094
|
-
if (!entry) continue;
|
|
11095
|
-
if (entry.lastDailyReportAt) {
|
|
11096
|
-
const lastReport = new Date(entry.lastDailyReportAt);
|
|
11097
|
-
const lastDate = `${lastReport.getUTCFullYear()}-${String(lastReport.getUTCMonth() + 1).padStart(2, "0")}-${String(lastReport.getUTCDate()).padStart(2, "0")}`;
|
|
11098
|
-
if (lastDate === nowDate) {
|
|
11099
|
-
dailyReportSkipped++;
|
|
11100
|
-
continue;
|
|
11101
|
-
}
|
|
11102
|
-
}
|
|
11103
|
-
const capsuleDir = path11.join(projectsBase, entry.slug);
|
|
11104
|
-
const statusContent = readFileOrNull(path11.join(capsuleDir, "STATUS.md"));
|
|
11105
|
-
const todoContent = readFileOrNull(path11.join(capsuleDir, "TODO.md"));
|
|
11106
|
-
const learningsContent = readFileOrNull(path11.join(capsuleDir, "LEARNINGS.md"));
|
|
11107
|
-
const doneContent = extractDoneSection(statusContent);
|
|
11108
|
-
const newLearnings = extractTodayLearnings(learningsContent);
|
|
11109
|
-
const blockers = extractBlockers(todoContent);
|
|
11110
|
-
const nextContent = extractNextActions(statusContent);
|
|
11111
|
-
const upcomingContent = extractUpcoming(statusContent);
|
|
11112
|
-
const health = computeHealth(entry.lastMessageAt, statusContent, blockers);
|
|
11113
|
-
const reportData = {
|
|
11114
|
-
name: entry.name,
|
|
11115
|
-
doneContent,
|
|
11116
|
-
learningsContent: newLearnings,
|
|
11117
|
-
blockersContent: blockers,
|
|
11118
|
-
nextContent,
|
|
11119
|
-
upcomingContent,
|
|
11120
|
-
health
|
|
11121
|
-
};
|
|
11122
|
-
try {
|
|
11123
|
-
const htmlReport = buildDailyReport(reportData, "html");
|
|
11124
|
-
await rateLimitedPost(report.groupId, report.threadId, htmlReport);
|
|
11125
|
-
dailyReportSuccesses++;
|
|
11126
|
-
dailyReportKeys.add(key);
|
|
11127
|
-
} catch (err) {
|
|
11128
|
-
const msg = err instanceof Error ? err.message : String(err);
|
|
11129
|
-
logger.error(`[doctor-all] Daily report post failed for ${entry.slug}: ${msg}`);
|
|
11130
|
-
}
|
|
11131
|
-
}
|
|
11132
|
-
}
|
|
11133
11195
|
await withRegistry(workspaceDir, (data) => {
|
|
11134
11196
|
data.lastDoctorAllRunAt = now.toISOString();
|
|
11135
11197
|
for (const [_key, entry] of Object.entries(data.topics)) {
|
|
@@ -11155,21 +11217,12 @@ async function handleDoctorAll(ctx) {
|
|
|
11155
11217
|
entry.consecutiveSilentDoctors = 0;
|
|
11156
11218
|
}
|
|
11157
11219
|
}
|
|
11158
|
-
for (const key of dailyReportKeys) {
|
|
11159
|
-
const entry = data.topics[key];
|
|
11160
|
-
if (entry) {
|
|
11161
|
-
entry.lastDailyReportAt = now.toISOString();
|
|
11162
|
-
}
|
|
11163
|
-
}
|
|
11164
11220
|
});
|
|
11165
11221
|
return {
|
|
11166
11222
|
text: buildDoctorAllSummary({
|
|
11167
11223
|
checkedTopics,
|
|
11168
11224
|
skippedTopics,
|
|
11169
11225
|
postFailures,
|
|
11170
|
-
dailyReportsSent: dailyReportSuccesses,
|
|
11171
|
-
dailyReportsSkipped: dailyReportSkipped,
|
|
11172
|
-
hasPostFn: !!ctx.postFn,
|
|
11173
11226
|
migrationGroups: migrationGroups.length,
|
|
11174
11227
|
errors
|
|
11175
11228
|
})
|
|
@@ -11253,38 +11306,49 @@ function formatSection(raw) {
|
|
|
11253
11306
|
if (isPlaceholder(raw)) return "_None yet._";
|
|
11254
11307
|
return raw;
|
|
11255
11308
|
}
|
|
11256
|
-
function
|
|
11257
|
-
|
|
11258
|
-
|
|
11259
|
-
|
|
11260
|
-
const
|
|
11309
|
+
function hasBlockers(blockers) {
|
|
11310
|
+
return blockers !== "None." && blockers !== "No tasks recorded yet.";
|
|
11311
|
+
}
|
|
11312
|
+
function formatStatus(data) {
|
|
11313
|
+
const { name, type, statusContent, todoContent, expanded } = data;
|
|
11314
|
+
const timestamp = extractTimestamp(statusContent);
|
|
11315
|
+
const nextRaw = extractSection(statusContent, NEXT_ACTIONS_RE2);
|
|
11316
|
+
const blockers = extractBlockers(todoContent);
|
|
11317
|
+
const doneMatch = statusContent.match(LAST_DONE_RE2);
|
|
11261
11318
|
let lastDoneBody = "";
|
|
11262
11319
|
if (doneMatch) {
|
|
11263
11320
|
const section = doneMatch[1]?.trim() ?? "";
|
|
11264
11321
|
lastDoneBody = section.replace(ISO_RE, "").trim();
|
|
11265
11322
|
}
|
|
11266
|
-
const lines = [
|
|
11267
|
-
|
|
11268
|
-
""
|
|
11269
|
-
];
|
|
11323
|
+
const lines = [];
|
|
11324
|
+
lines.push(`**${name}** \xB7 ${type}`);
|
|
11270
11325
|
if (timestamp) {
|
|
11271
11326
|
lines.push(`\u{1F552} **Last activity:** ${relativeTime(timestamp)}`);
|
|
11272
11327
|
}
|
|
11328
|
+
lines.push("");
|
|
11273
11329
|
if (lastDoneBody && !isPlaceholder(lastDoneBody)) {
|
|
11330
|
+
lines.push("\u2705 **Done recently**");
|
|
11274
11331
|
lines.push(lastDoneBody);
|
|
11332
|
+
lines.push("");
|
|
11275
11333
|
}
|
|
11276
|
-
lines.push("");
|
|
11277
11334
|
lines.push("\u{1F3AF} **Next actions**");
|
|
11278
11335
|
lines.push(formatSection(nextRaw));
|
|
11279
|
-
|
|
11280
|
-
if (upcomingFormatted !== "_None yet._") {
|
|
11336
|
+
if (hasBlockers(blockers)) {
|
|
11281
11337
|
lines.push("");
|
|
11282
|
-
lines.push("\
|
|
11283
|
-
lines.push(
|
|
11338
|
+
lines.push("\u26A0\uFE0F **Blockers**");
|
|
11339
|
+
lines.push(blockers);
|
|
11340
|
+
}
|
|
11341
|
+
if (expanded) {
|
|
11342
|
+
const upcomingRaw = extractSection(statusContent, UPCOMING_RE);
|
|
11343
|
+
if (!isPlaceholder(upcomingRaw)) {
|
|
11344
|
+
lines.push("");
|
|
11345
|
+
lines.push("\u{1F4C5} **Upcoming**");
|
|
11346
|
+
lines.push(upcomingRaw);
|
|
11347
|
+
}
|
|
11284
11348
|
}
|
|
11285
11349
|
return lines.join("\n");
|
|
11286
11350
|
}
|
|
11287
|
-
async function handleStatus2(ctx) {
|
|
11351
|
+
async function handleStatus2(ctx, args) {
|
|
11288
11352
|
const { workspaceDir, userId, groupId, threadId } = ctx;
|
|
11289
11353
|
if (!userId || !groupId || !threadId) {
|
|
11290
11354
|
return { text: "Something went wrong \u2014 this command must be run inside a Telegram forum topic." };
|
|
@@ -11311,10 +11375,18 @@ async function handleStatus2(ctx) {
|
|
|
11311
11375
|
if (!fs12.existsSync(statusPath)) {
|
|
11312
11376
|
return { text: "No status available yet. Run /tm doctor to diagnose." };
|
|
11313
11377
|
}
|
|
11378
|
+
const expanded = args.trim() === "--expanded";
|
|
11314
11379
|
try {
|
|
11315
|
-
const
|
|
11380
|
+
const statusContent = fs12.readFileSync(statusPath, "utf-8");
|
|
11381
|
+
const todoContent = readFileOrNull(path12.join(capsuleDir, "TODO.md"));
|
|
11316
11382
|
return {
|
|
11317
|
-
text: truncateMessage(formatStatus(
|
|
11383
|
+
text: truncateMessage(formatStatus({
|
|
11384
|
+
name: entry.name,
|
|
11385
|
+
type: entry.type,
|
|
11386
|
+
statusContent,
|
|
11387
|
+
todoContent,
|
|
11388
|
+
expanded
|
|
11389
|
+
}))
|
|
11318
11390
|
};
|
|
11319
11391
|
} catch (err) {
|
|
11320
11392
|
const msg = err instanceof Error ? err.message : String(err);
|
|
@@ -11539,6 +11611,35 @@ async function handleArchiveToggle(ctx, archive) {
|
|
|
11539
11611
|
}
|
|
11540
11612
|
}
|
|
11541
11613
|
});
|
|
11614
|
+
if (archive && entry.cronJobId) {
|
|
11615
|
+
await removeCronJob(rpc, entry.cronJobId, logger);
|
|
11616
|
+
await withRegistry(workspaceDir, (data) => {
|
|
11617
|
+
const topic = data.topics[key];
|
|
11618
|
+
if (topic) {
|
|
11619
|
+
topic.cronJobId = null;
|
|
11620
|
+
}
|
|
11621
|
+
});
|
|
11622
|
+
} else if (!archive && !entry.cronJobId) {
|
|
11623
|
+
try {
|
|
11624
|
+
const cronResult = await registerDailyReportCron(rpc, {
|
|
11625
|
+
topicName: entry.name,
|
|
11626
|
+
slug: entry.slug,
|
|
11627
|
+
groupId,
|
|
11628
|
+
threadId
|
|
11629
|
+
}, logger);
|
|
11630
|
+
if (cronResult.jobId) {
|
|
11631
|
+
await withRegistry(workspaceDir, (data) => {
|
|
11632
|
+
const topic = data.topics[key];
|
|
11633
|
+
if (topic) {
|
|
11634
|
+
topic.cronJobId = cronResult.jobId;
|
|
11635
|
+
}
|
|
11636
|
+
});
|
|
11637
|
+
}
|
|
11638
|
+
} catch (err) {
|
|
11639
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
11640
|
+
logger.error(`[archive] Cron re-registration failed: ${msg}`);
|
|
11641
|
+
}
|
|
11642
|
+
}
|
|
11542
11643
|
let restartMsg = "";
|
|
11543
11644
|
try {
|
|
11544
11645
|
const updatedRegistry = readRegistry(workspaceDir);
|
|
@@ -11620,7 +11721,7 @@ function createTopicManagerTool(deps) {
|
|
|
11620
11721
|
case "list":
|
|
11621
11722
|
return await handleList(ctx);
|
|
11622
11723
|
case "status":
|
|
11623
|
-
return await handleStatus2(ctx);
|
|
11724
|
+
return await handleStatus2(ctx, args);
|
|
11624
11725
|
case "sync":
|
|
11625
11726
|
return await handleSync(ctx);
|
|
11626
11727
|
case "rename":
|
package/dist/setup.js
CHANGED
|
@@ -1156,7 +1156,7 @@ var PLUGIN_FILES = ["openclaw.plugin.json", "dist/plugin.js", "skills", "package
|
|
|
1156
1156
|
var REQUIRED_PLUGIN_FILES = ["openclaw.plugin.json", "dist/plugin.js"];
|
|
1157
1157
|
var FLUSH_TAG = "[tm]";
|
|
1158
1158
|
var FLUSH_FINGERPRINTS = [FLUSH_TAG, "STATUS.md"];
|
|
1159
|
-
var SETUP_REGISTRY_VERSION =
|
|
1159
|
+
var SETUP_REGISTRY_VERSION = 6;
|
|
1160
1160
|
var MEMORY_FLUSH_INSTRUCTION = `If you are working on a Telegram topic folder (projects/<slug>/), update its STATUS.md with current "Last done (UTC)" and "Next actions (now)" before this context is compacted. ${FLUSH_TAG}`;
|
|
1161
1161
|
var SETUP_MARKER_START = "<!-- TM_AUTOPILOT_START -->";
|
|
1162
1162
|
var SETUP_MARKER_END = "<!-- TM_AUTOPILOT_END -->";
|
package/dist/tool.js
CHANGED