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.
Files changed (46) hide show
  1. package/README.md +1 -1
  2. package/dist/commands/archive.d.ts.map +1 -1
  3. package/dist/commands/archive.js +33 -0
  4. package/dist/commands/archive.js.map +1 -1
  5. package/dist/commands/daily-report.d.ts +0 -3
  6. package/dist/commands/daily-report.d.ts.map +1 -1
  7. package/dist/commands/daily-report.js +8 -49
  8. package/dist/commands/daily-report.js.map +1 -1
  9. package/dist/commands/doctor-all.d.ts +0 -3
  10. package/dist/commands/doctor-all.d.ts.map +1 -1
  11. package/dist/commands/doctor-all.js +4 -81
  12. package/dist/commands/doctor-all.js.map +1 -1
  13. package/dist/commands/init.d.ts.map +1 -1
  14. package/dist/commands/init.js +23 -0
  15. package/dist/commands/init.js.map +1 -1
  16. package/dist/commands/status.d.ts +9 -1
  17. package/dist/commands/status.d.ts.map +1 -1
  18. package/dist/commands/status.js +45 -25
  19. package/dist/commands/status.js.map +1 -1
  20. package/dist/lib/capsule.js +7 -7
  21. package/dist/lib/capsule.js.map +1 -1
  22. package/dist/lib/cron.d.ts +32 -0
  23. package/dist/lib/cron.d.ts.map +1 -0
  24. package/dist/lib/cron.js +84 -0
  25. package/dist/lib/cron.js.map +1 -0
  26. package/dist/lib/doctor-checks.d.ts +4 -0
  27. package/dist/lib/doctor-checks.d.ts.map +1 -1
  28. package/dist/lib/doctor-checks.js +13 -0
  29. package/dist/lib/doctor-checks.js.map +1 -1
  30. package/dist/lib/registry.d.ts.map +1 -1
  31. package/dist/lib/registry.js +11 -0
  32. package/dist/lib/registry.js.map +1 -1
  33. package/dist/lib/telegram.d.ts +1 -3
  34. package/dist/lib/telegram.d.ts.map +1 -1
  35. package/dist/lib/telegram.js +6 -19
  36. package/dist/lib/telegram.js.map +1 -1
  37. package/dist/lib/types.d.ts +3 -1
  38. package/dist/lib/types.d.ts.map +1 -1
  39. package/dist/lib/types.js +2 -1
  40. package/dist/lib/types.js.map +1 -1
  41. package/dist/plugin.js +262 -161
  42. package/dist/setup.js +1 -1
  43. package/dist/tool.js +1 -1
  44. package/dist/tool.js.map +1 -1
  45. package/package.json +1 -1
  46. 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 = 5;
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
- _URLs, paths, and service endpoints for this topic._
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
- _Components, data flow, dependencies, and design decisions._
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
- _Environments, deployment steps, rollback procedures, and infra details._
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
- _Papers, articles, datasets, APIs, and other reference material._
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
- _Conclusions, insights, data summaries, and recommendations._
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
- _Active campaigns, target audiences, channels, timelines, and budgets._
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
- _KPIs, conversion rates, engagement stats, and performance data._
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 today"),
9430
+ bold("Done"),
9362
9431
  esc(data.doneContent),
9363
9432
  "",
9364
- bold("New learnings"),
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("Upcoming"),
9374
- esc(data.upcomingContent),
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 formatStatus(name, content) {
11257
- const timestamp = extractTimestamp(content);
11258
- const nextRaw = extractSection(content, NEXT_ACTIONS_RE2);
11259
- const upcomingRaw = extractSection(content, UPCOMING_RE);
11260
- const doneMatch = content.match(LAST_DONE_RE2);
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
- `**${name}**`,
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
- const upcomingFormatted = formatSection(upcomingRaw);
11280
- if (upcomingFormatted !== "_None yet._") {
11336
+ if (hasBlockers(blockers)) {
11281
11337
  lines.push("");
11282
- lines.push("\u{1F4C5} **Upcoming**");
11283
- lines.push(upcomingFormatted);
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 content = fs12.readFileSync(statusPath, "utf-8");
11380
+ const statusContent = fs12.readFileSync(statusPath, "utf-8");
11381
+ const todoContent = readFileOrNull(path12.join(capsuleDir, "TODO.md"));
11316
11382
  return {
11317
- text: truncateMessage(formatStatus(entry.name, content))
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 = 5;
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
@@ -72,7 +72,7 @@ export function createTopicManagerTool(deps) {
72
72
  case 'list':
73
73
  return await handleList(ctx);
74
74
  case 'status':
75
- return await handleStatus(ctx);
75
+ return await handleStatus(ctx, args);
76
76
  case 'sync':
77
77
  return await handleSync(ctx);
78
78
  case 'rename':